Partager via


5 - Delivering and Consuming Media from Microsoft Azure Media Services

patterns & practices Developer CenterDownload Reference ImplementationDownload book

Microsoft Azure Media Services content can be delivered in numerous application scenarios. Content can be downloaded, or directly accessed by using locator URLs. In addition, content can be sent to another application or to another content provider. Content to be delivered can include media assets that are simply stored in Media Services, or media assets that have been encoded and processed in different ways.

This chapter describes how the Contoso developers incorporated Media Services' delivery and consumption functionality into their web service and Windows Store client application. It summarizes the decisions that they made in order to support their business requirements, and how they designed the code that requests and consumes encoded media.

Delivering media from Azure Media Services

Media Services can be used to deliver content that has been stored in Media Services, and it can also deliver content that has been processed in different ways with the results stored in Media Services.

There are a number of approaches that can be used to deliver Media Services content:

  • Direct download from Azure Storage.
  • Access media in Azure Storage.
  • Stream media to a client application.
  • Send media content to another application or to another content provider.

Media content can be downloaded directly from Azure Storage as long as you have the Media Services credentials for the account that uploaded and encoded the asset. Downloading may be necessary for archival purposes or for further processing outside the Media Services environment. Content can be downloaded by creating a shared access signature locator, which contains a URL to the asset that contains the requested file. The URL can then be used by anyone to download the asset. When downloading a storage encrypted file with the Media Services SDKs, the encrypted asset is automatically decrypted after download. For more information about downloading a file from storage see "Create a SAS Locator to On Demand Content."

A download that has not completed within 12 hours will fail.

You may want to give users access to content stored in Azure Storage. To do this you must create a full shared access signature URL to each file contained in the media asset. This is achieved by appending the file name to the shared access signature locator. For more information see "Processing the output assets from the encoding job."

Media Services also provides a way to directly access streaming media content. This can be accomplished by creating an on-demand origin locator, which allows direct access to Smooth Streaming or Apple HTTP Live Streaming (HLS) content. With on-demand origin locators, you build a full URL to a streaming manifest file in an asset. You can then provide the URL to a client application that can play streaming content.

When streaming media using on-demand origin locators you can take advantage of dynamic packaging. When using dynamic packaging, your video is stored in one encoded format, usually an adaptive bitrate MP4 file set. When a video player requests the video it specifies the format it requires, and the Origin Service converts the MP4 file to the format requested by the player. This allows you to store only one format of your videos, therefore reducing storage costs. The following figure illustrates dynamic packaging in action, whereby a video stored in one encoded format is converted by the Origin Service into multiple formats, as requested by the client applications.

A video is converted into multiple formats on-demand

A video is converted into multiple formats on-demand

Content can also be delivered by using a Content Delivery Network (CDN), in order to offer improved performance and scalability when streaming media with Origin Services. For more information see "How to Manage Origins in a Media Services Account." The following figure illustrates a content delivery network delivering video to multiple client applications after being converted into multiple formats by the Origin Service.

A video is converted into multiple formats on-demand and delivered through a CDN

A video is converted into multiple formats on-demand and delivered through a CDN

The Contoso web service is responsible for creating locators to give access to media assets, during the encoding/processing phase. The full URLs for media assets are then stored in the CMS database. Asset playback then simply involves retrieving the URL from the database and passing it to the appropriate control in the client application.

Azure Media Services Origin Service

The Origin Service handles requests for content by processing the outbound stream from storage to content delivery network or client application. It contains a content server which pulls the content from storage and delivers it, caching it as required in order to reduce the load on delivery channels and storage. It also provides content encryption and decryption in order to keep content protected. In addition it provides the ability to use dynamic packaging by storing media in one multi-bitrate format and converting it to the format requested by the client application in real-time.

Each Media Service account has at least one streaming origin called default associated with it. Media Services enables you to add multiple streaming origins to your account and to configure the origins. Examples of configurations include:

  • Setting the maximum caching period that will be specified in the cache control header of HTTP responses.
  • Specifying IP addresses that are allowed to connect to the published streaming endpoint.
  • Specifying configuration for g2o authentication requests from Akamai servers.

