天天看點

基于網絡音頻的Android播放程式簡單示例

随着釋出MP3檔案、播客以及流式音頻變得越來越受歡迎,建構可以利用這些服務的音頻播放程式的需求也越來越強烈。幸運的是,Android擁有豐富的功能用于處理網絡上存在的各種類型的音頻。

1.基于HTTP音頻播放

這是最簡單的的情況,僅僅播放線上的、可通過HTTP對其進行通路的音頻檔案。比如

http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3 但是這裡和通常示例化MediaPlayer的方式不同,首先使用的是MediaPlayer的無參構造函數來執行個體化對象,接着,調用其setDataSource方法,傳入想要播放的音頻的HTTP位置,随後我們調用prepare方法和start方法。

mediaPlayer = new MediaPlayer();
 
try {
  mediaPlayer
  .setDataSource("http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3");
  mediaPlayer.prepare();
  mediaPlayer.start();
} catch (IOException e) {
  Log.v("AUDIOHTTPPLAYER", e.getMessage());
}      

但是,在應用程式加載到播放音頻之間有一個明顯的滞後時間。延遲的長度取決于用于建構電話Internet連接配接的資料網絡的速度。如果詳細分析的話,可以找到是在調用prepare方法和start方法之間發生了這樣的延遲。在運作prepare期間,MediaPlayer将填充一個緩沖區,因為即使網絡速度緩慢也能平穩的播放音頻。當這麼操作時,prepare方法實際上發生了阻塞。這意味着應用程式可能要等到prepare方法完成之後才會響應。幸運的是,有一種方法可以解決這個問題,即prepareAsync方法。該方法會立即傳回,并在背景執行緩沖和其他工作,進而允許應用程式繼續運作。

完整示例代碼如下:

public class AudioHTTPPlayer extends Activity implements OnClickListener,
        OnErrorListener, OnCompletionListener, OnBufferingUpdateListener,
        OnPreparedListener
{
    /** Called when the activity is first created. */
    MediaPlayer mediaPlayer;
    Button stopButton, startButton;
    TextView statusTextView, bufferValueTextView;
 
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        stopButton = (Button) findViewById(R.id.EndButton);
        startButton = (Button) findViewById(R.id.StartButton);
        startButton.setOnClickListener(this);
        stopButton.setOnClickListener(this);
        startButton.setEnabled(false);
        stopButton.setEnabled(false);
 
        bufferValueTextView = (TextView) findViewById(R.id.BufferValueTextView);
        statusTextView = (TextView) findViewById(R.id.StatusDisplayTextView);
        statusTextView.setText("onCreate");
 
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setOnCompletionListener(this);
        mediaPlayer.setOnErrorListener(this);
        mediaPlayer.setOnBufferingUpdateListener(this);
        mediaPlayer.setOnPreparedListener(this);
        statusTextView.setText("MediaPlayer created");
        try
        {
            mediaPlayer
                    .setDataSource("http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3");
 
            // mediaPlayer.prepare();
            // mediaPlayer.start();
            statusTextView.setText("setDataSource done");
            statusTextView.setText("calling prepareAsync");
            mediaPlayer.prepareAsync();// 開始在背景緩沖音頻檔案并傳回
        } catch (IOException e)
        {
            Log.v("AUDIOHTTPPLAYER", e.getMessage());
        }
    }
 
    @Override
    public void onPrepared(MediaPlayer mp)
    {
        // TODO Auto-generated method stub
//  當完成prepareAsync方法時,将調用活動的onPrepared方法
        statusTextView.setText("onPrepared called");
        startButton.setEnabled(true);
    }
 
    @Override
    public void onBufferingUpdate(MediaPlayer mp, int percent)
    {
        // TODO Auto-generated method stub
//      當MediaPlayer正在緩沖時,将調用活動的onBufferingUpdate方法
        bufferValueTextView.setText(""+percent+"%");
    }
 
    @Override
    public void onCompletion(MediaPlayer mp)
    {
        // TODO Auto-generated method stub
        statusTextView.setText("onCompletion called");
        stopButton.setEnabled(false);
        startButton.setEnabled(true);
    }
 
    @Override
    public boolean onError(MediaPlayer mp, int what, int extra)
    {
        // TODO Auto-generated method stub
        switch (what)
        {
            case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
                statusTextView
                        .setText("MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK"
                                + extra);
                break;
            case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
                statusTextView.setText("MEDIA_ERROR_SERVER_DIED" + extra);
                break;
            case MediaPlayer.MEDIA_ERROR_UNKNOWN:
                statusTextView.setText("MEDIA_ERROR_UNKNOWN" + extra);
                break;
        }
        return false;
    }
 
    @Override
    public void onClick(View v)
    {
        // TODO Auto-generated method stub
        if (v == stopButton)
        {
            mediaPlayer.pause();
            statusTextView.setText("pause called");
            startButton.setEnabled(true);
        } else if (v == startButton)
        {
            mediaPlayer.start();
            statusTextView.setText("start called");
            startButton.setEnabled(false);
            stopButton.setEnabled(true);
        }
    }
}      

