Android Sound 範例-03

1 問題

分別利用 MediaPlayer 類別物件與 SoundPool 類別物件來播放不同的聲音檔。程式執行結果如下:

19-avd-run.jpg

圖1.1. 程式執行初始畫面。

 

20-avd-start-mp.jpg

圖1.2. 點按「用MediaPlayer播放聲音」後的畫面。

 

21-avd-stop-mp.jpg

圖1.3. 點按「停止MediaPlayer播放聲音」後的畫面。

 

22-avd-start-sp.jpg

圖1.4. 點按「用SoudPool播放音樂」後的畫面。

 

23-avd-stop-sp.jpg

圖1.5. 點按「停止SoundPool播放音樂」後畫面。

 

24-avd-start-sp-2.jpg

圖1.6. 在 SoundId欄位輸入要播放的第2首音樂,再點按「用SoudPool播放音樂」後的畫面。

 

2 相關原始程式檔

2.1 activity_sound_test.xml 檔案內容

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".SoundTest" >

    <TextView
        android:id="@+id/tv_hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <EditText
        android:id="@+id/ed_soundId"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/tv_hello_world"
        android:ems="10"
        android:inputType="number"
        android:text="@string/soundId">
        <requestFocus />
    </EditText>

    <Button
        android:id="@+id/btn_stopSoundPool"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/btn_startSoundPool"
        android:layout_below="@+id/ed_soundId"
        android:layout_marginTop="152dp"
        android:text="停止SoundPool播放音樂" />

    <Button
        android:id="@+id/btn_startMediaPlayer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/ed_soundId"
        android:layout_marginTop="17dp"
        android:text="用MediaPlayer播放聲音" />

    <Button
        android:id="@+id/btn_stopMediaPlayer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/btn_startMediaPlayer"
        android:layout_below="@+id/btn_startMediaPlayer"
        android:text="停止MediaPlayer播放聲音" />

    <Button
        android:id="@+id/btn_startSoundPool"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/btn_stopMediaPlayer"
        android:layout_below="@+id/btn_stopMediaPlayer"
        android:text="用SoudPool播放音樂" />

    <TextView
        android:id="@+id/tv_lb_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/btn_stopSoundPool"
        android:layout_below="@+id/btn_stopSoundPool"
        android:layout_marginTop="28dp"
        android:text="訊息:" />

    <TextView
        android:id="@+id/tv_out_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/tv_lb_message"
        android:layout_alignBottom="@+id/tv_lb_message"
        android:layout_alignRight="@+id/ed_soundId"
        android:layout_toRightOf="@+id/tv_lb_soundID" />

    <TextView
        android:id="@+id/tv_lb_soundID"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/ed_soundId"
        android:layout_alignBottom="@+id/ed_soundId"
        android:layout_alignLeft="@+id/tv_hello_world"
        android:text="SoundID:" />

    
</RelativeLayout>
 

2.2 strings.xml 檔案內容

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Sound03</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Android Sound 範例-03</string>
    <string name="soundId">1</string>
    
</resources>

2.3 SoundTest.java 檔案內容

package com.example.sound03;

import java.util.HashMap;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.media.SoundPool.OnLoadCompleteListener;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.view.View.OnClickListener;

public class SoundTest extends Activity implements OnClickListener{

    EditText ed_soundId;
    Button btn_startMediaPlayer;
    Button btn_stopMediaPlayer;
    Button btn_startSoundPool;
    Button btn_stopSoundPool;
    TextView tv_out_message;
    
    MediaPlayer mp;
    SoundPool sp;
    HashMap<Integer,Integer> spMap;
    SparseIntArray soundArray;
    