Akamai servers are a scalable network of servers around the world that make up a content delivery network. Server capacity is rented to customers who want their content to be delivered quickly from locations close to the user. When a user navigates to the URL of content belonging to an Akamai customer, they are transparently redirected to one of Akamai's copies of the content. Users then receive content from an Akamai server that is close to them, or which has a good connection, leading to faster download times.

For more information see "How to Manage Origins in a Media Services Account."

Azure Media Services dynamic packaging

When a video has been encoded it can be placed into a variety of file containers. This process is referred to as packaging. For example, you could convert an MP4 file into smooth streaming content by using the Azure Media Packager to place the encoded content into a different file container.

The Azure Media Packager is capable of performing static packaging and dynamic packaging. Static packaging involves creating a copy of your content in each format required by users. For example, an asset could be converted into smooth streaming content and HLS content if both formats are required by users. This would result in multiple copies of the content existing, in different formats. The following figure shows an example of an encoding and packaging workflow that uses static packaging.

An encoding and packaging workflow that uses static packaging

An encoding and packaging workflow that uses static packaging

Dynamic packaging enables video packaging to be performed when a client application requests a specific video format, allowing the video to be encoded just once, with it being converted in real-time to the format requested by the client. With dynamic packaging your video is typically stored as an adaptive bitrate MP4 file set. When a client application requests the video it specifies the required format. The Origin Service then converts the MP4 adaptive bitrate file to the format requested by the client in real-time. This ensures that only one format of your video has to be stored, therefore reducing the storage costs. The following figure shows an example of an encoding and packaging workflow that uses dynamic packaging.

An encoding and packaging workflow that uses dynamic packaging

An encoding and packaging workflow that uses dynamic packaging

To use dynamic packaging you must create an asset that contains a set of multi-bitrate MP4 files or multi-bitrate smooth streaming files. The on-demand streaming server will then ensure that clients receive the stream in the requested protocol, based on the specified format in the manifest. Dynamic packaging requires that you purchase on-demand streaming reserved units. For more information see "Dynamic packaging."

Dn735905.note(en-us,PandP.10).gifBharath says:
Bharath Dynamic packaging only supports unencrypted source files and produces unencrypted output streams.

The following source file formats are not supported by dynamic packaging:

  • Source files containing the following codecs:
    • Dolby digital in MP4 files.
    • Dolby digital in Smooth Streaming files.
  • Protected content:
    • Storage encrypted content.
    • PlayReady protected Smooth Streaming content.
    • AES-128 bit CBC protected HLS content.
  • HLS content:
    • HLS v4.
    • PlayReady protected HLS.
    • IIS MS HLS archives.
    • IIS MS HLS presentations from transform manager or the Media Services packager.
    • Segmented HLS.

Scaling Azure Media Services delivery

You can scale Media Services delivery by specifying the number of on-demand streaming reserved units that you would like your account to be provisioned with.

By default on-demand streaming is configured in a shared-instance mode in which server resources are shared with all users. On-demand streaming reserved units can be purchased to improve on-demand streaming throughput, with the units being purchased in increments of 200 Mbps. The allocation of any new on-demanding streaming reserved units takes around 20 minutes to complete.

The number of on-demand streaming reserved units can be configured on the Scale page of the Azure Management Portal.

By default every Media Services account can scale to up to 5 on-demand streaming reserved units. A higher limit can be requested by opening a support ticket. For more information about opening a support ticket see "Requesting Additional Reserved Units."

Securely delivering streaming content from Azure Media Services

The following figure summarizes how Media Services can deliver adaptive bitrate media assets using a number of techniques.

Options for delivering adaptive bitrate media assets

Options for delivering adaptive bitrate media assets

Media assets can be delivered in the clear, or securely by using common or envelope encryption. Each technique will now be discussed in turn.

Progressive download of storage encrypted content

Progressive download allows you to start playing media before the entire file has been downloaded, and is only supported with ISO standard MP4 files. To use progressive download you must create an on-demand locator and point your client application at the full URL of the MP4 file to play. However, on-demand locators do not support dynamic decryption of storage encrypted assets. Therefore, you must decrypt any storage encrypted assets that you wish to stream from the Origin Service for progressive download.

For more info see "Create a SAS Locator to On Demand Content."

Smooth Streaming content and MPEG-DASH

