vite-pwa / vite-plugin-pwa

Zero-config PWA for Vite
https://vite-pwa-org.netlify.app/
MIT License
3.23k stars 210 forks source link

runtimeCaching for Videos returned with 206 #793

Closed ExXTreMe315 closed 21 hours ago

ExXTreMe315 commented 5 days ago

Hey, currently I am working on a simple Demo Page with some Images and Videos on it. (I am using this small project as a POC)

I am working with Nuxt3 and configured the PWA in my Nuxt config (see below)

So I managed to cache the images with the Service Worker, but the Videos won't work correctly. While trying to cache the Videos, the Browser returns: Uncaught (in promise) TypeError: Failed to execute 'put' on 'Cache': Partial response (status code 206) is unsupported

So I added “rangeRequest: true”, but that still didn't fix it.

Is there a way to do this?

Thanks for your help

My Config:

export default defineNuxtConfig({
   //...
  modules: [
    '@vite-pwa/nuxt',
    //....
  ],

  nitro: {
    prerender: {
      routes: [ '/' ]
    }
  },

  pwa: {
    devOptions:{
      enabled: true
    },
    manifest: false,
    registerType: 'autoUpdate',
    injectRegister: 'auto',
    workbox: {
      globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
      runtimeCaching: [
        {
          urlPattern: /^https:\/\/videos\.pexels\.com\/.*/i,
          handler: 'CacheFirst',
          options: {
            cacheName: 'pexels-video-cache',
            expiration: {
              maxEntries: 500,
              maxAgeSeconds: 60 * 60 * 24 * 365 // <== 365 days
            },
            cacheableResponse: {
              statuses: [0, 200, 206]
            },
            rangeRequests: true
          }
        },
        {
          urlPattern: /^https:\/\/images\.pexels\.com\/.*/i,
          handler: 'CacheFirst',
          options: {
            cacheName: 'pexels-image-cache',
            expiration: {
              maxEntries: 20,
              maxAgeSeconds: 60 * 60 * 24 * 365 // <== 365 days
            },
            cacheableResponse: {
              statuses: [0, 200]
            }
          }
        }
      ]
    }
  }
})

I tried also handler: NetworkFirst and configuring a manifest won't even change anything. I am Using Chrome Version 131.0.6778.70 (Official Build) (arm64) on macOS Sequoia V15.1 While testing, I am using Incognito windows, so there are no extensions or other stuff blocking. I also tried everything on localhost while running dev and deployed on CF pages as well.

piotr-cz commented 5 days ago

Try this: https://web.dev/articles/sw-range-requests

ExXTreMe315 commented 5 days ago

Hey @piotr-cz thanks for replying.

This article tells about a problem with versions of chrome lower than V87 (I got 131). They are recommending to use a workbox, what is actually right that what i am doing. I even tell the Workbox to use the plugin for rangeRequests.

So from my view the only opportunity i get from the article is to modify the vite-pwa defaults or write my own? Isn't there a Vite-PWA way of doing this?

Or did I misunderstand the article?

ExXTreMe315 commented 5 days ago

After a few tests without a success I added back my old config. Now the error is gone and in he Network Tab of dev tools he tells 206 Partial Content (from service worker). After I thought "Yes its working" I checked the Application Tab and there in the Cache Storage area is nothing. The Cache pexels-video-cache exists but its empty. So how is he using the Videos from the Cache if the Cache is empty?

piotr-cz commented 5 days ago

In my app I'm precaching videos at runtime like so:

// vite.config.ts
export default defineConfig({
  plugins: [
    preact(),
    VitePWA({
      srcDir: 'src',
      filename: 'sw.ts',
      manifestFilename: 'app.webmanifest',
      strategies: 'injectManifest',
      injectRegister: false,
      registerType: 'prompt',
      manifest: false,
      injectManifest: {
        globPatterns: [
          // Default
          '**/*.{js,css,html}',
          // Imported static assets
          'assets/*.{jpg,png,svg,woff2}',
          'assets/*.{mp3,mp4}',
          // Our buddy favicon
          'assets/icon/favicon.svg',
        ],
        globIgnores: ['mockServiceWorker.js'],
        // Tweak to cache videos
        maximumFileSizeToCacheInBytes: 2.3 * 1024 * 1024,
      },
    }),
  ],
})

However I had to create my own /src/sw.ts file.

So probably you just have to set up workbox to add mp4 files to build cache

ExXTreMe315 commented 5 days ago

Okay, thanks,

so I guess I had to go the hard way and create an external file as long as there is no other way.

Is it to share a short snippet of what you do in your sw.ts so i don't need to code it from scratch?

piotr-cz commented 5 days ago

First try setting globPatterns with your setup

