Sigurd Snørteland



How I made the ‘myChannel9′ wp7 app (source code included) – Part 1

‘myChannel9′ is a Windows Phone 7 app for streaming of Channel9 content. It offers features like the ability to favorite a show, browse by tags or search by keyword and, of course, the ability to watch videos right on your device (in portrait and landscape mode). I participated in the Norwegian WP7 developer contest with ‘myChannel9′ and I was one of three lucky winners! The ‘myChannel9′ app is now available as a free app in the WP7 Marketplace, and the source code is available for download from codeplex (mychannel9.codeplex.com). I hope that you take a look at the code and helps making this a better Channel9-app!

Go to the bottom off this article to find a link to the source code.

The user experience
The main user experience is build up around the wp7 panorama control and consists of these 5 “blocks”:
● news
● shows
● tags (not visible in the wp7 marketplace version)
● favorites
● search

The ‘MainPage’ xaml looks like this:

<Grid x:Name="LayoutRoot" Background="Transparent">

        <controls:Panorama Title="  my channel 9">
            <controls:Panorama.Background>
                <ImageBrush ImageSource="PanoramaBackground.png"/>
            </controls:Panorama.Background>

            <controls:PanoramaItem Header="news">
                <Grid>
                    <local:Control_News x:Name="controlNews" />
                    <Image x:Name="btnSettings" Source="Images/logo.png" HorizontalAlignment="Left" VerticalAlignment="Top" Width="150" Height="150" Margin="-20,-290,0,0" MouseLeftButtonDown="btnSettings_MouseLeftButtonDown" />
                    <Image x:Name="btnRefresh" Source="Images/appbar_refresh.png" HorizontalAlignment="Right" VerticalAlignment="Top" Width="48" Height="48" Margin="0,-85,20,0" MouseLeftButtonDown="btnRefresh_MouseLeftButtonDown" />
                </Grid>
            </controls:PanoramaItem>

            <controls:PanoramaItem Header="shows" >
                <local:Control_Show x:Name="controlShow" />
            </controls:PanoramaItem>

            <controls:PanoramaItem Header="tags" >
                <local:Control_Tags x:Name="controlTags" />
            </controls:PanoramaItem>

            <controls:PanoramaItem Header="favorites" >
                <local:Control_Favorites x:Name="controlFavorites" />
            </controls:PanoramaItem>

            <controls:PanoramaItem Header="search" >
                <local:Control_Search x:Name="controlSearch" />
            </controls:PanoramaItem>

        </controls:Panorama>
    </Grid>

As you can see I use ‘user controls’ inside the panorama “blocks” to avoid to much code & xaml in the MainPage. I think this works great, except that it “destroys the “navigation-route”. If you navigate like this: this.NavigationService.Navigate(new Uri(“/Show.xaml”, UriKind.Relative)); inside a ‘user control’ then the ‘back’-button wont work correct. To fix this i use a event like this:

public event EventHandler goToVideoInfo'

inside the ‘user control’ and raise it like this:

goToVideoInfo(video, new EventArgs());

In the ‘MainPage’ I hook up to the event inside the ‘user control’ like this:

        public MainPage()
        {
            InitializeComponent();

            controlShow.goToShow += new EventHandler(controlShow_goToShow);
            ...
        }

and the method looks like this:

void controlShow_goToShow(object sender, EventArgs e)
        {
            this.NavigationService.Navigate(new Uri(/VideoInfo.xaml, UriKind.Relative));
        }

The ‘News’ page