Smooth Streaming assets can be protected using common encryption and PlayReady DRM. Common encryption protects a media stream during storage and download by using Advanced Encryption Standard (AES) 128-bit elementary stream encryption, which provides content protection through to a secure decoder. PlayReady DRM protects a media stream during playback by using a license server that protects the decryption key required to decrypt the stream. Client apps that use PlayReady must provide a secure and robust playback environment that meets the compliance rules for PlayReady. When a client application attempts to access a PlayReady protected asset it must pass the player ID and device information to a license server. The licensing server will then determine if the user has permission to access the stream and if the device is trusted to decrypt the stream. For more info see "Microsoft PlayReady."

Note

Microsoft does not provide a license delivery service for PlayReady as part of Media Services. You must implement your own or use a third-party provider.

To use dynamic packaging to deliver MPEG-DASH encrypted with PlayReady DRM you are required to convert your video into smooth streaming format first, and then protect it with PlayReady DRM. For more info see "Task Preset for Azure Media Encryptor."

Apple HLS content

Media Services supports delivering HLS assets protected by an envelope encryption method such as AES-128 transport stream encryption. Transport stream encrypted media must be decrypted prior to media processing. Therefore, media and keys are processed unencrypted inside client apps that do not have to establish trust or guarantee protection of keys and content. Content protected using this approach is less secure than content protected with a DRM technology.

Apple HLS content with PlayReady

Media Services supports delivering HLS assets protected with PlayReady by first creating a Smooth Streaming asset, protecting it with PlayReady, and then using the Azure Media Packager to convert the resulting asset to a HLS asset protected with the Apple HLS key delivery protocol.

Delivery and consumption process in the Contoso Azure Media Services applications

The following figure shows a high-level overview of how the Contoso video client applications consume media assets stored in Azure Storage.

A high-level overview of the Contoso delivery process

A high-level overview of the Contoso delivery process

Client apps request a video through a REST web interface. The Contoso web service queries the CMS which returns the URL of the media asset in Azure Storage. The media asset could be a single video file, or a manifest file which references multiple video files. The application then requests the URL content from the Media Services Origin Service, which processes the outbound stream from storage to client app.

Dn735905.note(en-us,PandP.10).gifMarkus says:
Markus The Contoso web service does not provide a mechanism for removing inappropriate content and the users who have uploaded it. In your own application you should implement a user feedback mechanism for notifying the service administrators of inappropriate content. You should also implement functionality that enables service administrators to remove inappropriate content, and suspend or delete any users who have uploaded such content.

The client apps consume content by allowing users to browse videos, play videos, and retrieve recommendations for other videos they may want to watch. Each item will now be discussed in turn.

Browsing videos

The following figure shows the interaction of the classes in the Contoso Windows Store Video application that implement retrieving and displaying thumbnails of the available videos, for the user to browse through.

The interaction of classes that implement browsing videos

The interaction of classes that implement browsing videos

For information on how browsing videos works in the Contoso video web application, see "Appendix C – Understanding the Contoso Video Applications."

The MainPage in the application allows the user to browse through thumbnails of the available videos, and select one for viewing. The video thumbnails are displayed in an AutoRotatingGridView custom control, with the ItemTemplate using an Image control to display each thumbnail.

<controls:AutoRotatingGridView 
    ...
    ItemsSource="{Binding Videos}"
    SelectionMode="None"
    ScrollViewer.IsHorizontalScrollChainingEnabled="False"
    IsItemClickEnabled="True">
        <interactivity:Interaction.Behaviors>
            <core:EventTriggerBehavior EventName="ItemClick">
                <behaviors:NavigateWithEventArgsToPageAction 
                    TargetPage="Contoso.WindowsStore.Views.VideoDetailPage"
                    EventArgsParameterPath="ClickedItem.Id" />
            </core:EventTriggerBehavior>
        </interactivity:Interaction.Behaviors>
        <controls:AutoRotatingGridView.ItemTemplate>
            <DataTemplate>
                ...
                <Image Stretch="Uniform" >
                    <Image.Source>
                        <BitmapImage UriSource="{Binding ThumbnailUrl}" />
                    </Image.Source>
                </Image>
                ...
            </DataTemplate>
        </controls:AutoRotatingGridView.ItemTemplate>
    ...
