Player with custom data source on Blackberry
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 datasource.
I checked the demo in the JDE 4.6 but I have still problems... I can't get the duration, it returns much more then expected so I'm sure I screwed up something while I modified the code to read the mp3 file locally from the filesystem.
Can somebody tell 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.
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)
{
开发者_Go百科 _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 to 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 it unfortunately returned around 64 minutes, so I'm sure something is not good inside the datasoruce....
code that converts
String getElapsedTimeMinutesSeconds(long elapsedTime) {
long Seconds=(elapsedTime/1000)%60;
long Minutes=(elapsedTime/(1000*60))%60;
long Hours=(elapsedTime/(1000*60*60))%24;
return ""+Minutes + ":"+Seconds;
}
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.
Sometime earlier I faced with the problem to play unlimited size audio. I fix it. Here's the link:
http://supportforums.blackberry.com/t5/Java-Development/Use-custom-DataSource-to-play-audio/m-p/1373247#M178928
精彩评论