Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to play InputStream of an audio file that's not within a url or storage?

Background

I've succeeded uploading an audio file (3gp) into Google-Drive.

Now I want to be able to play the file within the app.

The Google Drive API only allows to get the input stream of the file that's stored there.

The problem

All MediaPlayer capabilities of inputs aren't available in my case, which is only InputSteam:

http://developer.android.com/reference/android/media/MediaPlayer.html#setDataSource(java.io.FileDescriptor)

I know I can save the file from the Google-Drive to the cache and play it, but I want to avoid the storage handling, and play the file on the fly.

What I've tried

I tried to search for this issue, and only found that it might be possible using AudioTrack (here). It might also be possible using new Jelly-Bean features (shown here, found from here), but I'm not sure as it's quite low level.

Sadly, using AudioTrack I got wrong sounds being played (noise).

I've also noticed that MediaPlayer has the option to set the dataSource to be MediaDataSource (here) , but not only I'm not sure how to use it, it also requires API 23 and above.

Of course, I tried using a url that is given in Google-Drive, but this is only used for other purposes and isn't being directed to the audio file, so it can't be used using MediaPlayer.

The question

Given an InputStream, is it possible to use AudioTrack or something else, to play an audio 3gp file ?

Is there maybe a support library solution for this?

like image 268
android developer Avatar asked Apr 11 '16 15:04

android developer


2 Answers

If your minSdkVersion is 23 or higher, you can use setDataSource(MediaDataSource) and supply your own subclass of the abstract MediaDataSource class.

For older devices, you should be able to use a pipe created from ParcelFileDescriptor. You would have a thread that writes data to your end of the pipe, and pass the FileDescriptor (from getFileDescriptor()) for the player's end to setDataSource(FileDescriptor).

like image 61
CommonsWare Avatar answered Nov 09 '22 08:11

CommonsWare


The simplest MediaDataSource implementation example:

import android.media.MediaDataSource;
import android.os.Build;
import android.support.annotation.RequiresApi;

import java.io.IOException;
import java.io.InputStream;

@RequiresApi(api = Build.VERSION_CODES.M)
public class InputStreamMediaDataSource extends MediaDataSource {
    private InputStream is;
    private long streamLength = -1, lastReadEndPosition;

    public InputStreamMediaDataSource(InputStream is, long streamLength) {
        this.is = is;
        this.streamLength = streamLength;
        if (streamLength <= 0){
            try {
                this.streamLength = is.available(); //Correct value of InputStream#available() method not always supported by InputStream implementation!
            } catch (IOException e) {
                e.printStackTrace();
            }
        }                 
    }

    @Override
    public synchronized void close() throws IOException {
        is.close();
    }

    @Override
    public synchronized int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
        if (position >= streamLength)
            return -1;

        if (position + size > streamLength)
            size -= (position + size) - streamLength;

        if (position < lastReadEndPosition) {
            is.close();
            lastReadEndPosition = 0;
            is = getNewCopyOfInputStreamSomeHow();//new FileInputStream(mediaFile) for example.
        }

        long skipped = is.skip(position - lastReadEndPosition);
        if (skipped == position - lastReadEndPosition) {
            int bytesRead = is.read(buffer, offset, size);
            lastReadEndPosition = position + bytesRead;
            return bytesRead;
        } else {
            return -1;
        }
    }

    @Override
    public synchronized long getSize() throws IOException {
        return streamLength;
    }
}

To use it with API >= 23 you have to provide streamLength value and if (when) MediaPlayer goes back - i.e. position < lastReadEndPosition, you have to know how to create a new copy of InputStream.

Usage example:

You have to create Activity, initialize MediaPlayer class (there are a lot of examples of a file playback) and place following code istead of old player.setDataSource("/path/to/media/file.3gp")

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    File file = new File("/path/to/media/file.3gp");//It is just an example! If you have real file on the phone memory, you don't need to wrap it to the InputStream to play it in MediaPlayer!
    player.setDataSource(new InputStreamMediaDataSource(new FileInputStream(file), file.length()));
} else
    player.setDataSource(this, mediaUri);

If your file is an object com.google.api.services.drive.model.File on Google Drive and com.google.api.services.drive.Drive drive you can get

InputStream is = drive.getRequestFactory().buildGetRequest(new GenericUrl(file.getDownloadUrl())).execute().getContent();

In the case of Build.VERSION.SDK_INT < Build.VERSION_CODES.MI had to setup HTTP sever on the local host of an Android device (by means of NanoHTTPd) and transfer a byte stream thru this server to MediaPlayer by uri - player.setDataSource(this, mediaUri)

like image 34
isabsent Avatar answered Nov 09 '22 10:11

isabsent