</controls:AutoRotatingGridView>           

Note

The AutoRotatingGridView custom control is a view state detecting GridView control. When, for example, the view state changes from DefaultLayout to PortraitLayout the items displayed by the control will be automatically rearranged to use an appropriate layout for the view state. The advantage of this approach is that only one control is required to handle all the view states, rather than having to define multiple controls to handle the different view states.

The AutoRotatingGridView custom control binds to the Videos collection in the MainPageViewModel class, through the ItemsSource property. A custom Blend interaction named NavigateWithEventArgsToPageAction is used to invoke navigation to the VideoDetailPage. The EventTriggerBehavior binds the ItemClick event of the AutoRotatingGridView custom control to the NavigateWithEventArgsToPageAction. So when a GridViewItem is selected the NavigateWithEventArgsToPageAction is executed, which navigates from the MainPage to the VideoDetailPage, passing in the Id of the ClickedItem to the VideoDetailPage.

When a page is navigated to the OnNavigatedTo method in the page's view model is called. The OnNavigatedTo method allows the newly displayed page to initialize itself by loading any page state, and by using any navigation parameters passed to it. The following code example shows how the OnNavigatedTo method in the MainPageViewModel class populates the Videos collection with thumbnails to display to the user.

public async override void OnNavigatedTo(object navigationParameter, 
    string navigationMode, Dictionary<string, object> viewModelState)
{
    ...
    this.Videos = new IncrementalLoadingVideoCollection(this.videoService);
    // We retrieve the first 20 videos from the server.
    // The GridView will automatically request the rest of the items to 
    // the collection when needed.
    await this.Videos.LoadMoreItemsAsync(20);
    ...
}

The Videos property is of type IncrementalLoadingVideoCollection, and so this code creates a new instance of that type before calling the LoadMoreItemsAsync method to retrieve twenty video thumbnail URLs from the Contoso web service. In turn, the LoadMoreItemsAsync method calls the GetVideosAsync method of the VideoService class.

Dn735905.note(en-us,PandP.10).gifChristine says:
Christine The Contoso Video application supports incremental loading of thumbnail data on the main page in order to consume the paging functionality offered by the Contoso web service.
public async Task<ObservableCollection<Video>> GetVideosAsync(int pageIndex, 
    int pageSize)
{
    var requestUri = new Uri(string.Format(this.paginationQueryString, 
        this.videosBaseUrl, pageIndex, pageSize));
    var responseContent = await this.httpService.GetAsync(requestUri);

    var videos = 
        JsonConvert.DeserializeObject<ReadOnlyCollection<Video>>(responseContent);

    return new ObservableCollection<Video>(videos);            
}

This method creates a URI that specifies that the Get method will be called on the web service, with the paging options being passed as a parameter. The HttpService class, which implements the IHttpService interface, is used to make the call to the web service.

When the GetVideosAsync method calls HttpService.GetAsync, this calls the Get method in the VideosController class in the Contoso.Api project. For more information about how different methods are invoked in the Contoso web service, see "Appendix A – The Contoso Web Service."

public async Task<HttpResponseMessage> Get(int pageIndex = 1, int pageSize = 10)
{
    ...
    var videoInfos = await videoService.GetEncodedVideos(pageIndex, pageSize);
    var count = await videoService.GetEncodedVideosCount();

    var result = new List<VideoInfoDTO>();

    Mapper.Map(videoInfos, result);

    var response = Request.CreateResponse(HttpStatusCode.OK, result);

    ...
    return response;
}

This method calls the GetEncodedVideos method in the VideoService class. The VideoService class is registered as a type mapping against the IVideoService interface with the Unity dependency injection container. When the VideoController constructor accepts an IVideoService type, the Unity container will resolve the type and return an instance of the VideoService class.

When a collection of VideoInfo objects is returned from the GetEncodedVideos method a collection of VideoInfoDTO objects is created, with the returned VideoInfo objects being mapped onto the VideoInfoDTO objects, which are then returned in an HttpResponseMessage to the HttpService.GetAsync method.

The GetEncodedVideos method in the VideoService class simply calls the GetEncodedVideos method in the VideoRepository class, and returns the results as a collection of VideoInfo objects. The following code example shows the GetEncodedVideos method in the VideoRepository class.

