DCSIMG
Get ready for Windows Phone 7.5 – Part 3 – Background Audio (Hebrew) - Essential WPF

Get ready for Windows Phone 7.5 – Part 3 – Background Audio (Hebrew)

בפוסט הקודם דיברתי על Fast Application Switching, אחת מהיכולות החדשות והחשובות בגרסה החדשה של Windows Phone. בפוסט זה אציג תכונה חדשה שנקראת Background Audio המאפשרת ניגון ושליטה על אודיו ברקע.
הרעיון המרכזי בגרסה החדשה הוא לאפשר למשתמש לבצע פעולות ברקע, ועם זאת מבלי שה-Process ירוץ במקביל ויבזבז משאבים יקרים.
אחת מהפעולות שניתן להריץ ברקע בשיטה זו הינה ניגון ושליטה על אודיו מקוד שאתם כתבתם.
לדוגמה, אתם מעונינים ליצור נגן מדיה חדש, עם יכולות שלא קיימות בנגן המקורי. עם זאת אתם מעונינים שהמוסיקה תמשיך לנגן ברקע, תחליף שירים וניתן יהיה לשלוט עליה בעזרת הכפתורים הסטנדרטיים, למרות שיצאתם מהאפליקציה.
בפועל, יש שני סוגי אפליקציות של Background Audio. אחד שמנגן מדיה מקומית, והשני Stream.
 
Audio Playback Agent
כדי ליצור Background Audio שמנגן מדיה מקומית יש ליצור פרוייקט חדש מסוג Windows Phone Audio Playback Agent (זה בנוסף לפרוייקט הרגיל שמייצג את האפליקציה שלכם). בפרוייקט שמייצג את האפליקציה עצמה יש להוסיף Reference לפרוייקט שמכיל את ה- Agent.
 
imageimageimage
 
מימוש Audio Playback Agent
הרעיון מאחורי ה- Agent הוא למעשה אובייקט שרץ ברקע ומנוהל ע"י הסביבה עצמה. הסביבה שולחת פקודות לאובייקט זה כשיש בקשה לנגן קובץ אחר, לעצור את המוסיקה, וכו'.
להלן קטע קוד שמדגים כיצד לממש אובייקט זה:
 