    OnClickListener clickListener;
    OnLoadCompleteListener onLoadCompleteListener;
    boolean loaded=false;
     
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sound_test);
        
        init();
        
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.sound_test, menu);
        return true;
    }
    
    public void init(){
        ed_soundId = (EditText) findViewById(R.id.ed_soundId);
        btn_startMediaPlayer = (Button) findViewById(R.id.btn_startMediaPlayer);
        btn_stopMediaPlayer = (Button) findViewById(R.id.btn_stopMediaPlayer);
        btn_startSoundPool = (Button) findViewById(R.id.btn_startSoundPool);
        btn_stopSoundPool = (Button) findViewById(R.id.btn_stopSoundPool);
        tv_out_message = (TextView) findViewById(R.id.tv_out_message);
        
        mp = MediaPlayer.create(this, R.raw.sound1);
        
        // 語法:android.media.SoundPool.SoundPool(int maxStreams, int streamType, int srcQuality);
        // SoundPool 類別物件只能播放小於 1MB 的音樂檔,大於 1MB 的音樂檔部份會被捨棄不播放
        sp = new SoundPool(4,AudioManager.STREAM_MUSIC,100);
        
        // 建立音樂列表方式一:
        soundArray = new SparseIntArray();
        soundArray.append(1, sp.load(this,R.raw.sound2,1));
        soundArray.append(2, sp.load(this,R.raw.sound3,1));
        
        // 建立音樂列表方式二:
        spMap = new HashMap<Integer,Integer>();
        
        // 語法:int android.media.SoundPool.load(Context context, int resId, int priority);
        spMap.put(1,sp.load(this,R.raw.sound2,1));
        spMap.put(2,sp.load(this,R.raw.sound3,1));
        
        clickListener = new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                
                switch (v.getId()){
                case R.id.btn_startMediaPlayer:
                    tv_out_message.setText("使用 MediaPlayer 開始播放音樂");
                    if (!mp.isPlaying()) mp.start();
                    break;
                
                case R.id.btn_stopMediaPlayer:
                    tv_out_message.setText("停止 MediaPlayer 播放音樂");
                    if (mp.isPlaying()) mp.stop();
                    break;
                                            
                }
            }
        };
        
        
        btn_startMediaPlayer.setOnClickListener(clickListener);
        btn_stopMediaPlayer.setOnClickListener(clickListener);
        btn_startSoundPool.setOnClickListener(this);
        btn_stopSoundPool.setOnClickListener(this);
        
        onLoadCompleteListener = new OnLoadCompleteListener(){
            @Override
            public void onLoadComplete(SoundPool soundPool, int sampleId,
                    int status){
                loaded=true;
            }
        };
        sp.setOnLoadCompleteListener(onLoadCompleteListener);
    }
    
    
    
    public void playSound(int soundId,int loop){
        AudioManager am = (AudioManager)
                this.getSystemService(Context.AUDIO_SERVICE);
        float streamVolumeCurrent =
                am.getStreamVolume(AudioManager.STREAM_MUSIC);
        float streamVolumeMax =
                am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        float volume = streamVolumeCurrent / streamVolumeMax;
        
        if (loaded)
            // 語法:int android.media.SoundPool.play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate);
            
            //利用所建立音樂列表方式一來取用音樂:
            sp.play(soundArray.get(soundId), volume, volume, 1, loop, 1f);
            
            //利用所建立音樂列表方式二來取用音樂:
            //sp.play(spMap.get(soundId), volume, volume, 1, loop, 1f);
    }



    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        String str=ed_soundId.getText().toString();
        Integer soundId;
        Log.e("ed_soundId_Text=",str);//輸出至 LogCat視窗來進行人工查驗
        soundId = Integer.parseInt(str);
        Log.e("ed_soundId_Integer=",String.valueOf(soundId));
        if (soundId==null) soundId=1;
        
        switch (v.getId()){
        case R.id.btn_startSoundPool:
            tv_out_message.setText("使用 SoundPool 開始播放音樂");
            playSound(soundId,0);
            break;
            
        case R.id.btn_stopSoundPool:
            tv_out_message.setText("停止 SoundPool 播放音樂");
            sp.pause(soundId);
            break;
        
        }
    }
    
}

 

3 建立專案過程

01-new-sound03-proj.jpg

圖3.1. 建立一個名為「Sound03」的專案。

 

02-configure-proj.jpg

圖3.2. 對該專案使用預設的配置。

 

03-set-icon-set.jpg

圖3.3. 設定預設的圖示圖片來源。

 