public async Task<ICollection<VideoInfo>> GetEncodedVideos(int pageIndex, 
    int pageSize)
{
    using(VideoContext context = new VideoContext(this.NameOrConnectionString))
    {
        var videos = await context.Videos.Include(v => v.Thumbnails)
            .Where(v => v.EncodingStatus == (int)EncodingState.Complete)
            .OrderByDescending(v => v.LastUpdateDateTime)
            .Skip((pageIndex -1) * pageSize)
            .Take(pageSize)
            .AsNoTracking()
            .ToListAsync()
            .ConfigureAwait(false);

        var result = new List<VideoInfo>();

        foreach(var video in videos)
        {
            var videoInfo = new VideoInfo() 
            { 
                Id = video.Id, 
                Title = video.Title, 
                Length = video.Length, 
            };

            video.Thumbnails.ToList().ForEach(t => videoInfo.AddThumbnailUrl(
                new VideoThumbnail() { Id = t.Id, Url = t.ThumbnailUrl }));

            result.Add(videoInfo);
        }
        return result;
    }
}

Repository objects retrieve data from a context object by performing LINQ queries. Here, a LINQ query retrieves data from the Video table and its related tables, for any videos that match the paging parameters passed to the web service. VideoInfo objects are created for the data retrieved from the database, with the thumbnail data being added to the Thumbnails property of VideoInfo objects as VideoThumbnail objects. The collection of VideoInfo objects is then returned to the calling method.

For more information about using the Repository pattern to access data, see "Appendix A – The Contoso Web Service."

Playing videos

In the Contoso Video application video playback is achieved by using the Microsoft Player Framework, which is an open source video player. The framework allows you to easily add advanced video playback features to Windows Store apps. The player framework supports features such as:

  • Adaptive streaming and playback heuristics.
  • Closed captioning support.
  • DVR-style playback.
  • Skinning and styling support.

In turn, the Microsoft Player Framework is built on top of the Smooth Streaming Client SDK, which allows developers to create rich smooth streaming experiences. The SDK is composed of APIs that provide support for simple operations such as play, pause, and stop and also for more complex operations such as selecting and tracking bitrates for smooth streaming playback. For more information, see "Player Framework by Microsoft" and "Smooth Streaming Client SDK."

Closed captions and subtitles increase the accessibility of your videos. While this information can be embedded inside the video file itself, it is recommended that the data be stored in external files (often referred to as “sidecar” files). The Microsoft Player Framework used in the Contoso video application has support for captions and subtitles in both Timed Text Markup Language (TTML) and the Web Video Text Tracks (WebVTT) formats, while the HTML5 <video> tag currently supports only the WebVTT format. For an example of how to use the Microsoft Player Framework to display captions, see WebVTT Closed caption and subtitling support.

The following figure shows the interaction of the classes in the Contoso Windows Store Video application that implement retrieving and displaying a video for playback by the user.

The interaction of classes that implement retrieving and displaying a video for playback

The interaction of classes that implement retrieving and displaying a video for playback

For information on how playing videos works in the Contoso video web application, see "Appendix C – Understanding the Contoso Video Applications."

The VideoDetailPage in the application allows the user to view a video, while also listing video details and other recommended videos. The video is displayed in a MediaPlayer control, from the Player Framework, with the Source property of the control binding to the PlaybackUrl property in the VideoDetailPageViewModel class, which is of type string.

<mmppf:MediaPlayer
    ...
    Source="{Binding PlaybackUrl}" 
    AutoPlay="False"
    IsFullScreenVisible="True"
    IsFullScreenChanged="player_IsFullScreenChanged"
    HorizontalAlignment="Stretch"
    VerticalAlignment="Top">
        <mmppf:MediaPlayer.Plugins>
            <adaptive:AdaptivePlugin />
        </mmppf:MediaPlayer.Plugins>
</mmppf:MediaPlayer>                    

When a page is navigated to the OnNavigatedTo method in the page's view model is called. The OnNavigatedTo method allows the newly displayed page to initialize itself by loading any page state, and by using any navigation parameters passed to it. The following code example shows how the OnNavigatedTo method in the VideoDetailPageViewModel class populates the Video property with details of the selected video.

