65.9K
CodeProject 正在变化。 阅读更多。
Home

媒体播放后恢复用户音乐

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (16投票s)

2010年10月26日

Ms-PL

6分钟阅读

viewsIcon

50458

downloadIcon

903

如何在我们的 Silverlight/XNA WP7 应用程序中播放视频或音频后恢复用户正在播放的音乐。

ResumeMusicPlayTest.jpg

引言

如果您正在编写一款在 Windows Phone 7 上播放媒体的软件,并且希望该软件能在应用商店上架,那么您必须满足 WP7 应用程序认证要求 6.5.3。

播放视频或音频片段的应用程序

“应用程序可以中断当前正在播放的音乐,以播放一个非交互式的全动态视频或音频片段(例如,过场动画或媒体剪辑),而无需征得用户同意。如果在播放该片段之前有音乐正在播放,应用程序必须在片段完成后恢复音乐。”

背景

据我所知,由于一个我听说正在修复的小 bug,这目前还不能完全实现!目前没有任何 SDK 功能可以让你在媒体播放后简单地恢复播放。之所以不能实现,需要对系统 MediaPlayer 有一点了解。

  • 与此 MediaPlayer 交互的唯一方法是通过 Microsoft.Xna.Framework.Media 命名空间。
    MSDN 链接: http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.media.aspx
  • 这里的 MediaPlayer 类提供了控制系统 MediaPlayer 的函数。这个 MediaPlayer 将当前播放的歌曲存储在一个特殊的队列中,该队列也可以像数组一样进行索引。

基本上,这个队列存在两个主要问题。

  1. 如果您在某个 MediaElementSmoothStreamingMediaElement 上播放 MediaContent,它也会覆盖系统的 MediaElement 队列,所以您无法恢复当前的 MediaPlay
  2. 这是一个只读集合,这意味着如果您存储了播放前播放的歌曲,您就无法将它们恢复到队列中。

在 XNA 游戏中,您可以在不损坏队列的情况下使用音乐效果,但如果您想播放背景音乐,它会停止音乐并覆盖队列。微软 Game Twin Blades 的解决方案之一是,音乐在游戏开始时播放,它会询问用户是否要继续收听音乐,还是在听完游戏音乐后再停止。如果我们选择不停止音乐,我们就可以在后台播放自己的音乐。

解决方法

目前,我们只有一种方法可以覆盖队列,那就是播放媒体。手机上有四种背景音乐播放场景:

  1. 用户收听广播
  2. 用户收听音乐收藏中的一首歌曲
  3. 用户收听音乐收藏中的一张专辑
  4. 用户收听音乐收藏中的一个播放列表
  5. 用户将来自 2 张或更多专辑/播放列表的歌曲添加到“正在播放”集合中(请参见下文的解释)

因此,我们在播放媒体之前存储队列和当前的广播状态,然后在需要恢复之前的状态时,我们只需检查这 4 种场景是否与存储的歌曲列表匹配。

在存储了 RadioPower 状态后,我们需要遍历播放列表和专辑,查看哪个与存储的队列具有相同的项目数量,并包含存储队列中的所有歌曲。然后找到当前播放歌曲的索引,以便从那里开始播放。这样,我们就可以在 MediaPlayback 之后恢复 RadioPlayMusicPlay。这种变通方法的缺点是,我们无法在歌曲的暂停处恢复(XNA 播放器没有搜索方法),歌曲将从头开始播放。对于经常收听长 DJ Set 或 Mixes 的人来说,这可能是一个问题,但总比没有好。请注意,广播有时在实际开启时会返回 OFF 电源状态,所以我还在监视队列的 ActiveSongIndex 是否等于 -1。播放实际歌曲时,它始终大于 -1

有关队列的更多信息,请访问 http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.media.mediaqueue_members.aspx

用户将来自 2 张或更多专辑/播放列表的歌曲添加到“正在播放”集合中

由于 这个原因,我们无法创建自定义 SongCollections

Microsoft.Xna.Framework.Media 命名空间中的方法和属性返回的所有集合、播放列表和队列都是不可变的。您不能向这些集合或播放列表添加或删除对象。要创建自定义歌曲“播放列表”,游戏必须维护自己的歌曲列表,并通过调用 MediaPlayer.Play 来一次播放一首歌曲。”

