In this series of blog entries, I build an entire ASP.NET MVC application from start to finish. In this entry, I integrate the Silverlight Media Player into the Family Videos MVC application so I can play videos.
Before reading this blog entry, you might want to first read the previous two entries:
Family Video Website #1 – Upload the Videos – In this entry, I create an MVC controller that accepts large file uploads.
Family Video Website #2 –Add the Database – In this entry, I added a database so that I could associate additional information, such as a title and description, with each video.
In this blog entry, I make two major changes to the Family Videos Website. First, I redesign the view that renders the uploaded files so that it can display multiple types of media. The Family Video Website not only supports playing videos; it also supports uploading pictures and audio files.
Rendering Different Types of Media
I want to enable family members to upload different types of media. For example, a family member might want to upload a JPEG image, a GIF image, a RAW audio file, or a WMV video file.
Each different type of media must be rendered in a different way. The way that an image is displayed is very different than the way a video is displayed. When displaying an image, we just want to show it in an <img> tag. When displaying a video, we need to render a video player. An audio file might require a different player than a video file.
Also, I want to design the Family Video Website in such a way that it can be easily modified to support new types of media in the future. If holographic videos become hugely popular, I don’t want to have to rewrite any of the existing Family Video Website code in order to support this new media type.
When you know that code is likely to change in the future, it is a good idea to isolate that code as much as possible from the rest of the code in your application. That way, when you make changes to the isolated code, you lower the risk of introducing bugs into the existing code.
There is a software design principle for this idea named Encapsulate What Varies. The Encapsulate What Varies principle is closely related to the Single Responsibility Principle which states that a class should have only a single reason to change. If there are multiple reasons that a class might need to be changed in the future, then you should divide these different responsibilities into distinct classes.
I struggled with this issue for a while. I wanted to figure out a good way to separate out the rendering logic for different media types from the rest of my application. I also wanted an easy way to add new ways to render content.
I decided to represent each way of rendering a media type with a partial (in other words, a user control). I created a distinct partial for rendering each type of media. I created a RenderVideo.ascx partial for rendering videos and a RenderPicture.ascx partial for rendering images. If I need to render a new type of media in the future, I can just create a new partial.
For example, the RenderPicture.ascx partial is contained in Listing 1.
Listing 1 – Partials\RenderPicture.ascx
<%@ Control Language="VB" AutoEventWireup="false" CodeBehind="RenderPicture.ascx.vb" Inherits="FamilyVideos.RenderPicture" %> <img src='<%= ResolveUrl(ViewData.Model.MediaPath) %>' width="200px" alt="" />
The partial in Listing 1 simply displays an image. The RenderVideo.ascx partial is more complicated since it renders a Silverlight Media Player. We discuss the RenderViewo.ascx partial in detail in the second part of this blog entry.
So how does the application know which partial to use for each uploaded file that it displays? In order to solve this problem, I created a RendererMap class. This class maps file extensions to renderers. For example, the class maps the extension .jpeg to the RenderPicture.ascx partial.
The RendererMap class is contained in Listing 2.
Listing 2 – Models\RenderMap.vb
Imports System.Web.Configuration Imports System.IO Public Class RendererMap Private Shared ReadOnly _map As New Dictionary(Of String, String) Shared Sub New() Dim config As RendererConfigurationSection = WebConfigurationManager.GetSection("renderers") For Each item As RendererConfigurationElement In config.Renderers Dim extension = "." & item.Extension.ToLower() Dim rendererPath = item.RendererPath _map.Add(extension, rendererPath) Next End Sub Public Shared Function GetRenderer(ByVal fileName As String) As String Dim extension = Path.GetExtension(fileName) If Not _map.ContainsKey(extension) Then Throw New ArgumentException(extension + " is not defined in the renderers section of web.config.") End If Return _map(extension.ToLower()) End Function End Class
The RendererMap class exposes one method named GetRenderer() that returns the path to the partial that should be used to render a particular file. The method extracts the file extension in order to determine which renderer to use to render a particular file.
The RendererMap class loads up the associations between file extensions and renderers within its constructor. The associations between file extensions and renderers are maintained in the web configuration (web.config) file. The application root web.config file contains the configuration section in Listing 3.
Listing 3 – Renderer configuration
<renderers> <add extension="jpg" rendererPath="~/Partials/RenderPicture.ascx" /> <add extension="jpeg" rendererPath="~/Partials/RenderPicture.ascx" /> <add extension="gif" rendererPath="~/Partials/RenderPicture.ascx" /> <add extension="png" rendererPath="~/Partials/RenderPicture.ascx" /> <add extension="wmv" rendererPath="~/Partials/RenderVideo.ascx" /> </renderers>
The nice thing about storing the associations between file extensions and renderers in the web.config file is that it makes it easy to add new renderers for new media types in the future. If you need to support a new type of media then you simply need to add a new entry into the web.config file and create a new partial (user control). You don’t even need to recompile your application!
In order to create a custom section in the web.config file, I needed to create a set of custom configuration classes. These classes are contained in the Configuration folder in the project that you can download at the end of this blog entry.
I modified the Index view returned by the Home controller so that it takes advantage of the RendererMap. The Index view loops through all of the uploaded files and renders each file with the appropriate renderer with the help of the RendererMap class. The updated Index view is contained in Listing 4.
Listing 4 – Views\Home\Index.aspx
Integrating the Silverlight MediaPlayer in an MVC View
The whole point of the Family Video Website is to play videos. Therefore, I really needed to add support for playing video files to the application.
I wanted to take advantage of Silverlight because Silverlight enables you to play high quality videos without demanding a huge amount of bandwidth (or giant video files). Also, I really like the fact that the Silverlight Media Player works on both Internet Explorer and Firefox. My grandma happens to be a loyal user of Internet Explorer. But, who knows? Feeling rebellious, she might decide to switch to Firefox.
I also really like Microsoft Expression Encoder. You can import just about any type of video file into the encoder and you get a WMV file that is ready to be played by the Silverlight Media Player (see Figure 1). The Expression Encoder has built-in profiles for different bandwidths (for example, 256K DSL versus 512K DSL).
Figure 1 – Expression Encoder 2
After you encode a video, you can play the video with the Silverlight Media Player. You can download the ASP.NET Silverlight Media Player control as part of the Microsoft Silverlight Tools for Visual Studio 2008. I downloaded the Beta 2 version from the following address:
After you install the Microsoft Silverlight Tools, the Media Player control appears in the toolbox (see Figure 2). You can use this control in an ASP.NET Web Forms page by dragging it onto the page and setting the control’s MediaSource property to the path of a video file.
Figure 2 – The ASP.NET Media Player Control
Unfortunately, however, you cannot use the ASP.NET Media Player control in an ASP.NET MVC View. The problem is that the Media Player control requires you to add the ASP.NET ScriptManager control to the view. Because the ScriptManager control requires a server-side form control, you can’t use the Media Player control in an ASP.NET view.
Listing 5 – Partials\RenderVideo.ascx
The Media Player displayed by the RenderVideo.ascx partial works with both Internet Explorer and Firefox. After you upload a video file to the Family Video Website, you can play the video with the Media Player (see Figure 3).
Figure 3 – Displaying Videos with the Silverlight Media Player
I still have more work to do on the Family Video Website. We can now upload different types of media and display the media. We also can enter a title and description for each file upload. I feel like we have made some real progress, but the application is still very rough.
In the next entry, I want to figure out how to display a progress bar while uploading huge video files.