Controlling Background Audio from Your Foreground App

,

Most apps employing background audio are going to want to provide an enhanced interface within the foreground app for controlling playback and displaying track information. This section looks at the MainPage and associated classes of the WPUnleashed.BackgroundAudio project. The page allows the user to play audio via the BackgroundAudioPlayer class. It provides application bar buttons for play and pause, stop, rewind, and forward, and several fields for displaying the current track information.


Note

It is a Windows Phone Marketplace certification requirement that when the user is already playing background music on the phone when your app is launched, your app must ask the user for consent to stop playing or to adjust the background music. This prompt must occur each time the app launches, unless there is an opt-in setting provided to the user, and the user has used this setting to opt in.


A Testable BackgroundAudioPlayer

The BackgroundAudioPlayer class is inherently difficult to use in unit tests because of the cross-process interaction with the AudioPlayerAgent and its dependence on native phone components. For this reason I have provided a wrapper called BackgroundAudioPlayerProxy, which implements a custom IBackgroundAudioPlayer interface. In addition, a second implementation, MockBackgroundAudioPlayer, allows you to test whether your code called the background audio player as expected; it allows you to test, for example, whether your code attempted to play a track.

The code for IBackgroundAudioPlayer and its associated class implementations is not shown in this chapter because it is lengthy, and the methods, properties, and events of the IBackgroundAudioPlayer coincide with those of the built-in BackgroundAudioPlayer. You can, however, find the code in the downloadable sample code for the book.

MainPageViewModel

The viewmodel is declared as a field of the MainPage, as shown:

readonly MainPageViewModel viewModel = new MainPageViewModel(
                                          new BackgroundAudioPlayerProxy(),
                                          new IsolatedStorageUtility());

The MainPageViewModel class constructor accepts an IBackgroundAudioPlayer as well as an IIsolatedStorageUtility instance (see Listing 34.1). A subscription to the PlayStateChanged event of the background audio player allows the viewmodel to update various properties whenever the play state changes.

The viewmodel contains five commands for controlling the BackgroundAudioPlayer while the app is in the foreground, which provide for playing, pausing, stopping, and skipping between tracks.

The viewmodel’s Refresh method is called to update the viewmodel, which relies on the state of the BackgroundAudioPlayer. A Timer is used to monitor the progress of the track while it is being played.

LISTING 34.1. MainPageViewModel Constructor


public MainPageViewModel(
    IBackgroundAudioPlayer backgroundAudioPlayer,
    IIsolatedStorageUtility isolatedStorageUtility)
{
    player = ArgumentValidator.AssertNotNull(
                backgroundAudioPlayer, "backgroundAudioPlayer");
    ArgumentValidator.AssertNotNull(
                isolatedStorageUtility, "isolatedStorageUtility");

    player.PlayStateChanged += HandlePlayStateChanged;
    BackgroundAudioPlayerAgent.CopyAudioToIsolatedStorage(isolatedStorageUtility);

    playCommand = new DelegateCommand(obj => player.Play());
    pauseCommand = new DelegateCommand(obj => player.Pause());
    stopCommand = new DelegateCommand(obj => player.Stop());
    previousTrackCommand = new DelegateCommand(obj => player.SkipPrevious());
    nextTrackCommand = new DelegateCommand(obj => player.SkipNext());

    Refresh(false);

    timer = new Timer(HandleTimerTick, null, 3000, 1000);
}


The viewmodel’s TrackArtist and TrackTitle properties reflect the Artist and Title properties of the current AudioTrack.

The viewmodel’s VisualState property, which is of type string, determines the visual state of the view, and the visibility of various application bar buttons. The VisualState value is set to the PlayerState property of the BackgroundAudioPlayer, as shown:

void Refresh(bool setVisualState = true)
{
    switch (player.PlayerState)
    {
        case PlayState.Playing:
            CanPause = true;
            TrackArtist = player.Track.Artist;
            TrackTitle = player.Track.Title;
            break;
        case PlayState.Paused:
            CanPause = false;
            break;
    }

    BufferingProgress = player.BufferingProgress;

    if (setVisualState)
    {
        VisualState = player.PlayerState.ToString("G");
    }

    if (player.Error != null)
    {
        MessageService.ShowError("A problem occured:" + player.Error);
    }
}