这就是第五种场景无法恢复的原因。我们也无法在音乐中心添加歌曲到“正在播放”集合。我认为最好的解决方案是让用户手动恢复音乐。通过这种方法,队列保持不变,并且可以由用户恢复。如果有人有更好的解决方案,请发给我,我将在此发布。

在我的代码中,我创建了一些函数,这些函数可以在 **MS-PL 许可**下 **自由使用**,它们位于 XnaMusicUtil.cs 文件中。

幕后

首先,看一下 Save 函数。

public static void SaveCurrentMediaState(bool isStopping = false)
{
    currQueue.Clear();
    currSong = null;
    isRadio = false;
    hasSaved = false;

    currState = MediaPlayer.State;
    Debug.WriteLine(MediaPlayer.State.ToString()); // State of song: 
						// Playing / Stopped / Paused
    radioFrequency = Microsoft.Devices.Radio.FMRadio.Instance.Frequency;
    //Microsoft.Devices.MediaHistory mh = Microsoft.Devices.MediaHistory.Instance;
    //Microsoft.Devices.MediaHistoryItem item = mh.NowPlaying;
    if (MediaPlayer.Queue != null)
    {
        switch (MediaPlayer.Queue.Count)
        {
            case 0:
                break;
            case 1:// only one song in the queue, can be radio or a real song
                if ((Microsoft.Devices.Radio.FMRadio.Instance.PowerMode ==
                	Microsoft.Devices.Radio.RadioPowerMode.On)||
                		(MediaPlayer.Queue.ActiveSongIndex==-1))
                {
                    isRadio = true;
                    Debug.WriteLine("Radio :" +
                    	MediaPlayer.Queue.ActiveSong.Name); // Currently playing song
                    hasSaved = true;
                }
                else
                {
                   isRadio = false;
                   currQueue.Add(MediaPlayer.Queue[0]);
                    if (MediaPlayer.Queue.ActiveSong != null)
                    {
                        currSong = MediaPlayer.Queue.ActiveSong;
                        Debug.WriteLine(MediaPlayer.Queue.ActiveSong.Name); // Currently 
								//playing song
                    }
                    hasSaved = true;
                }
                break;
            default://mor song in the queue, save the whole queue and the active song too
                isRadio = false;
                for (int i = 0; i < MediaPlayer.Queue.Count; i++)
                {
                    currQueue.Add(MediaPlayer.Queue[i]);
                }
                if (MediaPlayer.Queue.ActiveSong != null)
                {
                    currSong = MediaPlayer.Queue.ActiveSong;
                    Debug.WriteLine(MediaPlayer.Queue.ActiveSong.Name); // Currently 
								//playing song
                }
                hasSaved = true;
                break;
        }
    }
    //if the user set the isStopping parameter we are stopping the playback right now
    if ((MediaPlayer.State == MediaState.Playing) && (isStopping)) MediaPlayer.Stop();
    if ((Microsoft.Devices.Radio.FMRadio.Instance.PowerMode ==
    	Microsoft.Devices.Radio.RadioPowerMode.On) &&
    		(isStopping)) Microsoft.Devices.Radio.FMRadio.Instance.PowerMode =
    			Microsoft.Devices.Radio.RadioPowerMode.Off;
    //FrameworkDispatcher.Update();
}

Restore 函数相比,保存当前状态非常简单。如果收音机开启或者 ActiveSong 等于 -1,则用户正在收听 Radio。在其他所有情况下,如果队列中有项目,那么用户正在听某种音乐。读取并存储,仅此而已。如果希望在保存结束时停止音乐,只需将 isStopping 参数设置为 True 即可。

现在是恢复。