ExXTreMe315 commented 5 days ago

Wont change anything, I took your config and updated it with my workbox settings (plus deleting manifest and fileNames). Still same Issue (Failed to execute 'put' on 'Cache': Partial response (status code 206) is unsupported)

my new config:

pwa: {
    devOptions:{
      enabled: true
    },

    strategies: 'generateSW',
    injectRegister: false,
    registerType: 'prompt',

    manifest: false,
    injectManifest: {
      globPatterns: [
        // Default
        '**/*.{js,css,html}',
        // Imported static assets
        'assets/*.{jpg,png,svg,woff2}',
        'assets/*.{mp3,mp4}'
      ],
      // Tweak to cache videos
      maximumFileSizeToCacheInBytes: 2.3 * 1024 * 1024,
    },

    workbox: {
      globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
      runtimeCaching: [
        {
          urlPattern: /^https:\/\/videos\.pexels\.com\/.*/i,
          handler: 'CacheFirst',
          options: {
            cacheName: 'pexels-video-cache',
            expiration: {
              maxEntries: 500,
              maxAgeSeconds: 60 * 60 * 24 * 365 // <== 365 days
            },
            cacheableResponse: {
              statuses: [0, 200, 206]
            },
            rangeRequests: true
          }
        },
        {
          urlPattern: /^https:\/\/images\.pexels\.com\/.*/i,
          handler: 'CacheFirst',
          options: {
            cacheName: 'pexels-image-cache',
            expiration: {
              maxEntries: 20,
              maxAgeSeconds: 60 * 60 * 24 * 365 // <== 365 days
            },
            cacheableResponse: {
              statuses: [0, 200]
            }
          }
        }
      ]
    }
  }
piotr-cz commented 5 days ago

The sw.ts file I'm using is similar to this one: https://github.com/vite-pwa/vite-plugin-pwa/blob/v0.21.0/examples/preact-router/src/prompt-sw.ts

In your config you then have to set strategies: 'injectManifest',.

However both strategies should work, the key is to add mp4 files in globPatterns.

I remember having exactly similar problem when I was serving app locally with serve (there is not problem with vite preview).

Maybe your hosting is somehow configured to serve partial content instead of whole files?

When serving app, with serve, response for videos includes Accept-Ranges: bytes. This header is not present when serving with vite preview or with my hosting.

Try this:

curl -I https://<host>/assets/<video file>.mp4
piotr-cz commented 4 days ago

If your case is like mine, then you are showing video using <video src="/assets/video.mp4" /> element right when app starts.

Browser fires a network request to stream video in chunks using header range: bytes=0- and receives first chunk with 206 Partial Content status and headers accept-ranges: byes and content-range: bytes 0-956124/956125 (more info here: HTTP range requests)

In the meantime service worker installs and downloads precached files.

For this probably browser re-uses previously made requests (including that video) and throws an error because it can only add requests to Cache that have 200 OK status.

Workarounds could be to fetch full video file:

It's possible to use <video poster /> property to show anything in meantime.

However users opening the app for the first time would have to wait for whole video to be downloaded to see it.

Proper solution would be that in case service worker would detect 206 Partial Content status, it would download full video, and optionally serve it as partial content (as described here: https://developer.chrome.com/docs/workbox/serving-cached-audio-and-video)

Anyway the issue is not directly related to this package, but to Workbox or Service Workers in general so you'd get better hints in Workbox repo.

If you find any working solution, please let me know.

ExXTreMe315 commented 4 days ago

Thank you for your answers. I have already tried some of your first reply, but it didn't work for me. I will try again and stick to your second message. If I get any solutions that work, I'll let you know.

userquin commented 4 days ago

Looks like the server needs to add Access-Control-Expose-Headers: content-range: https://github.com/GoogleChrome/workbox/issues/1644#issuecomment-1126871851

piotr-cz commented 4 days ago

Looks like the server needs to add Access-Control-Expose-Headers: content-range: GoogleChrome/workbox#1644 (comment)

Might be, but I'm able to reproduce the issue on localhost (no CORS involved) with

npm run build && npx serve -p 4173 dist

you need an app that uses <video src="/assets/some-video.mp4" /> on start page

userquin commented 4 days ago

Beware using opaque responses with caches: check the warning here https://developer.chrome.com/docs/workbox/caching-resources-during-runtime#opaque_responses_and_the_navigatorstorage_api

piotr-cz commented 3 days ago

@ExXTreMe315

This with this custom plugin: https://github.com/GoogleChrome/workbox/issues/3288#issuecomment-2493348294.

I think that this issue may be closed and the discussion can continue on the workbox repo

ExXTreMe315 commented 21 hours ago

thats true @piotr-cz I think it's more workbox related Thanks for your help guys!