Using Cordova for mobile applications to play local video files

Our iOS app, developed using PhoneGap / Cordova 4.3.0, loads an external website directly utilizing

<content src="http://example.com/foo" />
in the config.xml file. All functionality resides within this website, eliminating the need for local HTML or JS files.

To incorporate video playback functionality while maintaining offline access, we are caching videos locally on the device using the FileTransfer plugin alongside other resources like images and PDFs. Upon downloading, the files generate URLs with the file:// protocol, or alternatively, the cdvfile:// protocol. Surprisingly, while images display properly using cdvfile:// URLs, videos fail to play.

We have employed standard HTML5 video tags for playing the videos:

<video width="auto" height="100%" controls="controls" autoplay="true">
    <source src="..." type="video/mp4" />
</video>

The videos function correctly when accessed externally (i.e., from the server instead of the local filesystem). This issue appears linked to web concepts such as the same-origin policy and restrictions accessing the local filesystem. The puzzling aspect is why images behave as expected under these constraints.

Approaches attempted thus far include:

  1. Using file:// and cdvfile:// URLs as the video src, resulting in a black screen devoid of any visual content.
  2. Embedding an iframe with the video URL set as the src</code. While <code>file:// maintenance a black screen, switching to cdvfile:// unveils the iOS video player interface but fails to initiate video playback or provide a timeline.
  3. Creating a local file named video.html inside Cordova, intending to invoke this file via an iframe. Attempts to reference the video.html file yielded inconclusive outcomes despite trying various URLs referencing it. Some attempts included:
    cordova.file.applicationDirectory + 'www/video.html'
    , http://localhost/www/video.html,
    cdvfile://localhost/www/video.html
    .
  4. Pursuing Cordova plugins for video playback specifically for iOS, without success as most plugins cater to Android platforms.

This unconventional use case within Cordova prompts reconsideration of our approach. Ordinarily, Cordova applications store HTML/JS/CSS files locally, unlike our scenario where all content originates from external sources. We have adapted this method due to specific app requirements which I will outline below.

  • The intent is to deploy the app across multiple platforms initially targeting iOS, hence our use of PhoneGap.
  • The app aims to be accessible both online and offline, with all content retrieved from the server (sans locally produced content), necessitating the download and local storage of online resources.
  • Auto-updating capability without relying on App Store updates is desired, achieved through an external page with a cache.manifest ensuring control over web app code updates while permitting local caching. Noteworthy is that replicating this cache functionality within Cordova would entail significant JavaScript overhead.

My primary concern pertains to resolving the video playback issue at hand. I am open to exploring unconventional solutions! Should the current development choices render resolution unattainable, guidance on restructuring the application to meet the objectives while enabling video playback functionality will be greatly appreciated.

Thank you sincerely!

Answer №1

About a year ago, I worked on a project that had similarities to this one. We didn't encounter this issue back then because we bundled our HTML, JS, and CSS assets with the app.

The problem arises from trying to load a file:/// protocol URL from an HTML file served through http:///. This causes discomfort for the native UIWebView.

To resolve this issue, you can use a custom URL scheme like video://, but it requires writing some native code to intercept requests and redirect the actual video back to the URL loading system.

My approach:

This is how I implemented it using Cordova 4.3.0 and Objective-C:

  1. Create a new Objective-C class named VideoURLProtocol extending NSURLProtocol:

VideoURLProtocol.h:

#import <Foundation/Foundation.h>

@interface VideoURLProtocol : NSURLProtocol <NSURLConnectionDelegate>

@property (strong, nonatomic) NSURLConnection *connection;

@end

VideoURLProtocol.m:

#import "VideoURLProtocol.h"

@implementation VideoURLProtocol

@synthesize connection;

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    return [[[request URL] absoluteString] rangeOfString:@"video://"].location != NSNotFound;
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
    return [super requestIsCacheEquivalent:a toRequest:b];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.client URLProtocol:self didLoadData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self.client URLProtocolDidFinishLoading:self];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [self.client URLProtocol:self didFailWithError:error];
}