如上所示,MediaPlayer有良好的功能集,用來處理HTTP線上擷取的音頻檔案。

2.基于HTTP的流式音頻

線上音頻常用的線上傳輸方法之一是通過HTTP流。有多種流方法屬于HTTP流方法的分支,包括伺服器推送,這在曆史上一直用于在浏覽器中重新整理網絡攝像頭圖像顯示;以及一系列其他新方法。而聯機廣播事實上的标準則是ICY協定,其擴充了HTTP協定,目前大量的伺服器和播放軟體産品都支援這個協定。

幸運的是,android上的MediaPlayer支援播放ICY流,而無須開發人員費力地實作它。

然後,Internet廣播電台并不直接公布它們的音頻流的URL。這麼做是因為浏覽器通常不支援ICY流,而是需要一個輔助應用程式或插件來播放流。為了知道要打開的是一個輔助應用程式,Internet廣播電台會 傳遞一個特定的MIME類型的中間檔案,其中包含一個指向實際線上流的指針。在使用ICY流的情況下,這通常是一個PLS檔案或一個M3U檔案

PLS檔案:是一種多媒體播放清單檔案,其MIME類型是“audio/x-scpls”

M3U檔案:一個存儲多媒體播放清單的檔案,但是采用一種更基本的格式。它的MIME類型為“audio/x-mpegurl”。

例如M3U檔案的内容如下,其指向了一個虛假的線上流

#EXTM3U
#EXTINF:0,Live Stream Name
http://www.nostreamhere.org:8000/      

第一行的#EXTM3U是必須的,其指定下面是一個擴充的M3U檔案,其中可以包含額外的資訊。可以在播放清單條目的上一行指定額外資訊,其以#EXTINF:開始,随後是以秒為機關的持續時間和逗号,然後是媒體的名稱。

M3U檔案可以同時包含多個條目,這些條目依次指定一個檔案或流#EXTM3U#EXTINF:0,Live Stream Namehttp://www.nostreamhere.org:8000/#EXTINF:0,Other Live Stream Namehttp://www.nostreamhere.org/遺憾的是,android上的MediaPlayer不能自動分析M3U檔案。是以必須我們自己分析。下面就是一個示例,分析并播放來自聯機廣播電台的M3U檔案或在URL字段中輸入的任何M3U檔案。 

