Search code examples
javascriptgoogle-chrome-oshtml5-filesystem

Using File System as source of videos for playing offline


I am trying to add offline functionality to my HTML5 video player. I am attempting to write the files into the chrome file system as a blob and then read them from there. I believe that I am running into an issue where the files are not actually being written, just the file name. As my below code is currently constituted, it works, though still only if it is permanently connected to the internet. My goal is to have the files download to a persistent directory in the filesystem and then continue to play if the internet is disconnected.

$(document).ready(function() {


    var dir = "http://www.kevmoe.com/networks/gsplayer/";
    var fileextension = ".mp4";
    var srcfiles = $.ajax({
        //This will retrieve the contents of the folder if the folder is configured as 'browsable'
        url: dir,
        success: function(data) {
            //List all .mp4 file names in the page
            $(data).find("a:contains(" + fileextension + ")").each(function() {
                var filename = $(this).attr("href").replace(window.location.host, "").replace("http://", "");

                $("#container").append("<div id='div1' class='video'><video id='video1' class='vidarray' preload='none' poster='bkg.png'><source src='" + filename + "' type='video/mp4'></video></div>");
                async: false;


                window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;

                window.requestFileSystem(window.PERSISTANT, 200000 * 1024 * 1024, initFS, errorHandler);

                function initFS(fs) {
                    console.log('filesystem engaged'); // Just to check if everything is OK :)
                    // place the functions you will learn bellow here
                    function errorHandler(err) {
                        var msg = 'An error occured: ';
                    };

                    function createDir(rootDir, folders) {
                        rootDir.getDirectory(folders[0], {
                            create: true
                        }, function(dirEntry) {
                            if (folders.length) {
                                createDir(dirEntry, folders.slice(1));
                            }
                        }, errorHandler);
                    };

                    createDir(fs.root, 'files/video/'.split('/'));

                    fs.root.getDirectory('video', {}, function(dirEntry) {
                        var dirReader = dirEntry.createReader();
                        dirReader.readEntries(function(entries) {
                            for (var i = 0; i < entries.length; i++) {
                                var entry = entries[i];
                                if (entry.isDirectory) {
                                    console.log('Directory: ' + entry.fullPath);
                                } else if (entry.isFile) {
                                    console.log('File: ' + entry.fullPath);
                                }
                            }

                        }, errorHandler);
                    }, errorHandler);

                    fs.root.getFile(filename, {
                        create: true,
                        exclusive: true
                    }, function(fileEntry) {
                        fileEntry.createWriter(function(fileWriter) {
                            var blob = new Blob([data], {
                                type: 'video/mp4'
                            });
                            fileWriter.write(blob);
                        }, errorHandler);

                        console.log('file downloaded');
                    }, errorHandler);

                    //Try to add an event listener for when all files are finished loading into file system. Then use another function to source the videos locally.
                    var dirReader = fs.root.createReader();
                    var entries = [];

                    // Call the reader.readEntries() until no more results are returned.

                    dirReader.readEntries(function(results) {

                        //List all .mp4 file names in the page
                        $(results).find("a:contains(" + fileextension + ")").each(function() {
                            var filename = $(this).attr("href").replace(window.location.host, "").replace("http://", "");

                            $("#container").append("<div id='div1' class='video'><video id='video1' class='vidarray' preload='none' poster='bkg.png'><source src='" + filename + "' type='video/mp4'></video></div>");
                            async: false;

                        }, errorHandler);
                    });
                };

                function errorHandler() {
                    console.log('An error occured');
                };
            });


            var videos = $('.video');
            //handle ending of video
            videos.find('video').on('ended', function() {
                playNextVideo(videos);
            });

            // start with the first one
            playNextVideo(videos);


            function playNextVideo(videoList) {
                var activeVideo = videoList.filter('.active').removeClass('active'), // identify active video and remove active class
                    activeIndex = videoList.index(activeVideo), // get the active video index in the group
                    nextVideo = videoList.eq(activeIndex + 1), // get the next video in line
                    actualVideo;

                // if there is no next video start from first
                if (nextVideo.length == 0) nextVideo = videoList.first();

                // pause all videos
                videoList.find('video').each(function() {
                    this.pause();
                })

                // get reference to next video element
                actualVideo = nextVideo.find('video').get(0);

                // add active class to next video
                nextVideo.addClass('active');

                // load and play
                actualVideo.volume = 0.04;
                actualVideo.load();
                actualVideo.play();
            }
        }
    });
});