- (void)startLoading {
    NSString *currentURL = [[self.request URL] absoluteString];
    NSString *newURL = [currentURL stringByReplacingOccurrencesOfString:@"video://" withString:@"file:///"];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:newURL]];
    self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
}

- (void)stopLoading {
    [self.connection cancel];
    self.connection = nil;
}

@end
  1. Add the following line to the didFinishLaunchingWithOptions method of AppDelegate.m

    .
    .
    // These lines should already be there
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    
    // This line
    [NSURLProtocol registerClass:[VideoURLProtocol class]];
    .
    .    
    
  2. Here's the JavaScript code using the new URL scheme:

    document.addEventListener('deviceready', function(){    
        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem){        
            var caches = fileSystem.root.nativeURL.replace("Documents", "Library/Caches"), 
                videoPath = caches + "video.mp4";
            new FileTransfer().download("http://clips.vorwaerts-gmbh.de/VfE_html5.mp4", videoPath, function(entry){            
                document.getElementsByTagName("video")[0].src = caches.replace("file:///", "video://") + "video.mp4"
            }, function(error){
                alert("unable to download file: " + error);
            });
        });
    }, false);
    

Additional notes:

In the JavaScript code, I'm downloading the file to "/Library/Caches" instead of the default "/Documents" directory. Apple rejects apps that try to backup more than ~100 MB stored in the "/Documents" directory, which caught me off guard after my app was rejected.

You can set videos to autoplay by adding a line to your config.xml:

<preference name="MediaPlaybackRequiresUserAction" value="false" />    

To play videos inline, add the following line to your config.xml and include webkit-playsinline="true" attribute in your video tag:

<preference name="AllowInlineMediaPlayback" value="true" />

<video controls="controls" autoplay="true" webkit-playsinline="true" preload="auto">
</video>

For more information on NSURLProtocol, check out Ray's tutorial:

Answer №2

Through my own experiences, I have encountered issues when using the file:// protocol on iOS due to its starting point at the root of the device's filesystem.

In this scenario, it seems unlikely that Cross-Origin problems are causing any hindrances as Cordova does not implement Cross-Origin requests, treating all requests as originating from the requesting source itself. For more information, you can refer to this answer.

A potential solution could involve utilizing a relative URL instead of relying on a specific protocol. Implementing this approach may vary depending on the successful download of the file.

<video width="auto" height="100%" controls="controls" autoplay="true">
    <source src="/localhost/www/video.mp4" type="video/mp4" />
</video>

The path cdvfile://localhost/www/ would have been designated as the target argument in your call to fileTransfer.download(), as explained here.

You may need to either create the video element or set the video source in JavaScript once the successCallback has been triggered. Again, assign the source as a relative URL.

Note: Videos will not automatically play on mobile devices.

For further details, visit the Safari Developer Library's page on Device-Specific Considerations.

In Safari on iOS (across all devices, including iPad), where users might be on cellular networks with data usage charges, preload and autoplay functionalities are disabled.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Erase jQuery from the text

I am struggling with splitting or removing text from a filename. For example, if I have filenames like: 200726100_50-0002.JPG 230514008_60-0001.JPG The desired result should be: 230514008_60.JPG 200726100_50.JPG Am I not using the split function cor ...

JavaScript script to modify the parameter 'top' upon clicking

Here is the pen I've made. HTML <div class = 'cc'> <div class = 'bb'><div class = 'aa'> Some word </div></div> </div> CSS .cc { width: 100%; min-height: 90px; margin: 0; ...

Troubleshooting Firebase AppCheck v8 in React: Encountering a 400 error message stating "App ID is Invalid: 'undefined'"

I've been attempting to integrate appCheck into my Firebase project. Despite following the instructions in the Firebase documentation and consulting several StackOverflow posts, I'm encountering difficulties getting it to function correctly. When ...

Upon executing the `npm start` command, the application experiences a crash

When I tried following the steps of the Angular quickstart guide, I encountered some errors after running "npm start". Here are the errors displayed: node_modules/@angular/common/src/directives/ng_class.d.ts(46,34): error TS2304: Cannot find name 'Se ...

The state in Reactjs is not displaying as expected

Check out my ReactJS todo app that I created. However, I am facing an issue with deleting todos. Currently, it always deletes the last todo item instead of the one I click on. For example, when trying to remove 'Buy socks', it actually deletes ...