Code Snippet
  1. public class AudioPlayer : AudioPlayerAgent
  2. {
  3.     private static readonly AudioTracks _tracks = new AudioTracks();
  4.     /// <summary>
  5.     /// Called when the playstate changes, except for the Error state (see OnError)
  6.     /// </summary>
  7.     /// <param name="player">The BackgroundAudioPlayer</param>
  8.     /// <param name="track">The track playing at the time the playstate changed</param>
  9.     /// <param name="playState">The new playstate of the player</param>
  10.     /// <remarks>
  11.     /// Play State changes cannot be cancelled. They are raised even if the application
  12.     /// caused the state change itself, assuming the application has opted-in to the callback
  13.     /// </remarks>
  14.     protected override void OnPlayStateChanged(BackgroundAudioPlayer player, AudioTrack track, PlayState playState)
  15.     {
  16.         switch (playState)
  17.         {
  18.             case PlayState.TrackReady:
  19.                 player.Play();
  20.                 break;
  21.             case PlayState.TrackEnded:
  22.                 SetNextTrack(player);
  23.                 break;
  24.         }
  25.         NotifyComplete();
  26.     }
  27.     /// <summary>
  28.     /// Called when the user requests an action using system-provided UI and the application has requesed
  29.     /// notifications of the action
  30.     /// </summary>
  31.     /// <param name="player">The BackgroundAudioPlayer</param>
  32.     /// <param name="track">The track playing at the time of the user action</param>
  33.     /// <param name="action">The action the user has requested</param>
  34.     /// <param name="param">The data associated with the requested action.
  35.     /// In the current version this parameter is only for use with the Seek action,
  36.     /// to indicate the requested position of an audio track</param>
  37.     /// <remarks>
  38.     /// User actions do not automatically make any changes in system state; the agent is responsible
  39.     /// for carrying out the user actions if they are supported
  40.     /// </remarks>
  41.     protected override void OnUserAction(BackgroundAudioPlayer player, AudioTrack track, UserAction action, object param)
  42.     {
  43.         switch (action)
  44.         {
  45.             case UserAction.Play:
  46.                 SetCurrentTrack(player);
  47.                 break;
  48.             case UserAction.Pause:
  49.                 player.Pause();
  50.                 break;                               
  51.             case UserAction.SkipNext:
  52.                 SetNextTrack(player);
  53.                 break;
  54.                 
  55.             case UserAction.SkipPrevious:
  56.                 SetPrevTrack(player);
  57.                 break;                
  58.         }
  59.         NotifyComplete();
  60.     }
  61.     private void SetCurrentTrack(BackgroundAudioPlayer player)
  62.     {
  63.         player.Track = _tracks.Current;
  64.     }
  65.     private void SetNextTrack(BackgroundAudioPlayer player)
  66.     {
  67.         _tracks.Next();
  68.         SetCurrentTrack(player);
  69.     }
  70.     private void SetPrevTrack(BackgroundAudioPlayer player)
  71.     {
  72.         _tracks.Prev();
  73.         SetCurrentTrack(player);
  74.     }
  75.     /// <summary>
  76.     /// Called whenever there is an error with playback, such as an AudioTrack not downloading correctly
  77.     /// </summary>
  78.     /// <param name="player">The BackgroundAudioPlayer</param>
  79.     /// <param name="track">The track that had the error</param>
  80.     /// <param name="error">The error that occured</param>
  81.     /// <param name="isFatal">If true, playback cannot continue and playback of the track will stop</param>
  82.     /// <remarks>
  83.     /// This method is not guaranteed to be called in all cases. For example, if the background agent
  84.     /// itself has an unhandled exception, it won't get called back to handle its own errors.
  85.     /// </remarks>
  86.     protected override void OnError(BackgroundAudioPlayer player, AudioTrack track, Exception error, bool isFatal)
  87.     {
  88.         //TODO: Add code to handle error conditions
  89.         NotifyComplete();
  90.     }
  91.     /// <summary>
  92.     /// Called when the agent request is getting cancelled
  93.     /// </summary>
  94.     protected override void OnCancel()
  95.     {
  96.     }
  97. }
 
בקטע קוד הנ"ל ניתן לראות כי AudioPlayer יורש מ- AudioPlayerAgent ובכך מנוהל ע"י הסביבה לרוץ כ- Agent ברקע. הרעיון פה הוא לדעת כיצד לתפעל את BackgroundAudioPlayer שמבצע את ניגון האודיו ברקע בפועל.
 
המטודה OnPlayStateChanged פועלת בכל פעם שמשתנה מצב ב- BackgroundAudioPlayer, בין אם בעקבות פעולת המשתמש או בעקבות אירוע מעניין בסגנן "הסתיימה רצועת השמעה" או "רצועה חדשה מתחילה" וכו'. כאן תפקידנו להחליט מה אנחנו עושים.
 
בדוגמה שהבאתי פה, הגבתי ל- TackReady. פה אני מתחיל לנגן את רצועת השמע החדשה/נוכחית. באירוע TrackEnded אני מנגן את הרצועה הבאה. קריאה למטודה פרטית שכתבתי, SetNextTrack, קובעת את הרצועה הבאה באופן מעגלי כאשר כל הרצועות ידועות מראש למען הפשטות.
 
המטודה OnUserAction פועלת בכל פעם שהמשתמש ביצע פעולת אודיו. פה אני מטפל ב- Play, Pause, SkipNext ו- SkipPrevious בהתאמה.
שימו לב כי המימוש הוא חלקי. יש אירועים נוספים לא טיפלתי בהם, וגם צריך לטפל במקרי שגיאה.
 
כעת נותר להוסיף אל פרויקט האפליקציה עצמה קבצי Audio ולהפעיל את ה- Agent. שימו לב: את קבצי ה- Audio יש להוסיף כ- Content ואז לסמנם כ- Copy if newer.
 
imageimage
 
כדי של- Agent יהיה גישה לקבצ האודיו, העתקתי אותם ל- Isolated Storage עם הפעלת האפליקציה המרכזית בעזרת מחלקת עזר:
 