Solution

  • filesystem: protocol stores files with reference to same origin as document which requests LocalFileSystem. That is, if JavaScript at Question is created at, for example, http://example.org, the path to LocalFileSystem should be same origin as http://example.org, not file: protocol.

    If you are trying to store files or folders for accessing at file: protocol, offline, you can create an .html document to use as a template bookmark.

    Visit the local .html file once while online to get files and populate LocalFileSystem. If navigator.onLine is true, navigate to http://example.org, else get and process files and folders stored at LocalFileSystem.

    Create a list as JSON or JavaScript Array to store list of files to fetch, instead of parsing an .html document for file locations.

    Store local file as a bookmark. Launch Chromium, Chrome with --allow-file-access-from-files flag set to access filesystem: protocol from file: protocol and file: protocol at filesystem: protocol, if not online.

    <!DOCTYPE html>
    <html>
    <head>
      <title>LocalFileSystem Offline Videos Bookmark</title>
    </head>
    <body>
    <script>
    
    // location to visit if online
    const onLineURL = "https://lorempixel.com/" 
                      + window.innerWidth 
                      + "/" 
                      + window.innerHeight + "/cats";
    
    const props = {
      requestedBytes: 1024 * 1024 * 20000,
      folder: "videos",
      // list of files to fetch for offline viewing
      mediaList: [
        "http://mirrors.creativecommons.org/movingimages/webm/"
        + "ScienceCommonsJesseDylan_240p.webm"
      , "https://nickdesaulniers.github.io/netfix/demo/frag_bunny.mp4"
      ]
    };
    
    let grantedBytes = 0;
    
    function getLocalFileSystem ({requestedBytes = 0, mediaList=[], folder = ""}) {
        if (!requestedBytes || !mediaList.length || !folder) {
          throw new Error("requestedBytes: Number"
                         + " or mediaList: Array"
                         + " or folder: String not defined");
        };
        // do stuff with `filesystem:` URL
        function processLocalFilePath(localPath) {
            const video = document.createElement("video");
            document.body.appendChild(video);
            video.controls = true;
            video.src = localPath;
        }
    
        function errorHandler(err) {
            console.log(err);
        }
    
        function writeFile(dir, fn, fp, localPath) {
            console.log(dir, fn, fp, localPath);
            dir.getFile(fn, {}, function(fileEntry) {
                fileEntry.createWriter(function(fileWriter) {
                    fileWriter.onwriteend = function(e) {
                        // do stuff when file is written
                        console.log(e.type, localPath + " written");
                        window.webkitResolveLocalFileSystemURL(localPath
                        , function(file) {
                            // file exists in LocalFileSystem
                            processLocalFilePath(localPath);
                        }, errorHandler)
                    };
    
                    fileWriter.onerror = errorHandler;
                    fetch(fp).then(function(response) {
                        return response.blob()
                    }).then(function(blob) {
                        fileWriter.write(blob);
                    }).catch(errorHandler)
                }, errorHandler);
            }, errorHandler);
        }
    
        if (mediaList && mediaList.length) {
            navigator.webkitTemporaryStorage.requestQuota(requestedBytes
            , function(grantedBytes_) {
                grantedBytes = grantedBytes_;
                console.log("Requested bytes:", requestedBytes
                           , "Granted bytes:", grantedBytes);
                window.webkitRequestFileSystem(window.TEMPORARY
                , grantedBytes
                , function(fs) {
    
                    const url = fs.root.toURL();
    
                    mediaList.forEach(function(filename) {
    
                        const localPath = url + folder + "/" 
                                          + filename.split("/").pop();
    
                        window.webkitResolveLocalFileSystemURL(localPath
                        , function(file) {
                            // file exists in LocalFileSystem
                            console.log(localPath + " exists at LocalFileSystem");
                            processLocalFilePath(localPath)
    
                        }, function(err) {
                            console.log(err, localPath 
                            + " not found in LocalFileSystem");
                            // Exception is thrown if file 
                            // or folder path not found
                            // create `folder` directory, get files
                            fs.root.getDirectory(folder, {}
                            , function(dir) {
                                writeFile(dir
                                , filename.split("/").pop()
                                , filename
                                , localPath);
                            }),
                            errorHandler
                        })
                    })
    
                })
            }, errorHandler)
        }
    }
    
    if (location.href !== onLineURL && navigator.onLine) {
        location.href = onLineURL;
    } else {
        getLocalFileSystem(props);
    }
    
    </script>
    </body>
    </html>
    

    See also


    An alternative approach could be to utilize ServiceWorker