04-create-activity.jpg

圖3.4. 預設要建立一空白的 Activity 視窗。

 

05-create-SoundTest-activity.jpg

圖3.5. 命名Activity 視窗程式的名稱為「SoundTest」,視窗元件配置檔名為「activity_sound_test.xml」

 

4 程式設計解說

4.1 有關於 activity_sound_test.xml 的設計

由對照上述的視窗元件位置設計與 activity_sound_test.xml 的指令內容,會發現視窗元件指令的建立順序有些不同,這是因為筆者在設計過程中有做過一些新增元件並將之上移至最上方位置的調整,在該過程中由於 Android 系統的自動對齊功能太過多事,反而造成畫面不一致重疊在一起的現象,我的處理方式是,進入 activity_sound_test.xml 檔案中,將所有視窗元件中有關位置對齊的指令先全部刪除,例如「android:layout_alignLeft="@+id/btn_stopMediaPlayer"」將之刪除,接著進入 GUI 的設計介面中,配合右邊的視窗元件管理視窗,逐一點按所建立的視窗元件,然後再到 GUI 設計畫面中用滑鼠移動該視窗元件的位置,如此就可以重新進行視窗元件的位置配置。

另外,強列建議在每一次在GUI設計畫面中新增一個視窗元件後,就要立即到 activity 視窗 xml 配置檔,例如,此例的 activity_sound_test.xml,中立即更改每一個新增元件的識別名稱,如此 Android 才能將元件之間的對齊關係做正確的設定,否則事後再來更改視窗元件的識別名稱,將造成一團混亂的情況。

 

4.2 impot API 函數庫與變數宣告

4.2.1 import API函數庫

import java.util.HashMap;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.media.SoundPool.OnLoadCompleteListener;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.view.View.OnClickListener;

上述的內容為本範例所使用到的 import API 函數庫。

4.2.2 變數宣告

    EditText ed_soundId;
    Button btn_startMediaPlayer;
    Button btn_stopMediaPlayer;
    Button btn_startSoundPool;
    Button btn_stopSoundPool;
    TextView tv_out_message;
    
    MediaPlayer mp;
    SoundPool sp;
    HashMap<Integer,Integer> spMap;
    SparseIntArray soundArray;
    
    OnClickListener clickListener;
    OnLoadCompleteListener onLoadCompleteListener;
    boolean loaded=false;

上述的有關於 EditText, Button, TextView 類別的物件變數之宣告,其目皆是要與 activity_sound_test.xml 當中所建立的視窗元件物件進行連結。亦即對此處所宣告的變數做動作,就等同於對所連結的 activity_sound_test.xml 當中的視窗元件做同樣的動作。

 

4.3 有關於 android 播放聲音檔的方式

 基本上在 android 系統中,可以使用 MediaPlayer 類別物件SoundPool 類別物件來播放聲音檔。其中 SoundPool只能播放小於 1MB 的聲音檔,但是卻可以同時播放多首聲音檔。而 MediaPlayer就可以播放較大檔案的聲音檔,而且系統本身也已進行較佳的控制,所以播放與控制聲音檔就比較順暢與完整。反觀,SoundPool在播放聲音與控制上就比較弱,常會發生不正常播放、或延遲播放、或聲音檔未準備好的情況。

 

4.4 用 MediaPlayer 來播放聲音

利用 MediaPlayer 需要匯入相對應的API函數庫:

import android.media.AudioManager;
import android.media.MediaPlayer;

在主java程式檔中的 init()副程式中:

mp = MediaPlayer.create(this, R.raw.sound1);

上述指令就是建立一個 MediaPlayer 的物件,其物件名稱為之前已宣告的 mp,並且指定位在本專案目錄中的 res/raw 子目錄中,所存放的 sound1.mp3 (附檔名在程式中可以不用寫出來,系統會自動判斷),將是未來 MediaPlayer會播放的聲音檔。這也意謂者,所有要處理播放的聲音檔皆要事先存入該專案目錄下的 res/raw子目錄中。

