Android Mediaplayer: setDataSource issue for downloaded media file - android

I have an application that will record and play audio files. Some of the audio files are downloaded using simple standard http downloads using httpclient. It worked like a charm for a long time. Now all of a sudden I cannot play the files I download. It fails with this stack. I store the files on the SDCard and I experience the problem both on a handset and a USB connected device.
I have checked that the downloaded file is cool on the server, and I can play it without any issues.
These are the code snippets I use ( I know that recordingFile is a valid path for the file).
// inside the activity class
private void playRecording() throws IOException{
File recordingFile = new File(recordingFileName);
FileInputStream recordingInputStream = new FileInputStream(recordingFile);
audioMediaPlayer.playAudio(recordingInputStream);
}
Here is the media player code:
// inside my media player class which handles the recordings
public void playAudio(FileInputStream audioInputStream) throws IOException {
mediaPlayer.reset();
mediaPlayer.setDataSource(audioInputStream.getFD());
mediaPlayer.prepare();
mediaPlayer.start();
}
Here is the exception:
E/MediaPlayerService( 555): offset error
E/MediaPlayer( 786): Unable to to create media player
W/System.err( 786): java.io.IOException: setDataSourceFD failed.: status=0x80000000
W/System.err( 786): at android.media.MediaPlayer.setDataSource(Native Method)
W/System.err( 786): at android.media.MediaPlayer.setDataSource(MediaPlayer.java:632)
W/System.err( 786): at net.xxx.xxx.AudioMediaPlayer.playAudio(AudioMediaPlayer.java:69)
W/System.err( 786): at net.xxx.xxx.Downloads.playRecording(Downloads.java:299)
W/System.err( 786): at net.xxx.xxx.Downloads.access$0(Downloads.java:294)
W/System.err( 786): at net.xxx.xxx.Downloads$1.onClick(Downloads.java:135)
I have tried seeking some answer of the offset error, but not really clear what this issue might be.
PS I download the file with this code:
public FileOutputStream executeHttpGet(FileOutputStream fileOutputStream) throws ClientProtocolException, IOException{
try {
// Execute HTTP Post Request
httpResponse = httpClient.execute(httpPost, localContext);
int status = httpResponse.getStatusLine().getStatusCode();
// we assume that the response body contains the error message
if (status != HttpStatus.SC_OK) {
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
httpResponse.getEntity().writeTo(ostream);
fileOutputStream = null;
} else {
InputStream content = httpResponse.getEntity().getContent();
byte[] buffer = new byte[1024];
int len = 0;
while ( (len = content.read(buffer)) > 0 ) {
fileOutputStream.write(buffer,0, len);
}
fileOutputStream.close();
content.close(); // this will also close the connection
}
} catch (ClientProtocolException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
fileOutputStream = null;
} catch (IOException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
fileOutputStream = null;
}
return fileOutputStream;
}

I solved it on my own. As I put it my comment above the solution was this:
When I refactored part of the code I made a typo on a hash code I use to allow downloads and not. Unfortunately I didn't have the proper catch when I downloaded the file forcing the file to be empty. Basically I send a bad request header if you try to retrieve a file without a proper activation code.
The culprit was here:
if (status != HttpStatus.SC_OK) {
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
httpResponse.getEntity().writeTo(ostream);
fileOutputStream = null;
} else {
InputStream content = httpResponse.getEntity().getContent();
byte[] buffer = new byte[1024];
int len = 0;
while ( (len = content.read(buffer)) > 0 ) {
fileOutputStream.write(buffer,0, len);
}
fileOutputStream.close();
content.close(); // this will also close the connection
}
For cases where the status code came back a as bad (i.e. bad request header for blocked accesses). What I missed was to capture the case of a null pointer there and that caused a SQLite entry to be updated claiming to the app that the download was successful but yet it wasn't.
Lesson learnt: Always put in the null checks for these cases even for prototypes. :-)

First, make sure your device is not mounted. Either Android or the host PC can access the SD card, but not both simultaneously.
Second, it is unclear why you are using a FileInputStream and getFD(). Just pass the path to the file on the SD card to the MediaPlayer (e.g., new File(Environment.getExternalStorageDirectory(), "yourfile.mp3")) and let the player open the file.

