I know there are already a few questions like this on SO, but they relate to extracting the file before playing it.
In the Android docs here it explains that you can play files directly from a .zip file without extracting it.
Tip: If you're packaging media files into a ZIP, you can use media
playback calls on the files with offset and length controls (such as
MediaPlayer.setDataSource() and SoundPool.load()) without the need to
unpack your ZIP. In order for this to work, you must not perform
additional compression on the media files when creating the ZIP
packages. For example, when using the zip tool, you should use the -n
option to specify the file suffixes that should not be compressed:
zip -n .mp4;.ogg main_expansion media_files
I've made an (uncompressed) zip package, but I cannot figure out how to get from a ZipEntry to a FileDescriptor, and they don't explain any further. How do I get a FileDescriptor without unpacking the zip file?
You can use The APK Expansion Zip Library to do that even if you do not use APK Expansions in your app.
Follow this documentation to get the library: Using the APK Expansion Zip Library
Zip your sound files without compression using your favorite zip tool.
Use this code to load the music:
ZipResourceFile expansionFile = new ZipResourceFile("myZipFile.zip");
AssetFileDescriptor assetFileDescriptor = expansionFile.getAssetFileDescriptor("myMusic.mp3");
try {
mediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor());
mediaPlayer.prepare();
mediaPlayer.start();
}
catch (IOException e) {
// Handle exception
}
There aren't many recent solutions for this issue, so I was stuck on this, too – until I created a new instance of MediaPlayer in the function where I was trying to read the zip file. Suddenly playback started without problems. Now, instead, I'm passing my (global) MediaPlayer to the function like this: (Kotlin)
private fun preparePlayer(mp: MediaPlayer, position: Int) {
// Path of shared storage
val root: File = Environment.getExternalStorageDirectory()
Log.i("ROOT", root.toString())
// path of the zip file
val zipFilePath = File(root.absolutePath+"/Android/obb/MY_PACKAGE_NAME/MY_ZIP_FILE.zip")
// Is zip file recognized?
val zipFileExists = zipFilePath.exists()
Log.i("Does zip file exist?", zipFileExists.toString())
// Define the zip file as ZipResourceFile
val expansionFile = ZipResourceFile(zipFilePath.absolutePath)
// Your media in the zip file
val afd = expansionFile.getAssetFileDescriptor("track_01.mp3")
// val mp = MediaPlayer() // not necessary if you pass it as a function parameter
mp.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
mp.prepare()
mp.start() // Music should start playing automatically
Maybe this can help someone else. Good luck!
try {
ZipFile zf= new ZipFile(filename);
ZipEntry ze = zip.getEntry(fileName);
if (ze!= null) {
InputStream in = zf.getInputStream(ze);
File f = File.createTempFile("_AUDIO_", ".wav");
FileOutputStream out = new FileOutputStream(f);
IOUtils.copy(in, out);
// play f
}
} catch (IOException e) {
}
possible duplicate question
If you want to use a media file from a zip without unzipping you have to add also start offset and length to setDataSource.
ZipResourceFile expansionFile = new ZipResourceFile("myZipFile.zip");
AssetFileDescriptor assetFileDescriptor = expansionFile.getAssetFileDescriptor("myMusic.mp3");
try {
mediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(),
assetFileDescriptor.getStartOffset(),
assetFileDescriptor.getLength());
mediaPlayer.prepare();
mediaPlayer.start();
}
catch (IOException e) {
// Handle exception
}
You can create a custom MediaDataSource like this:
public class ZipMediaDataSource extends MediaDataSource {
private InputStream inputStream;
public ZipMediaDataSource(ZipFile file, ZipEntry zipEntry) throws IOException {
inputStream = file.getInputStream(zipEntry);
}
#Override
public int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
return inputStream.read(buffer, offset, size);
}
#Override
public long getSize() throws IOException {
return inputStream.available();
}
#Override
public void close() throws IOException {
if (inputStream != null) {
inputStream.close();
inputStream = null;
}
}
}
usage is:
ZipMediaDataSource zipMediaDataSource = new ZipMediaDataSource(zipFile, zipEntry);
mediaPlayer.setDataSource(zipMediaDataSource);
Related
I have been developing an App which need to play sounds and have large set of sounds files in .wav format that boast arounds near 1GB size. I have followed the official documentation for making the Expansion file.
First i made a .zip file with 0 compressions, all .wav files are under that files (without any subfolder. that is main.1.package contains all the sounds file).Then i renamed the .zip folder into .obb. After that i put that .obb file inside the apps folder under shared storage obb folder (com.moinul.app is my package name, so the folder name was it, also the expansion file were named main.1.com.moinul.app.obb )
Problem is when i try to read the files from my expansion files i get null pointer exceptions:
try
{
String param = (String) v.getTag();
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(context, 1, 0);
AssetFileDescriptor fd = expansionFile.getAssetFileDescriptor("p25_1.wav");
MediaPlayer mPlayer = new MediaPlayer();
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mPlayer.setDataSource(fd.getFileDescriptor());
mPlayer.prepare();
mPlayer.start();
}
catch (Exception e)
{
Log.w(TAG, e.getMessage() + "");
e.printStackTrace();
}
I get exceptions saying that fd is null. Any help would be appreciated.
Thanks
Try use: AssetFileDescriptor fd = expansionFile.getAssetFileDescriptor("assets/p25_1.wav");
Try
`public class Test extends Activity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String TAG = "Test";
Context context = this;
try {
MediaPlayer mediaPlayer = new MediaPlayer();
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(context, 1, 0);
AssetFileDescriptor assetFileDescriptor = expansionFile.getAssetFileDescriptor("assets/p25_1.wav");
if (assetFileDescriptor != null) {
final FileInputStream fis = assetFileDescriptor.createInputStream();
if (fis != null) {
mediaPlayer.setDataSource(fis.getFD());
fis.close();
mediaPlayer.prepare();
mediaPlayer.start();
} else {
Log.w(TAG, "/fis: null");
}
}
} catch (Exception e) {
Log.w(TAG, e.getMessage() + "");
}
}
}`
Ok so it worked finally. After changing into :
AssetFileDescriptor fd = expansionFile.getAssetFileDescriptor("Sounds/p25_1.wav");
Its working now. Its weird that i had to add Sounds, which was the folder name before compress (i actually compressed this folder and all .wav are under it). I think the compressor made another subfolder inside the .zip.
Anyway thanks for the help.
I'm trying to load a movie from a file within a mounted Google Play APK expansion OBB file.
mMediaPlayer = new MediaPlayer();
StorageManager storageManager = (StorageManager)mParentActivity.getSystemService(Context.STORAGE_SERVICE);
String obbPath = ExpansionHelper.getExpansionFilePath(mParentActivity);
File movie = new File(storageManager.getMountedObbPath(obbPath), filename);
Log.d(Constants.TAG, "Movie exists is " + movie.exists());
mMediaPlayer.setDataSource(obbPath);
Note: movie exists logs 'true'
E/MediaPlayer(27155): Error (1,-2147483648) Error while opening the
file. Unloading the media player (Unspecified media player error,
-2147483648) E/MediaPlayer(27155): stop called in state 0 E/MediaPlayer(27155): error (-38, 0)
How can I play a movie from an APK OBB expansion file (not the zip kind)?
I'm not exactly sure why this method does work but if you provide a FileDescriptor from FileInputStream is work like a charm!
FileInputStream fis = new FileInputStream(movie);
mMediaPlayer.setDataSource(fis.getFD());
fis.close();
Oh, just seen this question regards non-zipped files, well here is zipped version anyway:
private static void setMediaPlayerDataSourceFromZip(MediaPlayer mediaPlayer,
String zipFileName, String fileNameInZip) throws IOException,
FileNotFoundException {
ZipResourceFile zip = new ZipResourceFile(zipFileName);
FileInputStream fis = new FileInputStream(zipFileName);
try {
FileDescriptor zipfd = fis.getFD();
ZipEntryRO entry = zipFindFile(zip, fileNameInZip);
mediaPlayer.setDataSource(zipfd, entry.mOffset,
entry.mUncompressedLength);
} finally {
fis.close();
}
}
private static ZipEntryRO zipFindFile(ZipResourceFile zip, String fileNameInZip) {
for (ZipEntryRO entry : zip.getAllEntries()) {
if (entry.mFileName.equals(fileNameInZip))
return entry;
}
throw new RuntimeException(String.format("File \"%s\"not found in zip", fileNameInZip));
}
Usage:
setMediaPlayerDataSourceFromZip(mediaPlayer,
"/Some/zip/obb/withoutCompression.zip",
"path/within/zip/mymovie.mp4");
Reading mp3 file from expansion file
I have create and expansion file with name
"main.1.com.example.app.obb"
which contains and audio file name "about_eng.mp3"
Now the issue i have written the following code to read and play the mp3 file
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";
File main = new File(strMainPath);
if ( main.isFile() ) {
ret.add(strMainPath);
}
}
if ( patchVersion > 0 ) {
String strPatchPath = expPath + File.separator + "patch." +
mainVersion + "." + packageName + ".obb";
File main = new File(strPatchPath);
if ( main.isFile() ) {
ret.add(strPatchPath);
}
}
}
}
String[] retArray = new String[ret.size()];
ret.toArray(retArray);
return retArray;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
// Get a ZipResourceFile representing a merger of both the main and patch files
try {
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(this,1,1);
if(expansionFile!=null){
AssetFileDescriptor fd = expansionFile.getAssetFileDescriptor("about_eng.mp3");
//or
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource( fd.getFileDescriptor(),
fd.getStartOffset(),fd.getLength());
mediaPlayer.prepare();
mediaPlayer.start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Get an input stream for a known file inside the expansion file ZIPs
}
But it is always throwing exception at this line
mediaPlayer.setDataSource( fd.getFileDescriptor(),
fd.getStartOffset(),fd.getLength());
mediaPlayer.prepare();
because the variable fd is null.
Can any body help me to solve this
i have googled and found that we shold have to make .zip with 0% (No compression) that is mention in http://developer.android.com/google/play/expansion-files.html
Tip: If you're packaging media files into a ZIP, you can use media playback calls on the files with offset and length controls (such as MediaPlayer.setDataSource() and SoundPool.load()) without the need to unpack your ZIP. In order for this to work, you must not perform additional compression on the media files when creating the ZIP packages. For example, when using the zip tool, you should use the -n option to specify the file suffixes that should not be compressed:
zip -n .mp4;.ogg main_expansion media_files
so How to make 0% compression zip using winrar?
here see the compression method
so we should have to upload this zip in play store.
so you not need to use ZipHelper.java that is mentioned in other SO answer
just simply use
ZipResourceFile expansionFile=null;
try {
expansionFile = APKExpansionSupport.getAPKExpansionZipFile(getApplicationContext(),3,0);
AssetFileDescriptor fd = expansionFile.getAssetFileDescriptor("test.mp3");
MediaPlayer mPlayer = new MediaPlayer();
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mPlayer.setDataSource(fd.getFileDescriptor(),fd.getStartOffset(),fd.getLength());
mPlayer.prepare();
mPlayer.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Try to use expansionFile.getAssetFileDescriptor("main_expansion/about_eng.mp3").
If it doesn't work, call this:
ZipResourceFile.ZipEntryRO [] entries = expansionFile.getAllEntries();
and look at the file names:
entries[0].mFileName;.
That could be a hint how the path should look like.
I know that the question is old, but maybe it helps someone.
To read mp3, with mediaplayer, you can do it 3 ways
for example. my application package name is
com.bluewindsolution.android.sampleapp
then my expansion file should be
main.1.com.bluewindsolution.android.sampleapp.obb
in detail, you can followed in here
[main|patch].< expansion-version >.< package-name >.obb
From expansion you can get it via 3 ways:
via AssetFileDescriptor
// this one work with image file, media file
// Get a ZipResourceFile representing a specific expansion file
// mainContext, version no. of your main expansion, version no of your patch
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(this, 1, 0);
AssetFileDescriptor afd_img1 = expansionFile.getAssetFileDescriptor("your_path/your_file.jpg");
AssetFileDescriptor afd_mpbg = expansionFile.getAssetFileDescriptor("your_path/your_file.mp3");
// to set image
ImageView imageView1 = (ImageView)findViewById(R.id.iv1);
imageView1.setImageBitmap(BitmapFactory.decodeFileDescriptor(afd_iv1.getFileDescriptor()));
// to set sound
try {
mpbg.setDataSource(afd_mpbg.getFileDescriptor(), afd_mpbg.getStartOffset(), afd_mpbg.getDeclaredLength());
mpbg.prepareAsync();
mpbg.start();
} catch (IllegalStateException e) {
Log.w("Error=====", "Failed to prepare media player", e);
}
via InputStream
// this one work with image file, media file
// Get a ZipResourceFile representing a specific expansion file
ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip);
// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
via Uri
//this one can use in almost thing such as jpg, mp3, mp4, pdf, etc.
//you need to create new class to override APEZProvider
public class ZipFileContentProvider extends APEZProvider {
#Override
public String getAuthority() {
return "com.bluewindsolution.android.sampleapp";
}
}
//you need to add <provider> in AndroidManifest.xml
<provider
android:name="com.bluewindsolution.android.sampleapp.ZipFileContentProvider"
android:authorities="com.bluewindsolution.android.sampleapp"
android:exported="false"
android:multiprocess="true"
>
<meta-data android:name="mainVersion"
android:value="1"></meta-data>
</provider>
//after that you can use it any time by this code:
String filename2 = "image/i_commu_a_1_1_1_1.jpg"; // suppose you store put your file in directory in .obb
String uriString2 = "content://com.bluewindsolution.android.sampleapp" + File.separator + filename2;
Uri uri2 = Uri.parse(uriString2);
imageView2.setImageURI(uri2);
// Filename inside my expansion file
String filename = "video/v_do_1_1_1.mp4"; // suppose you store put your file in directory in .obb
String uriString = "content://com.bw.sample_extension_download_main" + File.separator + filename;
Uri uri = Uri.parse(uriString);
v1 = (VideoView)findViewById(R.id.videoView1);
v1.setVideoURI(uri);
v1.start();
I'm following all official expansion files guides, but i can't find it. I'm unable to access the contained obb file i need.
I need 6 audio files (80Mb) that i "stored" (uncompressed) in a zip file and renamed as 'main.2001.test.expansion.proj.obb' and stored in '/mnt/sdcard/Android/obb/test.expansion.proj/'
I'll try to access to the files
String mainFileName = Helpers.getExpansionAPKFileName(this,true,2001);
if(!Helpers.doesFileExist(this, mainFileName, 27959282L, false))
{
//download
} else {
Log.d("test_file","file exist");
}
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(this,2001,2001);
if(expansionFile!=null)
{
ZipEntryRO[] ziro = expansionFile.getAllEntries();
for (ZipEntryRO entry : ziro) {
Log.d("test_files_zip", "fileZip filename: "+entry.getZipFileName());
try{
AssetFileDescriptor ro = entry.getAssetFileDescriptor();
Log.d("test_files_zip", "--fileZip getfiledescriptor.tostring: "+ro.getFileDescriptor().toString());
Log.d("test_files_zip", "--fileZip createinputstring.tostring: "+ro.createInputStream().toString());
AssetFileDescriptor assetFileDescriptor = expansionFile.getAssetFileDescriptor(entry.getZipFileName()+"/audio02.mp3");
if(assetFileDescriptor!=null) {
Log.d("test_files_mp3", "length: "+assetFileDescriptor.getLength()); //checking it exists
}
}catch (IOException e){ Log.e("test_exp","IoExcp: "+e.getMessage()); }
}
}
In -> assetFileDescriptor = expansionFile.getAssetFileDescriptor(.....); i've tried all i figuret out and found in different places, but i was unable to take the file.
Is there any way to get the file from its name if it's inside the zip?
The app should play these files in an specificorder and we don't want to unzip the files and make them ""public"".
Edited. Answer to myself
Found, it was a line i didn't understand when i firstly read it or i simply miss it.
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(this,2001,2001);
if(expansionFile!=null){
FileDescriptor fd = expansionFile.getAssetFileDescriptor("audio_01.mp3");
//or
InputStream is = expansionFile.getInputStream("audio_01.mp3");
}
As Aarolama Bluenk suggested, here's the answer code (repited)
Found, it was a line i didn't understand when i firstly read it or i simply miss it.
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(this,2001,2001);
if(expansionFile!=null){
FileDescriptor fd = expansionFile.getAssetFileDescriptor("audio_01.mp3");
//or
InputStream is = expansionFile.getInputStream("audio_01.mp3");
}
I am trying to load a plugin implementation of an interface from a jar file which is in the /assets directory of my .apk file. The only way I've been able to get this to work is by extracting the jar file to private external storage and then passing that file to the DexClassLoader.
That works, but why should the jar have to exist in two places (the .apk and private external storage)? The DexClassLoader has to have a file path as its argument.
Is there a way to give it a direct path to the file that is in the /assets folder so that I don't have to use up external storage for an extra copy of what's already present?
Here are the relevant code snippets:
// somewhere in my main Activity ...
final File aExtractedDexFile = new File(getDir("dex", Context.MODE_PRIVATE),
LIBRARY_DEX_JAR);
extractDexTo(aExtractedDexFile);
loadLibraryProvider(aExtractedDexFile);
and
/** Extract the jar file that contains the implementation class.dex and place in private storage */
private void extractDexTo(File tJarInternalStoragePath) {
BufferedInputStream aJarInputStream = null;
OutputStream aDexOutputStream = null;
try {
aJarInputStream = new BufferedInputStream(getAssets().open(LIBRARY_DEX_JAR));
aJarOutputStream = new BufferedOutputStream(new FileOutputStream(tJarInternalStoragePath));
byte[] buf = new byte[BUF_SIZE];
int len;
while ((len = aJarInputStream.read(buf, 0, BUF_SIZE)) > 0)
{
aJarOutputStream.write(buf, 0, len);
}
aJarOutputStream.close();
aJarInputStream.close();
} catch (IOException e) {
if (aDexOutputStream != null) {
try {
aJarOutputStream.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
if (aJarInputStream != null) {
try {
aJarInputStream.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
and
/** Use DexClassLoader to load the classes from LibraryProvider */
private void loadLibraryProvider(File tFile) {
// Internal storage where the DexClassLoader writes the optimized dex file to.
final File aOptimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);
// Initialize the class loader with the secondary dex file.
DexClassLoader cl = new DexClassLoader(tFile.getAbsolutePath(),
aOptimizedDexOutputPath.getAbsolutePath(),
null,
getClassLoader());
Class<?> aLibProviderClazz = null;
try {
// Load the library class from the class loader.
aLibProviderClazz = cl.loadClass(LIBRARY_PROVIDER_CLASS);
sLibraryProvider = (LibraryInterface) aLibProviderClazz.newInstance();
} catch (Exception exception) {
// Handle exception gracefully here.
exception.printStackTrace();
}
}
Is there a way to give it a direct path to the file that is in the /assets folder so that I don't have to use up external storage for an extra copy of what's already present?
The answer is No. I suppose you follow this blog posted by official source implementing your code. if there is a better way of doing things, the bloger should recommend it in his blog.
Reason why you need optimizedDirectory is explained in the API:
This class loader requires an application-private, writable directory to cache optimized classes.
Also note that assets directory is not writable in apk, so it can't be done with purely assets directory.
Reason why you need copy jar file is a little bit subtle, mentioned in the blog:
First, it has to be copied to a storage location whose path can be supplied to the class loader.
Everything (folders/files) embedded within apk archive is not exposable (or interpretable) to the underlying file system at runtime. In another word, dexPath required in both DexClassLoader and PathClassLoader's constructor need a solid path string like /data/data/com.example/dex/common-lib.jar that represents the file in file system.