觸控與執行

Windows Phone 7.5 上的觸控音訊

查理斯 · Petzold

下載代碼示例


回舊時代的 MS-DOS,程式師可以用一種稱為終止和留下來的居民 (也稱為 TSR) 技術實現任務切換原油窗的體。TSR 程式安裝掛鉤到鍵盤中斷或其它作業系統的機制,然後終止,離開在記憶體中的程式準備踢進的行動中,當使用者按下特定的複合鍵或其他感興趣的東西發生了。

MS-DOS 不是有能力處理甚至這一級別的基本任務切換,因此這些 Tsr 創建一些嚴重的問題。他們會相互衝突,經常崩潰的整個作業系統。這是我們中的一些歡迎補充和後來替換 MS-DOS 視窗的主要原因之一。

我提到這個古代歷史,因為我要向您展示如何發揮 Windows Phone 7.5 的應用程式中,從背景中的音樂檔,我知道開發者傾向于認為在框外。通常,這是一件好事,但你不應該使用此技術的播放音樂檔以外的其他目的。這樣做可能會導致您的應用程式 Windows Phone 市場所拒絕。

你瞧的技術只是播放聲音或音樂檔從 Web 位置或獨立的存儲。如果您的應用程式需要播放歌曲從手機的正常的音樂庫,你可以使用我討論在上期的 MediaLibrary 和 MediaPlayer 類 (msdn.microsoft.com/magazine/hh708760)。

應用程式加上 DLL

Windows Phone 7.5,在協助一個應用程式後臺運行的物件稱為代理。要播放背景音樂檔,您可以使用從 AudioPlayerAgent 派生的類。此類必須是由該程式所引用的動態連結程式庫。程式碼不會本身運行在後臺 ; 什麼在後臺運行,是此從 AudioPlayerAgent 派生的類。

這篇文章是一個名為 SimpleBackgroundAudio 的 Visual Studio 解決方案包含在可下載的代碼。為清楚起見,此套裝程式含只是最小的獲取背景音訊工作所需的代碼量。通過指定 Windows Phone 應用程式,然後 Windows Phone 7.1,我從新建專案對話方塊創建解決方案。(7.1 指定內部使用的 Windows Phone 作業系統和應用程式 Windows Phone,但它意味著更常見的 7.5 指定相同)。

SimpleBackgroundAudio 專案的網頁類添加一個按鈕,該按鈕的 Click 處理常式:

void OnPlayButtonClick(object sender, RoutedEventArgs args)
{
  AudioTrack audioTrack = 
    new AudioTrack(new Uri("http://www.archive.org/.../Iv.Presto.mp3"), 
                   "Symphony No.
9: 4th Movement", 
                   "Felix Weingartner", 
                   "Beethoven: Symphony No.
9", 
                   null, 
                   null, 
                   EnabledPlayerControls.Pause);
  BackgroundAudioPlayer.Instance.Track = audioTrack;
}

