14 February 2014

Build for both–an instruction video pivot page for Windows store apps

For my game 2 Phone Pong – and its Windows 8.1 counterpart 2 Tablet Pong, that at the time of this writing is going through some interesting certification challenges  – I created a page with video instructions. I did not feel much like writing a lot of text on how to connect the apps and play the game – a short video usually tells so much more. That proved to be less straightforward than I had hoped, so I thought it a good idea to share how I got it to work.

Both platforms have their own limitations and idiosyncrasies. Windows 8 does not have a Pivot, and Windows Phone makes life pretty difficult when it comes to playing multiple videos on one page. I have solved the first problems by creating FlipViewPanoramaBehavior and I was successfully able to reuse that behavior, although it’s now running on top of the official Windows RT Behavior SDK, no longer using my stopgap project WinRtBehaviors.

In this post I will show you how to build a video page for Windows Store apps – the next one will show you how to do the same for Windows Phone. The Windows version is actually the most simple

When I set out to create the video instruction page I wanted it to do the following:

  • Start the video on the current visible pivot item automatically – and start the first video on the first pivot as soon as the user access the page.
  • Repeat that movie automatically when it ends
  • Stop it as soon as the user selects a different pivot item

This gives the following effect:

Store video instruction page demo

How I got to this, is explained below

Setting the stage

The beginning is pretty simple - it usually is ;-)

  • Created an Blank XAML store app
  • Bring in my WpWinNl library from Nuget
  • Add an empty page “Help.xaml”
  • Go to App.xaml.cs and change rootFrame.Navigate(typeof(MainPage), e.Arguments); to rootFrame.Navigate(typeof(Help), e.Arguments);
  • Bring in a couple of video’s you want to use. I took three of my actual instruction videos from 2 Tablet Pong.

The XAML basics

The page contains the usual stuff for a nice page header, but as far as the video instruction part is concerned, it only contains the following things:

  • A FlipView with some (very minor) styling for the header text
  • My FlipViewPanoramaBehavior attached to it
  • A few FlipViewItems, each containing a grid with a header and a MediaElement with the Video in it

That looks more or less like this:

<FlipView x:Name="VideoFlipView"  >
  <FlipView.Resources>
    <Style TargetType="TextBlock">
      <Setter Property="FontSize" Value="30"></Setter>
    </Style>
  </FlipView.Resources>
  <interactivity:Interaction.Behaviors>
    <behaviors:FlipViewPanoramaBehavior/>
  </interactivity:Interaction.Behaviors>
  <FlipViewItem >
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="40"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
      </Grid.RowDefinitions>
      <TextBlock Text="Connect via WiFi" ></TextBlock>
      <MediaElement x:Name="FirstMovieMediaElement" 
        Source="Video/ConnectWiFi.mp4" Grid.Row="1" >
       </MediaElement >
    </Grid>
  </FlipViewItem>
  <!-- More FlipViewItems with video -->
</FlipView>

Important to see is that both the FlipView and the first FlipViewItem have a name, this is because we need to reference it form the page code.

Some code to make it work

First order or business is getting this thing on the road, so we need to define some starter events in the constructor:

using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using WpWinNl;
using WpWinNl.Utilities;

namespace Win8VideoPivot
{
  public sealed partial class Help : Page
  {
    public Help()
    {
      InitializeComponent();
      VideoFlipView.SelectionChanged += SelectedIndexChanged;
      FirstMovieMediaElement.MediaOpened += 
        FirstMovieMediaElementMediaOpened;
    }
  }
}

Next up is a little helper property that give us a short cut to all the MediaElements on the page, using a helper method from WpWinNl:

private IEnumerable<MediaElement> AllMediaElements
{
  get
  {
    return VideoFlipView.GetVisualDescendents().OfType<MediaElement>();
  }
}
The main method of this page is StartMovieOnSelectedFlipViewItem - that finds the MediaElement on the current selected FlipViewItem, stops MediaElements on all the other FlipViewItems, and kicks off the current one to play it's movie:
private void StartMovieOnSelectedFlipViewItem()
{
  var pivotItem = (FlipViewItem)VideoFlipView.SelectedItem;
  var mediaItem = pivotItem.GetVisualDescendents().OfType<MediaElement>().FirstOrDefault();
  AllMediaElements.Where(p => p != mediaItem).ForEach(p => p.Stop());

  if (mediaItem != null)
  {
    mediaItem.Play();
  }
}

In the constructor we wired up FirstMovieMediaElementMediaOpened, to be fired when the first MediaElement has opened it's media file. It does, as you would expect, start the first movie file by simply calling StartMovieOnSelectedFlipViewItem  What it also does is setting all the MediaElement’s AutoPlay properties to false, and attach a method to their MediaEnded property, so that a movie is automatically restarted again when it ends.

private void FirstMovieMediaElementMediaOpened(object sender, RoutedEventArgs e)
{
  AllMediaElements.ForEach(p =>
  {
    p.MediaEnded += MovieMediaElementMediaEnded;
    p.AutoPlay = false;
  });
  StartMovieOnSelectedFlipViewItem();
}

private void MovieMediaElementMediaEnded(object sender, RoutedEventArgs e)
{
  ((MediaElement)sender).Play();
}

This might seem a mighty odd place to do it here, and not in the constructor – but I have found that it simply does not have the desired effect when I placed this code in a constructor. A typical ‘yeah whatever’ thing.

The only thing that is now missing is SelectedIndexChanged, that we also wired up in the constructor to be executed when a new FlipViewItem is selected:

private void SelectedIndexChanged(object sender, SelectionChangedEventArgs e)
{
  StartMovieOnSelectedFlipViewItem();
}

It simply restarts a movie that ends all by itself. Note – this is not fired when a movie is stopped by code.

And for good measure, to make sure we don’t introduce all kinds of memory leaks, we detach all the events again in the OnNavigatedTo

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
  base.OnNavigatedFrom(e);
  AllMediaElements.ForEach(p =>
  {
    p.MediaEnded -= MovieMediaElementMediaEnded;
  });
  VideoFlipView.SelectionChanged -= SelectedIndexChanged;
  FirstMovieMediaElement.MediaOpened -= FirstMovieMediaElementMediaOpened;
}

That’s all there is to it – you will find that most of the work will actually go into making videos that are good enough to convey the minimal message without enormously blowing up the size of your app. The code itself, as you see, is pretty easy.

Full demo solution can be found here.

Oh by the way – I did not suddenly do a 180 of MVVM versus code behind – this is just pure view stuff, no business logic needed to drive it, so it’s OK to use code behind in this case.

No comments: