Serving videos to iOS devices from EPiServer VPP folders

S

We recently launched an EPiServer site that made moderate, but high-profile usage of video and was also designed to be iOS device friendly – meaning no Flash and Html5 used to display video. Late on in development / integration we came across an issue where the mp4 / m4v videos file that were being served from an EPiServer VPP folder did not play when viewed on an iOS device. A little investigation found that the video files would play correctly when served natively from a static / non-content managed location.

(Note: If you’re serving a large amount of media content, then you’d be better off looking at a full media hosting solution rather than just serving from the EPiServer VPP).

Http-Headers

Obviously the files were identical, so the only difference was in how IIS was serving them. Below are the Http responses (captured using FireBug) for the identical .m4v video files, one served from a VPP, and one from a native path.

The significant difference is the Accept-Ranges : bytes header. Cool – so we can just add in this header to our response and we’re sorted right? WRONG I’m afraid!

HTTP/1.1 Accept-Ranges header field

The Accept-Ranges response-header field allows the server to indicate its acceptance of range requests for a resource. This means that the server is open to partial downloads of files, and that clients (web-browsers) can download a limited ‘chunk’ of a file by requesting a specific byte-range, for-example bytes 5001-9999.

Going back to our original issue of why the files didn’t play on an iOS device, a quick look on the Apple developer notes (via StackOverflow) confirms that the web server needs to be configured to correctly handle byte-range requests. This explains why our file doesn’t work on an iOS device when served from an EPiServer VPP.

EPiServer.Web.StaticFileHandler

Our site was running using IIS7.5 in integrated pipeline mode, meaning that pretty much the whole Request / Response pipeline is handled by ASP.NET modules. Looking in the EPiServer site web.config you can see that the location path elements for VPP paths (Global /PageFiles etc) contain the HttpHandlers that are responsible for serving VPP filess. This is a bespoke EPiServer file handler implementation (EPiServer.Web.StaticFileHandler), which unlike the default IIS7 StaticFileHandler does not support Range-Requests 🙁

The Solution

Now you didn’t think that I’d explain all of this, without heroically coming up with a solution did you? Well I am providing a solution, but its hardly heroic as most of the hard work was done many years ago by Scott Mitchell – see Range-Specific Requests in ASP.NET. In his post Scott explains and provides an example of how to roll a HttpHandler with support for range-specific HTTP requests.

I’ve inherited from the RangeRequestHandlerBase class that Scott posted in the above article, and overidden a few methods adding in some EPiServer specifics, such as mapping the request path to the physical VPP file path and also using the EPiServer.Web.MimeMapping class to handle mapping from an extension to a mime type.

    public class RangeRequestFileHandler : RangeRequestHandlerBase
    {
        ///
        /// Returns a FileInfo object from the mapped VirtualPathProviderFile
        ///
        /// <param name="context" />
        ///
        public override FileInfo GetRequestedFileInfo(HttpContext context)
        {
            UnifiedFile file = GetFileInfoFromVirtualPathProvider(context);

            if (file == null)
                return null;

            PreventRewriteOnOutgoingStream();
            return new FileInfo(file.LocalPath);
        }

        private static UnifiedFile GetFileInfoFromVirtualPathProvider(HttpContext context)
        {
            return GenericHostingEnvironment.VirtualPathProvider.GetFile(context.Request.FilePath) as UnifiedFile;
        }

        ///
        /// Returns the MIME type from the mapped VirtualPathProviderFile
        ///
        /// <param name="context" />
        ///
        public override string GetRequestedFileMimeType(HttpContext context)
        {
            UnifiedFile file = GetFileInfoFromVirtualPathProvider(context);
            return MimeMapping.GetMimeMapping(file.Name);
        }

        ///
        /// Prevents episerver rewrite on outgoing stream.
        ///
        private void PreventRewriteOnOutgoingStream()
        {
            if (UrlRewriteProvider.Module != null)
                UrlRewriteProvider.Module.FURLRewriteResponse = false;
        }

This handler can be used to serve files from your VPP folders by adding the following configuration (IIS7 only folks – which if you’re not using then you really should be pushing for an upgrade!). This handler listed below is set up only to serve files of type .m4v – and remember that it does not provide any of the native EPiServer functionality for setting the staticFile cache expiration time.

<location path="Global">
     <system.webServer>
         <handlers>
              <add name="webresources" path="WebResource.axd" verb="GET" type="System.Web.Handlers.AssemblyResourceLoader"/>
              <add name="videofiles" path="*.m4v" verb="*" type="FortuneCookie.RangeRequestFileHandler.RangeRequestFileHandler, FortuneCookie.RangeRequestFileHandler"/>
              <add name="wildcard" path="*" verb="*" type="EPiServer.Web.StaticFileHandler, EPiServer"/>
         </handlers>
      </system.webServer>
      <staticFile expirationTime="-1.0:0:0"/>
 </location>

The full source code can be downloaded from the Code section at EPiServer World and a NuGet package is available that will auto-magically add in the code and configure your Global and PageFile VPP’s to serve .mp4, .m4v and .mov video files using the RangeRequestFileHandler.

About the author

Mark Everard

I've worked across the digital industry for the past ten years, helping clients and colleagues across a diverse range of sectors meet numerous digital challenges, specifically focusing on web technologies, digital marketing and content management.

I've worked on large multi-supplier projects and led and managed both in-house and geographically-disperse development teams. And I've always approached my work with a smile on my face.

Mark Everard

I've worked across the digital industry for the past ten years, helping clients and colleagues across a diverse range of sectors meet numerous digital challenges, specifically focusing on web technologies, digital marketing and content management.

I've worked on large multi-supplier projects and led and managed both in-house and geographically-disperse development teams. And I've always approached my work with a smile on my face.

Get in touch