Related

Using cache in ExoPlayer for persisting streamed video

UPDATE:
I found this post, which details exactly the same problem I am seeing. It turns out that the fact I am using a Pipe approach in my DocumentsProvider to stream content from DropBox means that ExoPlayer doesn't know the size of the file ahead of time, and so by default was not saving it to the cache.
So I ended up doing what I presume the author did - I created a custom CacheDataSource for these situations that alters the DataSpec.flags variable in the open() method of that class:
public long open(DataSpec dataSpec) throws IOException {
try {
key = cacheKeyFactory.buildCacheKey(dataSpec);
uri = dataSpec.uri;
actualUri = getRedirectedUriOrDefault(cache, key, /* defaultUri= */ uri);
httpMethod = dataSpec.httpMethod;
if ( !dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH) ) { // <-- update here
flags = (dataSpec.flags | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
} else {
flags = dataSpec.flags;
}
readPosition = dataSpec.position;
Not the optimum solution, and I also chimed in on the other post with a request for a more supported way to indicate this flag should be set.
But at least now my streamed files are being saved in the cache.
I am implementing a customer CacheDataSourceFactory for ExoPlayer2, in order to implement a cache to store videos streamed to ExoPlayer.
I have reviewed several posts here, this one was helpful in getting the general approach right to have a video cached into the directory of my choice.
I noticed that when handling a URI that resolves to my custom DocumentsProvider, the Cache defined by the CacheDataSourceFactory is only used to store what looks like a "pointer" or "index" file ("cached_content_index.exi"). Looking in that file I see the URI of the video streamed by my custom DocumentsProvider. However the actual video is not in the cache.
Here is the relevant portion of my Provider, it's quite straight forward:
// Return a descriptor that will stream the file
Timber.d("In openDocument of DropboxProvider for Id: %s, streaming from source", documentId);
ParcelFileDescriptor[] pipe;
try {
pipe = ParcelFileDescriptor.createPipe();
// Get input stream for the pipe
DbxDownloader downloader = mDbxClient.files().download(fileMetadata.getPathLower(), fileMetadata.getRev());
new TransferThread(downloader.getInputStream(), new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]), signal, fileMetadata.getSize()).start();
return pipe[0];
} catch (DbxException dbe) {
Timber.d("Got IDbxException when streaming content: %s", dbe.getMessage());
} catch (IOException ioe) {
Timber.d("Got IOException when streaming content: %s", ioe.getMessage());
} catch (Exception e) {
Timber.d("Got Exception when streaming content: %s", e.getMessage());
}
return null;
And the TransferThread:
private static class TransferThread extends Thread {
final InputStream in;
final OutputStream out;
final CancellationSignal signal;
final long size;
TransferThread(InputStream in, OutputStream out, CancellationSignal signal, long size) {
this.in = in;
this.out = out;
this.signal = signal;
this.size = size;
}
#Override
public void run() {
int biteSize = (8*1024);
if ( size <= (biteSize * 8) ) {
biteSize = Math.max( ((int)(size / (biteSize*2))) * (biteSize * 2), biteSize);
}
Timber.d("TransferThread: File size is: %s, buffer biteSize set to: %d", InTouchUtils.getFormattedFileSize(size), biteSize);
byte[] buf = new byte[biteSize];
int len;
try {
while ( ((len=in.read(buf)) >= 0) && (signal == null || !signal.isCanceled()) ) {
out.write(buf, 0, len);
}
} catch (IOException e) {
// When Glide is used to request a URI where this provider resolves the query,
// it will close the stream out from under us once it has fetched enough bytes
// to render a single frame as an image if the if it is to a video, so
// we swallow that exception here, only logging the error if it isn't that EPIPE
// (broken pipe due to one end being closed) exception.
if ( !(e.getMessage().contains("EPIPE"))) {
Timber.d("TransferThread: Got IOException transferring file: %s", e.getMessage());
}
} finally {
try {
if (in != null) {
in.close();
}
if ( out != null ) {
out.flush();
out.close();
}
Timber.d("TransferThread: Finished streaming file.");
} catch (IOException ioe) {
Timber.d("TransferThread: Got IOException closing file: %s", ioe.getMessage());
}
}
}
}
Again - ExoPlayer seems quite happy with the ParcelFileDescriptor it receives from the DocumentsProvider in this case - it takes the bytes streamed to it and plays the video. I am just not seeing the video file end up in the cache.
I also tried an example streaming a video from my Google Drive (which uses the out-of-the-box documents provider from the SAF), and this time the video did wind up in the cache.
Since they both use the same MediaSource instance - there must be an approach that the Google Docs provider takes so that ExoPlayer knows to place the resulting streamed video in the cache that my custom Dropbox DocumentsProvider is not doing.
Does anyone know how to get to the source code of the DocumentsProvider that ships with the SAF that manages access to Google Docs? I'd like to see what it is doing in its openDocument() method.
Is the fact that the Dropbox provider is utilizing a Pipe in its ParcelFileDescriptor something that ExoPlayer doesn't handle?
Other Ideas?

