Search code examples
javascripthtmlcachingaudiocacheapi

HTML Audio element source from cache


I am building a web application for playing some audio files offline. I have read and tested the Cache part of the Service Worker API, and I can easily add all my audio files to a named cache. However, the browser does not seem to use it when I add a <audio src='/audio/file.mp3'> to the DOM and play() it, even if that exact URL is added to a cache.

It makes sense in some way that the cache is not automatically used, as then it would not be any point in naming the cache.

I am using create-react-app, and as I have not ejected, I'm not controlling the Service Worker. As far as I understand, though, it is not recommended to cache larger files up front anyway, so I'm created a function in the UI to let the user decide when to cache all files.

My question is: How can I put a cached audio file into the audio element?

EDIT: I thought the problem should be understandable from my question, but was asked to add code, so here is what I've tried.

The markup has an audio element and two buttons. First button loads an mp3 url into a cache, second button tries two different ways of setting this as a source for the audio element:

  1. Insert blob from cached response
  2. Set cached request url as audio src

None of them works, of course, that's why I write this. :)

<div>
    <audio src="/audio/shape_of_you.mp3" controls/>
</div>
<div>
    <button onclick="addToCache()">Add to cache</button>
    <button onclick="playCachedAudio()">Play cached audio</button>
</div>


<script>
    function addToCache() {
        caches.open('myCache').then((cache) => {
            cache.add('/audio/rise.mp3').then(() => {
                console.log('cached');
            });
        });
    }

    function playCachedAudio() {
        caches.open('myCache').then((cache) => {
            cache.match('/audio/rise.mp3').then((cachedAudio) => {
                cachedAudio.blob().then(cont => {
                    let audioEl = $('audio');
                    audioEl.attr('src', cont);
                    // ...fails with the following error in console:
                    // HTTP “Content-Type” of “text/html” is not supported. Load of media resource http://localhost:3000/[object%20Blob] failed.
                    // Cannot play media. No decoders for requested formats: text/html
                    audioEl.attr('src', '/audio/rise.mp3');
                    // Works online, but not offline (resource is not found)
                });
            });
        });
    }
</script>

I am looking for either

a way to add the blob as audio content

or

another way to programatically force browser to guarantee a list of audio files to be cached for offline use.


Solution

  • There are two concepts mentioned about. The Cache API and service workers. These are often used together but not part of the same specification. You can use the Cache API without Service workers. This is exactly what you are doing in the code you provided.

    Note that using the Cache API never guaranties that the files will remain forever, it is still a cache. However, browsers seems to try its best to keep files.

    Now, lets look at the problem. You want to load the content from the cache if it is stored there, and I assume you want to load it from network if not. One solution is to use a service worker. The service worker works in the background, intercepting fetch events (if you configure it to). I have created a sample application (Source code) that uses this approach.

    The audio-tag configured with a src-attribute like you normally do. Then I have a service worker that listens to fetch events, checks if the request is for an audio file, then returns the cached version or a network version. I added buttons to add/remove the file from cache, and a status field to see if its added. The remove button and status is not needed, but nice for demonstration.

    Note that the service worker uses caches.match(request) without specifying a cache name. The cache name is only used to group cached item for easier maintenance by the developer. It makes it easier to delete / replace many resources at the same time. It's also easier to inspect the cache in dev-tools or iterate over its items.

    I can not answer how to add the service worker to your specific application. Consult the documentation for create-react-app.