public static void RestoreCurrentMediaState()
{
    bool isFound = true;
    if (hasSaved)
    {
        if (isRadio)
        {
            Microsoft.Devices.Radio.FMRadio.Instance.PowerMode =
            		Microsoft.Devices.Radio.RadioPowerMode.On;
            Microsoft.Devices.Radio.FMRadio.Instance.Frequency = radioFrequency;
            if (Microsoft.Devices.Radio.FMRadio.Instance.Frequency !=
            	radioFrequency) Microsoft.Devices.Radio.FMRadio.Instance.Frequency =
            		radioFrequency; //doublecheck
        }
        else
        {
            MediaLibrary ml = new MediaLibrary();
            Debug.WriteLine("Before restore: " +
            MediaPlayer.State.ToString()); // State of song: Playing / Stopped / Paused
            switch (currQueue.Count)
            {
                case 0:
                    break;
                case 1:
                    if (currSong != null)//only one song in the queue, 
				//check if its available to play and play
                    {
                        if (ml.Songs.Contains(currSong)) MediaPlayer.Play(currSong);
                    }
                    break;
                default:
                    if (ml.Playlists.Count > 0)
                    {
                        for (int i = 0; i < ml.Playlists.Count; i++)
                        {
                            isFound = MatchAndPlay(ml.Playlists[i].Songs);
                            if (isFound) break;
                        }
                    }
                    if ((ml.Albums.Count > 0) && (!isFound))	//not a playlist, 
							//search albums
                    {
                        for (int i = 0; i < ml.Albums.Count; i++)
                        {
                            isFound = MatchAndPlay(ml.Albums[i].Songs);
                            if (isFound) break;
                        }
                    }
                    if ((currSong != null) && (!isFound))//happens when the user
                    	//adds items to the Now Playing collection from 
			//2 or more different albums
                    {
                        //MessageBox.Show("We can't resume your music, 
			//please resume it manually");
                    }
                    break;
            }
        }
        switch (currState) //restore the stored state
        {
            case MediaState.Paused:
                MediaPlayer.Pause();
                break;
            case MediaState.Playing:
                break;
            case MediaState.Stopped:
                MediaPlayer.Stop();
                break;
            default:
                break;
        }
    }
    currQueue.Clear();
    hasSaved = false;
    //FrameworkDispatcher.Update();
}

请注意,我们必须按顺序播放歌曲才能恢复队列,但如果原始状态是停止或暂停,我们会在播放后立即停止/暂停它们。这可能会产生一个小的音频故障,这是由于快速的音频开启-关闭造成的。我甚至不确定最终设备是否有这种“效果”。

播放列表和专辑都是 SongCollection 类。我使用以下函数将它们与保存的队列进行比较。如果 SongCollection 包含队列中的所有歌曲,则它将确定最后播放的歌曲的索引,并从该索引开始播放。

private static bool MatchAndPlay(SongCollection sc)//used for Compare Albums
				//and Playlists with the saved queue
{
    bool containsAll = true;
    bool isFound = false;
    int currIndex = 0;
    if (sc.Count == currQueue.Count)
    {
        for (int j = 0; j < currQueue.Count; j++)
        {
            if (!sc.Contains(currQueue[j])) containsAll = false;
        }
        isFound = containsAll;
        if (isFound)// the currently checked SongCollection(Album,Playlist)
        			// contains all the songs from our saved queue
        {
            for (int k = 0; k < sc.Count; k++)//SongCollection does not 
					//have .indexof so we iterate
            {
                if (sc[k] == currSong) //search for the saved activesong in the
                			// SongCollection to start the playback with it
                {
                    currIndex = k;
                    MediaPlayer.Play(sc, currIndex);
                    break;
                }
            }
        }
    }
    return isFound;
}

Using the Code

这段代码既可以在 Silverlight 环境中使用,也可以在 XNA 环境中使用。例如,如果您有一个专用于 MediaElement 的页面,可以像这样使用保存和恢复函数:

void MediaElementPage_Unloaded(object sender, RoutedEventArgs e)
{
    mediaElement.Stop();
    XnaMusicUtil.RestoreCurrentMediaState();
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    XnaMusicUtil.SaveCurrentMediaState();
    base.OnNavigatedTo(e);
}

App.xaml.cs 中,为了 proper 的 Tombstoning(断崖式休眠)处理:

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
    XnaMusicUtil.RestoreCurrentMediaState();
}

好消息

这篇文章也可以在我的 WP7 博客上找到:

XNA 框架需要定期调用 FrameworkDispatcher.Update() 函数才能正常工作。

在我测试期间,我发现只需在其他 XNA 函数调用之后调用此函数就足够了,但如果您想确保代码不会因此崩溃,您需要在此处实现 XNAAsyncDispatcher

也请阅读整篇文章,它对在 Silverlight 环境中使用 XNA 提供了很好的解释。

历史

  • 2010 年 11 月 9 日:文章和代码已更新
© . All rights reserved.