The news page is basically just a RSS reader that is showing the main Channel9-feed. I use linq to “convert” the feed to ‘VideoObjects’, add them to a list, and then bind the list to a listbox like this:

        public void loadNews()
        {
            lblLoading.Visibility = Visibility.Visible;
            lboNews.Visibility = Visibility.Collapsed;

            WebClient client = new WebClient();
            client.DownloadStringCompleted += ReadNews;
            string url = "http://channel9.msdn.com/Feeds/RSS/";
            client.DownloadStringAsync(new Uri((url)));
        }

        private void ReadNews(object Sender, DownloadStringCompletedEventArgs e)
        {
            List<VideoObject> videoList = new List<VideoObject>();

            try
            {
                if (!e.Cancelled)
                {
                    XDocument xDoc = XDocument.Parse(e.Result);
                    XElement xml = XElement.Parse(e.Result);

                    List<VideoObject> lst = new List<VideoObject>();
                    var queue = from item in xDoc.Descendants("item")

                                select new VideoObject
                                {
                                    Heading = item.Element("title").Value,
                                    Url = item.Element("link").Value,
                                    Description = Helper.removeHtml(item.Element("description").Value),
                                    Posted = item.Element("pubDate").Value,
                                    videoList = (from img in item.Elements(item.GetNamespaceOfPrefix("media") + "group").Elements(item.GetNamespaceOfPrefix("media") + "content")
                                                 select img.Attribute("url").Value).ToList(),
                                    imageList = (from img in item.Elements(item.GetNamespaceOfPrefix("media") + "thumbnail")
                                                 select img.Attribute("url").Value).ToList(),
                                    Lenght = "",
                                    LenghtDetails = "",
                                    PostedDate = Helper.getPostedDate(item.Element("pubDate").Value),
                                    PostedDetails = Helper.getPostedDetails(item.Element("pubDate").Value),
                                    Tags = "",
                                    Type = "",
                                    Favorite = false
                                };

                    videoList = queue.ToList<VideoObject>();

                    List<VideoObject> channel9List = new List<VideoObject>();

                    if (IsolatedStorageSettings.ApplicationSettings.Contains("channel9List"))
                        channel9List = IsolatedStorageSettings.ApplicationSettings["channel9List"] as List<VideoObject>;
                    else
                        IsolatedStorageSettings.ApplicationSettings.Add("channel9List", channel9List);

                    foreach (VideoObject video in videoList)
                    {
                        if (Helper.isUnique(video.Url, channel9List))
                        {
                            channel9List.Add(video);
                        }
                    }

                    IsolatedStorageSettings.ApplicationSettings["channel9List"] = channel9List;
                }

                lboNews.ItemsSource = videoList;
            }
            catch (Exception ex)
            {
                //MessageBox.Show(ex.ToString());
            }

            lboNews.Visibility = Visibility.Visible;
            lblLoading.Visibility = Visibility.Collapsed;
        }

It’s worth to mention that I save every unique videoObjects to a VideoObject-list, named ‘channel9List’, in the isolated storage. This list is among others used by the search-component (because the Channel9-site don’t offer a search-webservice). More about that late.

The ‘News’ page xaml looks like this:

<Grid>
        <ListBox x:Name="lboNews" HorizontalAlignment="Left" Width="400" SelectionChanged="lboNews_SelectionChanged">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Border Background="White" Margin="0,0,0,30" Width="400" Opacity="0.5" />
                        <StackPanel Orientation="Vertical">
                            <StackPanel Orientation="Horizontal" Margin="0,0,20,0">
                                <Image Source="{ Binding getImage }" Width="180" Margin="0,0,10,10" VerticalAlignment="Top" HorizontalAlignment="Left" />
                                <TextBlock Text="{ Binding PublishedDate}" Margin="10,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Center" TextWrapping="Wrap" Style="{StaticResource PanoramaTextBlock}" />
                            </StackPanel>
                            <TextBlock Text="{ Binding Heading}" TextWrapping="Wrap" Margin="20,0,25,40" Style="{StaticResource PanoramaTextBlock}" />
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <TextBlock x:Name="lblLoading" Text="loading..." FontSize="30" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,-50,0,0" />
    </Grid>

When the user selects an item in the listbox control the method ‘lboNews_SelectionChanged’ will be called. You can see the method below and what it does is to save the selected videoObject to isolated storage. Afterwards I raise the ‘goToVideoInfo’ event to navigate to the Video-page. I have chosen this way of moving data between pages, insted of the “url-parameters way”, because by using the isolated storage I can “move” a lot of data between pages easier.

private void lboNews_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            VideoObject video = lboNews.SelectedItem as VideoObject;
            if (video != null)
            {
                if (IsolatedStorageSettings.ApplicationSettings.Contains("Video"))
                    IsolatedStorageSettings.ApplicationSettings["Video"] = video;
                else
                    IsolatedStorageSettings.ApplicationSettings.Add("Video", video);

                goToVideoInfo(video, new EventArgs());
                lboNews.SelectedIndex = -1;
            }
        }

The ‘VideoInfo’ page