public async override void OnNavigatedTo(object navigationParameter, 
    string navigationModeString, Dictionary<string, object> viewModelState)
{
    ...
    int videoId;

    if (navigationParameter != null && 
        int.TryParse(navigationParameter.ToString(), out videoId))
    {
        Video selectedVideo = await this.videoService.GetVideoAsync(videoId);
        this.Video = selectedVideo;
        ...
    }
    ...
}

On the MainPage a custom Blend interaction is used to invoke navigation to the VideoDetailPage, passing in the ID of the selected video as a parameter. This parameter is parsed and used as a parameter to the GetVideoAsync method in the VideoService class, in order to retrieve the details for a specific video. When these details are retrieved the Video property is set and property change notification updates the PlaybackUrl property. The PlaybackUrl property is shown in the following code example.

public string PlaybackUrl
{
    get
    {
        if (this.Video == null)
        {
            return null;
        }

        if (this.Video.Videos.Any(x => x.EncodingType == 
            Constants.SmoothStreamingEncodingType))
        {
            return this.Video.Videos.First(x => x.EncodingType == 
                Constants.SmoothStreamingEncodingType).Url;
        }

        return this.video.Videos.Any(x => x.EncodingType == Constants.Mp4) ? 
            this.Video.Videos.First(x => x.EncodingType == Constants.Mp4).Url :  
            null;
    }
}

This property uses a LINQ query to return the URL of the video to be played, with the returned URL being dependent on the encoding type of the retrieved video.

The Windows Store Contoso Video application prioritizes playing Smooth Streaming assets. The Contoso web service encodes assets to multi-bitrate MP4 files, which are then converted to Smooth Streaming, Apple HLS, or MPEG-DASH on-demand by dynamic packaging. Therefore, there will always be multiple Smooth Streaming assets available through one Smooth Streaming URL, which is the address of the manifest for the multi-bitrate Smooth Streaming assets. However, the PlaybackUrl property demonstrates good practice by falling back to using the first available multi-bitrate MP4 URL if a Smooth Streaming URL is unavailable.

Dn735905.note(en-us,PandP.10).gifChristine says:
Christine The Windows Store and Windows Phone Contoso Video apps both play Smooth Streaming assets. However, the Android and iOS Contoso video apps play HLS assets.

As previously explained, the OnNavigatedTo method of the VideoDetailPageViewModel class calls the GetVideoAsync method in the VideoService class, in order to retrieve the details for a specific video. The following code example shows this method.

public async Task<Video> GetVideoAsync(int videoId)
{
    var requestUri = 
        new Uri(string.Format("{0}?id={1}", this.videosBaseUrl, videoId));
    var responseContent = await this.httpService.GetAsync(requestUri);

    var video = JsonConvert.DeserializeObject<Video>(responseContent);
    return video;
}

This method creates a URI that specifies that the Get method will be called on the web service, with the video ID being passed as a parameter. The HttpService class, which implements the IHttpService interface, is used to make the call to the web service.

When the GetVideoAsync method calls HttpService.GetAsync, this calls the Get method in the VideosController class in the Contoso.Api project. For more information about how different methods are invoked in the Contoso web service, see "Appendix A – The Contoso Web Service."

public async Task<HttpResponseMessage> Get(int id)
{
    ...
    var videoDetail = await this.videoService.GetVideo(id);
    if(videoDetail.Id != id)
    {
        return Request.CreateResponse(HttpStatusCode.NotFound);
    }

    var result = new VideoDetailDTO();

    Mapper.Map(videoDetail, result);

    return Request.CreateResponse(HttpStatusCode.OK, result);
    ...
}

This method calls the GetVideo method in the VideoService class. The VideoService class is registered as a type mapping against the IVideoService interface with the Unity dependency injection container. When the VideoController constructor accepts an IVideoService type, the Unity container will resolve the type and return an instance of the VideoService class.

When a VideoDetail object is returned from the GetVideo method a VideoDetailDTO object is created, with the returned VideoDetail object being mapped onto the VideoDetailDTO object, which is then returned in a HttpResponseMessage to the HttpService.GetAsync method. If the ID of the retrieved VideoDetail object doesn't match the ID that was passed into the Get method, a not found message is returned in the HttpResponseMessage object.