當建立好一個 MediaPlayer 物件,並指定要被 MediaPlayer 播放的聲音檔案後,接著就是利用所建立的 mp 物件中所內建的各種功能來處理該聲音檔。例如:

mp.start();

此start()指令就是播放所指定的聲音檔。

又,

mp.stop();

指 stop() 指令就是停止播放所指定的聲音檔。

因為本程式設計成用兩個按鈕來分別控制播放聲音與停止播放聲音。因此程式中就必須要先建立一個按鈕是否被按下的監聽者,當監聽者一但監聽到各按鈕被按下後,監聽者必須要分別依據所按下的不同按鈕來處理聲音。

因此第一種整個監聽的設計過程如下:

第一階段:建立一個獨立監聽者與監聽者該做的事

例如:

clickListener = new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                
                switch (v.getId()){
                case R.id.btn_startMediaPlayer:
                    tv_out_message.setText("使用 MediaPlayer 開始播放音樂");
                    if (!mp.isPlaying()) mp.start();
                    break;
                
                case R.id.btn_stopMediaPlayer:
                    tv_out_message.setText("停止 MediaPlayer 播放音樂");
                    if (mp.isPlaying()) mp.stop();
                    break;
                                            
                }
            }
        };

 

clickListener = new View.OnClickListener() 是建立一個屬於 View 類別系統中的 OnClickListener 類別物件,而且 clickListener 物件也已經在主程式中事先以「OnClickListener clickListener;」指令宣告過。

第二階段:指定監聽者要監聽那些物件發生了那些事件

接著此一 clickListneer 監聽者要指定去監聽那些其他物件是否有按下 Click 按鍵,因此必須在別的地方加以指定,而本程式是在 inti() 函數中進行如下的設定:

btn_startMediaPlayer.setOnClickListener(clickListener);
btn_stopMediaPlayer.setOnClickListener(clickListener);

亦即將 btn_startMediaPlayer 按鈕及 btn_stopMediaPlayer 按鈕的監聽是否按下 Click 鍵的工作,交給 clickListener這個監聽者來進行監聽。

 

而監聽的研判與後續的回應動作,必須在 clickListener 監聽者物件的內部去覆寫所繼承來的 public void onClick(View v){...}函數。由於本程式是要監聽兩個按鈕,所以可以利用函數所傳入的屬於View 類別的參數 v 來進行研判。由於 View 具有可以取得與處理畫面元件的相關函數,所以可以用 v.getId() 函數來取得負責監聽的兩個按鈕中到底是那一個按鈕是按下 Click 鍵。因此,利用 switch (...){case...}的指令來進行條件判斷與後續的處理:其中

case R.id.btn_startMediaPlayer 代表的是用來判斷 v.getId() 是否等於 R.id.btn_startMediaPlayer的意思。

而 tv_out_message.setText("使用 MediaPlayer 開始播放音樂"); 指令是用來指定 tv_out_message 此一 TextView 物件的文字為「使用 MediaPlayer 開始播放音樂」,以作為顯示的訊息之用。

而 if (!mp.isPlaying()) mp.start(); 指令中,mp.isPlaying() 指令是用來測試該 MediaPlayer 的物件 mp 是否正在播放,因此整個指令的意思就是說:如果 MediaPlayer 物件 mp 並未正在播放中,那麼就讓 mp 物件開始播放。

case R.id.btn_stopMediaPlayer 代表的是用來判斷 v.getId() 是否等於 R.id.btn_stopMediaPlayer的意思。接著,if (mp.isPlaying()) mp.stop(); 指令代表的是若 mp 物件正在播放音樂中,因為此時按了停止播放的按鈕,所以才執行 mp.stop() 來停止播放音樂。

 

4.5 用 SoundPool 來播放聲音

SoundPool 類別要特別注意的是只能播放小於 1MB 的聲音檔,對於一個超過 1MB 的聲音檔,SoundPool 類別物件雖然可以播放,但是只會播放前 1MB 的部份聲音檔,大約 30秒。

        // 語法:android.media.SoundPool.SoundPool(int maxStreams, int streamType, int srcQuality);
        // SoundPool 類別物件只能播放小於 1MB 的音樂檔,大於 1MB 的音樂檔部份會被捨棄不播放
        sp = new SoundPool(4,AudioManager.STREAM_MUSIC,100);