public class HTTPAudioPlaylistPlayer extends Activity implements
        OnClickListener, OnCompletionListener, OnPreparedListener
{
    Vector playlistItems;
    Button parseBtn, playBtn, stopBtn;
    EditText editTextUrl;
    String baseURL = "";
    MediaPlayer mediaPlayer;
 
    int currentPlaylistItemNumber = 0;
 
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main2);
        parseBtn = (Button) findViewById(R.id.ParseButton);
        playBtn = (Button) findViewById(R.id.PlayButton);
        stopBtn = (Button) findViewById(R.id.StopButton);
        editTextUrl=(EditText) findViewById(R.id.EditTextURL);
 
        playBtn.setOnClickListener(this);
        parseBtn.setOnClickListener(this);
        stopBtn.setOnClickListener(this);
 
        playBtn.setEnabled(false);
        stopBtn.setEnabled(false);
 
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setOnCompletionListener(this);
        mediaPlayer.setOnPreparedListener(this);
    }
 
    @Override
    public void onPrepared(MediaPlayer mp)
    {
        // TODO Auto-generated method stub
        stopBtn.setEnabled(true);
        Log.v("HTTPAUDIOPLAYLIST", "Playing");
        mediaPlayer.start();
    }
 
    @Override
    public void onCompletion(MediaPlayer mp)
    {
        // TODO Auto-generated method stub
        Log.v("ONCOMPLETION", "called");
        mediaPlayer.stop();
        mediaPlayer.reset();
 
        if (playlistItems.size() > currentPlaylistItemNumber + 1)
        {
            currentPlaylistItemNumber++;
            String path = ((PlaylistFile) playlistItems
                    .get(currentPlaylistItemNumber)).getFilePath();
 
            try
            {
                mediaPlayer.setDataSource(path);
                mediaPlayer.prepareAsync();
            } catch (IllegalArgumentException e)
            {
                e.printStackTrace();
            } catch (IllegalStateException e)
            {
                e.printStackTrace();
            } catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
 
    @Override
    public void onClick(View v)
    {
        // TODO Auto-generated method stub
        if (v == parseBtn)
        {
            // 下載下傳由editTextUrl對象中的URL指定的M3U檔案,并對它進行分析。
            // 分析的操作是選出任何表示待播放檔案的行,建立一個PlaylistItem對象,
            // 然後把它添加到playlistItems容器裡
            parsePlaylistFile();
        } else if (v == playBtn)
        {
            playPlaylistItems();
        } else if (v == stopBtn)
        {
            stop();
        }
    }
 
    private void parsePlaylistFile()
    {
        // TODO Auto-generated method stub
        playlistItems = new Vector();
        // 為了從Web擷取M3U檔案,可以使用Apache軟體基金會的HttpClient庫,
        // 它已被android所包括。
        // 首先建立一個HttpClient對象,其代表類似Web浏覽器的事物;
        HttpClient httpClient = new DefaultHttpClient();
        // 然後建立一個HttpGet對象,其表示指向一個檔案的具體請求。
        HttpGet getRequest = new HttpGet(editTextUrl.getText().toString());
        Log.v("URI", getRequest.getURI().toString());
        // HttpClient将執行HttpGet,并傳回一個HttpResponse
        try
        {
            HttpResponse httpResponse = httpClient.execute(getRequest);
            if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
            {
                Log.v("HTTP ERROR", httpResponse.getStatusLine()
                        .getReasonPhrase());
            } else
            {
                // 在送出請求之後,可以從HttpRequest中擷取一個InputStream,
                // 其包含了所請求檔案的内容
                InputStream inputStream = httpResponse.getEntity().getContent();
                // 借助一個BufferedReader可以逐行得周遊該檔案
                BufferedReader bufferedReader = new BufferedReader(
                        new InputStreamReader(inputStream));
                String line;
                while ((line = bufferedReader.readLine()) != null)
                {
                    Log.v("PLAYLISTLINE", "ORIG:" + line);
                    if (line.startsWith("#"))
                    {
                        // 中繼資料,可以做更多的處理,但現在忽略它
                    } else if (line.length() > 0)
                    {
                        // 如果它的長度大于0,那麼就假設它是一個播放清單條目
                        String filePath = "";
                        if (line.startsWith("http://"))
                        {
                            // 如果行以“http://”開頭那麼就把它作為流的完整URL
                            filePath = line;
                        } else
                        {
                            // 否則把它作為一個相對的URL,
                            // 同時把針對該M3U檔案的原始請求的URL附加上去
                            filePath = getRequest.getURI().resolve(line)
                                    .toString();
                        }
                        // 将其添加到播放清單條目的容器中去
                        PlaylistFile playlistFile = new PlaylistFile(filePath);
                        playlistItems.add(playlistFile);
                    }
                }
            }
        } catch (ClientProtocolException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        playBtn.setEnabled(true);
    }
 
    private void playPlaylistItems()
    {
        playBtn.setEnabled(false);
        currentPlaylistItemNumber = 0;
        if (playlistItems.size() > 0)
        {
            String path = ((PlaylistFile) playlistItems
                    .get(currentPlaylistItemNumber)).getFilePath();
            // 在提取出流的或者檔案的路徑之後,就可以在MediaPlayer上的setDataSource方法使用它了
            try
            {
                mediaPlayer.setDataSource(path);
                mediaPlayer.prepareAsync();
            } catch (IllegalArgumentException e)
            {
                e.printStackTrace();
            } catch (IllegalStateException e)
            {
                e.printStackTrace();
            } catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
 
    private void stop()
    {
        mediaPlayer.pause();
        playBtn.setEnabled(true);
        stopBtn.setEnabled(false);
    }
 
    class PlaylistFile
    {
        String filePath;
 
        public PlaylistFile(String _filePath)
        {
            filePath = _filePath;
        }
 
        public void setFilePath(String _filePath)
        {
            filePath = _filePath;
        }
 
        public String getFilePath()
        {
            return filePath;
        }
    }
}      

繼續閱讀