Blackberry Player, custom data source

Posted by Alex on Stack Overflow See other posts from Stack Overflow or by Alex
Published on 2010-03-29T06:49:54Z Indexed on 2010/03/29 6:53 UTC
Read the original article Hit count: 378

Filed under:
|
|
|

Hello

I must create a custom media player within the application with support for mp3 and wav files. I read in the documentation i cant seek or get the media file duration without a custom datasoruce. I checked the demo in the JDE 4.6 but i have still problems... I cant get the duration, it return much more then the expected so i`m sure i screwed up something while i modified the code to read the mp3 file locally from the filesystem.

Somebody can help me what i did wrong ? (I can hear the mp3, so the player plays it correctly from start to end)

I must support OSs >= 4.6.

Thank You

Here is my modified datasource

  • LimitedRateStreaminSource.java *
  • Copyright © 1998-2009 Research In Motion Ltd.
    • Note: For the sake of simplicity, this sample application may not leverage
  • resource bundles and resource strings. However, it is STRONGLY recommended
  • that application developers make use of the localization features available
  • within the BlackBerry development platform to ensure a seamless application
  • experience across a variety of languages and geographies. For more information
  • on localizing your application, please refer to the BlackBerry Java Development
  • Environment Development Guide associated with this release. */

package com.halcyon.tawkwidget.model;

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

import javax.microedition.io.Connector; import javax.microedition.io.file.FileConnection; import javax.microedition.media.Control; import javax.microedition.media.protocol.ContentDescriptor; import javax.microedition.media.protocol.DataSource; import javax.microedition.media.protocol.SourceStream;

import net.rim.device.api.io.SharedInputStream;

/** * The data source used by the BufferedPlayback's media player. / public final class LimitedRateStreamingSource extends DataSource { /* The max size to be read from the stream at one time. */ private static final int READ_CHUNK = 512; // bytes

/** A reference to the field which displays the load status. */
//private TextField _loadStatusField;

/** A reference to the field which displays the player status. */
//private TextField _playStatusField;

/**
 * The minimum number of bytes that must be buffered before the media file
 * will begin playing.
 */
private int _startBuffer = 200000;

/** The maximum size (in bytes) of a single read. */
private int _readLimit = 32000;

/**
 * The minimum forward byte buffer which must be maintained in order for
 * the video to keep playing. If the forward buffer falls below this
 * number, the playback will pause until the buffer increases.
 */
private int _pauseBytes = 64000;

/**
 * The minimum forward byte buffer required to resume
 * playback after a pause.
 */
private int _resumeBytes = 128000;

/** The stream connection over which media content is passed. */
//private ContentConnection _contentConnection;

private FileConnection _fileConnection;

/** An input stream shared between several readers. */
private SharedInputStream _readAhead;

/** A stream to the buffered resource. */
private LimitedRateSourceStream _feedToPlayer;

/** The MIME type of the remote media file. */
private String _forcedContentType;

/** A counter for the total number of buffered bytes */
private volatile int _totalRead;

/** A flag used to tell the connection thread to stop */
private volatile boolean _stop;

/**
 * A flag used to indicate that the initial buffering is complete. In
 * other words, that the current buffer is larger than the defined start
 * buffer size.
 */
private volatile boolean _bufferingComplete;

/** A flag used to indicate that the remote file download is complete. */
private volatile boolean _downloadComplete;

/** The thread which retrieves the remote media file. */
private ConnectionThread _loaderThread;

/** The local save file into which the remote file is written. */
private FileConnection _saveFile;

/** A stream for the local save file. */
private OutputStream _saveStream;

/**
 * Constructor.
 * @param locator The locator that describes the DataSource.
 */
public LimitedRateStreamingSource(String locator)
{
    super(locator);
}

/**
 * Open a connection to the locator.
 * @throws IOException
 */
public void connect() throws IOException
{
    //Open the connection to the remote file.

 _fileConnection = (FileConnection)Connector.open(getLocator(), Connector.READ);
    //Cache a reference to the locator.
    String locator = getLocator();

    //Report status.
    System.out.println("Loading: " + locator);
    //System.out.println("Size: " + _contentConnection.getLength());
    System.out.println("Size: " + _fileConnection.totalSize());

    //The name of the remote file begins after the last forward slash.
    int filenameStart = locator.lastIndexOf('/');

    //The file name ends at the first instance of a semicolon.
    int paramStart = locator.indexOf(';');

    //If there is no semicolon, the file name ends at the end of the line.
    if (paramStart < 0)
    {
        paramStart = locator.length();
    }

    //Extract the file name.
    String filename = locator.substring(filenameStart, paramStart);
    System.out.println("Filename: " + filename);

    //Open a local save file with the same name as the remote file.
    _saveFile = (FileConnection) Connector.open("file:///SDCard/blackberry/music" + filename, Connector.READ_WRITE);

    //If the file doesn't already exist, create it.
    if (!_saveFile.exists())
    {
        _saveFile.create();
    }
    System.out.println("---------- 1");
    //Open the file for writing.
    _saveFile.setReadable(true);

    //Open a shared input stream to the local save file to
    //allow many simultaneous readers.
    SharedInputStream fileStream = SharedInputStream.getSharedInputStream(_saveFile.openInputStream());

    //Begin reading at the beginning of the file.
    fileStream.setCurrentPosition(0);
    System.out.println("---------- 2");
    //If the local file is smaller than the remote file...
    if (_saveFile.fileSize() < _fileConnection.totalSize())
    {
     System.out.println("---------- 3");
        //Did not get the entire file, set the system to try again.
        _saveFile.setWritable(true);
        System.out.println("---------- 4");
        //A non-null save stream is used as a flag later to indicate that
        //the file download was incomplete.
        _saveStream = _saveFile.openOutputStream();
        System.out.println("---------- 5");
        //Use a new shared input stream for buffered reading.
        _readAhead = SharedInputStream.getSharedInputStream(_fileConnection.openInputStream());
        System.out.println("---------- 6");
    }
    else
    {
        //The download is complete.
     System.out.println("---------- 7");
     _downloadComplete = true;

        //We can use the initial input stream to read the buffered media.
        _readAhead = fileStream;
        System.out.println("---------- 8");
        //We can close the remote connection.
        _fileConnection.close();
        System.out.println("---------- 9");
    }

    if (_forcedContentType != null)
    {
        //Use the user-defined content type if it is set.
     System.out.println("---------- 10");
        _feedToPlayer = new LimitedRateSourceStream(_readAhead, _forcedContentType);
        System.out.println("---------- 11");
    }
    else
    {
     System.out.println("---------- 12");
        //Otherwise, use the MIME types of the remote file.
       // _feedToPlayer = new LimitedRateSourceStream(_readAhead, _fileConnection));
    }
    System.out.println("---------- 13");
}

/**
 * Destroy and close all existing connections.
 */
public void disconnect() {
    try 
    {
        if (_saveStream != null)
        {
            //Destroy the stream to the local save file.
            _saveStream.close();
            _saveStream = null;
        }

        //Close the local save file.
        _saveFile.close();

        if (_readAhead != null)
        {
            //Close the reader stream.
            _readAhead.close();
            _readAhead = null;   
        }

        //Close the remote file connection.
        _fileConnection.close();

        //Close the stream to the player.
        _feedToPlayer.close();
    }
    catch (Exception e)
    {
        System.err.println(e.getMessage());
    }
}

/**
 * Returns the content type of the remote file.
 * @return The content type of the remote file.
 */
public String getContentType()
{
    return _feedToPlayer.getContentDescriptor().getContentType();
}

/**
 * Returns a stream to the buffered resource.
 * @return A stream to the buffered resource.
 */
public SourceStream[] getStreams()
{
    return new SourceStream[] { _feedToPlayer };
}

/**
 * Starts the connection thread used to download the remote file.
 */
public void start() throws IOException
{
    //If the save stream is null, we have already completely downloaded
    //the file.
    if (_saveStream != null)
    {
        //Open the connection thread to finish downloading the file.
        _loaderThread = new ConnectionThread();
        _loaderThread.start();
    }
}

/**
 * Stop the connection thread.
 */
public void stop() throws IOException
{
    //Set the boolean flag to stop the thread.
    _stop = true;
}

/**
 * @see javax.microedition.media.Controllable#getControl(String)
 */
public Control getControl(String controlType)
{
    // No implemented Controls.
    return null;
}

/**
 * @see javax.microedition.media.Controllable#getControls()
 */
public Control[] getControls()
{
    // No implemented Controls.
    return null;
}

/**
 * Force the lower level stream to a given content type. Must be called
 * before the connect function in order to work.
 * @param contentType The content type to use.
 */
public void setContentType(String contentType)
{
    _forcedContentType = contentType;
}

/**
 * A stream to the buffered media resource.
 */
private final class LimitedRateSourceStream implements SourceStream
{
    /** A stream to the local copy of the remote resource. */
    private SharedInputStream _baseSharedStream;

    /** Describes the content type of the media file. */
    private ContentDescriptor _contentDescriptor;

    /**
     * Constructor. Creates a LimitedRateSourceStream from
     * the given InputStream.
     * @param inputStream The input stream used to create a new reader.
     * @param contentType The content type of the remote file.
     */
    LimitedRateSourceStream(InputStream inputStream, String contentType)
    {
     System.out.println("[LimitedRateSoruceStream]---------- 1");
        _baseSharedStream = SharedInputStream.getSharedInputStream(inputStream);
        System.out.println("[LimitedRateSoruceStream]---------- 2");
        _contentDescriptor = new ContentDescriptor(contentType);
        System.out.println("[LimitedRateSoruceStream]---------- 3");
    }

    /**
     * Returns the content descriptor for this stream.
     * @return The content descriptor for this stream.
     */
    public ContentDescriptor getContentDescriptor()
    {
        return _contentDescriptor;
    }

    /**
     * Returns the length provided by the connection.
     * @return long The length provided by the connection.
     */
    public long getContentLength()
    {
        return _fileConnection.totalSize();
    }

    /**
     * Returns the seek type of the stream.
     */
    public int getSeekType()
    {
     return RANDOM_ACCESSIBLE;
        //return SEEKABLE_TO_START;
    }

    /**
     * Returns the maximum size (in bytes) of a single read.
     */
    public int getTransferSize() 
    {
        return _readLimit;
    }

    /**
     * Writes bytes from the buffer into a byte array for playback.
     * @param bytes The buffer into which the data is read.
     * @param off The start offset in array b at which the data is written.
     * @param len The maximum number of bytes to read.
     * @return the total number of bytes read into the buffer, or -1 if
     * there is no more data because the end of the stream has been reached.
     * @throws IOException 
     */
    public int read(byte[] bytes, int off, int len) throws IOException
    {
     System.out.println("[LimitedRateSoruceStream]---------- 5");
        System.out.println("Read Request for: " + len + " bytes");

        //Limit bytes read to our readLimit.
        int readLength = len;
        System.out.println("[LimitedRateSoruceStream]---------- 6");
        if (readLength > getReadLimit())
        {
            readLength = getReadLimit();
        }

        //The number of available byes in the buffer.
        int available;

        //A boolean flag indicating that the thread should pause
        //until the buffer has increased sufficiently.
        boolean paused = false;
        System.out.println("[LimitedRateSoruceStream]---------- 7");
        for (;;)
        {
            available = _baseSharedStream.available();

            System.out.println("[LimitedRateSoruceStream]---------- 8");
            if (_downloadComplete)
            {
                //Ignore all restrictions if downloading is complete.
                System.out.println("Complete, Reading: " + len + " - Available: " + available);
                return _baseSharedStream.read(bytes, off, len);
            }
            else if(_bufferingComplete)
            {
                if (paused && available > getResumeBytes())
                {
                    //If the video is paused due to buffering, but the
                    //number of available byes is sufficiently high,
                    //resume playback of the media.
                    System.out.println("Resuming - Available: " + available);
                    paused = false;
                    return _baseSharedStream.read(bytes, off, readLength);
                }
                else if(!paused && (available > getPauseBytes() || available > readLength))
                {
                    //We have enough information for this media playback.

                    if (available < getPauseBytes())
                    {
                        //If the buffer is now insufficient, set the
                        //pause flag.
                        paused = true;
                    }

                    System.out.println("Reading: " + readLength + " - Available: " + available);
                    return _baseSharedStream.read(bytes, off, readLength);
                }
                else if(!paused)
                {
                    //Set pause until loaded enough to resume.
                    paused = true;
                }
            }
            else
            {
                //We are not ready to start yet, try sleeping to allow the
                //buffer to increase.
                try
                {
                    Thread.sleep(500);
                }
                catch (Exception e)
                {
                    System.err.println(e.getMessage());
                }
            }
        }
    }

    /**
     * @see javax.microedition.media.protocol.SourceStream#seek(long)
     */
    public long seek(long where) throws IOException
    {
        _baseSharedStream.setCurrentPosition((int) where);
        return _baseSharedStream.getCurrentPosition();
    }

    /**
     * @see javax.microedition.media.protocol.SourceStream#tell()
     */
    public long tell()
    {
        return _baseSharedStream.getCurrentPosition();
    }

    /**
     * Close the stream.
     * @throws IOException
     */
    void close() throws IOException
    {
        _baseSharedStream.close();
    }

    /**
     * @see javax.microedition.media.Controllable#getControl(String)
     */
    public Control getControl(String controlType)
    {
        // No implemented controls.
        return null;
    }

    /**
     * @see javax.microedition.media.Controllable#getControls()
     */
    public Control[] getControls()
    {
        // No implemented controls.
        return null;
    }
}

/**
 * A thread which downloads the remote file and writes it to the local file.
 */
private final class ConnectionThread extends Thread
{
    /**
     * Download the remote media file, then write it to the local
     * file.
     * @see java.lang.Thread#run()
     */
    public void run()
    {
        try
        {
            byte[] data = new byte[READ_CHUNK];
            int len = 0;

            //Until we reach the end of the file.
            while (-1 != (len = _readAhead.read(data)))
            {
                _totalRead += len;

                if (!_bufferingComplete && _totalRead > getStartBuffer())
                {
                    //We have enough of a buffer to begin playback.
                    _bufferingComplete = true;
                    System.out.println("Initial Buffering Complete");
                }

                if (_stop)
                {
                    //Stop reading.
                    return;
                }

            }

            System.out.println("Downloading Complete");
            System.out.println("Total Read: " + _totalRead);

            //If the downloaded data is not the same size
            //as the remote file, something is wrong.
            if (_totalRead != _fileConnection.totalSize())
            {
                System.err.println("* Unable to Download entire file *");
            }

            _downloadComplete = true;
            _readAhead.setCurrentPosition(0);

            //Write downloaded data to the local file.
            while (-1 != (len = _readAhead.read(data)))
            {
                _saveStream.write(data);
            }

        }
        catch (Exception e)
        {
            System.err.println(e.toString());
        }
    }
}

/**
 * Gets the minimum forward byte buffer which must be maintained in
 * order for the video to keep playing.
 * @return The pause byte buffer.
 */
int getPauseBytes()
{
    return _pauseBytes;
}

/**
 * Sets the minimum forward buffer which must be maintained in order
 * for the video to keep playing.
 * @param pauseBytes The new pause byte buffer.
 */
void setPauseBytes(int pauseBytes)
{
    _pauseBytes = pauseBytes;
}

/**
 * Gets the maximum size (in bytes) of a single read.
 * @return The maximum size (in bytes) of a single read.
 */ 
int getReadLimit()
{
    return _readLimit;
}

/**
 * Sets the maximum size (in bytes) of a single read.
 * @param readLimit The new maximum size (in bytes) of a single read.
 */
void setReadLimit(int readLimit)
{
    _readLimit = readLimit;
}

/**
 * Gets the minimum forward byte buffer required to resume
 * playback after a pause.
 * @return The resume byte buffer.
 */
int getResumeBytes()
{
    return _resumeBytes;
}

/**
 * Sets the minimum forward byte buffer required to resume
 * playback after a pause.
 * @param resumeBytes The new resume byte buffer.
 */
void setResumeBytes(int resumeBytes)
{
    _resumeBytes = resumeBytes;
}

/**
 * Gets the minimum number of bytes that must be buffered before the
 * media file will begin playing.
 * @return The start byte buffer.
 */
int getStartBuffer()
{
    return _startBuffer;
}

/**
 * Sets the minimum number of bytes that must be buffered before the
 * media file will begin playing.
 * @param startBuffer The new start byte buffer.
 */
void setStartBuffer(int startBuffer)
{
    _startBuffer = startBuffer;
}

}

And in this way i use it:


LimitedRateStreamingSource source = new LimitedRateStreamingSource("file:///SDCard/music3.mp3");
  source.setContentType("audio/mpeg");
  mediaPlayer = javax.microedition.media.Manager.createPlayer(source);
  mediaPlayer.addPlayerListener(this);
  mediaPlayer.realize();
  mediaPlayer.prefetch();

After start i use mediaPlayer.getDuration it returns lets say around 24:22 (the inbuild media player in the blackberry say the file length is 4:05)

I tried to get the duration in the listener and there unfortunatly returned around 64 minutes, so im sure something is not good inside the datasoruce....

© Stack Overflow or respective owner

Related posts about blackberry

Related posts about streaming