上述的指令就是用來建立一個名為 sp 的 SoundPool 物件,其中,第一個數值,代表可以記錄4首聲音檔的資料,第二個參數用以指名聲音檔的型態,第三個參數用以指定聲音檔的原始品質。

利用 SoundPool 來播放聲音檔要比利用 MediaPlayer 來播放聲音檔,要多建立一個儲存音樂檔資料的表格或陣列,並利用 AudioManager 類別物件的相關功能來輔助音樂檔等屬性的設定。又,由於本程式也要透過兩個按鈕來讓使用者控者播放音樂或是停止播放音樂,所以仍舊要建立一個監聽者來監聽這兩個按鈕是否出現要監聽的事件。

4.5.1 建立音樂資料檔表格或陣列

 要建立音樂資料集合的方式可以利用 HasMap 類別,或是 SparseIntArray 類別。Google 建議採用 SparseIntArray 類別會比較有效率。本範例同時皆有建立這個類別物件,用以儲存相同的聲音檔資料,但是最後被存取使用的是 SparseIntArray 類別物件。

4.5.1.1 SparseIntArray 陣列物件

soundArray = new SparseIntArray();
soundArray.append(1, sp.load(this,R.raw.sound2,1));
soundArray.append(2, sp.load(this,R.raw.sound3,1));

上述第一行指令是用以建立一個名為 soundArray的 SparseIntArray類別物件。

而 soundArray.append(1, sp.load(this,R.raw.sound2,1)); 指令中,第一個參數值是用以指定將第二個參數的值存入此陣列的第1個儲存格。而 sp.load(this,R.raw.sound2,1) 指令中,是指利用 sp 此一 SoundPool 類別物件去取得本 SoundTest 類別物件 (指this的意思)中,存放在 res/raw 子目錄中名為 sound2.mp3 的聲音檔,其中 mp3附檔名可以不用寫,而且實際聲音檔的附檔名也可以是別種格式檔,而sp.load()函數中的第3個參數原先的目的是可以指定優先順序 (priority) ,但是通常皆可以直接用1做為預設值即可。因此,soundArray.append(2, sp.load(this,R.raw.sound3,1)); 指令就是將 sound3.mp3 此一聲音檔的資料存入陣列中的第2個儲存格中。

 

4.5.1.2 HashMap 陣列

        spMap = new HashMap<Integer,Integer>();
        
        // 語法:int android.media.SoundPool.load(Context context, int resId, int priority);
        spMap.put(1,sp.load(this,R.raw.sound2,1));
        spMap.put(2,sp.load(this,R.raw.sound3,1));

上述第一行指令就是建立一個名為 spMap 的 HashMap 類別物件。而且利用 spMap.put()函數來分別儲存 soudn2.mp3 與 sound3.mp3 的聲音檔資料至陣列中的第1個及第2個儲存格中。

 

4.5.2 建立事件監聽者

上一節所建立的監聽者方式是另外建立一個新的且與 Activity 類別物件(即指本程式的 SoundTest 類別) 無關的監聽者,而此處要建立的監聽者是直接交由屬於Activity 類別的本程式 SoundTest 類別物件來自己監聽。因此,

第二種整個監聽設計的過程如下

第一階段:使用本Activity 視窗類別的 SoundTest 程式來自己擔任監聽者

public class SoundTest extends Activity implements OnClickListener{

上述的指令是指建立一個 Activity 為父類別並且名稱為 SoundTest 的延伸類別,同時去實作一個名為 OnClickListener 且屬於介面 (Interface) 的類別。而該 OnClickListener 介面類別中有一個 public void onClick(View v) {...} 函數是要在 SoundTest 類別中加以另外定義的。此一 onClick(View v){...} 函數就是用來處理監聽的事宜,亦即必須要在此一函數中來判斷到底是那一個被指定監聽的按鈕被按下 Click 鍵,並且要進行相對應的動作。此函數的內容如下:

@Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        String str=ed_soundId.getText().toString();
        Integer soundId;
        Log.e("ed_soundId_Text=",str);//輸出至 LogCat視窗來進行人工查驗
        soundId = Integer.parseInt(str);
        Log.e("ed_soundId_Integer=",String.valueOf(soundId));
        if (soundId==null) soundId=1;
        