SocketException in Nanohttpd

I want to play mp3 file from server
server side code:
if (uri.contains("mp3")) {
FileInputStream fis = null;
try {
fis = new FileInputStream(audioFile.getAbsoluteFile());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return newFixedLengthResponse(Response.Status.OK, MIME_TYPES.get("mp3"), fis,audioFile.getTotalSpace());
}
while I am calling
192.168.0.7:XXXX/mp3
Error throw but played mp3 file in browser so why this error occure:
java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:112)
at java.net.SocketOutputStream.write(SocketOutputStream.java:157)
at fi.iki.elonen.NanoHTTPD$Response.sendBody(NanoHTTPD.java:1694)
at fi.iki.elonen.NanoHTTPD$Response.sendBodyWithCorrectEncoding(NanoHTTPD.java:1667)
at fi.iki.elonen.NanoHTTPD$Response.sendBodyWithCorrectTransferAndEncoding(NanoHTTPD.java:1657)
at fi.iki.elonen.NanoHTTPD$Response.send(NanoHTTPD.java:1624)
at fi.iki.elonen.NanoHTTPD$HTTPSession.execute(NanoHTTPD.java:957)
at fi.iki.elonen.NanoHTTPD$ClientHandler.run(NanoHTTPD.java:192)
at java.lang.Thread.run(Thread.java:761)
Broken pipe usually happen when someone (here the server) is trying to write in a socket that was closed on the other side (here the client).
You should probably use length() (size of the file) instead of getTotalSpace() (size of the partition)
You could also try to use a chunked response:
return newChunkedResponse(Response.Status.OK, MIME_TYPES.get("mp3"), fis);

0-byte files not detected when downloading files on Android