This page is quite easy to understand, it just reads a VideoObject from Isolated storage and bindes the information to a couple off textblocks and a image control like this:

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            if (IsolatedStorageSettings.ApplicationSettings.Contains("Video"))
            {
                video = IsolatedStorageSettings.ApplicationSettings["Video"] as VideoObject;
            }

            if (video != null && video.Image != "")
            {
                lblDate.Text = video.PostedDetails;
                lblHeading.Text = video.Heading;
                lblDescription.Text = video.Description;

                if (video.LenghtDetails != null && video.LenghtDetails != "")
                {
                    lblLenght.Visibility = Visibility.Visible;
                    lblLenght.Text = "Lenght: " + video.LenghtDetails;
                }

                Uri uri = new Uri(video.getImage, UriKind.Absolute);
                ImageSource imgSource = new BitmapImage(uri);
                img.Source = imgSource;

                showCorrectFavoriteIcon();
            }
        }

One of the features on this page is the ability to add a video as a ‘favorite’ by clicking on the star-icon. The code looks like this:

private void btnFavorite_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            List<VideoObject> channel9List = null;

            if (IsolatedStorageSettings.ApplicationSettings.Contains("channel9List"))
            {
                channel9List = IsolatedStorageSettings.ApplicationSettings["channel9List"] as List<VideoObject>;

                if (channel9List != null)
                {
                    if (video.Favorite)
                    {
                        channel9List.Remove(video);
                        video.Favorite = false;
                        channel9List.Add(video);
                    }
                    else
                    {
                        channel9List.Remove(video);
                        video.Favorite = true;
                        channel9List.Add(video);
                    }

                    showCorrectFavoriteIcon();
                }

                IsolatedStorageSettings.ApplicationSettings["channel9List"] = channel9List;
            }
        }

To stream the video you have to click on the play-icon in the top right corner, and the app will send you to the ‘Video’-page.

The ‘VideoInfo’ page xaml looks like this:

<Grid x:Name="LayoutRoot" Background="Black">
        <Grid x:Name="InfoView">
            <Image x:Name="imgBackground" Source="Images/bg.png" Height="800" Width="500" VerticalAlignment="Top" HorizontalAlignment="Left" />

            <Border Background="White" Margin="0,85,0,0" Width="450" Opacity="0.5" />
            <ScrollViewer Margin="15,85,15,0">
                <StackPanel Orientation="Vertical" >
                    <Image x:Name="img" Width="450" HorizontalAlignment="Center" VerticalAlignment="Top" />
                    <TextBlock x:Name="lblDate" Margin="20,0,20,0" TextWrapping="Wrap" Style="{StaticResource PanoramaTextBlock}" />
                    <TextBlock x:Name="lblHeading" FontWeight="Bold"  Margin="20,20,20,0" TextWrapping="Wrap" Style="{StaticResource PanoramaTextBlock}" />
                    <TextBlock x:Name="lblLenght" Visibility="Collapsed"  Margin="20,20,20,0" TextWrapping="Wrap" Style="{StaticResource PanoramaTextBlock}" />
                    <TextBlock x:Name="lblDescription"  Margin="20,20,20,0" TextWrapping="Wrap" Style="{StaticResource PanoramaTextBlock}" />
                </StackPanel>
            </ScrollViewer>
        </Grid>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="15,15,0,0" >
            <Image Source="Images/imgStar.png" x:Name="btnFavorite" Width="48" Height="48" Margin="10,0,0,0" MouseLeftButtonDown="btnFavorite_MouseLeftButtonDown" />
        </StackPanel>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="15,15,15,0" >
            <Image x:Name="btnPlay" Source="Images/imgPlay.png" Width="48" Height="48" MouseLeftButtonDown="btnPlay_MouseLeftButtonDown" />
        </StackPanel>

    </Grid>

The ‘Video’ page

This page is used to stream videos and support both portrait and landscape mode.

        private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            OrientationChanged += new EventHandler<OrientationChangedEventArgs>(Video_OrientationChanged);

            if (IsolatedStorageSettings.ApplicationSettings.Contains("Video"))
            {
                video = IsolatedStorageSettings.ApplicationSettings["Video"] as VideoObject;
            }

            if (video != null)
            {
                media.Loaded += new RoutedEventHandler(media_Loaded);
                media.CurrentStateChanged += new RoutedEventHandler(media_Loaded);
                media.Source = new Uri(video.getVideo);
                play();
                showCorrectFavoriteIcon();
            }
        }

        void Video_OrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            if (Orientation == PageOrientation.Landscape || Orientation == PageOrientation.LandscapeLeft || Orientation == PageOrientation.LandscapeRight)
            {
                mediaControl.Width = 70;
                media.Margin = new Thickness(70, 0, 0, 0);

                btnFavorite.Visibility = Visibility.Collapsed;
                btnPlay.Visibility = Visibility.Collapsed;
            }
            else
            {
                mediaControl.Width = 0;
                media.Margin = new Thickness(0, 0, 0, 0);

                btnFavorite.Visibility = Visibility.Visible;
                btnPlay.Visibility = Visibility.Visible;
            }

            showCorrectFavoriteIcon();
        }