在 Microsoft.Phone.BackgroundAudio 命名空間中的音訊曲目和 BackgroundAudioPlayer 的類。 (這我已經縮短在這裡,以滿足該雜誌的列寬度) 的 URL 引用包含最後的貝多芬的交響樂號移動互聯網檔案館 (archive.org) 上的一個 MP3 檔 9 由菲力克斯 · 魏因加特納 1935年錄製中進行。 (您可以另外指定一個 URL 位址 ; 的獨立存儲中的檔, 使用 Uri 構造函數的 UriKind.Relative 參數如果真是這樣。未來三個音訊曲目的構造函數的參數提供專輯、 藝術家和跟蹤資訊。

BackgroundAudioPlayer 類是有點類似的 MediaElement 或 MediaPlayer 播放音訊檔,在前臺的類。 BackgroundAudioPlayer 有沒有構造函數 ; 相反,您獲取與靜態實例屬性的唯一實例。 在此代碼中,跟蹤屬性設置為剛剛創建的音訊曲目物件。

您可以編譯並運行該程式,但它不會做任何事。 要使 BackgroundAudioPlayer 唱,您需要一個 DLL 包含從 AudioPlayerAgent 派生的類。 Visual Studio 可以為您創建此類和庫的專案。 我的程式,為我按右鍵在 Visual Studio 中的 SimpleBackgroundAudio 解決方案名稱、 從功能表中選擇添加新專案,然後從範本清單中選擇 Windows Phone 音訊重播代理。 我命名為 SimpleAudioPlaybackAgent,這個新的專案。

Visual Studio 創建具有一個名為 AudioPlayer AudioPlayerAgent,初始化的骨架的幾種方法從派生類的庫專案。 這是在後臺運行的類。

非常重要:從對此 DLL 的應用程式創建一個引用 ! 為此,我按右鍵引用部分下的 SimpleBackgroundAudio 專案,選定的添加引用,然後在對話方塊中我選擇專案選項卡,然後 SimpleAudioPlaybackAgent 專案。

AudioPlayerAgent 導數,你要修改至少兩個方法:OnPlayStateChanged 和 OnUserAction。 完整的 AudioPlayer 類,從這個專案 (減去使用指令和評論) 所示圖 1

圖 1 中 SimpleBackgroundAudio 的 AudioPlayer 類

namespace SimpleAudioPlaybackAgent
{
  public class AudioPlayer : AudioPlayerAgent
  {
    protected override void OnPlayStateChanged(BackgroundAudioPlayer player,
                                      AudioTrack track, PlayState playState)
    {
      switch (playState)
      {
        case PlayState.TrackReady:
          player.Play();
          break;

        case PlayState.TrackEnded:
          player.Track = null;
          break;
      }
      NotifyComplete();
    }
    protected override void OnUserAction(BackgroundAudioPlayer player, 
                                      AudioTrack track, UserAction action, 
                                      object param)
    {
      switch (action)
      {
        case UserAction.Pause:
          player.Pause();
          break;

        case UserAction.Play:
          player.Play();
          break;
      }
      NotifyComplete();
    }

    protected override void OnError(BackgroundAudioPlayer player, 
                                      AudioTrack track, Exception error, 
                                      bool isFatal)
    {
        base.OnError(player, track, error, isFatal);
        NotifyComplete();
    }
    protected override void OnCancel()
    {
        base.OnCancel();
        NotifyComplete();
    }
  }
}

首先看看 OnPlayStateChanged 重寫。BackgroundAudioPlayer 的 PlayState 屬性更改時調用此方法。第一個參數是相同的 BackgroundAudioPlayer 引用的程式碼 ; 最後一個參數是 PlayState 枚舉的成員。

當程式 Click 事件處理常式中設置了跟蹤屬性的 BackgroundAudioPlayer 時,BackgroundAudioPlayer 訪問音樂檔通過互聯網 ; SimpleAudioPlaybackAgent DLL 被載入和 OnPlayStateChanged 方法最終獲取調用的 playState 參數設置為 PlayState.TrackReady。它是 OnPlayStateChanged 的 BackgroundAudioPlayer 物件,開始播放的曲目的播放方法調用的責任。

如果手機的定期音樂播放機出現打的東西時,它將被停止。此時,您可以導航到其他程式或甚至將其終止按下手機上的後退按鈕,和音樂將繼續播放。

您可以在 Windows Phone 模擬器,運行此程式,但你要試試實際的手機上,所以您可以按手機的音量控制。這將啟動下拉一點已知紫外作為通用卷控制 (C),這是背景音訊的重要組成部分。紫外 C 顯示正在播放的曲目名稱和藝術家,基於的應用程式提供的音訊曲目構造函數的參數。紫外 C 還顯示按鈕上一曲目、 暫停和下一曲目。在此程式中,只有暫停按鈕啟用了因為那是我在音訊曲目構造函數的最後一個參數中的指定。當您按暫停按鈕時,它暫停和播放之間切換。

AudioPlayer 類處理這些暫停和播放的命令,在所示的 OnUserAction 重寫圖 1。UserAction 參數指示使用者按下的特定按鈕。OnUserAction 回應通過 BackgroundAudioPlayer 物件中調用相應的方法。

當播放完軌道時,OnPlayStateChanged 獲取 PlayState.TrackEnded 的呼叫。通過設置為 null,而從紫外 C 中移除的項的 BackgroundAudioPlayer 的軌道屬性的方法做出回應。如果需要,可以返回到應用程式並開始重新播放的音樂。

對 NotifyComplete 的調用與 OnPlayStateChanged 和 OnUserAction 締結的通知:這是必需的。此外注意到兩種方法包括調用基類方法。這些基類調用是 Visual Studio,您創建的範本的一部分,但我遇到了問題 OnUserAction 重寫基方法被調用時。從微軟的背景音訊的示例代碼 (可用線上從 Windows Phone 7.5 文檔區域) 還不包括調用基方法。

很奇怪的 DLL

當嘗試背景音訊,你要保持在 Visual Studio 中的輸出視窗上的手錶。從調試器運行 SimpleBackgroundAudio 時,輸出視窗將列出所有載入在手機上運行該程式,以及程式本身,這是 SimpleBackgroundAudio.dll 的系統庫。在輸出視窗中列出這些庫的行開始改為"使用者介面任務",指示該程式。

當您點擊按鈕在 SimpleBackgroundAudio 中的時,您將看到許多相同的正在載入的 Dll — — 但現在與每行開頭的單詞"幕後工作"。這些 Dll 包含應用程式 DLL 以及 SimpleAudioPlaybackAgent.dll。這些 Dll 的載入是時間的它需要一點點開始播放你點擊按鈕後音樂的原因之一。

您嘗試播放背景音訊的程式,就可能要在所有方法重寫在 AudioPlayer 類中,插入 Debug.WriteLine 語句,然後研究他們即時的行為,在輸出視窗中。

您也可能想用另一個 Debug.WriteLine 語句創建一個 AudioPlayer 類的構造函數。你會發現每當調用需要製成 OnPlayStateChanged 或 OnUserAction AudioPlayer 的一個新實例被產生實體。每個調用獲取一個新的實例 !

這個簡單的事實已有深刻的含義:如果您需要此 AudioPlayer 類保持調用 OnPlayStateChanged 和 OnUserAction 之間的資訊,你必須保持該資訊­中的靜態欄位或屬性的資訊。

如果您需要從 SimpleBackgroundAudio 程式中的類的資訊傳遞到 SimpleAudioPlaybackAgent 庫中 AudioPlayer 類嗎?它看起來是合理的在 AudioPlayer 類中,定義一個公共的靜態方法,然後從首頁或另一個類的程式中調用該方法。事實上你可以,但它不會做你想要什麼:此方法從保存的任何東西不會可用期間調用 OnPlayStateChanged 和 OnUserAction。

為什麼不會可用?記得在 Visual Studio 輸出視窗中的使用者介面任務和任務背景型號。這些都是兩個單獨的進程。您的程式中引用的 dll 實例不是在後臺運行的實例相同,因此將在使用者介面任務和幕後工作之間共用此 DLL 中的類中不甚至靜態資料。

在測試時從調試器的後臺音訊應用程式在 Visual Studio 中,您會遇到有些額外的古怪。當您退出程式,發起了一些背景音訊時,一直播放音訊和 Visual Studio 指示代碼仍在運行。若要繼續編輯您的程式,你要停止調試直接從 Visual Studio 中,即使在當時音樂及將經常保持上。在開發背景音訊程式中,您可能會發現自己經常從手機卸載程式。

提高應用程式

SimpleBackgroundAudio 的程式具有很大的問題。雖然有開始音樂的播放按鈕,它有沒有辦法暫停或關閉它。它甚至不知道音樂的結論或其他的一切都在發生。是的一個使用者總是可以調用 UVC 控制背景音訊,但開始播放音樂的任何程式還應包括其自己把它關掉的控制項。

這些增強功能都包含在專案中名為播放清單­播放機。顧名思義,這個程式播放一系列連續音軌的 — — 在這種情況下,從克勞德 · 德彪西的前奏曲,12 的小鋼琴曲書 1,,因為在 1949 年由阿爾弗雷德 · 科爾托執行可從互聯網檔案館的錄音。

本來想創建程式中的所有音訊曲目物件,然後交給他們關閉背景音訊 DLL。這似乎是更廣義的程式結構,但我發現它沒有工作,因為應用程式正在訪問不同的實例不是在後臺運行的 dll。相反,我寫的 AudioPlayer 類本身創建音訊曲目的所有物件,並將它們存儲在稱為播放清單的泛型清單。

為了使程式在某種程度上更具挑戰性,我決定播放清單不會迴圈:我不想跳從上次的曲目,第一首曲目,或第一次到了最後。為此原因,音訊曲目的構造函數的第一個有的最後一個參數相結合的 EnabledPlayerControls.Pause 和 EnabledPlayerControls.SkipNext 的標誌 ; 最後一個音訊曲目結合的暫停和 SkipPrevious 的標誌。所有其他人都有所有三個標誌。這是如何的不同軌道的紫外 C 中啟用了三個按鈕。

圖 2 顯示的 OnPlayStateChanged 和 OnUserAction 的覆蓋。在 OnPlayStateChanged,一個軌道結束時,BackgroundAudioPlayer 的跟蹤屬性設置下一曲目。OnUserAction 重寫 UVC 命令通過將跟蹤屬性設置為播放清單中的上一個或下一個音訊曲目的上一曲目和下一曲目的控制碼。

圖 2 中 PlaylistPlayer 的 AudioPlayer 覆蓋

static List<AudioTrack> playlist = new List<AudioTrack>();
static int currentTrack = 0;

...
protected override void OnPlayStateChanged(BackgroundAudioPlayer player, 
  AudioTrack track, PlayState playState)
{
  switch (playState)
  {
    case PlayState.TrackReady:
      player.Play();
      break;

    case PlayState.TrackEnded:
      if (currentTrack < playlist.Count - 1)
      {
        currentTrack += 1;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Track = null;
      }
      break;
  }
  NotifyComplete();
}

protected override void OnUserAction(BackgroundAudioPlayer player, 
  AudioTrack track, UserAction action, object param)
{
  switch (action)
  {
    case UserAction.Play:
      if (player.Track == null)
      {
        currentTrack = 0;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Play();
      }
      break;

    case UserAction.Pause:
      player.Pause();
      break;

    case UserAction.SkipNext:
      if (currentTrack < playlist.Count - 1)
      {
        currentTrack += 1;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Track = null;
      }
      break;

    case UserAction.SkipPrevious:
      if (currentTrack > 0)
      {
        currentTrack -= 1;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Track = null;
      }
      break;

    case UserAction.Seek:
      player.Position = (TimeSpan)param;
      break;
  }
  NotifyComplete();
}

程式的網頁類有一系列的四個標準應用程式欄按鈕可以執行 UVC 相同的功能。它還將設置以使用當前的跟蹤資訊,更新螢幕的 BackgroundAudioPlayer PlayStateChanged 事件的事件處理常式和 CompositionTarget.Rendering 處理常式可以使用當前的跟蹤進度更新滑塊中所示圖 3


圖 3 PlaylistPlayer 程式

啟用和禁用應用程式欄按鈕的邏輯是相當簡單的:上一曲目和下一曲目按鈕啟用基於 PlayerControls 屬性的當前的音訊曲目 ; 因此,他們應符合 UVC。如果玩家玩 ;,啟用暫停按鈕 如果玩家已暫停,則會啟用播放按鈕。如果當前軌道為 null,戲已經啟用,並且所有其他按鈕被禁用。

四個應用程式欄按鈕的按一下處理常式使調用 BackgroundAudioPlayer 方法 SkipPrevious、 播放、 暫停和 SkipNext,分別。很重要的是要瞭解這些方法調用不直接控制音樂重播的運作。相反,這些方法調用的程式觸發 OnUserAction 在 AudioPlayer 類中,調用,它是 AudioPlayer,它實際上開始和停止音樂中的代碼。

這是有些特別,因為它意味著 BackgroundAudioPlayer 的播放和暫停的方法調用的行為以不同的方式取決於是否他們叫做從程式或 OnUserAction 重寫。

我還添加了滑塊移動到軌道中的特定位置的 ValueChanged 處理常式。該處理常式計算軌道的新位置,並將適當的 TimeSpan 物件設置為 BackgroundAudioPlayer 的位置屬性。類似于電話播放和暫停的情況,設置此屬性不會更改軌道的位置。相反,它調用了 OnUserAction 重寫中產生 AudioPlayer 與 UserAction.Seek 和編碼 param 參數中的 TimeSpan 指令引數。OnUserAction 重寫,然後將此 TimeSpan 物件設置為 BackgroundAudioPlayer,位置屬性,這是什麼實際造成跳轉。

在實踐中,此滑塊工作正常時,你只需要點擊它向前或向後在軌道中了 10%。如果您嘗試將其滑,尋求的多個調用似乎建立,,,結果是發聲的混亂。我非常喜歡使用常規的捲軸,而不是一個滑塊,因為,然後我可以等 EndScroll 事件,該事件當使用者停止操縱控制。不幸的是,我從未能夠說服工作了,Windows Phone 捲軸。

局限性

很有趣,請參閱如何 Windows Phone 7.5 給了程式師更直接訪問手機的硬體 (例如,飼料的視頻),以及執行一些幕後處理的能力。但我不能説明有一塊的背景音訊此實現中缺少的思維。

假設程式要讓使用者從清單中的音樂檔播放序列中選取。該程式不能交掉整個清單的 dll,所以它需要花 BackgroundAudioPlayer 跟蹤屬性設置每個跟蹤結束時的責任。但發生這種情況只有當該程式運行在前臺。

它是可能的應用程式和通過音訊曲目類的標記屬性的字串的背景 DLL 之間傳遞一些資訊。但不要打擾音訊曲目從派生新類,希望的將額外的資訊傳遞給該 DLL:音訊曲目物件傳遞到該 DLL 將覆蓋為應用程式創建的副本。

幸運的是,該 DLL 已經訪問應用程式的區域的獨立存儲,以便程式可以保存一個描述該播放清單的小檔,然後,DLL 可以從檔訪問播放清單。為此列的獎勵計畫是一個名為 PlaylistFilePlayer,其中演示了一種簡單的方法,這種技術的專案。

查理斯 · Petzold 是長期的 MSDN 雜誌特約編輯。他的新書"程式設計 Windows Phone 7"(微軟出版社,2010年) 是可免費下載在 bit.ly/cpebookpdf

多虧了以下的技術專家審查這篇文章: 馬克 · 霍普金斯