I have an app for Android which downloads hundreds of files from the Internet. Some files turn out to be 0-byte after download. The app attempts to detect such cases and delete such files after download but sometimes it fails. The problem is more frequent on Android 4.x devices.
Here is the method which does the downloading. I gets the number of actually read bytes from inputStream.read(buffer).
public class Utils
{
public static class DownloadFileData
{
int nTotalSize;
int nDownloadedSize;
}
public interface ProgressCallback
{
void onProgress(long nCurrent, long nMax);
}
public static boolean downloadFile(String sFileURL, File whereToSave, DownloadFileData fileData, ProgressCallback progressCallback)
{
InputStream inputStream = null;
FileOutputStream fileOutput = null;
try
{
URL url = new URL(sFileURL);
URLConnection connection = url.openConnection();
//set up some things on the connection
connection.setDoOutput(true);
connection.connect();
fileOutput = new FileOutputStream(whereToSave);
inputStream = connection.getInputStream();
fileData.nTotalSize = connection.getContentLength();
fileData.nDownloadedSize = 0;
byte[] buffer = new byte[1024];
int bufferLength = 0; //used to store a temporary size of the buffer
// now, read through the input buffer and write the contents to the file
while ((bufferLength = inputStream.read(buffer)) > 0)
{
// if interrupted, don't download the file further and return
// also restore the interrupted flag so that the caller stopped also
if (Thread.interrupted())
{
Thread.currentThread().interrupt();
return false;
}
// add the data in the buffer to the file in the file output stream
fileOutput.write(buffer, 0, bufferLength);
// add up the size so we know how much is downloaded
fileData.nDownloadedSize += bufferLength;
if (null != progressCallback && fileData.nTotalSize > 0)
{
progressCallback.onProgress(fileData.nDownloadedSize, fileData.nTotalSize);
}
}
return true;
}
catch (FileNotFoundException e)
{
return false; // swallow a 404
}
catch (IOException e)
{
return false; // swallow a 404
}
catch (Throwable e)
{
return false;
}
finally
{
// in any case close input and output streams
if (null != inputStream)
{
try
{
inputStream.close();
inputStream = null;
}
catch (Exception e)
{
}
}
if (null != fileOutput)
{
try
{
fileOutput.close();
fileOutput = null;
}
catch (Exception e)
{
}
}
}
}
Here is the piece of code which processes the downloads. Since sometimes the number of read bytes is incorrect (it is > 0 and the real file has the size 0 bytes) I check the size of the downloaded file with outputFile.length(). But this again gives a value > 0 even though the file is really 0 byte. I tried to also just create a new file and read its size with recheckSizeFile.length(). Still the size is determined as > 0 while it's really 0 byte.
Utils.DownloadFileData fileData = new Utils.DownloadFileData();
boolean bDownloadedSuccessully = Utils.downloadFile(app.sCurrenltyDownloadedFile, outputFile, fileData, new Utils.ProgressCallback()
{
... // progress bar is updated here
});
if (bDownloadedSuccessully)
{
boolean bIsGarbage = false;
File recheckSizeFile = new File(sFullPath);
long nDownloadedFileSize = Math.min(recheckSizeFile.length(), Math.min(outputFile.length(), fileData.nDownloadedSize));
// if the file is 0bytes, it's garbage
if (0 == nDownloadedFileSize)
{
bIsGarbage = true;
}
// if this is a video and if of suspiciously small size, it's
// garbage, too
else if (Utils.isStringEndingWith(app.sCurrenltyDownloadedFile, App.VIDEO_FILE_EXTENSIONS) && nDownloadedFileSize < Constants.MIN_NON_GARBAGE_VIDEO_FILE_SIZE)
{
bIsGarbage = true;
}
if (bIsGarbage)
{
++app.nFilesGarbage;
app.updateLastMessageInDownloadLog("File is fake, deleting: " + app.sCurrenltyDownloadedFile);
// delete the garbage file
if (null != outputFile)
{
if (!outputFile.delete())
{
Log.e("MyService", "Failed to delete garbage file " + app.sCurrenltyDownloadedFile);
}
}
}
else
{
... // process the normally downloaded file
}
I am not sure but I think there is a bug in Android with reading file size. Has anyone seen a similar problem? Or am I maybe doing something wrong here?
Thanks!
EDIT: how i determine that the files are 0-byte:
all the files which get downloaded go thru the described routines. When I then later view the download folder with a file browser (Ghost Commander), some of the files (like maybe 10%) are 0-byte. They can't be played by a video player (shown as "broken file" icon).
It looks to me like your problem is that you only check for "garbage" files if the Utils.downloadFile call returns true. If the download fails in the getInputStream call or the first read, you will have created a file with zero length which will never be deleted.
You should call flush() on your FileOutputStream to ensure that all data is written to the file. This should make your issue with 0-byte files occur less often.
To check for 0 byte files using File.length() should work properly. Can you open a shell (adb shell) on the device and run ls -l to see the byte count displayed by it is 0 (maybe your file manager has some weird issues). Also please debug (or put some log statements) that sFullPath contains the correct file paths. I can't see where sFullPath gets set in your code above and why you don't just use outputFile but recreate another File object.

Android download from Azure blob storage results in invalid file

From my Android app I try to download from the windows Azure blob storage using the following URL: http://iclyps.blob.core.windows.net/broadcasts/23_6.mp4
The resulting file is corrupt when I download it from within my app. Same error occurs when I download it using the default Browser or Chrome. Also from the Easy Downloader app, the same error occurs. Only a download from my PC or using Firefox Beta from the Android device (or emulator), the file is retrieved correctly.
I use the following code (snippet):
try {
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
//set up some things on the connection
urlConnection.setRequestMethod("GET");
urlConnection.setDoOutput(true);
//and connect!
urlConnection.connect();
bis = new BufferedInputStream(urlConnection.getInputStream(), BUFSIZE);
bos = new BufferedOutputStream(
context.openFileOutput(TMPFILE, Context.MODE_PRIVATE), BUFSIZE);
/*
* Read bytes to the buffer in chunks of BUFSIZE bytes until there is nothing more to read.
* Each chunk is written to the output file.
*/
byte[] buf = new byte[BUFSIZE];
int nBytes = 0;
int tBytes = 0;
while ((nBytes = bis.read(buf, 0, BUFSIZE)) > 0) {
bos.write(buf, 0, nBytes);
tBytes += nBytes;
}
if (tBytes == 0) throw new Exception("no bytes received");
bos.flush();
MobyLog.d(TAG, "download succeeded: #bytes = " + Integer.toString(tBytes));
return true;
} catch (Exception e) {
MobyLog.e(TAG, "download failed: " + e);
context.deleteFile(TMPFILE); // remove possibly present partial file.
return false;
} finally {
if (bis != null) try { bis.close(); } catch (IOException e) {MobyLog.e(TAG, "bis close exception: " + e); };
if (bos != null) try { bos.close(); } catch (IOException e) {MobyLog.e(TAG, "bos close exception: " + e); };
}
Analyzing the files shows that the first part (about 700K) of the original file is repeated a number of times in the corrupted files, resulting in an invalid mp4 file.
Putting the file on another webserver (Apache/IIS), and downloading the file from that location does result in a correct download.
Has anyone experienced a similar problem performing a download from Azure? Can someone provide a solution?
Cheers,
Harald...
Have you tried using the azure-sdk-for-java in your android app?
Our scenario is slightly different in that we using the sdk to pull and push images from blob storage to a custom android app. But the fundamentals should be the same.

Android live streaming of TV before Android 2.1

I am developing app in which I have to implement live TV streaming. My Google search has lead me to believe that live streaming is not possible till 2.1 Android.
Is it right?
As I get code of streaming music of mediaplayer and I can use type of it by setting below method:
mp.setAudioStreamType(2);
But i want to know is it sufficient for streaming just code like that and save file like below method:
private void setDataSource(String path) throws IOException {
if (!URLUtil.isNetworkUrl(path)) {
mp.setDataSource(path);
} else {
Log.i("enter the setdata","enter the setdata");
URL url = new URL(path);
URLConnection cn = url.openConnection();
cn.connect();
InputStream stream = cn.getInputStream();
if (stream == null)
throw new RuntimeException("stream is null");
File temp = File.createTempFile("mediaplayertmp", "dat");
String tempPath = temp.getAbsolutePath();
FileOutputStream out = new FileOutputStream(temp);
byte buf[] = new byte[128];
do {
int numread = stream.read(buf);
if (numread <= 0)
break;
out.write(buf, 0, numread);
} while (true);
mp.setDataSource(tempPath);
try {
stream.close();
Log.i("exit the setdata","exit the setdata");
}
catch (IOException ex) {
Log.e(TAG, "error: " + ex.getMessage(), ex);
}
}
}
Is there any extra stuff needed for live TV streaming?
Adressing "Is it sufficient" : absolutely not.
You're saving all the data from the URL to the device, then playing it back. This works if you can guarantee it's a small clip, but 'live tv streaming' implies we're talking about a stream of unknown length sent at a real-time rate.
The impact of this is :
A N-minute long program will take N-minutes to stream to the device before playback starts.
A long broadcast has the potential to fill up all available storage.
The MediaPlayer.setDataSource(FileDescriptor fd) method should read data from any source you can get a FileDescriptor for, including sockets.
The exact details of how to use this will vary based on the protocol you're using, but essentially you need to read data from the broadcast source, transcode it to a suitable form, and pipe it to the fd.

Categories

Resources