聲音範例:Podcast Player

Flash Player 9 以及更新的版本,Adobe AIR 1.0 以及更新的版本

Podcast 是在網際網路上以點播或訂閱方式散佈的聲音檔案。Podcast 通常會發佈做為一系列中的某一部分,而該系列又稱為 Podcast 頻道。因為 Podcast 的廣播節目可以在任何地方持續一分鐘到許多小時,所以在播放時通常會以串流的方式進行。Podcast 廣播節目 (也稱為項目) 通常是以 MP3 檔案格式傳送。雖然視訊 Podcast 也很流行,但是此樣本應用程式只會播放使用 MP3 檔案的音效 Podcast。

此範例不是具有完整功能的 Podcast Aggregator 應用程式。例如,它並不會管理特定 Podcast 的訂閱,也不會在下一次應用程式執行時,記住使用者已聽過哪些 Podcast。它可以做為功能更加完整之 Podcast Aggregator 的起點。

Podcast Player 範例將說明下列 ActionScript 程式設計技術:

  • 讀取外部 RSS Feed 並解析其 XML 內容

  • 建立 SoundFacade 類別以簡化聲音檔案的載入和播放作業

  • 顯示聲音播放進度

  • 暫停和繼續聲音播放

若要取得此樣本的應用程式檔案,請參閱 www.adobe.com/go/learn_programmingAS3samples_flash_tw。您可以在 Samples/PodcastPlayer 資料夾中找到 Podcast Player 應用程式檔案,此應用程式是由下列檔案組成:

檔案

說明

PodcastPlayer.mxml

PodcastPlayer.fla

應用程式使用者介面,在 Flash 中為 FLA,在 Flex 中為 MXML。

comp/example/programmingas3/podcastplayer/PodcastPlayer.as

文件類別,包含 Podcast Player 的使用者介面邏輯 (僅適用於 Flash)。

SoundPlayer.mxml

可顯示播放按鈕和進度列,並控制聲音播放的 MXML 組件,僅適用於 Flex。

main.css

應用程式使用者介面的樣式 (僅適用於 Flex)。

images/

製作按鈕樣式的圖示 (僅適用於 Flex)。

comp/example/programmingas3/podcastplayer/SoundPlayer.as

SoundPlayer 影片片段元件的類別,包含聲音播放程式的使用者介面邏輯 (僅適用於 Flash)。

comp/example/programmingas3/podcastplayer/PlayButtonRenderer.as

在資料格儲存格中顯示播放按鈕的自訂儲存格輸出器 (僅適用於 Flash)。

com/example/programmingas3/podcastplayer/RSSBase.as

基底類別,為 RSSChannel 類別與 RSSItem 類別提供一般的屬性與方法。

com/example/programmingas3/podcastplayer/RSSChannel.as

ActionScript 類別,用以儲存 RSS 頻道的資料。

com/example/programmingas3/podcastplayer/RSSItem.as

ActionScript 類別,用以儲存 RSS 項目的資料。

com/example/programmingas3/podcastplayer/SoundFacade.as

應用程式的主要 ActionScript 類別。它會封裝 Sound 類別與 SoundChannel 類別的方法與事件,並增加對暫停和繼續播放的支援。

com/example/programmingas3/podcastplayer/URLService.as

從遠端 URL 擷取資料的 ActionScript 類別。

playerconfig.xml

包含 RSS Feed 之清單的 XML 檔案,這些 RSS Feed 都代表 Podcast 頻道。

comp/example/programmingas3/utils/DateUtil.as

用來簡單設定日期格式的類別 (僅適用於 Flash)。

讀取 Podcast 頻道的 RSS 資料

Podcast Player 應用程式會從讀取一些 Podcast 頻道及其廣播節目的資訊開始。

1. 首先,應用程式會讀取 XML 設定檔 (此檔案包含 Podcast 頻道的清單),並向使用者顯示頻道清單。

2. 當使用者選取其中一個 Podcast 頻道時,它會讀取該頻道的 RSS Feed,並顯示頻道節目的清單。

此範例會使用 URLLoader 公用程式類別,從遠端位置或本機檔案擷取文字資料。Podcast Player 首先會建立 URLLoader 物件,以從 playerconfig.xml 檔案取得 XML 格式的 RSS Feed 清單。接著,當使用者從清單選取特定的內容時,就會建立新的 URLLoader 物件,以便從該 Feed 的 URL 讀取 RSS 資料。

使用 SoundFacade 類別簡化聲音載入和播放作業

ActionScript 3.0 的聲音架構非常強大,但卻也複雜。只需要基本聲音載入和播放功能的應用程式可以使用一個類別,藉由提供一組較簡單的方法呼叫與事件,隱藏某些複雜度。在軟體設計模式的世界中,這種類別就稱為「外觀」。

SoundFacade 類別會提供單一介面,以執行下列工作:

  • 使用 Sound 物件、SoundLoaderContext 物件以及 SoundMixer 類別載入聲音檔案。

  • 使用 Sound 物件和 SoundChannel 物件播放聲音檔案

  • 傳送播放進度事件

  • 使用 Sound 物件和 SoundChannel 物件來暫停和繼續播放聲音。

