בפוסט הקודם דיברתי על Fast Application Switching, אחת מהיכולות החדשות והחשובות בגרסה החדשה של Windows Phone. בפוסט זה אציג תכונה חדשה שנקראת Background Audio המאפשרת ניגון ושליטה על אודיו ברקע.
הרעיון המרכזי בגרסה החדשה הוא לאפשר למשתמש לבצע פעולות ברקע, ועם זאת מבלי שה-Process ירוץ במקביל ויבזבז משאבים יקרים.
אחת מהפעולות שניתן להריץ ברקע בשיטה זו הינה ניגון ושליטה על אודיו מקוד שאתם כתבתם.
לדוגמה, אתם מעונינים ליצור נגן מדיה חדש, עם יכולות שלא קיימות בנגן המקורי. עם זאת אתם מעונינים שהמוסיקה תמשיך לנגן ברקע, תחליף שירים וניתן יהיה לשלוט עליה בעזרת הכפתורים הסטנדרטיים, למרות שיצאתם מהאפליקציה.
בפועל, יש שני סוגי אפליקציות של Background Audio. אחד שמנגן מדיה מקומית, והשני Stream.
Audio Playback Agent
כדי ליצור Background Audio שמנגן מדיה מקומית יש ליצור פרוייקט חדש מסוג Windows Phone Audio Playback Agent (זה בנוסף לפרוייקט הרגיל שמייצג את האפליקציה שלכם). בפרוייקט שמייצג את האפליקציה עצמה יש להוסיף Reference לפרוייקט שמכיל את ה- Agent.
מימוש Audio Playback Agent
הרעיון מאחורי ה- Agent הוא למעשה אובייקט שרץ ברקע ומנוהל ע"י הסביבה עצמה. הסביבה שולחת פקודות לאובייקט זה כשיש בקשה לנגן קובץ אחר, לעצור את המוסיקה, וכו'.
להלן קטע קוד שמדגים כיצד לממש אובייקט זה:
Code Snippet
- public class AudioPlayer : AudioPlayerAgent
- {
- private static readonly AudioTracks _tracks = new AudioTracks();
- /// <summary>
- /// Called when the playstate changes, except for the Error state (see OnError)
- /// </summary>
- /// <param name="player">The BackgroundAudioPlayer</param>
- /// <param name="track">The track playing at the time the playstate changed</param>
- /// <param name="playState">The new playstate of the player</param>
- /// <remarks>
- /// Play State changes cannot be cancelled. They are raised even if the application
- /// caused the state change itself, assuming the application has opted-in to the callback
- /// </remarks>
- protected override void OnPlayStateChanged(BackgroundAudioPlayer player, AudioTrack track, PlayState playState)
- {
- switch (playState)
- {
- case PlayState.TrackReady:
- player.Play();
- break;
- case PlayState.TrackEnded:
- SetNextTrack(player);
- break;
- }
- NotifyComplete();
- }
- /// <summary>
- /// Called when the user requests an action using system-provided UI and the application has requesed
- /// notifications of the action
- /// </summary>
- /// <param name="player">The BackgroundAudioPlayer</param>
- /// <param name="track">The track playing at the time of the user action</param>
- /// <param name="action">The action the user has requested</param>
- /// <param name="param">The data associated with the requested action.
- /// In the current version this parameter is only for use with the Seek action,
- /// to indicate the requested position of an audio track</param>
- /// <remarks>
- /// User actions do not automatically make any changes in system state; the agent is responsible
- /// for carrying out the user actions if they are supported
- /// </remarks>
- protected override void OnUserAction(BackgroundAudioPlayer player, AudioTrack track, UserAction action, object param)
- {
- switch (action)
- {
- case UserAction.Play:
- SetCurrentTrack(player);
- break;
- case UserAction.Pause:
- player.Pause();
- break;
- case UserAction.SkipNext:
- SetNextTrack(player);
- break;
-
- case UserAction.SkipPrevious:
- SetPrevTrack(player);
- break;
- }
- NotifyComplete();
- }
- private void SetCurrentTrack(BackgroundAudioPlayer player)
- {
- player.Track = _tracks.Current;
- }
- private void SetNextTrack(BackgroundAudioPlayer player)
- {
- _tracks.Next();
- SetCurrentTrack(player);
- }
- private void SetPrevTrack(BackgroundAudioPlayer player)
- {
- _tracks.Prev();
- SetCurrentTrack(player);
- }
- /// <summary>
- /// Called whenever there is an error with playback, such as an AudioTrack not downloading correctly
- /// </summary>
- /// <param name="player">The BackgroundAudioPlayer</param>
- /// <param name="track">The track that had the error</param>
- /// <param name="error">The error that occured</param>
- /// <param name="isFatal">If true, playback cannot continue and playback of the track will stop</param>
- /// <remarks>
- /// This method is not guaranteed to be called in all cases. For example, if the background agent
- /// itself has an unhandled exception, it won't get called back to handle its own errors.
- /// </remarks>
- protected override void OnError(BackgroundAudioPlayer player, AudioTrack track, Exception error, bool isFatal)
- {
- //TODO: Add code to handle error conditions
- NotifyComplete();
- }
- /// <summary>
- /// Called when the agent request is getting cancelled
- /// </summary>
- protected override void OnCancel()
- {
- }
- }
בקטע קוד הנ"ל ניתן לראות כי AudioPlayer יורש מ- AudioPlayerAgent ובכך מנוהל ע"י הסביבה לרוץ כ- Agent ברקע. הרעיון פה הוא לדעת כיצד לתפעל את BackgroundAudioPlayer שמבצע את ניגון האודיו ברקע בפועל.
המטודה OnPlayStateChanged פועלת בכל פעם שמשתנה מצב ב- BackgroundAudioPlayer, בין אם בעקבות פעולת המשתמש או בעקבות אירוע מעניין בסגנן "הסתיימה רצועת השמעה" או "רצועה חדשה מתחילה" וכו'. כאן תפקידנו להחליט מה אנחנו עושים.
בדוגמה שהבאתי פה, הגבתי ל- TackReady. פה אני מתחיל לנגן את רצועת השמע החדשה/נוכחית. באירוע TrackEnded אני מנגן את הרצועה הבאה. קריאה למטודה פרטית שכתבתי, SetNextTrack, קובעת את הרצועה הבאה באופן מעגלי כאשר כל הרצועות ידועות מראש למען הפשטות.
המטודה OnUserAction פועלת בכל פעם שהמשתמש ביצע פעולת אודיו. פה אני מטפל ב- Play, Pause, SkipNext ו- SkipPrevious בהתאמה.
שימו לב כי המימוש הוא חלקי. יש אירועים נוספים לא טיפלתי בהם, וגם צריך לטפל במקרי שגיאה.
כעת נותר להוסיף אל פרויקט האפליקציה עצמה קבצי Audio ולהפעיל את ה- Agent. שימו לב: את קבצי ה- Audio יש להוסיף כ- Content ואז לסמנם כ- Copy if newer.
כדי של- Agent יהיה גישה לקבצ האודיו, העתקתי אותם ל- Isolated Storage עם הפעלת האפליקציה המרכזית בעזרת מחלקת עזר:
Code Snippet
- private void Application_Launching(object sender, LaunchingEventArgs e)
- {
- IsolatedStorageHelper.CopyFile("Audio", "Kalimba.mp3", "Maid with the Flaxen Hair.mp3", "Sleep Away.mp3");
- }
כעת, באפליקציה המרכזית יש לבצע 3 פעולות:
-
להרשם לאירוע BackgroundAudioPlayer.PlayStateChanged.
-
לעדכן את ממשק המשתמש עם נתוני האודיו החדשים בעת הפעלת האירוע.
-
לעדכן את ממשק המשתמש עם נתוני האודיו החדשים בעת העלייה.
-
לתפעל את BackgroundAudoPlayer בהתאמה.
Code Snippet
- public partial class MainPage : PhoneApplicationPage
- {
- // Constructor
- public MainPage()
- {
- InitializeComponent();
- BackgroundAudioPlayer.Instance.PlayStateChanged += BackgroundAudioPlayer_PlayStateChanged;
- }
- protected override void OnNavigatedTo(NavigationEventArgs e)
- {
- if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
- {
- buttonPlay.Content = "Pause";
- UpdateTrackInfo();
- }
- else
- {
- buttonPlay.Content = "Play";
- UpdateTrackInfo(true);
- }
- }
- private void BackgroundAudioPlayer_PlayStateChanged(object sender, EventArgs e)
- {
- switch (BackgroundAudioPlayer.Instance.PlayerState)
- {
- case PlayState.Playing:
- buttonPlay.Content = "Pause";
- break;
- case PlayState.Paused:
- case PlayState.Stopped:
- buttonPlay.Content = "Play";
- break;
- }
- if (BackgroundAudioPlayer.Instance.Track != null)
- {
- UpdateTrackInfo();
- }
- }
- private void UpdateTrackInfo(bool reset = false)
- {
- if (reset)
- {
- imageAlbumArt.Source = null;
- textBlockCurrentTitle.Text = string.Empty;
- textBlockCurrentArtist.Text = string.Empty;
- textBlockCurrentAlbum.Text = string.Empty;
- }
- else
- {
- imageAlbumArt.Source = new BitmapImage(BackgroundAudioPlayer.Instance.Track.AlbumArt);
- textBlockCurrentTitle.Text = BackgroundAudioPlayer.Instance.Track.Title;
- textBlockCurrentArtist.Text = BackgroundAudioPlayer.Instance.Track.Artist;
- textBlockCurrentAlbum.Text = BackgroundAudioPlayer.Instance.Track.Album;
- }
- }
- private void buttonPrev_Click(object sender, RoutedEventArgs e)
- {
- BackgroundAudioPlayer.Instance.SkipPrevious();
- }
- private void buttonPlay_Click(object sender, RoutedEventArgs e)
- {
- if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
- {
- BackgroundAudioPlayer.Instance.Pause();
- }
- else
- {
- BackgroundAudioPlayer.Instance.Play();
- }
- }
- private void buttonNext_Click(object sender, RoutedEventArgs e)
- {
- BackgroundAudioPlayer.Instance.SkipNext();
- }
- }
לפני שמורידים את הדוגמה, אציין שלא העליתי את קבצי ה- Audio בשל גודלם. ניתן למצוא את שלושת הקבצים בכל Windows 7 תיקיית Audio משותפת.
מה בפוסט הבא?