Question

I would like to use an arbitrary InputStream as a data source for a MediaPlayer object.

The reason for this is that the InputStream I am using is in fact an authorized HTTPS connection to a media resource on a remote server. Passing the URL in that case will obviously not work as an authentication is required. I can however do the authentication separately and get an InputStream to the resource - problem is what do I do once I have it?

I thought about the option of using a named pipe and passing its FileDescriptor to the setDataResource method of MediaPlayer. Is there a way to create named pipes in Android (without using NDK)?

Any other suggestion is most welcome.

Was it helpful?

Solution 2

Another solution would be to start a proxy HTTP server on localhost. The media player will connect to this server with setDataSource(Context context, Uri uri). This solution works better than the previous and does not cause playback to glitch.

OTHER TIPS

I think I have found a solution. I would appreciate it if others who are interested would try this on their own and report the results with their device models and SDK version.

I have seen similar posts which direct to this but I thought I would post it anyway since it is newer and seems to work on newer versions of the SDK - so far it works on my Nexus One running Android 2.3.6.

The solution relies on bufferring the input stream to a local file (I have this file on the external storage but it will probably be possible to place it on the intenal storage as well) and providing that file's descriptor to the MediaPlayer instance.

The following runs in a doInBackground method of some AsyncTask that does AudioPlayback:

@Override
protected
Void doInBackground(LibraryItem... params)
{
    ...

    MediaPlayer player = new MediaPlayer();
    setListeners(player);

    try {
        _remoteStream = getMyInputStreamSomehow();
        File tempFile = File.createTempFile(...);
        tempFile.deleteOnExit();
        _localInStream = new FileInputStream(tempFile);
        _localOutStream = new FileOutputStream(tempFile);
        int buffered = bufferMedia(
            _remoteStream, _localOutStream, BUFFER_TARGET_SIZE      // = 128KB for instance
        );

        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        player.setDataSource(_localInStream.getFD());
        player.prepareAsync();

        int streamed = 0;
        while (buffered >= 0) {
            buffered = bufferMedia(
                _remoteStream, _localOutStream, BUFFER_TARGET_SIZE
            );
        }
    } 
    catch (Exception exception) {
        // Handle errors as you see fit
    }

    return null;
}

The bufferMedia method buffers nBytes bytes or until the end of input is reached:

private
int bufferMedia(InputStream inStream, OutputStream outStream, int nBytes)
throws IOException
{
    final int BUFFER_SIZE = 8 * (1 << 10);
    byte[] buffer = new byte[BUFFER_SIZE];          // TODO: Do static allocation instead

    int buffered = 0, read = -1;
    while (buffered < nBytes) {
        read = inStream.read(buffer);
        if (read == -1) {
            break;
        }           
        outStream.write(buffer, 0, read);
        outStream.flush();
        buffered += read;
    }

    if (read == -1 && buffered == 0) {
        return -1;
    }

    return buffered;
}

The setListeners method sets handlers for various MediaPlayer events. The most important one is the OnCompletionListener which is invoked when playback is complete. In cases of buffer underrun (due to, say, temporary slow network connection) the player will reach the end of the local file and transit to the PlaybackCompleted state. I identify those situations by comparing the position of _localInStream against the size of the input stream. If the position is smaller, then playback is now really completed and I reset the MediaPlayer:

private
void setListeners(MediaPlayer player)
{
    // Set some other listeners as well

    player.setOnSeekCompleteListener(
        new MediaPlayer.OnSeekCompleteListener()
        {
            @Override
            public
            void onSeekComplete(MediaPlayer mp)
            {
                mp.start();
            }
        }
    );

    player.setOnCompletionListener(
        new MediaPlayer.OnCompletionListener() 
        {
            @Override
            public 
            void onCompletion(MediaPlayer mp) 
            {
                try {
                    long bytePosition = _localInStream.getChannel().position();
                    int timePosition = mp.getCurrentPosition();
                    int duration = mp.getDuration();

                    if (bytePosition < _track.size) {                           
                        mp.reset();
                        mp.setDataSource(_localInStream.getFD());
                        mp.prepare();
                        mp.seekTo(timePosition);
                    } else {                            
                        mp.release();
                    }
                } catch (IOException exception) {
                    // Handle errors as you see fit
                }
            }
        }
    );
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top