Published on

Implementing Video Caching in the Browser with IndexedDB

Authors

With the rising demand for efficient video playback on the web, caching strategies are critical to enhance performance and reduce bandwidth consumption. This article will guide you through implementing video caching in a browser using IndexedDB. The example code provided will help you fetch videos from a remote server, store them in IndexedDB, and retrieve them when needed.

Setting Up IndexedDB

First, initialize IndexedDB to create a database for storing video data. The database will have a single object store named 'videos' with 'url' as the key path.

const dbName = 'videoStore';
const dbVersion = 1;

const openDB = () => {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open(dbName, dbVersion);
        let db;

        request.onupgradeneeded = (event) => {
            db = event.target.result;
            db.createObjectStore('videos', { keyPath: 'url' });
        };

        request.onsuccess = (event) => {
            db = event.target.result;
            resolve(db);
        };

        request.onerror = (event) => {
            reject(event.target.error);
        };
    });
};

Fetching and Storing Video in IndexedDB

Next, create a function to fetch the video from a remote server (e.g., an S3 bucket) and store it in IndexedDB.

const fetchAndStoreVideo = async (url, db) => {
    const response = await fetch(url);
    const blob = await response.blob();

    const transaction = db.transaction('videos', 'readwrite');
    const store = transaction.objectStore('videos');

    const videoData = {
        url: url,
        blob: blob,
        timestamp: Date.now()
    };

    store.put(videoData);

    return blob;
};

Retrieving Video from IndexedDB

Create a function to retrieve the video from IndexedDB. If the video is not available, it should be fetched and stored using the previous function.

const getVideoFromDB = (url, db) => {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction('videos', 'readonly');
        const store = transaction.objectStore('videos');
        const request = store.get(url);

        request.onsuccess = (event) => {
            resolve(event.target.result ? event.target.result.blob : null);
        };

        request.onerror = (event) => {
            reject(event.target.error);
        };
    });
};

Cleaning Up Old Videos

To manage storage effectively, clean up videos older than a week. This function deletes old videos from IndexedDB.

const cleanUpOldVideos = (db) => {
    const oneWeek = 7 * 24 * 60 * 60 * 1000;
    const now = Date.now();
    const transaction = db.transaction('videos', 'readwrite');
    const store = transaction.objectStore('videos');
    const request = store.openCursor();

    request.onsuccess = (event) => {
        const cursor = event.target.result;
        if (cursor) {
            const videoData = cursor.value;
            if (now - videoData.timestamp > oneWeek) {
                store.delete(videoData.url);
            }
            cursor.continue();
        }
    };

    request.onerror = (event) => {
        console.error('Failed to clean up old videos:', event.target.error);
    };
};

Loading the Video

The main function to load the video checks IndexedDB first and fetches the video if it's not available.

const loadVideo = async (videoUrl) => {
    const videoPlayer = document.getElementById('video-src');

    try {
        const db = await openDB();
        cleanUpOldVideos(db);
        let videoBlob = await getVideoFromDB(videoUrl, db);

        if (!videoBlob) {
            videoBlob = await fetchAndStoreVideo(videoUrl, db);
        }
        const blobVideoURL = URL.createObjectURL(videoBlob);
        videoPlayer.src = blobVideoURL;
        videoPlayer.play();

    } catch (error) {
        console.error('Failed to load video:', error);
    }
};

Considerations for Autoplay Policy

When loading the video, be mindful of the browser's autoplay policy. If you need to play the video with sound, adjust the policy accordingly. For muted playback, set the muted attribute to true.

videoPlayer.muted = true;