Added Android Expansion File Support (#142)
* Add Android Expansion File Support * Added notes for usage with Android Expansion file * Update README.md Added notes re: expansion file usage. * formatting issue fixes
This commit is contained in:
parent
8982843311
commit
4b8101398c
32
README.md
32
README.md
@ -116,6 +116,38 @@ var styles = StyleSheet.create({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Android Expansion File Usage
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Within your render function, assuming you have a file called
|
||||||
|
// "background.mp4" in your expansion file. Just add your main and (if applicable) patch version
|
||||||
|
<Video source={{uri: "background", mainVer: 1, patchVer: 0}} // Looks for .mp4 file (background.mp4) in the given expansion version.
|
||||||
|
rate={1.0} // 0 is paused, 1 is normal.
|
||||||
|
volume={1.0} // 0 is muted, 1 is normal.
|
||||||
|
muted={false} // Mutes the audio entirely.
|
||||||
|
paused={false} // Pauses playback entirely.
|
||||||
|
resizeMode="cover" // Fill the whole screen at aspect ratio.
|
||||||
|
repeat={true} // Repeat forever.
|
||||||
|
onLoadStart={this.loadStart} // Callback when video starts to load
|
||||||
|
onLoad={this.setDuration} // Callback when video loads
|
||||||
|
onProgress={this.setTime} // Callback every ~250ms with currentTime
|
||||||
|
onEnd={this.onEnd} // Callback when playback finishes
|
||||||
|
onError={this.videoError} // Callback when video cannot be loaded
|
||||||
|
style={styles.backgroundVideo} />
|
||||||
|
|
||||||
|
// Later on in your styles..
|
||||||
|
var styles = Stylesheet.create({
|
||||||
|
backgroundVideo: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### Load files with the RN Asset System
|
### Load files with the RN Asset System
|
||||||
|
|
||||||
The asset system [introduced in RN `0.14`](http://www.reactnative.com/react-native-v0-14-0-released/) allows loading image resources shared across iOS and Android without touching native code. As of RN `0.31` [the same is true](https://github.com/facebook/react-native/commit/91ff6868a554c4930fd5fda6ba8044dbd56c8374) of mp4 video assets for Android. As of [RN `0.33`](https://github.com/facebook/react-native/releases/tag/v0.33.0) iOS is also supported. Requires `react-native-video@0.9.0`.
|
The asset system [introduced in RN `0.14`](http://www.reactnative.com/react-native-v0-14-0-released/) allows loading image resources shared across iOS and Android without touching native code. As of RN `0.31` [the same is true](https://github.com/facebook/react-native/commit/91ff6868a554c4930fd5fda6ba8044dbd56c8374) of mp4 video assets for Android. As of [RN `0.33`](https://github.com/facebook/react-native/releases/tag/v0.33.0) iOS is also supported. Requires `react-native-video@0.9.0`.
|
||||||
|
2
Video.js
2
Video.js
@ -147,6 +147,8 @@ export default class Video extends Component {
|
|||||||
isNetwork,
|
isNetwork,
|
||||||
isAsset,
|
isAsset,
|
||||||
type: source.type || 'mp4',
|
type: source.type || 'mp4',
|
||||||
|
mainVer: source.mainVer || 0,
|
||||||
|
patchVer: source.patchVer || 0,
|
||||||
},
|
},
|
||||||
onVideoLoadStart: this._onLoadStart,
|
onVideoLoadStart: this._onLoadStart,
|
||||||
onVideoLoad: this._onLoad,
|
onVideoLoad: this._onLoad,
|
||||||
|
@ -0,0 +1,287 @@
|
|||||||
|
package com.android.vending.expansion.zipfile;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//To implement APEZProvider in your application, you'll want to change
|
||||||
|
//the AUTHORITY to match what you define in the manifest.
|
||||||
|
|
||||||
|
import com.android.vending.expansion.zipfile.ZipResourceFile.ZipEntryRO;
|
||||||
|
|
||||||
|
import android.content.ContentProvider;
|
||||||
|
import android.content.ContentProviderOperation;
|
||||||
|
import android.content.ContentProviderResult;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.OperationApplicationException;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.content.pm.ProviderInfo;
|
||||||
|
import android.content.res.AssetFileDescriptor;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This content provider is an optional part of the library.
|
||||||
|
*
|
||||||
|
* <p>Most apps don't need to use this class. This defines a
|
||||||
|
* ContentProvider that marshalls the data from the ZIP files through a
|
||||||
|
* content provider Uri in order to provide file access for certain Android APIs
|
||||||
|
* that expect Uri access to media files.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract class APEZProvider extends ContentProvider {
|
||||||
|
|
||||||
|
private ZipResourceFile mAPKExtensionFile;
|
||||||
|
private boolean mInit;
|
||||||
|
|
||||||
|
public static final String FILEID = BaseColumns._ID;
|
||||||
|
public static final String FILENAME = "ZPFN";
|
||||||
|
public static final String ZIPFILE = "ZFIL";
|
||||||
|
public static final String MODIFICATION = "ZMOD";
|
||||||
|
public static final String CRC32 = "ZCRC";
|
||||||
|
public static final String COMPRESSEDLEN = "ZCOL";
|
||||||
|
public static final String UNCOMPRESSEDLEN = "ZUNL";
|
||||||
|
public static final String COMPRESSIONTYPE = "ZTYP";
|
||||||
|
|
||||||
|
public static final String[] ALL_FIELDS = {
|
||||||
|
FILEID,
|
||||||
|
FILENAME,
|
||||||
|
ZIPFILE,
|
||||||
|
MODIFICATION,
|
||||||
|
CRC32,
|
||||||
|
COMPRESSEDLEN,
|
||||||
|
UNCOMPRESSEDLEN,
|
||||||
|
COMPRESSIONTYPE
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final int FILEID_IDX = 0;
|
||||||
|
public static final int FILENAME_IDX = 1;
|
||||||
|
public static final int ZIPFILE_IDX = 2;
|
||||||
|
public static final int MOD_IDX = 3;
|
||||||
|
public static final int CRC_IDX = 4;
|
||||||
|
public static final int COMPLEN_IDX = 5;
|
||||||
|
public static final int UNCOMPLEN_IDX = 6;
|
||||||
|
public static final int COMPTYPE_IDX = 7;
|
||||||
|
|
||||||
|
public static final int[] ALL_FIELDS_INT = {
|
||||||
|
FILEID_IDX,
|
||||||
|
FILENAME_IDX,
|
||||||
|
ZIPFILE_IDX,
|
||||||
|
MOD_IDX,
|
||||||
|
CRC_IDX,
|
||||||
|
COMPLEN_IDX,
|
||||||
|
UNCOMPLEN_IDX,
|
||||||
|
COMPTYPE_IDX
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This needs to match the authority in your manifest
|
||||||
|
*/
|
||||||
|
public abstract String getAuthority();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(Uri arg0, String arg1, String[] arg2) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType(Uri uri) {
|
||||||
|
return "vnd.android.cursor.item/asset";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri insert(Uri uri, ContentValues values) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private final String NO_FILE = "N";
|
||||||
|
|
||||||
|
private boolean initIfNecessary() {
|
||||||
|
if ( !mInit ) {
|
||||||
|
Context ctx = getContext();
|
||||||
|
PackageManager pm = ctx.getPackageManager();
|
||||||
|
ProviderInfo pi = pm.resolveContentProvider(getAuthority(), PackageManager.GET_META_DATA);
|
||||||
|
PackageInfo packInfo;
|
||||||
|
try {
|
||||||
|
packInfo = pm.getPackageInfo(ctx.getPackageName(), 0);
|
||||||
|
} catch (NameNotFoundException e1) {
|
||||||
|
e1.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int patchFileVersion;
|
||||||
|
int mainFileVersion;
|
||||||
|
int appVersionCode = packInfo.versionCode;
|
||||||
|
String[] resourceFiles = null;
|
||||||
|
if ( null != pi.metaData ) {
|
||||||
|
mainFileVersion = pi.metaData.getInt("mainVersion", appVersionCode);
|
||||||
|
patchFileVersion = pi.metaData.getInt("patchVersion", appVersionCode);
|
||||||
|
String mainFileName = pi.metaData.getString("mainFilename", NO_FILE);
|
||||||
|
if ( NO_FILE != mainFileName ) {
|
||||||
|
String patchFileName = pi.metaData.getString("patchFilename", NO_FILE);
|
||||||
|
if ( NO_FILE != patchFileName ) {
|
||||||
|
resourceFiles = new String[] { mainFileName, patchFileName };
|
||||||
|
} else {
|
||||||
|
resourceFiles = new String[] { mainFileName };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mainFileVersion = patchFileVersion = appVersionCode;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if ( null == resourceFiles ) {
|
||||||
|
mAPKExtensionFile = APKExpansionSupport.getAPKExpansionZipFile(ctx, mainFileVersion, patchFileVersion);
|
||||||
|
} else {
|
||||||
|
mAPKExtensionFile = APKExpansionSupport.getResourceZipFile(resourceFiles);
|
||||||
|
}
|
||||||
|
mInit = true;
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AssetFileDescriptor openAssetFile(Uri uri, String mode)
|
||||||
|
throws FileNotFoundException {
|
||||||
|
initIfNecessary();
|
||||||
|
String path = uri.getEncodedPath();
|
||||||
|
if ( path.startsWith("/") ) {
|
||||||
|
path = path.substring(1);
|
||||||
|
}
|
||||||
|
return mAPKExtensionFile.getAssetFileDescriptor(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContentProviderResult[] applyBatch(
|
||||||
|
ArrayList<ContentProviderOperation> operations)
|
||||||
|
throws OperationApplicationException {
|
||||||
|
initIfNecessary();
|
||||||
|
return super.applyBatch(operations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor openFile(Uri uri, String mode)
|
||||||
|
throws FileNotFoundException {
|
||||||
|
initIfNecessary();
|
||||||
|
AssetFileDescriptor af = openAssetFile(uri, mode);
|
||||||
|
if ( null != af ) {
|
||||||
|
return af.getParcelFileDescriptor();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor query(Uri uri, String[] projection, String selection,
|
||||||
|
String[] selectionArgs, String sortOrder) {
|
||||||
|
initIfNecessary();
|
||||||
|
// lists all of the items in the file that match
|
||||||
|
ZipEntryRO[] zipEntries;
|
||||||
|
if ( null == mAPKExtensionFile ) {
|
||||||
|
zipEntries = new ZipEntryRO[0];
|
||||||
|
} else {
|
||||||
|
zipEntries = mAPKExtensionFile.getAllEntries();
|
||||||
|
}
|
||||||
|
int[] intProjection;
|
||||||
|
if ( null == projection ) {
|
||||||
|
intProjection = ALL_FIELDS_INT;
|
||||||
|
projection = ALL_FIELDS;
|
||||||
|
} else {
|
||||||
|
int len = projection.length;
|
||||||
|
intProjection = new int[len];
|
||||||
|
for ( int i = 0; i < len; i++ ) {
|
||||||
|
if ( projection[i].equals(FILEID) ) {
|
||||||
|
intProjection[i] = FILEID_IDX;
|
||||||
|
} else if ( projection[i].equals(FILENAME) ) {
|
||||||
|
intProjection[i] = FILENAME_IDX;
|
||||||
|
} else if ( projection[i].equals(ZIPFILE) ) {
|
||||||
|
intProjection[i] = ZIPFILE_IDX;
|
||||||
|
} else if ( projection[i].equals(MODIFICATION) ) {
|
||||||
|
intProjection[i] = MOD_IDX;
|
||||||
|
} else if ( projection[i].equals(CRC32) ) {
|
||||||
|
intProjection[i] = CRC_IDX;
|
||||||
|
} else if ( projection[i].equals(COMPRESSEDLEN) ) {
|
||||||
|
intProjection[i] = COMPLEN_IDX;
|
||||||
|
} else if ( projection[i].equals(UNCOMPRESSEDLEN) ) {
|
||||||
|
intProjection[i] = UNCOMPLEN_IDX;
|
||||||
|
} else if ( projection[i].equals(COMPRESSIONTYPE) ) {
|
||||||
|
intProjection[i] = COMPTYPE_IDX;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MatrixCursor mc = new MatrixCursor(projection, zipEntries.length);
|
||||||
|
int len = intProjection.length;
|
||||||
|
for ( ZipEntryRO zer : zipEntries ) {
|
||||||
|
MatrixCursor.RowBuilder rb = mc.newRow();
|
||||||
|
for ( int i = 0; i < len; i++ ) {
|
||||||
|
switch (intProjection[i]) {
|
||||||
|
case FILEID_IDX:
|
||||||
|
rb.add(i);
|
||||||
|
break;
|
||||||
|
case FILENAME_IDX:
|
||||||
|
rb.add(zer.mFileName);
|
||||||
|
break;
|
||||||
|
case ZIPFILE_IDX:
|
||||||
|
rb.add(zer.getZipFileName());
|
||||||
|
break;
|
||||||
|
case MOD_IDX:
|
||||||
|
rb.add(zer.mWhenModified);
|
||||||
|
break;
|
||||||
|
case CRC_IDX:
|
||||||
|
rb.add(zer.mCRC32);
|
||||||
|
break;
|
||||||
|
case COMPLEN_IDX:
|
||||||
|
rb.add(zer.mCompressedLength);
|
||||||
|
break;
|
||||||
|
case UNCOMPLEN_IDX:
|
||||||
|
rb.add(zer.mUncompressedLength);
|
||||||
|
break;
|
||||||
|
case COMPTYPE_IDX:
|
||||||
|
rb.add(zer.mMethod);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int update(Uri uri, ContentValues values, String selection,
|
||||||
|
String[] selectionArgs) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package com.android.vending.expansion.zipfile;
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Vector;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class APKExpansionSupport {
|
||||||
|
// The shared path to all app expansion files
|
||||||
|
private final static String EXP_PATH = "/Android/obb/";
|
||||||
|
|
||||||
|
static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {
|
||||||
|
String packageName = ctx.getPackageName();
|
||||||
|
Vector<String> ret = new Vector<String>();
|
||||||
|
if (Environment.getExternalStorageState().equals(
|
||||||
|
Environment.MEDIA_MOUNTED)) {
|
||||||
|
// Build the full path to the app's expansion files
|
||||||
|
File root = Environment.getExternalStorageDirectory();
|
||||||
|
File expPath = new File(root.toString() + EXP_PATH + packageName);
|
||||||
|
|
||||||
|
// Check that expansion file path exists
|
||||||
|
if (expPath.exists()) {
|
||||||
|
if ( mainVersion > 0 ) {
|
||||||
|
String strMainPath = expPath + File.separator + "main." + mainVersion + "." + packageName + ".obb";
|
||||||
|
// Log.d("APKEXPANSION", strMainPath);
|
||||||
|
File main = new File(strMainPath);
|
||||||
|
if ( main.isFile() ) {
|
||||||
|
ret.add(strMainPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( patchVersion > 0 ) {
|
||||||
|
String strPatchPath = expPath + File.separator + "patch." + patchVersion + "." + packageName + ".obb";
|
||||||
|
File main = new File(strPatchPath);
|
||||||
|
if ( main.isFile() ) {
|
||||||
|
ret.add(strPatchPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String[] retArray = new String[ret.size()];
|
||||||
|
ret.toArray(retArray);
|
||||||
|
return retArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public ZipResourceFile getResourceZipFile(String[] expansionFiles) throws IOException {
|
||||||
|
ZipResourceFile apkExpansionFile = null;
|
||||||
|
for (String expansionFilePath : expansionFiles) {
|
||||||
|
if ( null == apkExpansionFile ) {
|
||||||
|
apkExpansionFile = new ZipResourceFile(expansionFilePath);
|
||||||
|
} else {
|
||||||
|
apkExpansionFile.addPatchFile(expansionFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return apkExpansionFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public ZipResourceFile getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion) throws IOException{
|
||||||
|
String[] expansionFiles = getAPKExpansionFiles(ctx, mainVersion, patchVersion);
|
||||||
|
return getResourceZipFile(expansionFiles);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,428 @@
|
|||||||
|
|
||||||
|
package com.android.vending.expansion.zipfile;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import android.content.res.AssetFileDescriptor;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.MappedByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Vector;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
public class ZipResourceFile {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Read-only access to Zip archives, with minimal heap allocation.
|
||||||
|
//
|
||||||
|
static final String LOG_TAG = "zipro";
|
||||||
|
static final boolean LOGV = false;
|
||||||
|
|
||||||
|
// 4-byte number
|
||||||
|
static private int swapEndian(int i)
|
||||||
|
{
|
||||||
|
return ((i & 0xff) << 24) + ((i & 0xff00) << 8) + ((i & 0xff0000) >>> 8)
|
||||||
|
+ ((i >>> 24) & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2-byte number
|
||||||
|
static private int swapEndian(short i)
|
||||||
|
{
|
||||||
|
return ((i & 0x00FF) << 8 | (i & 0xFF00) >>> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Zip file constants.
|
||||||
|
*/
|
||||||
|
static final int kEOCDSignature = 0x06054b50;
|
||||||
|
static final int kEOCDLen = 22;
|
||||||
|
static final int kEOCDNumEntries = 8; // offset to #of entries in file
|
||||||
|
static final int kEOCDSize = 12; // size of the central directory
|
||||||
|
static final int kEOCDFileOffset = 16; // offset to central directory
|
||||||
|
|
||||||
|
static final int kMaxCommentLen = 65535; // longest possible in ushort
|
||||||
|
static final int kMaxEOCDSearch = (kMaxCommentLen + kEOCDLen);
|
||||||
|
|
||||||
|
static final int kLFHSignature = 0x04034b50;
|
||||||
|
static final int kLFHLen = 30; // excluding variable-len fields
|
||||||
|
static final int kLFHNameLen = 26; // offset to filename length
|
||||||
|
static final int kLFHExtraLen = 28; // offset to extra length
|
||||||
|
|
||||||
|
static final int kCDESignature = 0x02014b50;
|
||||||
|
static final int kCDELen = 46; // excluding variable-len fields
|
||||||
|
static final int kCDEMethod = 10; // offset to compression method
|
||||||
|
static final int kCDEModWhen = 12; // offset to modification timestamp
|
||||||
|
static final int kCDECRC = 16; // offset to entry CRC
|
||||||
|
static final int kCDECompLen = 20; // offset to compressed length
|
||||||
|
static final int kCDEUncompLen = 24; // offset to uncompressed length
|
||||||
|
static final int kCDENameLen = 28; // offset to filename length
|
||||||
|
static final int kCDEExtraLen = 30; // offset to extra length
|
||||||
|
static final int kCDECommentLen = 32; // offset to comment length
|
||||||
|
static final int kCDELocalOffset = 42; // offset to local hdr
|
||||||
|
|
||||||
|
static final int kCompressStored = 0; // no compression
|
||||||
|
static final int kCompressDeflated = 8; // standard deflate
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The values we return for ZipEntryRO use 0 as an invalid value, so we want
|
||||||
|
* to adjust the hash table index by a fixed amount. Using a large value
|
||||||
|
* helps insure that people don't mix & match arguments, e.g. to
|
||||||
|
* findEntryByIndex().
|
||||||
|
*/
|
||||||
|
static final int kZipEntryAdj = 10000;
|
||||||
|
|
||||||
|
static public final class ZipEntryRO {
|
||||||
|
public ZipEntryRO(final String zipFileName, final File file, final String fileName) {
|
||||||
|
mFileName = fileName;
|
||||||
|
mZipFileName = zipFileName;
|
||||||
|
mFile = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final File mFile;
|
||||||
|
public final String mFileName;
|
||||||
|
public final String mZipFileName;
|
||||||
|
public long mLocalHdrOffset; // offset of local file header
|
||||||
|
|
||||||
|
/* useful stuff from the directory entry */
|
||||||
|
public int mMethod;
|
||||||
|
public long mWhenModified;
|
||||||
|
public long mCRC32;
|
||||||
|
public long mCompressedLength;
|
||||||
|
public long mUncompressedLength;
|
||||||
|
|
||||||
|
public long mOffset = -1;
|
||||||
|
|
||||||
|
public void setOffsetFromFile(RandomAccessFile f, ByteBuffer buf) throws IOException {
|
||||||
|
long localHdrOffset = mLocalHdrOffset;
|
||||||
|
try {
|
||||||
|
f.seek(localHdrOffset);
|
||||||
|
f.readFully(buf.array());
|
||||||
|
if (buf.getInt(0) != kLFHSignature) {
|
||||||
|
Log.w(LOG_TAG, "didn't find signature at start of lfh");
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
int nameLen = buf.getShort(kLFHNameLen) & 0xFFFF;
|
||||||
|
int extraLen = buf.getShort(kLFHExtraLen) & 0xFFFF;
|
||||||
|
mOffset = localHdrOffset + kLFHLen + nameLen + extraLen;
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
ioe.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the offset of the start of the Zip file entry within the
|
||||||
|
* Zip file.
|
||||||
|
*
|
||||||
|
* @return the offset, in bytes from the start of the file of the entry
|
||||||
|
*/
|
||||||
|
public long getOffset() {
|
||||||
|
return mOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* isUncompressed
|
||||||
|
*
|
||||||
|
* @return true if the file is stored in uncompressed form
|
||||||
|
*/
|
||||||
|
public boolean isUncompressed() {
|
||||||
|
return mMethod == kCompressStored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssetFileDescriptor getAssetFileDescriptor() {
|
||||||
|
if (mMethod == kCompressStored) {
|
||||||
|
ParcelFileDescriptor pfd;
|
||||||
|
try {
|
||||||
|
pfd = ParcelFileDescriptor.open(mFile, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
|
return new AssetFileDescriptor(pfd, getOffset(), mUncompressedLength);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getZipFileName() {
|
||||||
|
return mZipFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getZipFile() {
|
||||||
|
return mFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<String, ZipEntryRO> mHashMap = new HashMap<String, ZipEntryRO>();
|
||||||
|
|
||||||
|
/* for reading compressed files */
|
||||||
|
public HashMap<File, ZipFile> mZipFiles = new HashMap<File, ZipFile>();
|
||||||
|
|
||||||
|
public ZipResourceFile(String zipFileName) throws IOException {
|
||||||
|
addPatchFile(zipFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ZipEntryRO[] getEntriesAt(String path) {
|
||||||
|
Vector<ZipEntryRO> zev = new Vector<ZipEntryRO>();
|
||||||
|
Collection<ZipEntryRO> values = mHashMap.values();
|
||||||
|
if (null == path)
|
||||||
|
path = "";
|
||||||
|
int length = path.length();
|
||||||
|
for (ZipEntryRO ze : values) {
|
||||||
|
if (ze.mFileName.startsWith(path)) {
|
||||||
|
if (-1 == ze.mFileName.indexOf('/', length)) {
|
||||||
|
zev.add(ze);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ZipEntryRO[] entries = new ZipEntryRO[zev.size()];
|
||||||
|
return zev.toArray(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZipEntryRO[] getAllEntries() {
|
||||||
|
Collection<ZipEntryRO> values = mHashMap.values();
|
||||||
|
return values.toArray(new ZipEntryRO[values.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getAssetFileDescriptor allows for ZipResourceFile to directly feed
|
||||||
|
* Android API's that want an fd, offset, and length such as the
|
||||||
|
* MediaPlayer. It also allows for the class to be used in a content
|
||||||
|
* provider that can feed video players. The file must be stored
|
||||||
|
* (non-compressed) in the Zip file for this to work.
|
||||||
|
*
|
||||||
|
* @param assetPath
|
||||||
|
* @return the asset file descriptor for the file, or null if the file isn't
|
||||||
|
* present or is stored compressed
|
||||||
|
*/
|
||||||
|
public AssetFileDescriptor getAssetFileDescriptor(String assetPath) {
|
||||||
|
ZipEntryRO entry = mHashMap.get(assetPath);
|
||||||
|
if (null != entry) {
|
||||||
|
return entry.getAssetFileDescriptor();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getInputStream returns an AssetFileDescriptor.AutoCloseInputStream
|
||||||
|
* associated with the asset that is contained in the Zip file, or a
|
||||||
|
* standard ZipInputStream if necessary to uncompress the file
|
||||||
|
*
|
||||||
|
* @param assetPath
|
||||||
|
* @return an input stream for the named asset path, or null if not found
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public InputStream getInputStream(String assetPath) throws IOException {
|
||||||
|
ZipEntryRO entry = mHashMap.get(assetPath);
|
||||||
|
if (null != entry) {
|
||||||
|
if (entry.isUncompressed()) {
|
||||||
|
return entry.getAssetFileDescriptor().createInputStream();
|
||||||
|
} else {
|
||||||
|
ZipFile zf = mZipFiles.get(entry.getZipFile());
|
||||||
|
/** read compressed files **/
|
||||||
|
if (null == zf) {
|
||||||
|
zf = new ZipFile(entry.getZipFile(), ZipFile.OPEN_READ);
|
||||||
|
mZipFiles.put(entry.getZipFile(), zf);
|
||||||
|
}
|
||||||
|
ZipEntry zi = zf.getEntry(assetPath);
|
||||||
|
if (null != zi)
|
||||||
|
return zf.getInputStream(zi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer mLEByteBuffer = ByteBuffer.allocate(4);
|
||||||
|
|
||||||
|
static private int read4LE(RandomAccessFile f) throws EOFException, IOException {
|
||||||
|
return swapEndian(f.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Opens the specified file read-only. We memory-map the entire thing and
|
||||||
|
* close the file before returning.
|
||||||
|
*/
|
||||||
|
void addPatchFile(String zipFileName) throws IOException
|
||||||
|
{
|
||||||
|
File file = new File(zipFileName);
|
||||||
|
RandomAccessFile f = new RandomAccessFile(file, "r");
|
||||||
|
long fileLength = f.length();
|
||||||
|
|
||||||
|
if (fileLength < kEOCDLen) {
|
||||||
|
throw new java.io.IOException();
|
||||||
|
}
|
||||||
|
|
||||||
|
long readAmount = kMaxEOCDSearch;
|
||||||
|
if (readAmount > fileLength)
|
||||||
|
readAmount = fileLength;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure this is a Zip archive.
|
||||||
|
*/
|
||||||
|
f.seek(0);
|
||||||
|
|
||||||
|
int header = read4LE(f);
|
||||||
|
if (header == kEOCDSignature) {
|
||||||
|
Log.i(LOG_TAG, "Found Zip archive, but it looks empty");
|
||||||
|
throw new IOException();
|
||||||
|
} else if (header != kLFHSignature) {
|
||||||
|
Log.v(LOG_TAG, "Not a Zip archive");
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Perform the traditional EOCD snipe hunt. We're searching for the End
|
||||||
|
* of Central Directory magic number, which appears at the start of the
|
||||||
|
* EOCD block. It's followed by 18 bytes of EOCD stuff and up to 64KB of
|
||||||
|
* archive comment. We need to read the last part of the file into a
|
||||||
|
* buffer, dig through it to find the magic number, parse some values
|
||||||
|
* out, and use those to determine the extent of the CD. We start by
|
||||||
|
* pulling in the last part of the file.
|
||||||
|
*/
|
||||||
|
long searchStart = fileLength - readAmount;
|
||||||
|
|
||||||
|
f.seek(searchStart);
|
||||||
|
ByteBuffer bbuf = ByteBuffer.allocate((int) readAmount);
|
||||||
|
byte[] buffer = bbuf.array();
|
||||||
|
f.readFully(buffer);
|
||||||
|
bbuf.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scan backward for the EOCD magic. In an archive without a trailing
|
||||||
|
* comment, we'll find it on the first try. (We may want to consider
|
||||||
|
* doing an initial minimal read; if we don't find it, retry with a
|
||||||
|
* second read as above.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// EOCD == 0x50, 0x4b, 0x05, 0x06
|
||||||
|
int eocdIdx;
|
||||||
|
for (eocdIdx = buffer.length - kEOCDLen; eocdIdx >= 0; eocdIdx--) {
|
||||||
|
if (buffer[eocdIdx] == 0x50 && bbuf.getInt(eocdIdx) == kEOCDSignature)
|
||||||
|
{
|
||||||
|
if (LOGV) {
|
||||||
|
Log.v(LOG_TAG, "+++ Found EOCD at index: " + eocdIdx);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eocdIdx < 0) {
|
||||||
|
Log.d(LOG_TAG, "Zip: EOCD not found, " + zipFileName + " is not zip");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Grab the CD offset and size, and the number of entries in the
|
||||||
|
* archive. After that, we can release our EOCD hunt buffer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int numEntries = bbuf.getShort(eocdIdx + kEOCDNumEntries);
|
||||||
|
long dirSize = bbuf.getInt(eocdIdx + kEOCDSize) & 0xffffffffL;
|
||||||
|
long dirOffset = bbuf.getInt(eocdIdx + kEOCDFileOffset) & 0xffffffffL;
|
||||||
|
|
||||||
|
// Verify that they look reasonable.
|
||||||
|
if (dirOffset + dirSize > fileLength) {
|
||||||
|
Log.w(LOG_TAG, "bad offsets (dir " + dirOffset + ", size " + dirSize + ", eocd "
|
||||||
|
+ eocdIdx + ")");
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
if (numEntries == 0) {
|
||||||
|
Log.w(LOG_TAG, "empty archive?");
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOGV) {
|
||||||
|
Log.v(LOG_TAG, "+++ numEntries=" + numEntries + " dirSize=" + dirSize + " dirOffset="
|
||||||
|
+ dirOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
MappedByteBuffer directoryMap = f.getChannel()
|
||||||
|
.map(FileChannel.MapMode.READ_ONLY, dirOffset, dirSize);
|
||||||
|
directoryMap.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
byte[] tempBuf = new byte[0xffff];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Walk through the central directory, adding entries to the hash table.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int currentOffset = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate the local directory information
|
||||||
|
*/
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(kLFHLen);
|
||||||
|
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < numEntries; i++) {
|
||||||
|
if (directoryMap.getInt(currentOffset) != kCDESignature) {
|
||||||
|
Log.w(LOG_TAG, "Missed a central dir sig (at " + currentOffset + ")");
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* useful stuff from the directory entry */
|
||||||
|
int fileNameLen = directoryMap.getShort(currentOffset + kCDENameLen) & 0xffff;
|
||||||
|
int extraLen = directoryMap.getShort(currentOffset + kCDEExtraLen) & 0xffff;
|
||||||
|
int commentLen = directoryMap.getShort(currentOffset + kCDECommentLen) & 0xffff;
|
||||||
|
|
||||||
|
/* get the CDE filename */
|
||||||
|
|
||||||
|
directoryMap.position(currentOffset + kCDELen);
|
||||||
|
directoryMap.get(tempBuf, 0, fileNameLen);
|
||||||
|
directoryMap.position(0);
|
||||||
|
|
||||||
|
/* UTF-8 on Android */
|
||||||
|
String str = new String(tempBuf, 0, fileNameLen);
|
||||||
|
if (LOGV) {
|
||||||
|
Log.v(LOG_TAG, "Filename: " + str);
|
||||||
|
}
|
||||||
|
|
||||||
|
ZipEntryRO ze = new ZipEntryRO(zipFileName, file, str);
|
||||||
|
ze.mMethod = directoryMap.getShort(currentOffset + kCDEMethod) & 0xffff;
|
||||||
|
ze.mWhenModified = directoryMap.getInt(currentOffset + kCDEModWhen) & 0xffffffffL;
|
||||||
|
ze.mCRC32 = directoryMap.getLong(currentOffset + kCDECRC) & 0xffffffffL;
|
||||||
|
ze.mCompressedLength = directoryMap.getLong(currentOffset + kCDECompLen) & 0xffffffffL;
|
||||||
|
ze.mUncompressedLength = directoryMap.getLong(currentOffset + kCDEUncompLen) & 0xffffffffL;
|
||||||
|
ze.mLocalHdrOffset = directoryMap.getInt(currentOffset + kCDELocalOffset) & 0xffffffffL;
|
||||||
|
|
||||||
|
// set the offsets
|
||||||
|
buf.clear();
|
||||||
|
ze.setOffsetFromFile(f, buf);
|
||||||
|
|
||||||
|
// put file into hash
|
||||||
|
mHashMap.put(str, ze);
|
||||||
|
|
||||||
|
// go to next directory entry
|
||||||
|
currentOffset += kCDELen + fileNameLen + extraLen + commentLen;
|
||||||
|
}
|
||||||
|
if (LOGV) {
|
||||||
|
Log.v(LOG_TAG, "+++ zip good scan " + numEntries + " entries");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package com.brentvatne.react;
|
package com.brentvatne.react;
|
||||||
|
|
||||||
|
import android.content.res.AssetFileDescriptor;
|
||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@ -16,6 +17,10 @@ import com.facebook.react.uimanager.events.RCTEventEmitter;
|
|||||||
import com.yqritc.scalablevideoview.ScalableType;
|
import com.yqritc.scalablevideoview.ScalableType;
|
||||||
import com.yqritc.scalablevideoview.ScalableVideoView;
|
import com.yqritc.scalablevideoview.ScalableVideoView;
|
||||||
|
|
||||||
|
import com.android.vending.expansion.zipfile.APKExpansionSupport;
|
||||||
|
import com.android.vending.expansion.zipfile.ZipResourceFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -86,7 +91,11 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
private float mRate = 1.0f;
|
private float mRate = 1.0f;
|
||||||
private boolean mPlayInBackground = false;
|
private boolean mPlayInBackground = false;
|
||||||
|
|
||||||
|
private int mMainVer = 0;
|
||||||
|
private int mPatchVer = 0;
|
||||||
|
|
||||||
private boolean mMediaPlayerValid = false; // True if mMediaPlayer is in prepared, started, paused or completed state.
|
private boolean mMediaPlayerValid = false; // True if mMediaPlayer is in prepared, started, paused or completed state.
|
||||||
|
|
||||||
private int mVideoDuration = 0;
|
private int mVideoDuration = 0;
|
||||||
private int mVideoBufferedDuration = 0;
|
private int mVideoBufferedDuration = 0;
|
||||||
private boolean isCompleted = false;
|
private boolean isCompleted = false;
|
||||||
@ -160,11 +169,18 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset) {
|
public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset) {
|
||||||
|
setSrc(uriString,type,isNetwork,isAsset,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset, final int expansionMainVersion, final int expansionPatchVersion) {
|
||||||
|
|
||||||
mSrcUriString = uriString;
|
mSrcUriString = uriString;
|
||||||
mSrcType = type;
|
mSrcType = type;
|
||||||
mSrcIsNetwork = isNetwork;
|
mSrcIsNetwork = isNetwork;
|
||||||
mSrcIsAsset = isAsset;
|
mSrcIsAsset = isAsset;
|
||||||
|
mMainVer = expansionMainVersion;
|
||||||
|
mPatchVer = expansionPatchVersion;
|
||||||
|
|
||||||
|
|
||||||
mMediaPlayerValid = false;
|
mMediaPlayerValid = false;
|
||||||
mVideoDuration = 0;
|
mVideoDuration = 0;
|
||||||
@ -199,12 +215,29 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
setDataSource(uriString);
|
setDataSource(uriString);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
ZipResourceFile expansionFile= null;
|
||||||
|
AssetFileDescriptor fd= null;
|
||||||
|
if(mMainVer>0) {
|
||||||
|
try {
|
||||||
|
expansionFile = APKExpansionSupport.getAPKExpansionZipFile(mThemedReactContext, mMainVer, mPatchVer);
|
||||||
|
fd = expansionFile.getAssetFileDescriptor(uriString.replace(".mp4","") + ".mp4");
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(fd==null) {
|
||||||
setRawData(mThemedReactContext.getResources().getIdentifier(
|
setRawData(mThemedReactContext.getResources().getIdentifier(
|
||||||
uriString,
|
uriString,
|
||||||
"raw",
|
"raw",
|
||||||
mThemedReactContext.getPackageName()
|
mThemedReactContext.getPackageName()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
setDataSource(fd.getFileDescriptor(), fd.getStartOffset(),fd.getLength());
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return;
|
return;
|
||||||
@ -214,11 +247,18 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
src.putString(ReactVideoViewManager.PROP_SRC_URI, uriString);
|
src.putString(ReactVideoViewManager.PROP_SRC_URI, uriString);
|
||||||
src.putString(ReactVideoViewManager.PROP_SRC_TYPE, type);
|
src.putString(ReactVideoViewManager.PROP_SRC_TYPE, type);
|
||||||
src.putBoolean(ReactVideoViewManager.PROP_SRC_IS_NETWORK, isNetwork);
|
src.putBoolean(ReactVideoViewManager.PROP_SRC_IS_NETWORK, isNetwork);
|
||||||
|
if(mMainVer>0) {
|
||||||
|
src.putInt(ReactVideoViewManager.PROP_SRC_MAINVER, mMainVer);
|
||||||
|
if(mPatchVer>0) {
|
||||||
|
src.putInt(ReactVideoViewManager.PROP_SRC_PATCHVER, mPatchVer);
|
||||||
|
}
|
||||||
|
}
|
||||||
WritableMap event = Arguments.createMap();
|
WritableMap event = Arguments.createMap();
|
||||||
event.putMap(ReactVideoViewManager.PROP_SRC, src);
|
event.putMap(ReactVideoViewManager.PROP_SRC, src);
|
||||||
mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD_START.toString(), event);
|
mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD_START.toString(), event);
|
||||||
|
|
||||||
prepareAsync(this);
|
// not async to prevent random crashes on Android playback from local resource due to race conditions
|
||||||
|
prepare(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResizeModeModifier(final ScalableType resizeMode) {
|
public void setResizeModeModifier(final ScalableType resizeMode) {
|
||||||
@ -443,9 +483,16 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
protected void onAttachedToWindow() {
|
protected void onAttachedToWindow() {
|
||||||
|
|
||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
|
|
||||||
|
if(mMainVer>0) {
|
||||||
|
setSrc(mSrcUriString, mSrcType, mSrcIsNetwork,mSrcIsAsset,mMainVer,mPatchVer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
setSrc(mSrcUriString, mSrcType, mSrcIsNetwork,mSrcIsAsset);
|
setSrc(mSrcUriString, mSrcType, mSrcIsNetwork,mSrcIsAsset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHostPause() {
|
public void onHostPause() {
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ public class ReactVideoViewManager extends SimpleViewManager<ReactVideoView> {
|
|||||||
public static final String PROP_SRC_URI = "uri";
|
public static final String PROP_SRC_URI = "uri";
|
||||||
public static final String PROP_SRC_TYPE = "type";
|
public static final String PROP_SRC_TYPE = "type";
|
||||||
public static final String PROP_SRC_IS_NETWORK = "isNetwork";
|
public static final String PROP_SRC_IS_NETWORK = "isNetwork";
|
||||||
|
public static final String PROP_SRC_MAINVER = "mainVer";
|
||||||
|
public static final String PROP_SRC_PATCHVER = "patchVer";
|
||||||
public static final String PROP_SRC_IS_ASSET = "isAsset";
|
public static final String PROP_SRC_IS_ASSET = "isAsset";
|
||||||
public static final String PROP_RESIZE_MODE = "resizeMode";
|
public static final String PROP_RESIZE_MODE = "resizeMode";
|
||||||
public static final String PROP_REPEAT = "repeat";
|
public static final String PROP_REPEAT = "repeat";
|
||||||
@ -72,6 +74,21 @@ public class ReactVideoViewManager extends SimpleViewManager<ReactVideoView> {
|
|||||||
|
|
||||||
@ReactProp(name = PROP_SRC)
|
@ReactProp(name = PROP_SRC)
|
||||||
public void setSrc(final ReactVideoView videoView, @Nullable ReadableMap src) {
|
public void setSrc(final ReactVideoView videoView, @Nullable ReadableMap src) {
|
||||||
|
int mainVer = src.getInt(PROP_SRC_MAINVER);
|
||||||
|
int patchVer = src.getInt(PROP_SRC_PATCHVER);
|
||||||
|
if(mainVer<0) { mainVer = 0; }
|
||||||
|
if(patchVer<0) { patchVer = 0; }
|
||||||
|
if(mainVer>0) {
|
||||||
|
videoView.setSrc(
|
||||||
|
src.getString(PROP_SRC_URI),
|
||||||
|
src.getString(PROP_SRC_TYPE),
|
||||||
|
src.getBoolean(PROP_SRC_IS_NETWORK),,
|
||||||
|
src.getBoolean(PROP_SRC_IS_ASSET)
|
||||||
|
mainVer,
|
||||||
|
patchVer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
videoView.setSrc(
|
videoView.setSrc(
|
||||||
src.getString(PROP_SRC_URI),
|
src.getString(PROP_SRC_URI),
|
||||||
src.getString(PROP_SRC_TYPE),
|
src.getString(PROP_SRC_TYPE),
|
||||||
@ -79,6 +96,7 @@ public class ReactVideoViewManager extends SimpleViewManager<ReactVideoView> {
|
|||||||
src.getBoolean(PROP_SRC_IS_ASSET)
|
src.getBoolean(PROP_SRC_IS_ASSET)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_RESIZE_MODE)
|
@ReactProp(name = PROP_RESIZE_MODE)
|
||||||
public void setResizeMode(final ReactVideoView videoView, final String resizeModeOrdinalString) {
|
public void setResizeMode(final ReactVideoView videoView, final String resizeModeOrdinalString) {
|
||||||
|
Loading…
Reference in New Issue
Block a user