preface

Recently, I’ve been working on compatibility issues with

Special remind

about<audio>The compatibility problem of the label mobile terminal can not be solved simply by the front-end debugging, it needs to be synchronized to the back-end commissioning!

First debugging

I’ve done a lot of research on the audio tag, including stackOverflow and other communities (including Nuggets), and even found that the currentTime property of < Audio > in earlier versions of Chrome must be set to a string. More strange knowledge!

First of all, the project is on VUE, so I have built a simple audio component with imperfect functions. As a test purpose, of course, you can also refer to it.

Github address: github.com/Chrischenny…

Of course, it is also available for download on NPM

npm install ch-audio
Copy the code

Okay, let’s get down to business!

The problem: With the custom audio component, the progress bar can’t be dragged, or dragged back to the starting point.

Let’s start with our component drag logic:

data(){
    return{
        url:' '.stop:true.// Pause the flag
        progressX:0.// Progress bar width value, used for cache
        offset:0.// Distance between the progress bar and the screen
        audioitself:null./ / the cache audio
        block:null// Cache the touch area}},methods:{
    playAudio(){ / / play
        this.stop = !this.stop;
        this.$refs.oad.play();
    },
    stopAudio(){ / / pause
        this.stop = !this.stop;
        this.$refs.oad.pause();
    },
    handleTouchstart(e){// Touch the event to start the event
        // Remove the timeUpdate event to prevent interference with the progress bar;
        this.audioitself.removeEventListener('timeupdate'.this.handleTimeupdate);
        this.progressX = e.changedTouches[0].clientX - this.offset;
    },

    handleTouchmove(e){// Touch the move listening event
        if(e.changedTouches[0].clientX - this.offset>this.width){
            this.progressX = this.width;
        }else if(e.changedTouches[0].clientX - this.offset<0) {this.progressX = 0;
        }else{
            this.progressX = e.changedTouches[0].clientX - this.offset;
        }
    },
    handleTouchend(e){// Touch ends the listening event
        this.audioitself.currentTime = ((e.changedTouches[0].clientX-this.offset)/this.width)*this.audioitself.duration;
        // The touch event ends, and add the timeUpdate event again
        this.audioitself.addEventListener('timeupdate'.this.handleTimeupdate);
    },
    handleTimeupdate(){// Play a progress listening event
        this.progressX = this.width*(this.audioitself.currentTime/this.audioitself.duration)
    },
}
Copy the code

Without further logic, the basic progress bar is set in this way (VUE may be more convenient to set because of two-way binding of data). Let’s talk about the problems encountered. We must be careful when observing the problems, rather than simply suspending the superficial problems: 1. The local commissioning did not find any problems, so the code was thrown to the server, and the mobile test found that the progress bar was not unable to drag, but did not let go. The progress bar could follow the hand. This tells us that the problem is with the Touchend event

Second, the second debugging

The code assumes that the problem is with the currentTime setup, so there is a legacy of chrome. [bug Mc-10887] – A new problem with currentTime debugging is that the progress bar on the browser will return to the original point after being dragged… Strange! (I’m using the page on the server here). I printed currentTime: Normal

There was no problem with local commissioning, but there was a problem with putting the page on the server! So, is the problem on the server side?

After a few rounds of talking, I came up with the answer: we need a response header on the server side:

"Accept-Ranges":"bytes"
Copy the code

Yes, if you set the response header, you can drag the progress bar correctly, so why? Knowing it, not knowing why, that and not knowing the difference! Keep looking!

Let’s first look at what our request header and corresponding header look like without the response header set.

"Accept-Ranges":"bytes"

Accept-ranges:

Let’s take a look at what MDN has to say about this request:

When the browser finds"Accept-Ranges"When heading, you can try to continue the interrupted download instead of restarting it.

Let’s look at the debug screen:

"Accept-Ranges"

In other words, if we don’t set “Accept-Ranges” correctly, the browser will think that we are going to start downloading the video again, not from the breakpoint, there is no such thing as caching, so the browser will forbid us to set currentTime artificially (although after hitting play, I started to download the video, but the response header has keepalive, so the browser thinks it’s a link, so currentTime still can’t be set. In fact, even if the connection is timed out, as long as your response header doesn’t have “Accept-Ranges”:”bytes” it still won’t let you set it. It looks like IE11 can be set up, there are big guys willing to try, hey hey)

Here are two references, not necessarily the best, but very helpful to me!

www.php.cn/js-tutorial…

www.cnblogs.com/simonbaker/…

Third, the third debugging

With the above conclusion, overjoyed! Was it successful? Start mobile phone debugging, Android success! IOS is still failing!

Why is IOS still failing when it’s about to crash? IOS is the nightmare of mobile compatibility! Continue to debug it, the next debugging all need to be carried out in the mobile phone, very convenient…

Problem code area:

handleTouchend(e){// Touch ends the listening event
    this.audioitself.currentTime = ((e.changedTouches[0].clientX-this.offset)/this.width)*this.audioitself.duration;
    // The touch event ends, and add the timeUpdate event again
    this.audioitself.addEventListener('timeupdate'.this.handleTimeupdate);
},
Copy the code

1, Check currentTime first, error, NaN! Since it’s NaN, there must be a problem with some value in the numerical calculation!

E.touches [0]. Touches[0].

This. offset,this.width are both numbers.

4, in the end, this. Audioitself. Duration, out of the question, the display is infinity!!!!

Why infinity?

The first thing that comes to mind is, how did our

Content-Length:****
<audio>

“Range”:”bytes=0-“, “bytes=0-“, “I’m going to ask you for files starting from 0bytes, how many to give, depending on your heart.” Well, the server just gave you the data.

IOS Range request header

IOS: Range: bytes=0-1 IOS: Range: bytes=0-1 You’re so petty, you want 1byte? If you return the request header the Android way, they don’t care about you anymore, they don’t ask for bytes, and your duration becomes infinity! See the request header below:

No pun intended, the Content-range header. Let’s look at MDN’s definition of content-range:

In HTTP, the response headerContent-RangeDisplays the location of a piece of data in the entire file.

Look at the format:

Content-Range: <unit> <range-start>-<range-end>/<size>
Content-Range: <unit> <range-start>-<range-end>/*
Content-Range: <unit> */<size>
Copy the code

So, if IOS needs to get information about the audio size, it doesn’t recognize the Content-Length header. So the only way to do that is from the content-range… (= =; The first of these three formats has a file size, so it is clear. Therefore, the interface of the back end can not be directly static to access, but to dynamically respond to the requirements of IOS, and return the corresponding response header, can refer to the following code, using the KOA framework:

router.get('/getsource/:filename'.async ctx=>{
    var mp3 = path.resolve('./view/source/' + ctx.params.filename);// Obtain the specific path of the server audio resource
    ctx.set({
        'Content-Type': 'audio/mpeg'.'Content-Length': fs.statSync(mp3).size,// If not statically obtained, these two headers need to be added themselves and must be added
    })

    if (ctx.headers.range === 'bytes=0-1') {// Check whether a pre-request is initiated by IOS
        ctx.set('Content-Range'.`bytes 0-1/${fs.statSync(mp3).size}`)   // That's the point
        ctx.body = '1'
    } else {
        ctx.set({
            'Accept-Ranges': 'bytes',})const src = fs.createReadStream(mp3)
        ctx.body = src
    }
});
Copy the code

We’re almost done here! Test it out. You can drag it. Everything works! It’s just that the server is a little slow. Loading takes time… Then, finally, let’s see what IOS will send us after we correctly respond to an IOS request:

As you can see, the second IOS request is still a small number of bytes — just over 16,000 bytes — but the third request takes all 1.5 million bytes at once.

In addition, I also found that every time I re-tested IOS, the number of secondary bytes was the same number, which is about 1% more than the total number of bytes. It may be fixed

conclusion

This time about

Write the last words

Small hit an advertisement, there are big men who need SMS business, or number authentication ability, you can see here! China Unicom Innovation capability platform operator official platform! No middleman to earn the difference ~