The ‘Video’ page xaml looks like this:

    <Grid x:Name="LayoutRoot" Background="Black">

        <MediaElement Visibility="Visible" x:Name="media" />

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="15,15,0,0" >
            <Image Source="Images/imgStar.png" x:Name="btnFavorite" Width="48" Height="48" Margin="10,0,0,0" MouseLeftButtonDown="btnFavorite_MouseLeftButtonDown" />
        </StackPanel>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="15,15,15,0" >
            <Image x:Name="btnPlay" Source="Images/imgPlay.png" Width="48" Height="48" MouseLeftButtonDown="btnPlay_MouseLeftButtonDown" />
        </StackPanel>

        <StackPanel x:Name="mediaControl" Background="Black" Width="0" VerticalAlignment="Center" HorizontalAlignment="Left" Orientation="Vertical" Margin="0,0,0,0">
            <Image Source="Images/imgStar.png" x:Name="btnFavorite2" Width="48" Height="48" Margin="0,15,0,0" MouseLeftButtonDown="btnFavorite_MouseLeftButtonDown" />
            <Image x:Name="btnPlay2" Source="Images/imgPlay.png" Width="48" Height="48" Margin="0,15,0,0" MouseLeftButtonDown="btnPlay_MouseLeftButtonDown" />
        </StackPanel>

        <TextBlock x:Name="lblLoading" Text="loading..." FontSize="30" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,-50,0,0" />
    </Grid>

Download the source code:

Next post
In the next post, I will go through details on how I built the ‘Show’, ‘Tags’ and ‘Search’ features. I will also list some of the coming changes and new features.

About these ads

Trackbacks & Pingbacks

  1. Media « Sigurd Snørteland pingbacked on 4 years ago
  2. Tweets that mention How I made the ‘myChannel9′ wp7 app (source code included) – Part 1 « Sigurd Snørteland -- Topsy.com pingbacked on 4 years ago
  3. Windows Phone 7 Developer Roundup #2 for 11/05/2010 « "A" Developer`s Life pingbacked on 4 years ago
  4. 1st post after migrating to WordPress~ | CactuarJ's NotePad pingbacked on 4 years ago
  5. WP7 Marketplace downloads and sales data « Sigurd Snørteland pingbacked on 3 years, 11 months ago
  6. WP7 Marketplace downloads and sales data – one month later « Sigurd Snørteland pingbacked on 3 years, 10 months ago
  7. myChannel9 | WP7COMP pingbacked on 3 years, 8 months ago

Comments

  1. * Mark says:

    Thanks! It’s a nice app.

    | Reply Posted 4 years ago
  2. * Soon Studios says:

    Cool!

    | Reply Posted 4 years ago
  3. Thank you for sharing your thoughts. I really appreciate your efforts and I
    will be waiting for your next write ups thanks once again.

    | Reply Posted 2 years, 2 months ago
  4. * Darcy says:

    Fantastic beat ! I wish to apprentice while you amend your
    site, how could i subscribe for a blog web site?
    The account helped me a acceptable deal. I had been tiny bit acquainted of this your broadcast provided bright clear idea

    | Reply Posted 1 year, 7 months ago
  5. * Hollis says:

    I must thank you for the efforts you’ve put in penning this blog. I’m hoping
    to check out the same high-grade content by you later on
    as well. In fact, your creative writing abilities has encouraged me
    to get my own, personal blog now ;)

    | Reply Posted 1 year, 6 months ago
  6. My spouse and I stumbled over here by a different page and thought
    I might check things out. I like what I see so i am just following you.
    Look forward to looking over your web page again.

    | Reply Posted 1 year, 6 months ago
  7. * Cotcodac says:

    Thanks for sɦaring such a fastidioսs idea, pɑragraph
    iis fastidious, thats why i have read it fully

    | Reply Posted 8 months, 3 weeks ago


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: