views:

729

answers:

1

Hello

I must create a custom media player within the application with support for mp3 and wav files. I read in the documentation i can`t 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 can't 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....

A: 

Player.setMediaTime() and Player.getMediaTime() both refer to time in microseconds, not milliseconds. So to get the number of elapsed seconds, you need to divide by 1000000 instead of just 1000.

Marc Novakowski