PWA - cached video will not play in Mobile Safari (11.4)

后端 未结 1 1008
醉酒成梦
醉酒成梦 2020-12-17 01:56

I\'m struggling to create a simple POC for iOS PWA with a small video.

https://test-service-worker.azurewebsites.net/

I have simple service worker registrat

相关标签:
1条回答
  • 2020-12-17 02:41

    It turns out, Safari is just quite strict. I'm leaving the question here - hopefully it will save someones time.

    What's happening:

    1. Safari requests only part of the video - first it will request 'range: bytes=0-1' response. It expects HTTP 206 response which will reveal size of the file

    2. Based on the response it learns what is the length of the video and then it asks for individual byte ranges of the file (for example range: bytes=0-20000 etc.)

    If your response is longer than requested Safari will immediately stop processing subsequent requests.

    This is exactly what is happening in Google Chrome example and what was happening in my POC. So if you use fetch like this it will work both online & offline:

    //This code is based on  https://googlechrome.github.io/samples/service-worker/prefetch-video/ 
    
    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(CURRENT_CACHES.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;
          });
        })
        );
      }
    });

    0 讨论(0)
提交回复
热议问题