SoundFacade 類別會嘗試以較低的複雜度,提供 ActionScript 聲音類別的大部分功能。

下列程式碼會顯示類別宣告、類別屬性以及 SoundFacade() 建構函式方法:

public class SoundFacade extends EventDispatcher 
{ 
    public var s:Sound; 
    public var sc:SoundChannel; 
    public var url:String; 
    public var bufferTime:int = 1000; 
 
    public var isLoaded:Boolean = false; 
    public var isReadyToPlay:Boolean = false; 
    public var isPlaying:Boolean = false; 
    public var isStreaming:Boolean = true; 
    public var autoLoad:Boolean = true; 
    public var autoPlay:Boolean = true; 
         
    public var pausePosition:int = 0; 
         
    public static const PLAY_PROGRESS:String = "playProgress"; 
    public var progressInterval:int = 1000; 
    public var playTimer:Timer; 
         
    public function SoundFacade(soundUrl:String, autoLoad:Boolean = true, 
                                    autoPlay:Boolean = true, streaming:Boolean = true,  
                                    bufferTime:int = -1):void 
    { 
        this.url = soundUrl; 
 
        // Sets Boolean values that determine the behavior of this object 
        this.autoLoad = autoLoad; 
        this.autoPlay = autoPlay; 
        this.isStreaming = streaming; 
 
        // Defaults to the global bufferTime value 
        if (bufferTime < 0) 
        { 
            bufferTime = SoundMixer.bufferTime; 
        } 
 
        // Keeps buffer time reasonable, between 0 and 30 seconds 
        this.bufferTime = Math.min(Math.max(0, bufferTime), 30000); 
         
        if (autoLoad) 
        { 
            load(); 
        } 
    }

SoundFacade 類別會擴充 EventDispatcher 類別,讓它可以傳送屬於自己的事件。該類別程式碼首先會宣告 Sound 物件與 SoundChannel 物件的屬性。此類別也會儲存聲音檔案的 URL 值,以及串流聲音時要使用的 bufferTime 屬性值。除此之外,它還會接受一些會影響載入和播放行為的 Boolean 參數值:

  • autoLoad 參數會告訴物件,只要一建立物件就開始載入聲音。

  • autoPlay 參數會指出只要載入足夠的聲音資料就開始播放聲音。如果這是串流聲音,則只要已載入 bufferTime 屬性所指定的足夠資料就會開始播放。

  • streaming 參數會指出在完成載入之前,聲音檔案可以開始播放。

bufferTime 參數預設值為 -1。如果建構函式方法在 bufferTime 參數中偵測到負值,它會將 bufferTime 屬性設定為 SoundMixer.bufferTime 的值。這可以讓應用程式將預設值設定為所需的全域 SoundMixer.bufferTime 值。

如果 autoLoad 參數已設定為 true,則建構函式方法會立即呼叫下列 load() 方法,開始載入聲音檔案:

public function load():void 
{ 
    if (this.isPlaying) 
    { 
        this.stop(); 
        this.s.close(); 
    } 
    this.isLoaded = false; 
     
    this.s = new Sound(); 
     
    this.s.addEventListener(ProgressEvent.PROGRESS, onLoadProgress); 
    this.s.addEventListener(Event.OPEN, onLoadOpen); 
    this.s.addEventListener(Event.COMPLETE, onLoadComplete); 
    this.s.addEventListener(Event.ID3, onID3); 
    this.s.addEventListener(IOErrorEvent.IO_ERROR, onIOError); 
    this.s.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onIOError); 
     
    var req:URLRequest = new URLRequest(this.url); 
     
    var context:SoundLoaderContext = new SoundLoaderContext(this.bufferTime, true); 
    this.s.load(req, context); 
}

load() 方法會建立新的 Sound 物件,然後為所有重要的聲音事件增加偵聽程式。接著,它會告訴 Sound 物件使用 SoundLoaderContext 物件傳入 bufferTime 值,以載入聲音檔案。

由於您可以變更 url 屬性,因此可以使用 SoundFacade 實體來連續播放不同的聲音檔案:只需變更 url 屬性,再呼叫 load() 方法,就會載入新的聲音檔案。

下列三個事件偵聽程式方法將說明 SoundFacade 物件如何追蹤載入的進度,並決定何時開始播放聲音:

public function onLoadOpen(event:Event):void 
{ 
    if (this.isStreaming) 
    { 
        this.isReadyToPlay = true; 
        if (autoPlay) 
        { 
            this.play(); 
        } 
    } 
    this.dispatchEvent(event.clone()); 
} 
 
public function onLoadProgress(event:ProgressEvent):void 
{  
    this.dispatchEvent(event.clone()); 
} 
 
public function onLoadComplete(event:Event):void 
{ 
    this.isReadyToPlay = true; 
    this.isLoaded = true; 
    this.dispatchEvent(evt.clone()); 
     
    if (autoPlay && !isPlaying) 
    { 
        play(); 
    } 
}