        switch (v.getId()){
        case R.id.btn_startSoundPool:
            tv_out_message.setText("使用 SoundPool 開始播放音樂");
            playSound(soundId,0);
            break;
            
        case R.id.btn_stopSoundPool:
            tv_out_message.setText("停止 SoundPool 播放音樂");
            sp.pause(soundId);
            break;
        
        }
    }

 

上述程式區段中,在函數名稱之前的指令 @Override 是代表用來覆寫 OnClickListener 此一介面類別中原本的 onClick(View v) 函數,以便於由自己來控制想要如何進行監聽。

 

第二階段:指定監聽者要監聽那些物件

在使用者所自定的 init() 函數中有如下的指令:

btn_startSoundPool.setOnClickListener(this);
btn_stopSoundPool.setOnClickListener(this);

亦即代表會被監聽的物件為 btn_startSoundPool 物件,以及 btn_stopSoundPool 物件。而 setOnClickListener(...) 函數是用來指定監聽者是誰,而 setOnClickListener(this)中的 this 代表的就是 SoundTest 自己來扮演監聽者的角色。因為 init() 是屬於 SoundTest 此一類別物件中的函務,自然 this 所代表的就是本 SoundTest 此一類別物件。

 

接著再回來討論 onCLick(View v){...} 監聽函數的內容:

 

String str=ed_soundId.getText().toString();
Integer soundId;
Log.e("ed_soundId_Text=",str);//輸出至 LogCat視窗來進行人工查驗
soundId = Integer.parseInt(str);
Log.e("ed_soundId_Integer=",String.valueOf(soundId));
if (soundId==null) soundId=1;

 

上述這幾列指列是為了測試在畫面中的輸入欄位 EditText 的類別物件 ed_soundId,到底使用者是輸入了要播放那一首編號的音樂檔。其中, Log.e("ed_soundId_test=", str) 指令是用來將虛擬手機傳送兩個訊息給 Eclipse 中的 Android LogCat視窗,以便於進行人工的查驗,這兩個訊息分別是字串 「ed_soundId_Text=」,以及字串變數 str 的內容。因為筆者在測式的過程中,曾發現程式並未完整取得音樂檔而造成執行錯誤的情況,所以就寫了這幾行指令以人工查驗是否該程式有正確的取得 EditText 類別物件 ed_soundId 的內容。

 

又 Integer.parseInt(str) 是用來將字串變數 str 轉成整睥,而 String.valueOf(soundId) 是用來將 soundId 此一整數值轉成字串。

if (soundId==null) soundId=1;

此一指令是用來判斷若由 ed_soundId 物件所取得的值是一個空值 (null) 那麼就將 soundId 的值設為1,代表預設要處理第一首音樂。

 

如前所述,我們可以利用監聽函數所傳進來的 View 類別的參睥 v 來進行研判,其中, v.getId() 此一指令可以取得按下 Click 鍵的物件識別名稱為何,並配合 switch (v.getId()) {case...} 的指令來分別進行研判。所以

case R.id.btn_startSoundPool: 所代表的就是在判斷是否 v.getId() 所取得的物件識別名稱為 R.id.btn_startSoundPool,亦即代表是否是 btn_startSoundPool 按鈕被按下 Click 鍵。若是如此,則利用

tv_out_message.setText("使用 SoundPool 開始播放音樂");

此一指令來在畫面上利用 TextView 類別物件 tv_out_message 來指定顯示「使用 SoundPool 開始播放音樂」此一訊息。

playSound(soundId,0);

此一函數是使用者自定的函數,其中,第一個參數的意思是要播放第幾首音樂,而第二個參數用以指定是否要徝環播放,0代表不循環播放,1代表要循環播放。

