DiskLruCache for Videos - android

My Android app downloads a bunch of photos and videos from a server and I want to cache this data. I've used DiskLruCach library to cache the images and it works fine but now I want to cache the videos also.
I've tried something like this but it doesn't seem to work - I can't find anything in the cache directory for the videos:
private boolean writeVideoToFile(String videoUri, DiskLruCache.Editor editor ) throws IOException, FileNotFoundException {
OutputStream out = null;
FileOutputStream fos;
try {
out = new BufferedOutputStream( editor.newOutputStream(0), Utils.IO_BUFFER_SIZE );
File videoFile = Utils.createFile(Utils.TYPE_VIDEO_FILE);
fos = new FileOutputStream(videoFile);
fos.write(videoUri.getBytes());
fos.close();
return true;
} finally {
if ( out != null ) {
out.close();
}
}
}
Can anyone give me an ideea on how I can accomplish this?

Not enough info, but I guess the call to getBytes() is the problem, probably an OutOfMemory exception.
Don't read the entire video file into memory (calling getBytes). Use a small intermediate buffer instead, writing/caching the video file chunk by chunk.

You are calling getBytes() for String videoUri.
Is that really what you meant to do?

Related

Can you create a .dat file in Android

I am trying to write my first app for Android. I knew Java formerly but it has been a year or two since I used it.
I want to create a simple file in internal storage - I understand I do not have to set any permissions to create such a file?
Is it a .dat file I need if I want to save an ArrayList? Does Android require me to use file extension when creating it?
Even with just trying the basic file creation - checking for existence of file and then creating it if it does not exist - does not work. Can anyone tell me what I am doing wrong?
(I have commented out the attempt to read the ArrayList, as I cannot even create the file. Just trying the basic file creation.)
(Also, I have tried the code with "Shares.dat" instead of just "Shares" as filename, that didn't work either. I don't even know whether Android recognises .dat files and to be honest I am not 100% sure that is the file I need.)
(If by any chance anyone can help, I may not be able to test any solution until next weekend......)
As for the last but one line, originally it read 'context.getFileDir()' but my class extends ActionBarActivity and I found on internet a suggestion to change to this.getFileDir(). I got a null pointer warning when I used context.getFileDir()
file = new File("Shares");
if (file.exists()){
url.setText("File Exists");
/*try{
is = openFileInput("Shares");
oi = new ObjectInputStream(is);
details = (ArrayList<Action>)oi.readObject();//warning
oi.close();//need finally??
}
catch(Exception e){url.setText((e.getMessage()));}
url.setText(details.get(0).getAddresse());*/
}
else
{
try
{
**file = new File(this.getFilesDir(), "Shares");**
}
catch(Exception e){url.setText((e.getMessage()));}
}
If you want a reference to a file that's created in private storage, you'd want to use getFileStreamPath("shares.dat") instead of creating a new File object. File extension shouldn't matter, but it's a good practice to add a file extension to keep track for yourself what those files are for.
For example:
private boolean fileExists(Context _context, String _filename) {
File temp = _context.getFileStreamPath(_filename);
if(temp == null || !temp.exists()) {
return false;
}
return true;
}
Then, if you wanted to write to a file named "shares.dat" then you'd use openFileOutput("shares.dat", Context.MODE_PRIVATE). If you wanted to read in from that file, you'd use openFileInput("shares.dat").
// Read in from file
if(fileExists(this, "shares.dat")) {
FileInputStream fis = this.openFileInput("shares.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
ArrayList<Action> actions = (ArrayList<Action>)ois.readObject();
ois.close();
}
// Write out to file
FileOutputStream fos = this.openFileOutput("shares.dat", Context.MODE_PRIVATE);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(actions);
oos.close();
All stream operations shown above have the ability to throw an IOException, so be sure to wrap that code in a try/catch block as needed.

Is it possible to retrieve album art from remote mp3 file in Android?

I'm currently writing a UPnP remote control app which is used to connect a remote MediaServer to a remote MediaRenderer. Since the actual MP3 files aren't sent to the Android device, I'd like to be able to get the album art of the currently playing file without having to download the entire MP3 file to my phone.
I've read that MediaMetadataRetriever is useful for this kind of thing, but I haven't been able to get it to work. Each way I try it, I keep getting an IllegalArgumentException by the call to MediaMetadataRetriever#setDataSource, which indicates that my file handle or URI is invalid.
MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();
The following works since it's a direct file path on the device itself:
metaRetriever.setDataSource("/sdcard/Music/Daft_Punk/Homework/01 - Daftendirekt.mp3");
However, any of the following fail with the same error:
metaRetriever.setDataSource(appCtx, Uri.parse("http://192.168.1.144:49153/content/media/object_id/94785/res_id/1/rct/aa"));
metaRetriever.setDataSource(appCtx, Uri.parse("http://192.168.1.144:49153/content/media/object_id/94785/res_id/0/ext/file.mp3"));
metaRetriever.setDataSource("http://192.168.1.144:49153/content/media/object_id/94785/res_id/0/ext/file.mp3");
The first one is the albumArtURI pulled from the UPnP metadata (no *.mp3 extension, but the file will download if pasted into a web browser).
The second and third attempts are using the "res" value from the UPnP metadata, which points to the actual file on the server.
I'm hoping I'm just parsing the URI incorrectly, but I'm out of ideas.
Any suggestions? Also, is there a better way to do this entirely when pulling from a UPnP server? FWIW, I'm using the Cling UPnP library.
== SOLUTION ==
I started looking into william-seemann's answer and it led me to this: MediaMetadataRetriever.setDataSource(String path) no longer accepts URLs
Comment #2 on this post mentions using a different version of setDataSource() that still accepts remote URLs.
Here's what I ended up doing and it's working great:
private Bitmap downloadBitmap(final String url) {
final MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();
metaRetriever.setDataSource(url, new HashMap<String, String>());
try {
final byte[] art = metaRetriever.getEmbeddedPicture();
return BitmapFactory.decodeByteArray(art, 0, art.length);
} catch (Exception e) {
Logger.e(LOGTAG, "Couldn't create album art: " + e.getMessage());
return BitmapFactory.decodeResource(getResources(), R.drawable.album_art_missing);
}
}
FFmpegMediaMetadataRetriever will extract metadata from a remote file (Disclosure: I created it). I has the same interface as MediaMetadataRetriever but it uses FFmpeg as it's backend. Here is an example:
FFmpegMediaMetadataRetriever mmr = new FFmpegMediaMetadataRetriever();
mmr.setDataSource(mUri);
String album = mmr.extractMetadata(FFmpegMediaMetadataRetriever.METADATA_KEY_ALBUM);
String artist = mmr.extractMetadata(FFmpegMediaMetadataRetriever.METADATA_KEY_ARTIST);
byte [] artwork = mmr.getEmbeddedPicture();
mmr.release();
Looking at the source code for MediaMetadataRetriever (not from the official Android repo, but it should still be similar, if not equivalent) showed me this:
if (uri == null) {
throw new IllegalArgumentException();
}
And this:
ContentResolver resolver = context.getContentResolver();
try {
fd = resolver.openAssetFileDescriptor(uri, "r");
} catch(FileNotFoundException e) {
throw new IllegalArgumentException();
}
if (fd == null) {
throw new IllegalArgumentException();
}
FileDescriptor descriptor = fd.getFileDescriptor();
if (!descriptor.valid()) {
throw new IllegalArgumentException();
}
Your exception is coming from one of those blocks.
From looking at the MediaMetadataRetriever documentation and source code, it seems to me that the file has to be on the device. You can use a Uri, but I think it has to be something like "file:///android_asset/mysound.mp3". I could be wrong though; are you sure that MediaMetadataRetriever can be used to resolve files over a network?

How can I read from a read-only RandomAccessFile while a MediaPlayer is playing from it in a different position

I have a custom file format (similar to a zip file) that packs small files (small images, mp3 files) into 1 physical file. My android app downloads this file, and it displays one image from it. The user can touch the image and it'll start to play one of the small mp3 "files" inside the packed file. He can also swipe left or right, and the app displays the previous or next image.
In order to make things smoother I am holding 3 "cards" in the memory: the one currently displayed, and the prevous and the next one. This way when it's swiped, I can immediatelly show the next image. In order to do this, I am preloading the images and the mp3 into the MediaPlayer. The problem is that because of this it is multi threaded, as the preloading is done in the background. I have a bug: when I start to play the mp3, and during it's playing I swipe, the image I preaload is cut in the middle. After lots of debugging, I found the reason: while I load the image, the MediaPlayer is moving the file pointer in the file descriptor, and that causes the next read to read from the middle of the mp3 instead of the image.
Here's the code:
InputStream imageStream = myPackedFile.getBaseStream("cat.jpg"); // this returns an InputStream representing "cat.jpg" from my packed file (which is based on a RandomAccessFile)
Drawable image = Drawable.createFromStream(imageStream, imagePath);
FileDescriptor fd = myPackedFile.getFD();
long pos = myPackedFile.getPos("cat.mp3");
long len = myPackedFile.getLength("cat.mp3");
player.setDataSource(fd, pos, len);
player.prepare();
This is what worked for me: Instead of creating a RandomAccessFile and holding to it, I create a File, and every time I need to access it as a RandomAccessFile I create a new one:
public class PackagedFile {
private File file;
PackagedFile(String filename) {
file = new File(filename);
}
public RandomAccessFile getRandomAccessFile() {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(file, "r");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return raf;
}
}
and the above code became:
InputStream imageStream = myPackedFile.getBaseStream("cat.jpg"); // this returns an InputStream representing "cat.jpg" from my packed file (which is based on a RandomAccessFile)
Drawable image = Drawable.createFromStream(imageStream, imagePath);
FileDescriptor fd = myPackedFile.getRandomAccessFile().getFD();
long pos = myPackedFile.getPos("cat.mp3");
long len = myPackedFile.getLength("cat.mp3");
player.setDataSource(fd, pos, len);
player.prepare();
For API Level 13 and above, one can consider ParcelFileDescriptor.dup to duplicate the file descriptors. For more information, please refer to this link: http://androidxref.com/4.2.2_r1/xref/frameworks/base/core/java/android/app/ActivityThread.java#864

Proper way to share an Image (using Intents)

I create images in my app and want to share these social networks (facebook), mail apps (gmail), and other apps that can "receive" images.
The origin of the problem (I think) is that I don't want to use the external storage as a base for my images. I want to either use my data folder or my cache folder since neither of these require any permission to access.
The code which I use to write my image to file (and I specify the MODE_WORLD_READABLE so that other apps can read them):
FileOutputStream fos = null;
try {
fos = context.openFileOutput("image.jpg", Context.MODE_WORLD_READABLE);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
} finally {
if (fos != null)
fos.close();
}
And this is the code where I share the image:
File internalFile = context.getFileStreamPath("image.jpg");
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(internalFile));
intent.setType("image/jpeg");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(Intent.createChooser(intent, "share"));
This solution is very easy and works fine for apps like facebook but not for example gmail which failes with:
file:// attachment paths must point to file:///mnt/sdcard
There are a number of "hacks" (see below) to get it to work with gmail but I leaves me asking myself if there is an even better way to share images that works without hacks, something I overlooked. So, to the questions:
What is the best way to share images? (external storage?)
Is there any more apps that (mis-)behave just like gmail? (I have seen some trouble with google+)
If there is no other way: Can I write special intents for sharing to specific apps. I have a default way of sharing and override it when the user selects an app on my watch list?
Hacks
Using a path-hack by simply pointing the Uri to:
file:///mnt/sdcard/../../my/package/name/...
This solution doesn't feel right.
Using a ContentProvider as described here. But quoted from the link:
Warning: the method described in the post works well for Gmail, but apparently has some issues with other ACTION_SEND handlers (e.g. the MMS composer).
(Issue: It crashes the MMS composer)
Did you try ParecelableFileDescriptor?
http://developer.android.com/reference/android/os/ParcelFileDescriptor.html
Create with
static ParcelFileDescriptor open(File file, int mode, Handler handler, ParcelFileDescriptor.OnCloseListener listener)
Create a new ParcelFileDescriptor accessing a given file.
static ParcelFileDescriptor open(File file, int mode)
Create a new ParcelFileDescriptor accessing a given file.
Receiver side like this:
Returning an Input Stream from Parcel File Descriptor using Androids DownloadManager
You should to make 3 steps.
Take picture.
public Bitmap takeScreenshot() {
View rootView = findViewById(android.R.id.content).getRootView();
rootView.setDrawingCacheEnabled(true);
return rootView.getDrawingCache();
}
Save picture.
public String saveBitmap(Bitmap bitmap) {
File imagePath = new File(Environment.getExternalStorageDirectory() + “/screenshot.png”);
FileOutputStream fos;
try {
fos = new FileOutputStream(imagePath);
bitmap.compress(CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
Log.e(“GREC”, e.getMessage(), e);
} catch (IOException e) {
Log.e(“GREC”, e.getMessage(), e);
}
return imagePath.getAbsolutePath();
}
Share to social network.

How should I decompress a large data file in AsyncTask.doInBackground?

I have a large data file, which is zipped, and approximately 20MB. When it's unzipped, it's up to about 50MB. The following source code works fine. I found the original on the web somewhere else and modified it a bit. And this method is called within the AsyncTask.doInBackground.
So, what I want to know is, how can I save the on going status(? sorry, I don't know the proper English word) and resume the procedure later? I mean, this method takes a bit long time (about a minute on an emulator), and I know there is no way since the data is kind of huge. So, if a main activity of this method gets killed, I want to save the current status of decompressing the file, and when the activity gets active, I want to resume decompressing from the last point. Hope my explanation clears my intent.
I was thinking using a service, but I also want to interact with UI, such as showing a progress or whatever. I can't find good information to do that in the service when I roughly scan the reference, but is there a way to do that in the service? And do you think I should use it?
Anyway, my main point is how to resume decompressing a file.
private final static int CHUNK_SIZE = 32 * 1024;
byte[] _fileIOBuffer = new byte[CHUNK_SIZE];
public void unzipFile(DBFileDownloader downloader, File zipFile, String directory)
throws IOException
{
ZipInputStream in = null;
FileOutputStream os = null;
try
{
in = new ZipInputStream (new FileInputStream(zipFile));
ZipEntry entry = null;
while ((entry = in.getNextEntry ())!= null)
{
String entryName = entry.getName();
if (entry.isDirectory ()) {
File file = new File (directory, entryName);
file.mkdirs();
}
else {
File file = new File(directory, entryName);
if (file.exists()){
file.delete(); // I don't know how to append, so delete it always
}
os = new FileOutputStream (file);
int bytesRead = 0;
while ((bytesRead = in.read (_fileIOBuffer))!= -1) {
os.write(_fileIOBuffer, 0, bytesRead);
// progress procedure
}
os.close();
}
}
}
catch (FileNotFoundException e) {
Log.v("unzip", e.getMessage());
}
catch (IOException e) {
Log.v("unzip", e.getMessage());
}
finally{
if (in != null ){
in.close();
}
if (os != null ){
os.close();
}
}
}
Thanks in advance,
yokyo
So, if a main activity of this method
gets killed, I want to save the
current status of decompressing the
file, and when the activity gets
active, I want to resume decompressing
from the last point.
That will be extremely difficult, if not impossible.
I was thinking using a service, but I
also want to interact with UI, such as
showing a progress or whatever.
This is a fine plan. Just have the activity register a listener with the service, and the service calls that listener for "a progress or whatever".

Categories

Resources