onLoadOpen() 方法會在聲音載入作業開始時執行。如果可以在串流模式中播放聲音,則 onLoadComplete() 方法會立即將 isReadyToPlay 旗標設定為 trueisReadyToPlay 旗標會決定應用程式是否可以開始聲音播放,也許回應如按一下「播放」按鈕等使用者動作。由於 SoundChannel 類別會管理聲音資料的緩衝,因此在呼叫 play() 方法之前,不需要明確檢查是否已載入足夠的資料。

onLoadProgress() 方法會在載入程序期間定期執行。它會直接傳送其 ProgressEvent 物件之副本,供使用此 SoundFacade 物件的程式碼使用。

已完全載入聲音資料後,就會執行 onLoadComplete() 方法,並視需要為非串流聲音呼叫 play() 方法。play() 方法本身如下所示。

public function play(pos:int = 0):void 
{ 
    if (!this.isPlaying) 
    { 
        if (this.isReadyToPlay) 
        { 
            this.sc = this.s.play(pos); 
            this.sc.addEventListener(Event.SOUND_COMPLETE, onPlayComplete); 
            this.isPlaying = true; 
             
            this.playTimer = new Timer(this.progressInterval); 
            this.playTimer.addEventListener(TimerEvent.TIMER, onPlayTimer); 
            this.playTimer.start(); 
        } 
    } 
}

play() 方法會在聲音可以播放時呼叫 Sound.play() 方法。產生的 SoundChannel 物件是儲存在 sc 屬性中。play() 方法接著會建立 Timer 物件,該物件將用以定期傳送播放進度事件。

顯示播放進度

建立 Timer 物件以驅動播放監視功能是一項複雜的作業,因此您最好只撰寫一次程式碼就好。將此 Timer 邏輯封裝在如 SoundFacade 等可重複使用的類別中,可讓應用程式在聲音載入和播放時,偵聽相同種類的進度事件。

SoundFacade.play() 方法所建立的 Timer 物件每秒都會傳送 TimerEvent 實體。每當有新的 TimerEvent 到達時,下列 onPlayTimer() 方法就會執行:

public function onPlayTimer(event:TimerEvent):void  
{ 
    var estimatedLength:int =  
        Math.ceil(this.s.length / (this.s.bytesLoaded / this.s.bytesTotal)); 
    var progEvent:ProgressEvent =  
        new ProgressEvent(PLAY_PROGRESS, false, false, this.sc.position, estimatedLength); 
    this.dispatchEvent(progEvent); 
}

onPlayTimer() 方法會實作在監視播放作業一節中描述的大小估算技術。接著,它會建立具有 SoundFacade.PLAY_PROGRESS 事件類型的新 ProgressEvent 實體,其中 bytesLoaded 屬性會設定為 SoundChannel 物件的目前位置,而且 bytesTotal 屬性會設定為聲音資料的估算長度。

暫停和繼續播放

之前顯示的 SoundFacade.play() 方法可接受對應至聲音資料中開始位置的 pos 參數。如果 pos 值為零,就會從一開始播放聲音。

SoundFacade.stop() 方法也會接受 pos 參數,如下所示:

public function stop(pos:int = 0):void 
{ 
    if (this.isPlaying) 
    { 
        this.pausePosition = pos; 
        this.sc.stop(); 
        this.playTimer.stop(); 
        this.isPlaying = false; 
    }     
}

每當呼叫 SoundFacade.stop() 方法時,它就會設定 pausePosition 屬性,這樣一來,如果使用者想要繼續播放相同聲音時,應用程式就會知道要放置播放磁頭的位置。

下面所顯示的 SoundFacade.pause()SoundFacade.resume() 方法會分別叫用 SoundFacade.stop()SoundFacade.play() 方法,每一次都會傳遞 pos 參數值。

public function pause():void 
{ 
    stop(this.sc.position); 
} 
 
public function resume():void 
{ 
    play(this.pausePosition); 
}

pause() 方法會將目前的 SoundChannel.position 值傳遞給 play() 方法,它會將該值儲存在 pausePosition 屬性中。resume() 方法會使用 pausePosition 值做為起點,再次開始播放相同的聲音。

擴充 Podcast Player 範例

此範例顯示極為精簡的 Podcast Player,以展示可重複使用的 SoundFacade 類別的用法。您可以增加其它功能以增強此應用程式的有用性,其中包括下列項目:

  • 將每個節目的 Feed 清單與使用資訊儲存在 SharedObject 實體中,以利下次使用者執行應用程式時使用。

  • 讓使用者將自己的 RSS Feed 加入 Podcast 頻道的清單。

  • 當使用者停止或離開節目時,記住播放磁頭的位置。這樣一來,下次使用者執行應用程式時就可以從該點重新啟動該節目。

  • 下載節目的 MP3 檔案,當使用者未連線到網際網路時便可以離線聆賞。

  • 增加訂閱功能,定期檢查在 Podcast 頻道中的新節目並自動更新節目清單。

  • 使用如 Odeo.com 等提供 Podcast 廣播服務的網站所提供的 API,增加 Podcast 搜尋和瀏覽功能。