本事例的界面很简单,一个按钮用来开始缓冲音乐,一个进度条用来显示音乐的缓冲进度和播放进度,后面是一个文本用来显示音乐的播放时间,还有一个图片按钮用来播放和暂停音乐。
在main.xml中的代码如下;
<?xml version="1.0" encoding="utf-8"?> <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" android:background="@drawable/back"> <TextView android:id="@+id/text_kb_streamed" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="流媒体测试" /> <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="开始缓冲" /> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <SeekBar android:id="@+id/progress_bar" android:layout_height="wrap_content" android:layout_width="200px" style="?android:attr/progressBarStyleHorizontal" /> <TextView android:id="@+id/playTime" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/progress_bar" android:text="00:00"></TextView> </RelativeLayout> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <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" /> </RelativeLayout> </LinearLayout>
流媒体类
新建流媒体类StreamingMediaPlayer。
1. 构造函数StreamingMediaPlayerStreamingMediaPlayer(Context context, ImageButton playButton, Button streamButton, SeekBar progressBar,TextView playTime)传递context(上下文)和主界面UI元素,以便在程序中更新界面。代码如下;
public StreamingMediaPlayer(Context context, ImageButton playButton, Button streamButton, SeekBar progressBar,TextView playTime) { this.context = context; this.playButton = playButton; this.playTime=playTime; //播放的进度时刻 this.progressBar = progressBar; }
2. startStreaming(final String mediaUrl, long mediaLengthInKb, long mediaLengthInSeconds)方法,传递音频的URL以及在更新进度条中使用的两个元素。在startStreaming()中开启一个线程,然后调用downloadAudioIncrement()方法。
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(); }
3. downloadAudioIncrement(String mediaUrl)方法通过传递的URL地址下载音频文件,因为多媒体播放器锁定它所播放的文件,所以我们不能简单的追加内容,然后通过testMediaBuffer()方法测试,如果是第一次播放,则判断缓冲的内容是否达到足够的内容,达到的话就调用startMediaPlayer()方法进行播放,如果不是第一次播放的话,并且多媒体已播放的进度和多媒体的总进度的差小于一秒,调用transferBufferToMediaPlayer()方法。代码如下;
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"); 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;//totalKbRead表示已经下载的文件大小 testMediaBuffer(); fireDataLoadUpdate(); } while (validateNotInterrupted()); stream.close(); if (validateNotInterrupted()) { fireDataFullyLoaded(); } } private boolean validateNotInterrupted() { if (isInterrupted) { if (mediaPlayer != null) { mediaPlayer.pause(); } return false; } else { return true; } } private void testMediaBuffer() { Runnable updater = new Runnable() { public void run() { if (mediaPlayer == null) { 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 ){ transferBufferToMediaPlayer(); } } }; handler.post(updater); }
4. startMediaPlayer()方法用来播放音频文件,首先调用moveFile()方法将下载的文件复制,用mediaPlayer的API播放文件,startPlayProgressUpdater()方法更新进度条。
private void startMediaPlayer() { try { File bufferedFile = new File(context.getCacheDir(),"playingMedia" + (counter++) + ".dat"); moveFile(downloadingMediaFile,bufferedFile); Log.e(getClass().getName(),"Buffered File path: " + bufferedFile.getAbsolutePath()); Log.e(getClass().getName(),"Buffered File length: " + bufferedFile.length()+""); mediaPlayer = createMediaPlayer(bufferedFile); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.start(); startPlayProgressUpdater(); playButton.setEnabled(true); } catch (IOException e) { Log.e(getClass().getName(), "Error initializing the MediaPlayer.", e); } }
5. createMediaPlayer()方法用来得到一个MediaPlayer对象。
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; } }); FileInputStream fis = new FileInputStream(mediaFile); mPlayer.setDataSource(fis.getFD());//此方法返回与流相关联的文件说明符。 mPlayer.prepare(); return mPlayer; }
6. transferBufferToMediaPlayer()方法将下载的文件复制到一个临时的缓存文件中,暂停音频播放,如果音频正在播放或者多媒体的总长度和多媒体已播放的长度之差小于一秒的时候继续播放。
private void transferBufferToMediaPlayer() { try { boolean wasPlaying = mediaPlayer.isPlaying(); int curPosition = mediaPlayer.getCurrentPosition(); File oldBufferedFile = new File(context.getCacheDir(),"playingMedia" + counter + ".dat"); File bufferedFile = new File(context.getCacheDir(),"playingMedia" + (counter++) + ".dat"); bufferedFile.deleteOnExit(); moveFile(downloadingMediaFile,bufferedFile); mediaPlayer.pause(); mediaPlayer = createMediaPlayer(bufferedFile); mediaPlayer.seekTo(curPosition); boolean atEndOfFile = mediaPlayer.getDuration() - mediaPlayer.getCurrentPosition() <= 1000; if (wasPlaying || atEndOfFile){ mediaPlayer.start(); } oldBufferedFile.delete(); }catch (Exception e) { Log.e(getClass().getName(), "Error updating to newly loaded content.", e); } }
7. fireDataLoadUpdate()方法开启一个线程根据已下载的文件大小更新进度条。
private void fireDataLoadUpdate() { Runnable updater = new Runnable() { public void run() { float loadProgress = ((float)totalKbRead/(float)mediaLengthInKb); progressBar.setSecondaryProgress((int)(loadProgress*100)); } }; handler.post(updater); }
8. fireDataFullyLoaded()方法开启一个线程调用transferBufferToMediaPlayer()方法复制缓存文件,之后播放,然后删除下载的临时文件。
private void fireDataFullyLoaded() { Runnable updater = new Runnable() { public void run() { transferBufferToMediaPlayer(); downloadingMediaFile.delete(); } }; handler.post(updater); }
9. startPlayProgressUpdater()方法根据播放的进度更新进度条,并且显示播放的时间进度。
public void startPlayProgressUpdater() { float progress = (((float)mediaPlayer.getCurrentPosition()/1000)/mediaLengthInSeconds); progressBar.setProgress((int)(progress*100)); int pos=mediaPlayer.getCurrentPosition(); int min = (pos/1000)/60; int sec = (pos/1000)%60; if(sec<10) playTime.setText(""+min+":0"+sec);//把音乐播放的进度,转换成时间 else playTime.setText(""+min+":"+sec); if (mediaPlayer.isPlaying()) { Runnable notification = new Runnable() { public void run() { startPlayProgressUpdater(); } }; handler.postDelayed(notification,1000); } }
10. moveFile(File oldLocation, File 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]; 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() ); } }
业务代码
在MediaPlayer中实现流媒体的测试,通过流媒体的构造函数实例化流媒体类,然后调用流媒体的tartStreaming()方法根据传递的URL地址,实现音乐的在线播放。代码如下;
package com.sharpandroid.music.activity; import java.io.IOException; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ImageButton; import android.widget.SeekBar; import android.widget.TextView; import com.sharpandroid.music.R; import com.sharpandroid.music.StreamingMediaPlayer; public class MediaPlayer extends Activity { private Button streamButton; private ImageButton playButton; private boolean isPlaying; private TextView playTime; private StreamingMediaPlayer audioStreamer; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); initControls(); } private void initControls() { playTime=(TextView) findViewById(R.id.playTime); streamButton = (Button) findViewById(R.id.button_stream); streamButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { startStreamingAudio(); }}); 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() { try { final SeekBar progressBar = (SeekBar) findViewById(R.id.progress_bar); if ( audioStreamer != null) { audioStreamer.interrupt(); } audioStreamer = new StreamingMediaPlayer(this, playButton, streamButton, progressBar,playTime); audioStreamer.startStreaming("http://www.ktcm.net/music/00.mp3",5208, 216); streamButton.setEnabled(false); } catch (IOException e) { Log.e(getClass().getName(), "Error starting to stream audio.", e); } } }