When the viewmodel’s VisualState property changes, the view responds by refreshing the VisualStateManager state. This is performed from the MainPage constructor, as shown:

public MainPage()
{
    InitializeComponent();

    DataContext = viewModel;

    viewModel.PropertyChanged
        += (sender, args) =>
            {
                if (args.PropertyName == "VisualState")
                {
                    SetVisualState();
                }
            };
}

The view’s custom AppBar includes icon buttons bound to the viewmodel’s commands, as shown:

<u:AppBar>
    <u:AppBarIconButton
            Command="{Binding PreviousTrackCommand}"
            Text="Previous"
            IconUri="/Images/ApplicationBarIcons/Previous.png" />

    <!-- A toggle button is used rather than visual state to avoid
         repopulating the app bar when the user taps play or pause. -->
    <u:AppBarToggleButton
            x:Name="Button_Play"
            Command1="{Binding PlayCommand}"
            Text1="Play"
            Icon1Uri="/Images/ApplicationBarIcons/Play.png"
            Command2="{Binding PauseCommand}"
            Text2="Pause"
            Icon2Uri="/Images/ApplicationBarIcons/Pause.png"
            Toggled="{Binding CanPause}"/>

    <u:AppBarIconButton
            x:Name="Button_Stop"
            Command="{Binding StopCommand}"
            Text="Stop"
            IconUri="/Images/ApplicationBarIcons/Stop.png" />

    <u:AppBarIconButton
            Command="{Binding NextTrackCommand}"
            Text="Previous"
            IconUri="/Images/ApplicationBarIcons/Next.png" />
</u:AppBar>

The viewmodel’s ViewState property determines the visibility of each button. The XAML that defines the VisualStateGroups is not shown, but if you are interested, see the downloadable sample code.

Two TextBlock controls are used to display the viewmodel’s TrackArtist and TrackTitle properties, like so:

<TextBlock Text="{Binding TrackArtist}"
            Style="{StaticResource PhoneTextTitle3Style}"
            TextWrapping="Wrap" />
<TextBlock Text="{Binding TrackTitle}"
            Style="{StaticResource PhoneTextTitle2Style}"
            TextWrapping="Wrap" />

Whenever the track changes, these fields are updated according to the new track.

Monitoring Playback Progress

The viewmodel contains a Position property that reflects the progress of the current AudioTrack. Because the BackgroundAudioPlayer does not have facility to monitor the progress of a track directly, the viewmodel uses a Timer to periodically raise a property changed event for the Position property. The tick handler is shown in the following excerpt:

void HandleTimerTick(object state)
{
    if (player.PlayerState == PlayState.Playing)
    {
        OnPropertyChanged(() => Position);
    }
}

When a Position property change is detected in the view, it prompts a Slider control to reread the property. The Position get accessor calculates the position value, which is returned as a value between 0 and 1, as shown:

public double Position
{
    get
    {
        if (player.Track == null || player.Track.Duration.TotalSeconds < 1)
        {
            return 0;
        }
        double result = player.Position.TotalSeconds
                            / player.Track.Duration.TotalSeconds;
        return result;
    }
    set
    {
        if (player.Track != null)
        {
            double newSeconds = player.Track.Duration.TotalSeconds * value;
            TimeSpan newPosition = TimeSpan.FromSeconds(newSeconds);
            player.Position = newPosition;
        }
        OnPropertyChanged(() => Position);
    }
}

Raising a property changed event for the Position property causes the Slider, which is bound to the property, to be updated. The Slider is defined like so:

<Slider Value="{Binding Position, Mode=TwoWay}" Minimum="0" Maximum="1" />

Figure 34.3 shows the MainPage with the slider indicating the progress of the current track and the application bar icon buttons for controlling playback.

Image

FIGURE 34.3 A user interface to control the BackgroundAudioPlayer from a foreground app.

Numerous possibilities exist for extending an app such as this. For example, it could be extended to include a view for the playlist or an image for the album art—the sky’s the limit!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.119.229