case R.id.btn_stopSoundPool: 所代表的就是在判斷是否 v.getId() 所取得的物件識別名稱為 R.id.btn_stopSoundPool,亦即代表是否是 btn_stopSoundPool 按鈕被按下 Click 鍵。若是如此,則利用

tv_out_message.setText("停止 SoundPool 播放音樂");

此一指令來在畫面上利用 TextView 類別物件 tv_out_message 來指定顯示「停止 SoundPool 播放音樂」此一訊息。

sp.pause(soundId);

此一指令就是讓名為 sp 的 SoundPool 類別物件停止播放變數 soundId 所指定的那一首音樂。

 

 

4.5.3 建立為 SoundPool 用來監聽是否已經音樂檔完整讀取的 OnLoadCompletListener 監聽者

       onLoadCompleteListener = new OnLoadCompleteListener(){
            @Override
            public void onLoadComplete(SoundPool soundPool, int sampleId,
                    int status){
                loaded=true;
            }
        };
        sp.setOnLoadCompleteListener(onLoadCompleteListener);

其中,onLoadCompleteListener = new OnLoadCompleteListener(){...} 指令是建立一個名為 onLoadCompleteListener 的 OnLoadCompleteListener 類別的物件,用以監聽 SoudPool 類別物件是否已完整讀取聲音檔。

而 loaded=true; 中,loaded 是之前已宣告的變數,代表聲音檔已完整的讀取。

而 sp.setOnLoadCompleteListener(onLoadCompleteListener); 指令是將 sp 此一 SoundPool 類別物件,指定由所建立的 onLoadCompleteListener 來進行監聽是否已完整的讀取聲音檔。

 

4.5.4 使用者自訂函數 playSound 函數

 

   public void playSound(int soundId,int loop){
        AudioManager am = (AudioManager)
                this.getSystemService(Context.AUDIO_SERVICE);
        float streamVolumeCurrent =
                am.getStreamVolume(AudioManager.STREAM_MUSIC);
        float streamVolumeMax =
                am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        float volume = streamVolumeCurrent / streamVolumeMax;
        
        if (loaded)
            // 語法:int android.media.SoundPool.play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate);
            
            //利用所建立音樂列表方式一來取用音樂:
            sp.play(soundArray.get(soundId), volume, volume, 1, loop, 1f);
            
            //利用所建立音樂列表方式二來取用音樂:
            //sp.play(spMap.get(soundId), volume, volume, 1, loop, 1f);
    }

要利用 SoundPool 類別物件來播放音樂必須要指定左聲道與右聲道的聲音大小,這必須藉助 AudioManager 類別物件來讀取相關的系統音響資訊來達成。

其中,AudioManager am = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); 指令是利用本 SoundTest 的 Activity 類別物件中的 getSystemService(...) 函數來建立一個名為 am 的 AudioManager 類別物件。

float streamVolumeCurrent = am.getStreamVolume(AudioManager.STREAM_MUSIC);
float streamVolumeMax = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
float volume = streamVolumeCurrent / streamVolumeMax;

這三行指令就是要取得系統最大的音量與目前音量的比值,來做為 SoundPool 的播放音量。

而 if (loaded) {...} 此一指令就是用以判斷由 onLoadCompleteListener 監聽者是否已監聽到已完整的讀取聲音檔,若是,則開始要由 SoundPool 類別物件來進行播放。

sp.play(soundArray.get(soundId), volume, volume, 1, loop, 1f);

就是利用所建立名為 soundArray 的 SparseIntArray 類別物件的陣列內容來讓 SoudnPool 類別物件 sp來播放音樂。

sp.play(spMap.get(soundId), volume, volume, 1, loop, 1f);

就是利用所建立名為 spMap 的 HashMap 類別物件的陣列內容來讓 SoudnPool 類別物件 sp來播放音樂。

因為本範例是使用 soundArray 物件來讀取音樂陣列中的聲音檔資料,所以在此 sp.play(spMap.get(soundId), volume, volume, 1, loop, 1f);指令的前面用兩個倒斜線來註解掉,不讓系統執行。

 

 

arrow
arrow

    xx3d2ybnf 發表在 痞客邦 留言(0) 人氣()