对于播客的音频应该是连续多个的列表,作为在后台连续播放。在网上搜了一下,通过wp8后台音频代理播放,而且例子都是静态的播放列表,不满足动态生成列表播放。
尝试着将播放列表对象声明为公有静态的,在外部对列表进行操作,发现这个静态的播放列表在agent里和我的操作类不是同一个引用,此方法行不通。
最后在
在wp中agent可以访问app的isolated storage,所以最好app和agent在isolated storage中_共同维护一个播放列表。
最终得到如下的audioagent:
/* Copyright (c) 2011 Microsoft Corporation. All rights reserved. Use of this sample source code is subject to the terms of the Microsoft license agreement under which you licensed this sample source code and is provided AS-IS. If you did not accept the terms of the license agreement, you are not authorized to use this sample source code. For the terms of the license, please see the license agreement between you and Microsoft. To see all Code Samples for Windows Phone, visit http://go.microsoft.com/fwlink/?LinkID=219604 */ using System; using System.Windows; using Microsoft.Phone.BackgroundAudio; using System.Collections.Generic; using System.Diagnostics; using Newtonsoft.Json.Linq; using MyPodcast; using MyPodcast.Controller; namespace MyPodcast { public class AudioPlayer : AudioPlayerAgent { private static volatile bool _classInitialized; // What's the current track? static int currentTrackNumber = 0; // A playlist made up of AudioTrack items. public static List<AudioTrack> _playList; private static IList<Track> allTracks; /// <remarks> /// AudioPlayer instances can share the same process. /// Static fields can be used to share state between AudioPlayer instances /// or to communicate with the Audio Streaming agent. /// </remarks> public AudioPlayer() { if (!_classInitialized) { _classInitialized = true; // Subscribe to the managed exception handler Deployment.Current.Dispatcher.BeginInvoke(delegate { Application.Current.UnhandledException += AudioPlayer_UnhandledException; }); } } /// Code to execute on Unhandled Exceptions private void AudioPlayer_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) { if (System.Diagnostics.Debugger.IsAttached) { // An unhandled exception has occurred; break into the debugger System.Diagnostics.Debugger.Break(); } } /// <summary> /// Increments the currentTrackNumber and plays the correpsonding track. /// </summary> /// <param name="player">The BackgroundAudioPlayer</param> private void PlayNextTrack(BackgroundAudioPlayer player) { if (++currentTrackNumber >= _playList.Count) { currentTrackNumber = 0; } PlayTrack(player); } /// <summary> /// Decrements the currentTrackNumber and plays the correpsonding track. /// </summary> /// <param name="player">The BackgroundAudioPlayer</param> private void PlayPreviousTrack(BackgroundAudioPlayer player) { if (--currentTrackNumber < 0) { currentTrackNumber = _playList.Count - 1; } PlayTrack(player); } /// <summary> /// Plays the track in our playlist at the currentTrackNumber position. /// </summary> /// <param name="player">The BackgroundAudioPlayer</param> private void PlayTrack(BackgroundAudioPlayer player) { if (PlayState.Paused == player.PlayerState) { // If we're paused, we already have // the track set, so just resume playing. player.Play(); } else { // Set which track to play. When the TrackReady state is received // in the OnPlayStateChanged handler, call player.Play(). //对_playList 和 currentTrackNumber防止出错 if (_playList != null && _playList.Count > 0 && _playList.Count > currentTrackNumber) player.Track = _playList[currentTrackNumber]; } } /// <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. /// /// Notable playstate events: /// (a) TrackEnded: invoked when the player has no current track. The agent can set the next track. /// (b) TrackReady: an audio track has been set and it is now ready for playack. /// /// Call NotifyComplete() only once, after the agent request has been completed, including async callbacks. /// </remarks> protected override void OnPlayStateChanged(BackgroundAudioPlayer player, AudioTrack track, PlayState playState) { switch (playState) { case PlayState.TrackEnded: PlayNextTrack(player); break; case PlayState.TrackReady: player.Play(); break; } NotifyComplete(); } /// <summary> /// Called when the user requests an action using application/system provided UI /// </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. /// /// Call NotifyComplete() only once, after the agent request has been completed, including async callbacks. /// </remarks> protected override void OnUserAction(BackgroundAudioPlayer player, AudioTrack track, UserAction action, object param) { #region 加载保存好的播放列表 if (_playList == null) { //读取存放在 isolated storage中的播放列表 allTracks = MyPodcast.TrackManager.GetAllTracks(); if (allTracks != null) { _playList = new List<AudioTrack>(); foreach (Track _track in allTracks) { try { string trackurl = null; if (_track.playUrl64 != null) trackurl = _track.playUrl64; else if (_track.playUrl32 != null) trackurl = _track.playUrl32; if (trackurl != null) { _playList.Add(new AudioTrack( new Uri(trackurl, UriKind.Absolute), _track.title, _track.nickname, _track.albumTitle, new Uri(_track.coverLarge, UriKind.Absolute))); } } catch (ArgumentNullException) { continue; } } } } #endregion switch (action) { case UserAction.Play: PlayTrack(player); break; case UserAction.Pause: player.Pause(); break; case UserAction.SkipPrevious: PlayPreviousTrack(player); break; case UserAction.SkipNext: PlayNextTrack(player); break; } NotifyComplete(); } /// <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) { if (isFatal) { Abort(); } else { NotifyComplete(); } } /// <summary> /// Called when the agent request is getting cancelled /// </summary> /// <remarks> /// Once the request is Cancelled, the agent gets 5 seconds to finish its work, /// by calling NotifyComplete()/Abort(). /// </remarks> protected override void OnCancel() { } } }
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.IsolatedStorage; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; namespace MyPodcast { public class TrackManager { public static IList<Track> GetAllTracks() { using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) { string _fileName = "myPocastPlayList.json"; if (storage.FileExists(_fileName)) { using (IsolatedStorageFileStream file = storage.OpenFile(_fileName, FileMode.Open)) { using (StreamReader sr = new StreamReader(file)) { string json = sr.ReadToEnd(); var playerList = JsonConvert.DeserializeObject<List<Track>>(json); Debug.WriteLine("{0}---->GetAllTracks", DateTime.Now.ToLongTimeString()); return playerList; } } } else { Debug.WriteLine("{0} 不存在---->GetAllTracks", _fileName); } } return null; } } }
考虑到在APP运行时有多种可能:
在APP里需要实现的功能有:
具体的使用逻辑:
在播放页面首先对后台音频状态进行检测,如果处于停止状态,则可以直接播放,系统会调用audioagent里的OnUserAction,此时播放列表为空,则读取本地的列表文件,之后进行播放操作;如果处于其他状态,在重新保存播放列表到本地后,则需要先停止后台音频,通过调用BackgroundAudioPlayer.Instance.Close();清空列表,需要注意的是,Audioagent 响应操作不是立即的,要等到BackgroundAudioPlayer.PlayerState 不为Playing才能继续后面的BackgroundAudioPlayer.Instance.Play()。
//关闭后台音频 BackgroundAudioPlayer.Instance.Close(); //等待后台音频状态为close状态后继续后面操作 await Task.Factory.StartNew<int>(() => { Thread.Sleep(100); while (BackgroundAudioPlayer.Instance.Track != null || BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing) { Thread.Sleep(50); } return 0; }); List<Track> list = trackList.ItemsSource as List<Track>; List<Track> listNewer = list; int selectedIndex = listNewer.IndexOf(((RadDataBoundListBox)sender).SelectedItem as Track); if (selectedIndex > 0) { listNewer = new List<Track>(list.GetRange(selectedIndex, list.Count - selectedIndex)); listNewer.AddRange(new List<Track>(list.GetRange(0, selectedIndex))); } //保存播放列表到本地 MyPodcast.TrackManager.SaveTracksToIsolatedStorage(listNewer); //开始播放后台音频 BackgroundAudioPlayer.Instance.Play();