Code Snippet
  1. private void Application_Launching(object sender, LaunchingEventArgs e)
  2. {
  3.     IsolatedStorageHelper.CopyFile("Audio", "Kalimba.mp3", "Maid with the Flaxen Hair.mp3", "Sleep Away.mp3");
  4. }
 
כעת, באפליקציה המרכזית יש לבצע 3 פעולות:
  1. להרשם לאירוע BackgroundAudioPlayer.PlayStateChanged.
  2. לעדכן את ממשק המשתמש עם נתוני האודיו החדשים בעת הפעלת האירוע.
  3. לעדכן את ממשק המשתמש עם נתוני האודיו החדשים בעת העלייה.
  4. לתפעל את BackgroundAudoPlayer בהתאמה.

Code Snippet
  1. public partial class MainPage : PhoneApplicationPage
  2. {
  3.     // Constructor
  4.     public MainPage()
  5.     {
  6.         InitializeComponent();
  7.         BackgroundAudioPlayer.Instance.PlayStateChanged += BackgroundAudioPlayer_PlayStateChanged;
  8.     }
  9.     protected override void OnNavigatedTo(NavigationEventArgs e)
  10.     {
  11.         if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
  12.         {
  13.             buttonPlay.Content = "Pause";
  14.             UpdateTrackInfo();
  15.         }
  16.         else
  17.         {
  18.             buttonPlay.Content = "Play";
  19.             UpdateTrackInfo(true);                
  20.         }
  21.     }        
  22.     private void BackgroundAudioPlayer_PlayStateChanged(object sender, EventArgs e)
  23.     {
  24.         switch (BackgroundAudioPlayer.Instance.PlayerState)
  25.         {
  26.             case PlayState.Playing:
  27.                 buttonPlay.Content = "Pause";
  28.                 break;
  29.             case PlayState.Paused:
  30.             case PlayState.Stopped:
  31.                 buttonPlay.Content = "Play";
  32.                 break;
  33.         }
  34.         if (BackgroundAudioPlayer.Instance.Track != null)
  35.         {
  36.             UpdateTrackInfo();
  37.         }
  38.     }
  39.     private void UpdateTrackInfo(bool reset = false)
  40.     {
  41.         if (reset)
  42.         {
  43.             imageAlbumArt.Source = null;
  44.             textBlockCurrentTitle.Text = string.Empty;
  45.             textBlockCurrentArtist.Text = string.Empty;
  46.             textBlockCurrentAlbum.Text = string.Empty;
  47.         }
  48.         else
  49.         {
  50.             imageAlbumArt.Source = new BitmapImage(BackgroundAudioPlayer.Instance.Track.AlbumArt);
  51.             textBlockCurrentTitle.Text = BackgroundAudioPlayer.Instance.Track.Title;
  52.             textBlockCurrentArtist.Text = BackgroundAudioPlayer.Instance.Track.Artist;
  53.             textBlockCurrentAlbum.Text = BackgroundAudioPlayer.Instance.Track.Album;
  54.         }
  55.     }
  56.     private void buttonPrev_Click(object sender, RoutedEventArgs e)
  57.     {
  58.         BackgroundAudioPlayer.Instance.SkipPrevious();
  59.     }
  60.     private void buttonPlay_Click(object sender, RoutedEventArgs e)
  61.     {
  62.         if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
  63.         {
  64.             BackgroundAudioPlayer.Instance.Pause();
  65.         }
  66.         else
  67.         {
  68.             BackgroundAudioPlayer.Instance.Play();
  69.         }
  70.     }
  71.     private void buttonNext_Click(object sender, RoutedEventArgs e)
  72.     {
  73.         BackgroundAudioPlayer.Instance.SkipNext();
  74.     }
  75. }
 
לפני שמורידים את הדוגמה, אציין שלא העליתי את קבצי ה- Audio בשל גודלם. ניתן למצוא את שלושת הקבצים בכל Windows 7 תיקיית Audio משותפת.
 
מה בפוסט הבא?
 
Published Saturday, July 02, 2011 11:42 PM by Tomer Shamam

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above:
Powered by Community Server (Commercial Edition), by Telligent Systems