The GetVideo method in the VideoService class simply calls the GetVideo method in the VideoRepository class, and returns the results as a VideoDetail object. The following code example shows the GetVideo method in the VideoRepository class.

public async Task<VideoDetail> GetVideo(int id)
{
    using(VideoContext context = new VideoContext(this.NameOrConnectionString))
    {
        var video = await context.Videos
            .AsNoTracking()
            .SingleOrDefaultAsync(v => v.Id == id)
            .ConfigureAwait(false);

        if (video != null)
        {
            // this map is going to force the lazy load of child collections; 
            // this is to reduce the payload caused by eager loading the 
            // child objects
            return MapVideoDetail(video);
        }
        else
        {
            return new VideoDetail();
        }
    }
}

Repository objects retrieve data from a context object by performing LINQ queries. Here, a LINQ query retrieves data from the Video table and its related tables, for the first video that matches the ID passed into the GetVideo method. A VideoDetail object is then created, populated with the data retrieved from the database, and returned, if a match was found.

For more information about using the Repository pattern to access data, see "Appendix A – The Contoso Web Service."

Retrieving recommendations

As well as allowing the user to view a video, the VideoDetailPage also lists video details and other recommended videos. The following figure shows the interaction of the classes in the Contoso Windows Store Video application that implement retrieving and displaying video recommendations for the user.

The interaction of the classes that retrieve recommendations

The interaction of the classes that retrieve recommendations

For information on how retrieving recommendations works in the Contoso video web application, see "Appendix C – Understanding the Contoso Video Applications."

The VideoDetailPage displays video recommendations in a AutoRotatingGridView custom control, with the ItemTemplate using an Image control to display a thumbnail for each recommendation.

<controls:AutoRotatingGridView 
    ...
    ItemsSource="{Binding RelatedVideos}"
    SelectionMode="None"
    ScrollViewer.IsHorizontalScrollChainingEnabled="False"
    IsItemClickEnabled="True">
        <interactivity:Interaction.Behaviors>
            <core:EventTriggerBehavior EventName="ItemClick">
                <behaviors:NavigateWithEventArgsToPageAction 
                    TargetPage="Contoso.WindowsStore.Views.VideoDetailPage"
                    EventArgsParameterPath="ClickedItem.Id" />
            </core:EventTriggerBehavior>
        </interactivity:Interaction.Behaviors>
        <GridView.ItemTemplate>
            <DataTemplate>
                ...
                <Image Stretch="Uniform" >
                    <Image.Source>
                        <BitmapImage UriSource="{Binding ThumbnailUrl}" />
                     </Image.Source>
                </Image>
                ...
            </DataTemplate>
        </GridView.ItemTemplate>
</controls:AutoRotatingGridView>

The AutoRotatingGridView custom control binds to the RelatedVideos collection in the VideoDetailPageViewModel class, through the ItemsSource property. A custom Blend interaction named NavigateWithEventArgsToPageAction is used to invoke navigation to the VideoDetailPage. The EventTriggerBehavior binds the ItemClick event of the AutoRotatingGridView custom control to the NavigateWithEventArgsToPageAction. So when a GridViewItem is selected the NavigateWithEventArgsToPageAction is executed, which navigates from the VideoDetailPage to the VideoDetailPage, passing in the ID of the ClickedItem to the VideoDetailPage. The overall effect is to reload the page with the video selected from the recommendations being the video available for playback.

When a page is navigated to the OnNavigatedTo method in the page's view model is called. The OnNavigatedTo method allows the newly displayed page to initialize itself by loading any page state, and by using any navigation parameters passed to it. The following code example shows how the OnNavigatedTo method in the VideoDetailPageViewModel class populates the RelatedVideos property with details of the recommended videos.

public async override void OnNavigatedTo(object navigationParameter, 
    string navigationModeString, Dictionary<string, object> viewModelState)
{
    ...
    ReadOnlyCollection<Video> videos = await this.videoService.GetRecommendationsAsync(this.Video.Id);

    this.RelatedVideos = videos;
    ...
}

This method calls the GetRecommendationsAsync method of the VideoService class to retrieve the details of the recommended videos, passing in the ID of the video that can currently be viewed on the VideoDetailPage. When the recommended video details are retrieved the RelatedVideos property is set and property change notification updates the Boolean HasRelatedVideos property. The following code example shows the GetRecommendationsAsync method in the VideoService class.

