Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Resume the User’s Music after MediaPlay

0.00/5 (No votes)
8 Nov 2010 1  
How to Resume the User's music after we played a video or audio from our Silverlight/XNA WP7 Application.
ResumeMusicPlayTest.jpg

Introduction

If you are writing a software which plays media on Windows Phone 7 and you want that software up in the marketplace, you will have to fulfill the wp7 application certification requirement 6.5.3:

Applications that Play a Video or Audio Segment

"An application may interrupt the currently playing music to play a non-interactive full motion video or audio segment (e.g. cut-scene or media clip) without asking for user consent. If music was playing prior to the segment, the application must resume music when the segment has completed."

Background

As I know, this can't be done completely yet, because of a little bug which I heard is under fixing! Currently there is no SDK functionality that lets you just resume the playback after your mediaplay. The reason why it can't be done requires a little understanding about the system's MediaPlayer:

  • The only way to interact with this MediaPlayer is through the Microsoft.Xna.Framework.Media namespace.
    MSDN Link: http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.media.aspx
  • The MediaPlayer class here provides the functions to control the system's MediaPlayer. This MediaPlayer stores the currently played songs in a special Queue which can be indexed like arrays too.

Basically there are 2 main problems with this Queue:

  1. If you play a MediaContent on some MediaElement or SmoothStreamingMediaElement, it overrides the System's MediaElement Queue too, so you can't resume the Current MediaPlay.
  2. It's a read-only collection, that means if you store the Songs played before your play, you can't restore them back to the Queue.

In XNA games, you can use music effects without damaging the queue, but if you want to play background music, that will stop the music and override the queue. One of Microsoft's solution with the Game Twin Blades is that the music plays when the game starts, it asks the user whether he/she wants to continue listening to the music or stop and hear the game music after. If we choose not to stop the music, we can play with our own music in the background.

The Workaround

Currently we have only one way to override the Queue, Playing Media. There are 4 scenarios of Background music playing on the Phone:

  1. The user listens to the Radio
  2. The user listens to a Song from the Music collection
  3. The user listens to an Album from the Music Collection
  4. The user listens to a PlayList from the Music Collection
  5. The user adds songs from 2 or more albums/playlists to the now playing collection (find the explanation below)

So we store the Queue and the current Radio state before our media play, and then when we need to restore the previous state, we just check these 4 scenarios if it fits the stored list of songs or not.

After checking the stored RadioPower state, we need to iterate over the playlists and albums, and see which one has the same item number, and contains all songs from the stored queue. Then find the index of the currently played song to start the Playing from there. This way, we can restore the RadioPlay or the MusicPlay after our MediaPlayback. The downsides of this workaround is that we can't resume the current song where we left it (no XNA player method for seeking), the song will start from the beginning. It may be an issue for those who listen to long DJ Sets or Mixes regularly, but better than nothing. Note that the Radio sometimes gives back OFF power state while it's actually ON, so I am also watching that the queue's ActiveSongIndex is equal to -1 or not. When playing a real song, it is always greater than -1.

More from the Queue can be found at http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.media.mediaqueue_members.aspx.

The User Adds Songs from 2 or More Albums/Playlists to the Now Playing Collection

We can't create custom SongCollections because of this:

"All of the collections, playlists, and queues returned by methods and properties in the Microsoft.Xna.Framework.Media namespace are immutable. You cannot add or remove objects from those collections or playlists. To create a custom 'playlist' of songs, games must maintain their own list of songs to play, and play those songs one at a time by calling MediaPlayer.Play."

This is the reason the 5th scenario cannot be restored. We also can't add items to the Now Playing collection in the Music Hub. I think the best solution is to simply let the user resume the music manually. With this approach, the queue stays intact and can be resumed by the user. If anybody has a better solution, please send it to me and I will post it here.

In my code, I made some functions which can be used freely for anyone under the MS-PL license, they are in the XnaMusicUtil.cs.

Under the Hood

First, have a look at the Save Function:

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();
}

Saving the current state is really easy compared to the Restore function. If the radio is on or the ActiveSong equals -1, the user listens to the Radio. In every other case, if the queue has items, then the user Listens to some sort of Music. Read and store, that's all. We can stop the Music at the end of the Save if we want by simply setting the isStopping parameter to True.

Now the Restoring:

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();
}

Notice that we have to Play the Songs in order to restore the Queue, but if the original state was Stopped or Paused, we Stop/Pause them right after Playing. This could produce a small audio glitch resulted by the quick Audio on-off. I'm not even sure the final devices have this "effect" or not.

Both the Playlists and the Albums are SongCollection classes. I'm using the following function to compare them with the saved queue. If the SongCollection has all the Songs of the queue, then it determines the index of the last played song and starts the play with it.

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

This code can be used both in Silverlight and XNA environments. For example, if you have a page dedicated to a MediaElement use the save and restore function like this:

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

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

In the App.xaml.cs for proper Tombstoning:

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

Good to Know

This article can be found on my WP7 Blog too:

The XNA framework requires the FrameworkDispatcher.Update() function to call regularly for proper function.

During my tests, I figured out that calling this function just after the other XNA function calls is enough, but if you want to make sure your code won't collapse because of this, you have to implement the XNAAsyncDispatcher from here:

Read the whole article too, it contains a good explanation about using XNA in Silverlight environment.

History

  • 2010. November 9: Article and code updated

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here