The following code works fine on Android 2.1update1 -
package com.troubadorian.android.teststreaming;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.Gallery;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Gallery.LayoutParams;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.ViewSwitcher;
public class TestStreaming extends Activity
{
private Button streamButton;
private ImageButton playButton;
private TextView textStreamed;
private boolean isPlaying;
private StreamingMediaPlayer audioStreamer;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
initControls();
}
protected void onDestroy()
{
super.onDestroy();
Toast.makeText(TestStreaming.this, "...exiting application..." ,Toast.LENGTH_SHORT).show();
if ( audioStreamer != null)
{
audioStreamer.interrupt();
}
}
private void initControls()
{
textStreamed = (TextView) findViewById(R.id.text_kb_streamed);
streamButton = (Button) findViewById(R.id.button_stream);
streamButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View view)
{
String urlstring2 = "url to a shoutcase stream";
Toast
.makeText(
TestStreaming.this,
"The following stream is about to start" + urlstring2,
Toast.LENGTH_LONG).show();
startStreamingAudio(urlstring2);
}
});
playButton = (ImageButton) findViewById(R.id.button_play);
playButton.setEnabled(false);
playButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View view)
{
if (audioStreamer.getMediaPlayer().isPlaying())
{
audioStreamer.getMediaPlayer().pause();
playButton.setImageResource(R.drawable.button_play);
} else
{
audioStreamer.getMediaPlayer().start();
audioStreamer.startPlayProgressUpdater();
playButton.setImageResource(R.drawable.button_pause);
}
isPlaying = !isPlaying;
}
});
}
private void startStreamingAudio(String urlstring) {
try {
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
if ( audioStreamer != null) {
audioStreamer.interrupt();
}
audioStreamer = new StreamingMediaPlayer(this,textStreamed, playButton, streamButton,progressBar);
audioStreamer.startStreaming(urlstring,5208, 216);
streamButton.setEnabled(false);
} catch (Exception e)
{
Log.e(getClass().getName(), "Error starting to stream audio.", e);
}
}
public void onItemSelected(AdapterView parent, View v, int position, long id)
{
mSwitcher.setImageResource(mImageIds[position]);
}
public void onNothingSelected(AdapterView parent)
{
}
public View makeView()
{
ImageView i = new ImageView(this);
i.setBackgroundColor(0xFF000000);
i.setScaleType(ImageView.ScaleType.FIT_CENTER);
i.setLayoutParams(new ImageSwitcher.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
return i;
}
private ImageSwitcher mSwitcher;
public class ImageAdapter extends BaseAdapter
{
public ImageAdapter(Context c)
{
mContext = c;
}
public int getCount()
{
return mThumbIds.length;
}
public Object getItem(int position)
{
return position;
}
public long getItemId(int position)
{
return position;
}
public View getView(int position, View convertView, ViewGroup parent)
{
ImageView i = new ImageView(mContext);
i.setImageResource(mThumbIds[position]);
i.setAdjustViewBounds(true);
i.setLayoutParams(new Gallery.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
i.setBackgroundResource(R.drawable.picture_frame);
return i;
}
private Context mContext;
}
private Integer[] mThumbIds =
{ R.drawable.calculator, R.drawable.calendar, R.drawable.camera };
private Integer[] mImageIds =
{ R.drawable.calculator, R.drawable.calendar, R.drawable.camera };
}
with the StreamingMediaPlayer class as follows -
package com.troubadorian.android.teststreaming;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Handler;
import android.util.Log;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
/**
* MediaPlayer does not yet support streaming from external URLs so this class
* provides a pseudo-streaming function by downloading the content incrementally
* & playing as soon as we get enough audio in our temporary storage.
*/
public class StreamingMediaPlayer
{
private static final int INTIAL_KB_BUFFER = 96 * 10 / 8;// assume
// 96kbps*10secs/8bits
// per byte
private TextView textStreamed;
private ImageButton playButton;
private ProgressBar progressBar;
// Track for display by progressBar
private long mediaLengthInKb, mediaLengthInSeconds;
private int totalKbRead = 0;
// Create Handler to call View updates on the main UI thread.
private final Handler handler = new Handler();
private MediaPlayer mediaPlayer;
private File downloadingMediaFile;
private boolean isInterrupted;
private Context context;
private int counter = 0;
public StreamingMediaPlayer(Context context, TextView textStreamed,
ImageButton playButton, Button streamButton, ProgressBar progressBar)
{
this.context = context;
this.textStreamed = textStreamed;
this.playButton = playButton;
this.progressBar = progressBar;
}
/**
* Progressivly download the media to a temporary location and update the
* MediaPlayer as new content becomes available.
*/
public void startStreaming(final String mediaUrl, long mediaLengthInKb,
long mediaLengthInSeconds) throws IOException
{
this.mediaLengthInKb = mediaLengthInKb;
this.mediaLengthInSeconds = mediaLengthInSeconds;
Runnable r = new Runnable()
{
public void run()
{
try
{
downloadAudioIncrement(mediaUrl);
} catch (IOException e)
{
Log.e(getClass().getName(),
"Unable to initialize the MediaPlayer for fileUrl="
+ mediaUrl, e);
return;
}
}
};
new Thread(r).start();
}
/**
* Download the url stream to a temporary location and then call the
* setDataSource for that local file
*/
public void downloadAudioIncrement(String mediaUrl) throws IOException
{
URLConnection cn = new URL(mediaUrl).openConnection();
cn.connect();
InputStream stream = cn.getInputStream();
if (stream == null)
{
Log.e(getClass().getName(),
"Unable to create InputStream for mediaUrl:" + mediaUrl);
}
downloadingMediaFile = new File(context.getCacheDir(),
"downloadingMedia.dat");
// Just in case a prior deletion failed because our code crashed or
// something, we also delete any previously
// downloaded file to ensure we start fresh. If you use this code,
// always delete
// no longer used downloads else you'll quickly fill up your hard disk
// memory. Of course, you can also
// store any previously downloaded file in a separate data cache for
// instant replay if you wanted as well.
if (downloadingMediaFile.exists())
{
downloadingMediaFile.delete();
}
FileOutputStream out = new FileOutputStream(downloadingMediaFile);
byte buf[] = new byte[16384];
int totalBytesRead = 0, incrementalBytesRead = 0;
do
{
int numread = stream.read(buf);
if (numread <= 0)
break;
out.write(buf, 0, numread);
totalBytesRead += numread;
incrementalBytesRead += numread;
totalKbRead = totalBytesRead / 1000;
testMediaBuffer();
fireDataLoadUpdate();
} while (validateNotInterrupted());
stream.close();
if (validateNotInterrupted())
{
fireDataFullyLoaded();
}
}
private boolean validateNotInterrupted()
{
if (isInterrupted)
{
if (mediaPlayer != null)
{
mediaPlayer.pause();
// mediaPlayer.release();
}
return false;
} else
{
return true;
}
}
/**
* Test whether we need to transfer buffered data to the MediaPlayer.
* Interacting with MediaPlayer on non-main UI thread can causes crashes to
* so perform this using a Handler.
*/
private void testMediaBuffer()
{
Runnable updater = new Runnable()
{
public void run()
{
if (mediaPlayer == null)
{
// Only create the MediaPlayer once we have the minimum
// buffered data
if (totalKbRead >= INTIAL_KB_BUFFER)
{
try
{
startMediaPlayer();
} catch (Exception e)
{
Log.e(getClass().getName(),
"Error copying buffered conent.", e);
}
}
} else if (mediaPlayer.getDuration()
- mediaPlayer.getCurrentPosition() <= 1000)
{
// NOTE: The media player has stopped at the end so transfer
// any existing buffered data
// We test for < 1second of data because the media player
// can stop when there is still
// a few milliseconds of data left to play
transferBufferToMediaPlayer();
}
}
};
handler.post(updater);
}
private void startMediaPlayer()
{
try
{
File bufferedFile = new File(context.getCacheDir(), "playingMedia"
+ (counter++) + ".dat");
// We double buffer the data to avoid potential read/write errors
// that could happen if the
// download thread attempted to write at the same time the
// MediaPlayer was trying to read.
// For example, we can't guarantee that the MediaPlayer won't open a
// file for playing and leave it locked while
// the media is playing. This would permanently deadlock the file
// download. To avoid such a deadloack,
// we move the currently loaded data to a temporary buffer file that
// we start playing while the remaining
// data downloads.
moveFile(downloadingMediaFile, bufferedFile);
Log.e(getClass().getName(),
"Buffered File path: " + bufferedFile.getAbsolutePath());
Log.e(getClass().getName(),
"Buffered File length: " + bufferedFile.length() + "");
mediaPlayer = createMediaPlayer(bufferedFile);
// We have pre-loaded enough content and started the MediaPlayer so
// update the buttons & progress meters.
mediaPlayer.start();
startPlayProgressUpdater();
playButton.setEnabled(true);
} catch (IOException e)
{
Log.e(getClass().getName(), "Error initializing the MediaPlayer.",
e);
return;
}
}
private MediaPlayer createMediaPlayer(File mediaFile) throws IOException
{
MediaPlayer mPlayer = new MediaPlayer();
mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener()
{
public boolean onError(MediaPlayer mp, int what, int extra)
{
Log.e(getClass().getName(), "Error in MediaPlayer: (" + what
+ ") with extra (" + extra + ")");
return false;
}
});
// It appears that for security/permission reasons, it is better to pass
// a FileDescriptor rather than a direct path to the File.
// Also I have seen errors such as "PVMFErrNotSupported" and
// "Prepare failed.: status=0x1" if a file path String is passed to
// setDataSource(). So unless otherwise noted, we use a FileDescriptor
// here.
FileInputStream fis = new FileInputStream(mediaFile);
mPlayer.setDataSource(fis.getFD());
mPlayer.prepare();
return mPlayer;
}
/**
* Transfer buffered data to the MediaPlayer. NOTE: Interacting with a
* MediaPlayer on a non-main UI thread can cause thread-lock and crashes so
* this method should always be called using a Handler.
*/
private void transferBufferToMediaPlayer()
{
try
{
// First determine if we need to restart the player after
// transferring data...e.g. perhaps the user pressed pause
boolean wasPlaying = mediaPlayer.isPlaying();
int curPosition = mediaPlayer.getCurrentPosition();
// Copy the currently downloaded content to a new buffered File.
// Store the old File for deleting later.
File oldBufferedFile = new File(context.getCacheDir(),
"playingMedia" + counter + ".dat");
File bufferedFile = new File(context.getCacheDir(), "playingMedia"
+ (counter++) + ".dat");
// This may be the last buffered File so ask that it be delete on
// exit. If it's already deleted, then this won't mean anything. If
// you want to
// keep and track fully downloaded files for later use, write
// caching code and please send me a copy.
bufferedFile.deleteOnExit();
moveFile(downloadingMediaFile, bufferedFile);
// Pause the current player now as we are about to create and start
// a new one. So far (Android v1.5),
// this always happens so quickly that the user never realized we've
// stopped the player and started a new one
mediaPlayer.pause();
// Create a new MediaPlayer rather than try to re-prepare the prior
// one.
mediaPlayer = createMediaPlayer(bufferedFile);
mediaPlayer.seekTo(curPosition);
// Restart if at end of prior buffered content or mediaPlayer was
// previously playing.
// NOTE: We test for < 1second of data because the media player can
// stop when there is still
// a few milliseconds of data left to play
boolean atEndOfFile = mediaPlayer.getDuration()
- mediaPlayer.getCurrentPosition() <= 1000;
if (wasPlaying || atEndOfFile)
{
mediaPlayer.start();
}
// Lastly delete the previously playing buffered File as it's no
// longer needed.
oldBufferedFile.delete();
} catch (Exception e)
{
Log.e(getClass().getName(),
"Error updating to newly loaded content.", e);
}
}
private void fireDataLoadUpdate()
{
Runnable updater = new Runnable()
{
public void run()
{
textStreamed.setText((totalKbRead + " Kb read"));
float loadProgress = ((float) totalKbRead / (float) mediaLengthInKb);
progressBar.setSecondaryProgress((int) (loadProgress * 100));
}
};
handler.post(updater);
}
private void fireDataFullyLoaded()
{
Runnable updater = new Runnable()
{
public void run()
{
transferBufferToMediaPlayer();
// Delete the downloaded File as it's now been transferred to
// the currently playing buffer file.
downloadingMediaFile.delete();
textStreamed
.setText(("Audio full loaded: " + totalKbRead + " Kb read"));
}
};
handler.post(updater);
}
public MediaPlayer getMediaPlayer()
{
return mediaPlayer;
}
public void startPlayProgressUpdater()
{
float progress = (((float) mediaPlayer.getCurrentPosition() / 1000) / mediaLengthInSeconds);
progressBar.setProgress((int) (progress * 100));
if (mediaPlayer.isPlaying())
{
Runnable notification = new Runnable()
{
public void run()
{
startPlayProgressUpdater();
}
};
handler.postDelayed(notification, 1000);
}
}
public void interrupt()
{
playButton.setEnabled(false);
isInterrupted = true;
validateNotInterrupted();
}
/**
* Move the file in oldLocation to newLocation.
*/
public void moveFile(File oldLocation, File newLocation) throws IOException
{
if (oldLocation.exists())
{
BufferedInputStream reader = new BufferedInputStream(
new FileInputStream(oldLocation));
BufferedOutputStream writer = new BufferedOutputStream(
new FileOutputStream(newLocation, false));
try
{
// byte[] buff = new byte[8192];
/* changing the size of the buffer */
byte[] buff = new byte[16384];
int numChars;
while ((numChars = reader.read(buff, 0, buff.length)) != -1)
{
writer.write(buff, 0, numChars);
}
} catch (IOException ex)
{
throw new IOException("IOException when transferring "
+ oldLocation.getPath() + " to "
+ newLocation.getPath());
} finally
{
try
{
if (reader != null)
{
writer.close();
reader.close();
}
} catch (IOException ex)
{
Log.e(getClass().getName(),
"Error closing files when transferring "
+ oldLocation.getPath() + " to "
+ newLocation.getPath());
}
}
} else
{
throw new IOException(
"Old location does not exist when transferring "
+ oldLocation.getPath() + " to "
+ newLocation.getPath());
}
}
}
and the layout file as follows -
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:id="@+id/Logo"
android:orientation="vertical">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10px">
<TextView android:id="@+id/text_kb_streamed"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="...streaming audio..."/>
<Button android:id="@+id/button_stream"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10px"
style="?android:attr/buttonStyleSmall"
android:text="Start Streaming"/>
<ProgressBar android:id="@+id/progress_bar"
android:layout_width="200px"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
style="?android:attr/progressBarStyleHorizontal"/>
<ImageButton android:id="@+id/button_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5px"
style="?android:attr/buttonStyleSmall"
android:src="@drawable/button_pause"/>
</LinearLayout>
</LinearLayout>
<ViewFlipper
android:id="@+id/MainFlipper"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="@+id/Logo"
android:layout_marginBottom="37dip">
</ViewFlipper>
<FrameLayout
android:id="@+id/MediaPlayer"
android:layout_width="fill_parent"
android:layout_height="130dip"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true">
</FrameLayout>
</RelativeLayout>
But the same code does not work in Android 2.2. Can it be fixed up to run on 2.2 ?
Here's the error I am getting - E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): Error initializing the MediaPlayer. E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): java.io.IOException: setDataSourceFD failed.: status=0x80000000 E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.media.MediaPlayer.setDataSource(Native Method) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.media.MediaPlayer.setDataSource(MediaPlayer.java:749) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.troubadorian.android.teststreaming.StreamingMediaPlayer.createMediaPlayer(StreamingMediaPlayer.java:272) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.troubadorian.android.teststreaming.StreamingMediaPlayer.startMediaPlayer(StreamingMediaPlayer.java:237) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.troubadorian.android.teststreaming.StreamingMediaPlayer.access$2(StreamingMediaPlayer.java:212) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.troubadorian.android.teststreaming.StreamingMediaPlayer$2.run(StreamingMediaPlayer.java:190) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.os.Handler.handleCallback(Handler.java:587) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.os.Handler.dispatchMessage(Handler.java:92) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.os.Looper.loop(Looper.java:123) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.app.ActivityThread.main(ActivityThread.java:4627) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at java.lang.reflect.Method.invokeNative(Native Method) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at java.lang.reflect.Method.invoke(Method.java:521) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at dalvik.system.NativeStart.main(Native Method) I/global ( 1423): Default buffer size used in BufferedInputStream constructor. It would be better to be explicit if an 8k buffer is required. I/global ( 1423): Default buffer size used in BufferedOutputStream constructor. It would be better to be explicit if an 8k buffer is required. E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): Buffered File path: /data/data/com.troubadorian.android.teststreaming/cache/playingMedia34.dat E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): Buffered File length: 193188 E/MediaPlayer( 1423): Unable to to create media player E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): Error initializing the MediaPlayer. E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): java.io.IOException: setDataSourceFD failed.: status=0x80000000 E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.media.MediaPlayer.setDataSource(Native Method) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.media.MediaPlayer.setDataSource(MediaPlayer.java:749) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.troubadorian.android.teststreaming.StreamingMediaPlayer.createMediaPlayer(StreamingMediaPlayer.java:272) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.troubadorian.android.teststreaming.StreamingMediaPlayer.startMediaPlayer(StreamingMediaPlayer.java:237) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.troubadorian.android.teststreaming.StreamingMediaPlayer.access$2(StreamingMediaPlayer.java:212) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.troubadorian.android.teststreaming.StreamingMediaPlayer$2.run(StreamingMediaPlayer.java:190) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.os.Handler.handleCallback(Handler.java:587) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.os.Handler.dispatchMessage(Handler.java:92) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.os.Looper.loop(Looper.java:123) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at android.app.ActivityThread.main(ActivityThread.java:4627) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at java.lang.reflect.Method.invokeNative(Native Method) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at java.lang.reflect.Method.invoke(Method.java:521) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626) E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): at dalvik.system.NativeStart.main(Native Method) I/global ( 1423): Default buffer size used in BufferedInputStream constructor. It would be better to be explicit if an 8k buffer is required. I/global ( 1423): Default buffer size used in BufferedOutputStream constructor. It would be better to be explicit if an 8k buffer is required. E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): Buffered File path: /data/data/com.troubadorian.android.teststreaming/cache/playingMedia35.dat E/com.troubadorian.android.teststreaming.StreamingMediaPlayer( 1423): Buffered File length: 194588 E/MediaPlayer( 1423): Unable to to create media player
Please help.