Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed to work on KitKat (issue #20) #26

Merged
merged 2 commits into from
Nov 1, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ This plugin enables the use of normal HTML5 <video> tags for local video - other
Android Webview (which Cordova is based upon) limits access to local files (such as videos) and prohibits reading them, both via relative files and `file:///` URIs. Cordova Html5Video Plugin solves this by giving each of you video a `android.resource://` path and updating your `<video>` tags accordingly.

### Limitations ###
For Android only. Tested on Android API 15-18 (testing for API 19 is undergoing).
For Android only. Tested on Android API 15-19.

For API >= 19, a workaround is employed to copy the video files over to your application's data directory, as world-readable.

*WARNING*: this is potentially insecure - other apps will be able to read your videos! However it is the only way to get around Chrome's strict limitations on content:// URLs. See [Issue #20](https://github.com/jaeger25/Html5Video/issues/20) for details.

### Install ###

Expand Down
142 changes: 139 additions & 3 deletions src/android/Html5Video.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
package org.apache.cordova.plugin;

import java.util.Timer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;

import org.apache.cordova.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources.NotFoundException;
import android.os.Build;
import android.util.Log;

public class Html5Video extends CordovaPlugin {
private static final String TAG = "Html5VideoCordovaPlugin";

public final String ACTION_INITIALIZE = "initialize";
public final String ACTION_PLAY = "play";

FileDataStore dataStore = new FileDataStore();

@Override
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {

Expand All @@ -25,8 +39,29 @@ public boolean execute(String action, JSONArray args, final CallbackContext call
if (tagNames != null) {
for (int i = 0; i < tagNames.length(); i++) {
String[] video = videos.getString(tagNames.getString(i)).split("\\.");
int videoId = this.cordova.getActivity().getResources().getIdentifier(video[0], "raw", packageName);
convertedVideos.put(tagNames.getString(i), "android.resource://" + packageName + "/" + videoId);
String newUrl = null;

// Kitkat and above don't allow loading android.resource:// URLs,
// (see https://code.google.com/p/android/issues/detail?id=63033 ),
// so we do a little trick.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// copy vids to global-accessible directory, then replace with that URL.
// idea from this thread: https://github.com/jaeger25/Html5Video/issues/23#issuecomment-51174558
String newFilePath = dataStore.getFilePath(video[0]);

if (newFilePath != null) {
newUrl = "file://" + newFilePath;
Log.d(TAG, "Using KitKat, can't load local videos. Loading from: " + newUrl);
}
}


if (newUrl == null) { // do the old way if the KitKat way failed (or if we're not on KitKat)
int videoId = this.cordova.getActivity().getResources().getIdentifier(video[0], "raw", packageName);
newUrl = "android.resource://" + packageName + "/" + videoId;
}

convertedVideos.put(tagNames.getString(i), newUrl);

LOG.d("Html5Video", "Id: " + tagNames.getString(i) + " , src: " + convertedVideos.getString(tagNames.getString(i)));
}
Expand Down Expand Up @@ -55,4 +90,105 @@ public void run() {
public Object onMessage(String id, Object data) {
return super.onMessage(id, data);
}


private class FileDataStore {

/***
* Gets the file:// URL of a video file in the res/raw directory, copying it over if necessary.
*
* Note: the file will be world-readable (not-recommended by Google), so be careful: other apps will be able to read your videos!
*
* @param rawFileName Name of the file in the raw directory.
* @return The absolute path of the file, or null if copy failed.
*/
public String getFilePath(String rawFileName) {
String fileName = "html5video_" + rawFileName; // path separators not allowed
File f = cordova.getActivity().getFileStreamPath(fileName);

// transfer raw resrouce file into app data directory so it can be read globally.
if (!f.exists()) {

if (!copyResourceFileToDataDir(rawFileName, fileName)) {
return null;
}
}

return f.getAbsolutePath();
}

/***
* Copies a raw file from resources directory to the app's private data directory.
* @param rawFileName
* @param newFileName
* @return True on success, false otherwise
*/
private boolean copyResourceFileToDataDir(String rawFileName, String newFileName) {
String packageName = cordova.getActivity().getPackageName();

AssetFileDescriptor fd = null;
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
Activity activity = cordova.getActivity();
int id = activity.getResources().getIdentifier(rawFileName, "raw", packageName);
fd = activity.getResources().openRawResourceFd(id);
if (fd == null) {
throw new NotFoundException("Raw file not found: " + rawFileName);
}

in = new BufferedInputStream(fd.createInputStream()); // this is auto-close!
// well we need world readable because that's the only way chrome can read it!
// don't worry about their scare-tactic "oooh security holes" warnings: who cares if other apps can read your videos?
out = new BufferedOutputStream(activity.openFileOutput(newFileName, Context.MODE_WORLD_READABLE));

// copy the file over. it's 2014, we still have to do this by hand?
byte buffer[] = new byte[8192];
while(in.available() > 0) {
int bytesRead = in.read(buffer);

if (bytesRead > 0) {
out.write(buffer, 0, bytesRead);
}
}

in.close();
out.close();

return true;
} catch (NotFoundException e) {
Log.e(TAG, "Failed to copy video file to data dir.", e);
} catch (IOException e) {
Log.e(TAG, "Failed to copy video file to data dir.", e);
}
finally {

try {
if (fd != null) {
fd.close();
}
} catch (IOException e) {
Log.e(TAG, "Failed to fail to copy video file to data dir.", e);
}

try {
if (in != null) {
in.close();
}
} catch (IOException e) {
Log.e(TAG, "Failed to fail to copy video file to data dir.", e);
}

try {
if (out != null) {
out.close();
}
} catch (IOException e) {
Log.e(TAG, "Failed to fail to copy video file to data dir.", e);
}
}

return false;
}
}
}