How do the authentication and authorization processes in my frontend connect with the backend of my API system?

Currently, my frontend is built with Vue while my backend relies on an express rest API that fetches data from mysql. My goal is to ensure security for both the frontend (specifically a login form) and backend API without limiting access solely to my front ...

Using jQuery to Check Cookies and Hide Content Depending on the Value and Data Attributes

On a webpage, I have a collection of coupons with unique data attributes (data-coupon). Currently, I am using cookies to store the value associated with each coupon (ranging from 1 to 4). While my code is functional, it feels repetitive and cumbersome. S ...

Configuring Proxy Settings for WebpackDevServer

I need assistance setting up a proxy using WebpackDevServer in my React/Node chrome extension. Currently, my server is running on localhost:4000, and the React frontend is on localhost:5000. When trying to access the route /api/user/ticket using Axios, I ...

Instructions on creating an input field and a slider simultaneously

Currently, I am exploring the world of CSS 3D transforms and I am particularly interested in creating sliders to manage these transforms. I recently stumbled upon the Three.js editor and was impressed by how it handles the positioning, rotation, and scalin ...

Leveraging Ajax for Executing a MySQL Query in PHP upon Clicking the Facebook Like Button

Is it possible to execute a MySQL query whenever a Facebook Like button is clicked on a webpage? I am aware that FB.Event.subscribe('edge.create', function(response) {} is used for such actions. However, my lack of knowledge in Javascript and AJA ...

Time when the client request was initiated

When an event occurs in the client browser, it triggers a log request to the server. My goal is to obtain the most accurate timestamp for the event. However, we've encountered issues with relying on Javascript as some browsers provide inaccurate times ...

Display complete information of the selected list in a modal window by clicking on it in PHP Yii

I recently started working with the Yii framework and encountered a challenge when trying to pass data from a list to a modal using AJAX. The modal is located within the same view as the list. Here's a snippet of my code: This is my view: <div id ...

methods for retrieving nested JSON data from an API endpoint

My data has been exported in JSON format { "count":79, "stories":{ "23658975":{ "title":"NOMINATIVO", "description":"BUSDRAGHI PIERGIORGIO", "updated_at":"2013-06-16T18:55:56+02:00", "created_at":"2013-06-16T18:39:06+02:00", "du ...

Transferring data from Node.js server to React client with axios communication

I have a project in the works that involves tracking chefs and their new recipes. I am developing a simple frontend application where users can input a chef's username, which will then be sent to the backend for scraping several cooking websites to re ...

In node.js, the global variable fails to update within a while loop

I need to store data in an array every 5 seconds. My initial approach was: setInterval(function() { data.push({ price: getCurrentPrice(), time: moment().format() }) }, 5000); However, after running for exactly half an hour, the s ...

Assigning a specific data type value to an element as it enters the top of the viewport

I have a unique color code stored for each section, and when a section reaches the top of the screen (specifically -180px for the header), I want to dynamically change the text color of the header element as you scroll through the sections. Despite no erro ...

Having trouble incorporating autocomplete search results into an HTML table using Jquery, Ajax, and JSP

My current project involves an App that utilizes AJAX with jQuery to communicate with a Spring-boot REST controller. While the app is functional, I am facing difficulty in displaying the search results neatly within HTML tables. https://i.sstatic.net/7sw8 ...

What is the best way to generate mock JSON data on the client side using Objective-C for iOS development?

I am looking to generate static dummy data in JSON format for my application to handle on the client side without needing to fetch anything from the network. Most of the examples I've come across involve using NSData* variables to store data retrieve ...

What is the process for running child_process when a user clicks on a view in an application

Just starting out with Node.js and utilizing express along with hogan or moustache templating for my views. I've successfully used the following code in my routing files, index.js as shown below: /* Test Shell Execute. */ router.get('/shell&apo ...

Navigating through search results on an XML page using jQuery

Context: I am currently facing a challenge involving the integration of Google search outcomes into a webpage I'm constructing. These findings are presented in XML format. My current approach to importing the XML is as follows: if (window.XMLHttpReq ...