silviolleite / laravel-pwa

Looks like an app, feels like an app, but IS NOT an app.
MIT License
937 stars 152 forks source link

.mp4 broken video in Safari Mac Os and Ios #23

Open rrroulio opened 4 years ago

rrroulio commented 4 years ago

.mp4 videos are no longer played in Safari Mac Os and iOS after installing the PWA. After research, I found a solution. I'm not a pro in SW, I can't have a crooked look at this JS but it works fine. The origin is : https://stackoverflow.com/questions/52087208/pwa-cached-video-will-not-play-in-mobile-safari-11-4

Here is the adapted code for laravel-pwa (CURRENT_CACHES change to staticCacheName ) Only fetch part is changed in the original script. ///////////////////////////////

self.addEventListener('fetch', function(event) {

headersLog = [];
for (var pair of event.request.headers.entries()) {
    console.log(pair[0]+ ': '+ pair[1]);
    headersLog.push(pair[0]+ ': '+ pair[1])
}
console.log('Handling fetch event for', event.request.url, JSON.stringify(headersLog));

if (event.request.headers.get('range')) {
    console.log('Range request for', event.request.url);
    var rangeHeader=event.request.headers.get('range');
    var rangeMatch =rangeHeader.match(/^bytes\=(\d+)\-(\d+)?/)
    var pos =Number(rangeMatch[1]);
    var pos2=rangeMatch[2];
    if (pos2) { pos2=Number(pos2); }

    console.log('Range request for '+ event.request.url,'Range: '+rangeHeader, "Parsed as: "+pos+"-"+pos2);
    event.respondWith(
        caches.open(staticCacheName.prefetch)
            .then(function(cache) {
                return cache.match(event.request.url);
            }).then(function(res) {
            if (!res) {
                console.log("Not found in cache - doing fetch")
                return fetch(event.request)
                    .then(res => {
                        console.log("Fetch done - returning response ",res)
                        return res.arrayBuffer();
                    });
            }
            console.log("FOUND in cache - doing fetch")
            return res.arrayBuffer();
        }).then(function(ab) {
            console.log("Response procssing")
            let responseHeaders=  {
                status: 206,
                statusText: 'Partial Content',
                headers: [
                    ['Content-Type', 'video/mp4'],
                    ['Content-Range', 'bytes ' + pos + '-' +
                    (pos2||(ab.byteLength - 1)) + '/' + ab.byteLength]]
            };

            console.log("Response: ",JSON.stringify(responseHeaders))
            var abSliced={};
            if (pos2>0){
                abSliced=ab.slice(pos,pos2+1);
            }else{
                abSliced=ab.slice(pos);
            }

            console.log("Response length: ",abSliced.byteLength)
            return new Response(
                abSliced,responseHeaders
            );
        }));
} else {
    console.log('Non-range request for', event.request.url);
    event.respondWith(
        // caches.match() will look for a cache entry in all of the caches available to the service worker.
        // It's an alternative to first opening a specific named cache and then matching on that.
        caches.match(event.request).then(function(response) {
            if (response) {
                console.log('Found response in cache:', response);
                return response;
            }
            console.log('No response found in cache. About to fetch from network...');
            // event.request will always have the proper mode set ('cors, 'no-cors', etc.) so we don't
            // have to hardcode 'no-cors' like we do when fetch()ing in the install handler.
            return fetch(event.request).then(function(response) {
                console.log('Response from network is:', response);

                return response;
            }).catch(function(error) {
                // This catch() will handle exceptions thrown from the fetch() operation.
                // Note that a HTTP error response (e.g. 404) will NOT trigger an exception.
                // It will return a normal response object that has the appropriate error code set.
                console.error('Fetching failed:', error);

                throw error;
            });
        })
    );
}

});

Vi5tar commented 3 years ago

I had this issue as well.

To add clarity, the above post is suggesting using that code snippet in serviceworker.js. For me that was located public/servicewoker.js just as the docs described in The Service Worker section on how one would customize the service worker's functionality.

Ultimately the above code didn't work for me.

I did end up finding a solution that worked for me by customizing my service worker. I decided to not serve my .mp4 urls from cache. Here's my code snippet:

// Serve from Cache
self.addEventListener("fetch", event => {
    if (!event.request.url.endsWith('.mp4')) {
        event.respondWith(
            caches.match(event.request)
                .then(response => {
                    return response || fetch(event.request);
                })
                .catch(() => {
                    return caches.match('offline');
                })
        )   
    }
});

There may be a better/more elegant solution and I'm open to hearing them! But for now my videos are playing in Safari :D