public async Task<ReadOnlyCollection<Video>> GetRecommendationsAsync(int videoId)
{
    var requestUri = new Uri(string.Format("{0}/{1}/recommendations", 
        this.videosBaseUrl, videoId));
    var responseContent = await this.httpService.GetAsync(requestUri);
            
    var videos =  
        JsonConvert.DeserializeObject<ReadOnlyCollection<Video>>(responseContent);
    return videos;
}

This method creates a URI that specifies that the GetRecommendations method will be called on the web service, with the video ID being passed as a parameter. The HttpService class, which implements the IHttpService interface, is used to make the call to the web service.

When the GetRecommendationsAsync method calls HttpService.GetAsync, this calls the GetRecommendations method in the VideosController class in the Contoso.Api project. For more information about how different methods are invoked in the Contoso web service, see "Appendix A – The Contoso Web Service."

public async Task<HttpResponseMessage> GetRecommendations(int videoId)
{
    ...
    var videoInfos = await this.videoService.GetRecommendations(videoId);
    var result = new List<VideoInfoDTO>();

    Mapper.Map(videoInfos, result);

    return Request.CreateResponse(HttpStatusCode.OK, result);
    ...
}

This method calls the GetRecommendations method in the VideoService class. The VideoService class is registered as a type mapping against the IVideoService interface with the Unity dependency injection container. When the VideoController constructor accepts an IVideoService type, the Unity container will resolve the type and return an instance of the VideoService class.

When a VideoInfo collection is returned from the GetRecommendations method, a collection of VideoInfoDTO objects is created, with the returned VideoInfo collection being mapped onto the VideoInfoDTO collection, which is then returned in a HttpResponseMessage to the HttpService.GetAsync method.

The GetRecommendations method in the VideoService class simply calls the GetRecommendations method in the VideoRepository class, and returns the results as a collection of VideoInfo objects. The following code example shows the GetRecommendations method in the VideoRepository class.

using (var context = new VideoContext(this.NameOrConnectionString))
{
    // This code currently just pulls 5 randomly encoded videos just for the sake of example. Normally you would build your recommendation engine to your specific needs.
    List<VideoEntity> videos = null;

    var random = new Random();
    var limit = context.Videos.Count(s => s.EncodingStatus == (int) EncodingState.Complete) - 5;
    var skip = random.Next(0, limit);
    videos = await context.Videos
            .Where(v => v.EncodingStatus == (int)EncodingState.Complete)
            .Include(v => v.Thumbnails)
            .OrderByDescending(v => v.LastUpdateDateTime)
            .Skip(skip).Take(5)
            .AsNoTracking()
            .ToListAsync()
            .ConfigureAwait(false);
                
    var result = new List<VideoInfo>();
    foreach (var video in videos)
    {
        var videoInfo = new VideoInfo()
        {
            Id = video.Id,
            Title = video.Title,
            Length = video.Length,
        };

        video.Thumbnails.ToList().ForEach(t => videoInfo.AddThumbnailUrl(new VideoThumbnail() { Id = t.Id, Url = t.ThumbnailUrl }));

        result.Add(videoInfo);
    }

    return result;
}

Repository objects retrieve data from a context object by performing queries. Here, a LINQ query retrieves data from the Video table and its related tables, for five random videos. VideoInfo objects are then created for the data retrieved from the database, with the thumbnail data being added to the Thumbnails property of VideoInfo objects as VideoThumbnail objects. The collection of VideoInfo objects is then returned to the calling method.

For more information about using the Repository pattern to access data, see "Appendix A – The Contoso Web Service."

Summary

This chapter has described how the Contoso developers incorporated Media Services' delivery and consumption functionality into their web service and Windows Store client application. It summarized the decisions that they made in order to support their business requirements, and how they designed the code that requests and consumes encoded media.

In this chapter, you saw how dynamic packaging allows a video to be encoded just once, with it being converted in real-time to the format requested by the client, and the role that origin services play in delivering content from storage to the client application. The chapter also discussed how to scale delivery by reserving on-demand streaming reserved units.

More information

Next Topic | Previous Topic | Home | Community