currently I'm working on an application that's supposed to download videos onto its internal storage (unfortunately they do NOT have a sd card) and simply play them.
What I've tried so far is:
Standard VideoView, set the /data/data/.../file.mp4 as path - did not work.
Use MediaPlayer with a SurfaceView, use path or file descriptor - did not work.
What I'm having here right now is a slightly modded version of the VideoView, which has the following changes:
if(mUri != null)
{
mMediaPlayer.setDataSource(mContext, mUri);
}
else
{
mMediaPlayer.setDataSource(mFd);
}
Also a setFD method allowing me to set my File Descriptor. This is way easier and cleaner - imo - than putting the whole MediaPlayer/SurfaceView into my activity.
The problem is the following:
The file in the internal storage is created like this:
FileInputStream fis = openFileInput(Downloader.TMP_FILENAME);
FileOutputStream fos = openFileOutput(Downloader.FILENAME
+ ext, Context.MODE_WORLD_READABLE);
byte[] buf = new byte[2048];
while (fis.read(buf) > 0) {
fos.write(buf);
}
fis.close();
fos.close();
deleteFile(Downloader.TMP_FILENAME);
Log.d(TAG, "Replacement done!");
Basically it's reading a temporary file (as I don't want to overwrite the currently played one.. but that doesn't really matter) and writing it (after onCompletion) into the new file, afterwards deleting the temporary file.
I've tested it on 3 different Android versions and devices so far:
- Tablet with Android 2.2
- Nexus 7 with Android 4.2
- HTC Sensation with Android 4.1.2
It simply did not work. It plays lets say the first 100ms of the video incl. sound then a popup pops up and logcat is telling me:
12-19 14:33:48.074: W/MediaPlayer(5560): info/warning (3, 0)
12-19 14:33:48.074: I/MediaPlayer(5560): Info (3,0)
12-19 14:33:48.394: E/MediaPlayer(5560): error (1, -1007)
12-19 14:33:48.394: E/MediaPlayer(5560): Error (1,-1007)
But I really have no idea what this is supposed to mean.
I've searched for it in the whole internet, all I found was "use WORLD_READABLE, it will solve the problem!" or "just use the file descriptor, seems to not care about permissions!". But for me, it does not work.
I'd really appreciate any help.
EDIT:
FileInputStream fid = new FileInputStream((getFilesDir() + "/"
+ Downloader.FILENAME + ext)));
mVideoView.setVideoFD(fid.getFD());
This is how I add the File Descriptor to the MediaPlayer.
After reencoding the file, this is what I get:
12-19 15:06:45.664: E/MediaPlayer(7616): Unable to to create media player
12-19 15:06:45.664: W/System.err(7616): java.io.IOException: setDataSourceFD failed.: status=0x80000000
12-19 15:06:45.664: W/System.err(7616): at android.media.MediaPlayer.setDataSource(Native Method)
12-19 15:06:45.664: W/System.err(7616): at android.media.MediaPlayer.setDataSource(MediaPlayer.java:976)
This is how I download it from my webserver:
Log.i(TAG, "Opening remote connection to " + mFile.getHost()
+ mFile.getPath());
URLConnection c = mFile.openConnection();
c.connect();
final long lastModified = c.getLastModified();
final String mExtension;
if (c.getHeaderField("Content-Disposition") != null) {
final String mFilename = c
.getHeaderField("Content-Disposition").split("=")[1];
Log.i("Downloader", "Filename is " + mFilename
+ ", split length is " + mFilename.split("\\.").length);
mExtension = mFilename.split("\\.")[mFilename
.split("\\.").length - 1];
} else {
mExtension = "mp4";
}
InputStream is = c.getInputStream();
Log.i(TAG, "Creating temporary local file");
// create local temporary file
FileOutputStream fos = mContext.openFileOutput(TMP_FILENAME,
Context.MODE_WORLD_READABLE);
// start reading
byte[] buf = new byte[BUFFER_SIZE];
int bytesRead = 0;
int curRead = 0;
Log.i(TAG, "Starting download.. to " + TMP_FILENAME);
if (mDownloadChangeListener != null)
mDownloadChangeListener.onDownloadStart();
while ((curRead = is.read(buf)) > -1) {
fos.write(buf);
bytesRead += curRead;
}
Log.i(TAG, "Read " + bytesRead + " bytes in total.");
Log.i(TAG, "Download finished!");
// end of stream, tell app to rename file.
if (mDownloadChangeListener != null)
mDownloadChangeListener.onDownloadFinished(TMP_FILENAME,
mExtension);
is.close();
fos.close();
After downloading it, on the onDownloadFinished listener, I execute the following code:
FileInputStream fis = openFileInput(Downloader.TMP_FILENAME);
FileOutputStream fos = openFileOutput(Downloader.FILENAME
+ ext, Context.MODE_WORLD_WRITEABLE);
byte[] buf = new byte[2048];
while (fis.read(buf) > 0) {
fos.write(buf);
}
fis.close();
fos.close();
deleteFile(Downloader.TMP_FILENAME);
Log.d(TAG, "Replacement done!");
Any ideas what could possibly go wrong? My only other idea would be that it's because of the internal storage-thing.. but the error message says something else?
Error -1007 stands for MEDIA_ERROR_MALFORMED. It means that the file you're trying to play doesn't conform to the file specification put down in the official documentation. Something is either wrong with the media you're receiving, or in your method of saving it.
Personally, your method of saving it looks fine to me, so I'm guessing that the source file itself is the problem.
Found the solution. Every way I've tested worked probably, because all errors I received came from "wrong downloaded media files"...
while((curRead = in.read(buf)) {
out.write(buf);
}
Was simply missing a ",0 ,curRead" - all working now. It's obviously because read() can always return less than BUFFER_SIZE and then writes 0-bytes into the rest or something...
Thanks!
Related
I've got a rather odd problem. I'm writing an Android application using the Xamarin framework, and I also have an iOS version of the same app also written in Xamarin. In the app the user can send photos and videos to their friends, and their friends may be on either iOS or Android. This all works fine, and videos taken on an iPhone can be played on an Android device and vice versa.
The problem I am having is when I try to programmatically save a video to the Android gallery, then that video is not able to be played in the gallery. It does appear that the video data it's self is actually copied, but the video is somehow not playable.
My videos are encoded to the mp4 format using the H.264 codec. I believe this is fully supported in Android, and like I said the videos play just fine when played via a VideoView in the app.
The code I am using to copy the videos to the gallery is below. Does anyone have any idea what I am doing wrong here?
public static void SaveVideoToGallery(Activity activity, String filePath) {
// get filename from path
int idx = filePath.LastIndexOf("/") + 1;
String name = filePath.Substring(idx, filePath.Length - idx);
// set in/out files
File inFile = new File(filePath);
File outDir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryMovies);
File outFile = new File(outDir, name);
// Make sure the Pictures directory exists.
outDir.Mkdirs();
// save the file to disc
InputStream iStream = new FileInputStream(inFile);
OutputStream oStream = new FileOutputStream(outFile);
byte[]data = new byte[iStream.Available()];
iStream.Read();
oStream.Write(data);
iStream.Close();
oStream.Close();
// Tell the media scanner about the new file so that it is
// immediately available to the user.
MediaScannerConnection.ScanFile(
activity.ApplicationContext,
new String[] { outFile.ToString() },
null,
null);
}
NOTE: I know this is all in C#, but keep in mind that all the Xamarin framework does is provide an API to the native Android methods. Everything I am using is either Java or Android backed classes/functions.
Thanks!
Your issue is in this code snippet:
byte[]data = new byte[iStream.Available()];
iStream.Read();
oStream.Write(data);
There are a few issues here:
You never read the files contents into the data buffer; iStream.Read() will only read a single byte and return it as an integer.
new byte[iStream.Available()] will only allocate the amount of data bytes that are available to be read without blocking. It isn't the full file. See the docs on the available method.
oStream.Write(data) writes out a garbage block of data as nothing is ever read into it.
The end result is the outputted video file is just a block of empty data hence why the gallery cannot use it.
Fix it reading in the data from the file stream and then writing them into the output file:
int bytes = 0;
byte[] data = new byte[1024];
while ((bytes = iStream.Read(data)) != -1)
{
oStream.Write (data, 0, bytes);
}
Full sample:
public static void SaveVideoToGallery(Activity activity, String filePath) {
// get filename from path
int idx = filePath.LastIndexOf("/") + 1;
String name = filePath.Substring(idx, filePath.Length - idx);
// set in/out files
File inFile = new File(filePath);
File outDir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryMovies);
File outFile = new File(outDir, name);
// Make sure the Pictures directory exists.
outDir.Mkdirs();
// save the file to disc
InputStream iStream = new FileInputStream(inFile);
OutputStream oStream = new FileOutputStream(outFile);
int bytes = 0;
byte[] data = new byte[1024];
while ((bytes = iStream.Read(data)) != -1)
{
oStream.Write (data, 0, bytes);
}
iStream.Close();
oStream.Close();
// Tell the media scanner about the new file so that it is
// immediately available to the user.
MediaScannerConnection.ScanFile(
activity.ApplicationContext,
new String[] { outFile.ToString() },
null,
null);
}
I have this snippet of code in which I want to handle the downlaod of the file being clicked:
else if (url.startsWith("http://rapahh.com/songs2/Music%20Promotion/Download/")) {
}
return false;
Although I have no idea how to handle downloads in Android, so does anyone have a snippet of code I can use to download the file in the background to a folder.. the download folder is fine. Thanks.
What version of android are you building for?
Starting with API lvl 9 there is the DownloadManager that can handle this for you. If at all possible you should use the DownloadManager, because it will automatically handle network interuptions and resume the downloads for you.
If you are aiming for lower API lvl than that you'll have to make the download code yourself. You'll have an inputStream coming from your web source and an outputStream going to your local file and you will loop through the inputStream writing chunks until there are none left.
Something like this:
try {
URL url = new URL(URL); //URL of the video
//Set our file to the correct path and name.
File file = new File(PATH + fileName);
//keep the start time so we can display how long it took to the Log.
long startTime = System.currentTimeMillis();
Log.d(myTag, "download begining");
//Log.d(myTag, "download url:" + url);
Log.d(myTag, "downloaded file name:" + fileName);
/* Open a connection to that URL. */
URLConnection ucon = url.openConnection();
// this will be useful so that you can show a tipical 0-100% progress bar
int lenghtOfFile = ucon.getContentLength();
Log.i(myTag, "Opened Connection");
/************************************************
* Define InputStreams to read from the URLConnection.
************************************************/
InputStream is = ucon.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
Log.i(myTag, "Got InputStream and BufferedInputStream");
/************************************************
* Define OutputStreams to write to our file.
************************************************/
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
Log.i(myTag, "Got FileOutputStream and BufferedOutputStream");
/************************************************
* Start reading the and writing our file.
************************************************/
byte data[] = new byte[1024];
long total = 0;
int count;
//loop and read the current chunk
while ((count = bis.read(data)) != -1) {
//Post our progress update back to the UI thread
postProgress((int)(total*100/lenghtOfFile));
//write this chunk
total += count;
bos.write(data, 0, count);
}
//Have to call flush or the video file can get corrupted and won't play correctly.
bos.flush();
bos.close();
Log.d(myTag, "download ready in "
+ ((System.currentTimeMillis() - startTime))
+ " milisec");
} catch (IOException e) {
Log.d(myTag, "Error: " + e);
}
You'll need to implement the postProgress(int progress) method to do whatever is appropriate for your application to inform the user of what percentage complete the download is.
Edit:
you can comment out the logs to get it to work. I leave them on while I am debugging though to make the process easier. Log statements such as Log.i(String tag, String text)
are similar to System.out.println(String txt) The difference is that these statements are printed into the log file ( which you can see in the DDMS perspective in eclipse) And they have an additional parameter called "tag" you can pass it whatever string you like and this string will show up along side of your text in the log file. You can also filter the log output basted on these tags in the DDMS perspective. It is common practice to declare your tag as a static String so that you can just use that reference to it for all of your log statements and you are guaranteed to always have the same tag. So if you add something like this to your class it should fix your error:
final static String myTag = "NameOfYourActivity";
This question already has an answer here:
I need to be able to store sound files for my application on sdcard
(1 answer)
Closed 2 years ago.
I found this code which appears to be what I need in that it will copy byte for byte a file to the SDCard.
But how do I use it? say I have a text file called mytext.txt where do I put it in my application? and how would I reference it? I am using Eclipse
public static final void copyfile(String srFile, String dtFile){
try{
File f1 = new File(srFile);
File f2 = new File(dtFile);
InputStream in = new FileInputStream(f1);
OutputStream out = new FileOutputStream(f2);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0){
out.write(buf, 0, len);
}
in.close();
out.close();
System.out.println("File copied to " + f2.getAbsolutePath());
} catch(FileNotFoundException ex){
System.out.println(ex.getMessage() + " in the specified directory.");
System.exit(0);
} catch(IOException e){
System.out.println(e.getMessage());
}
}
I would make a FileUtilities class or somesuch. Have you looked at the examples here?
http://download.oracle.com/javase/tutorial/essential/io/copy.html
http://www.java2s.com/Code/Java/File-Input-Output/FileCopyinJava.htm
You don't want to blindly execute this code. It looks like it's meant for a java console app. System Printlines do not go anywhere that the user would see in an android application. I do not know what System.exit() does in an Android application, but you don't want to do this either. Depending on your application, you may want to add a toast notification that a copy fails. You want to at least log this.
Depending on the size of files you are copying, you may want to do this in a background thread as to not clog up your UI.
Well, at first glance that appears to be a sound method, except that you'd want to replace the System.out print statements with an android Log method... but besides that you could copy/paste that and include that method in a class.
To use it, however... you should have a look at the External Storage documentation.
http://developer.android.com/guide/topics/data/data-storage.html#filesExternal
You're going to need to use Android methods to get correct sdcard directories, etc...
You can add it as another method of your own Activity if your code is small, or you can create a utility class, let's suppose
class MyUtilities {
public static final void copyfile(String srFile, String dtFile) throws IOException, FileNotFoundException{
File f1 = new File(srFile);
File f2 = new File(dtFile);
InputStream in = new FileInputStream(f1);
OutputStream out = new FileOutputStream(f2);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0){
out.write(buf, 0, len);
}
in.close();
out.close();
Log.d("MyUtilities", "File copied to " + f2.getAbsolutePath());
}
}
and you will use it as:
TextEdit text1 = findViewById(R.id.text1);
TextEdit text2 = findViewById(R.id.text2);
String file1 = text1.getText();
String file2 = text2.getText();
if (text1 != null and text2 != null) {
try{
MyUtilities.copyfile (file1, file2);
} catch(FileNotFoundException ex){
Log.e("MyUtilities", ex.getMessage() + " in the specified directory.");
} catch(IOException e){
Log.e("MyUtilities", e.getMessage());
}
}
I added logs instead of the System.out and changed the Exception mechanism to better match android needs.
I've read a lot of topics but none seem to cover what I need.
I basically have a load of sound files and I want to be able to play them in the application from the sdcard.
I also want to be able to install them there in the first place when the application is installed.
I am using Eclipse with the android SDK and currently my Target project is v1.6
Can anyone help?
Thanks
OK so I found the answer!
First we need to get the external Storage Directory to a variable called baseDir.
String baseDir = Environment.getExternalStorageDirectory().getAbsolutePath();
Then Create the directory mysounds on the SDcard
File folder = new File(Environment.getExternalStorageDirectory() + "/mysounds");
boolean success = false;
if(!folder.exists())
{
success = folder.mkdir();
}
if (!success)
{
// Do something on success
}
else
{
// Do something else on failure
}
Then This following bit of code will copy all the files with sound at the beginning of the name from the assets directory to the mysounds directory you have already created.
try {
AssetManager am = getAssets();
String[] list = am.list("");
for (String s:list) {
if (s.startsWith("sound")) {
Log.d("Notice", "Copying asset file " + s);
InputStream inStream = am.open(s);
int size = inStream.available();
byte[] buffer = new byte[size];
inStream.read(buffer);
inStream.close();
FileOutputStream fos = new FileOutputStream(baseDir + "/mysounds/" + s);
fos.write(buffer);
fos.close();
}
}
}
Hope this helps someone!
I am working on an application that plays a video file on a loop. Up until now I was just mounting the device and copying the video file onto the SD card and then using the file path to start it up on my VideoView. I am trying to implement a way that I can remotely update what video it plays so I have moved to storing my video online. Inside the app I check for a local copy and download if it doesn't exist, or if there is a newer one. I have tested it on two different video files both .mp4s. After downloading one of them plays the first time but upon trying to start again for the loop it tells me video cannot be played. The other won't even play the first time, it just gives me the dialog that says the video cannot be played. Both of these files work correctly with my app if I copy them onto the SD card via the USB cable. They work if I exit my app and manually download them with something else(dropbox) but not if I download them from within my app. Here is the code I am using to download the file:
public static void DownloadFromUrl(String fileName) { //this is the downloader method
try {
URL url = new URL("http://dl.dropbox.com/u/myfile.mp4");
File file = new File(PATH + fileName);
long startTime = System.currentTimeMillis();
Log.d(myTag, "download begining");
Log.d(myTag, "download url:" + url);
Log.d(myTag, "downloaded file name:" + fileName);
/* Open a connection to that URL. */
URLConnection ucon = url.openConnection();
Log.i(myTag, "Opened Connection");
/*
* Define InputStreams to read from the URLConnection.
*/
InputStream is = ucon.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
Log.i(myTag, "Got InputStream and BufferedInputStream");
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
Log.i(myTag, "Got FileOutputStream and BufferedOutputStream");
/*
* Read bytes to the Buffer until there is nothing more to read(-1).
*/
int current = 0;
Log.i(myTag, "About to write");
while ((current = bis.read()) != -1) {
bos.write(current);
}
fos.close();
Log.d(myTag, "download ready in"
+ ((System.currentTimeMillis() - startTime))
+ " sec");
} catch (IOException e) {
Log.d(myTag, "Error: " + e);
}
}
I know the dropbox url in this snippet is not correct I changed it only for this post, in my app the url is pointing to a file correctly. And the variable PATH thats used when creating the File is set in my code outside of this snippet.
Is there something about this code snippet that could be corrupting my mp4 files?
That method was corrupting the file somehow, I am still not quite sure how but I changed part of it and now it is fixed.
I am now using this:
byte data[] = new byte[1024];
long total = 0;
int count;
while ((count = bis.read(data)) != -1) {
total += count;
fos.write(data, 0, count);
}
fos.flush();
fos.close();
instead of the old while loop and it works correctly.