Corporate YouTube and Video Delivery via SharePoint 2013

Want to deliver an internal/corporate “YouTube” for your organization using SharePoint?  Looking to maximize your SharePoint deployment by incorporating video/media delivery?  Worried about the storage/bandwidth implications of allowing anyone in the enterprise to contribute video/media?  Then then post if for you!  I will outline a solution that addresses many of the limitations to native media delivery in SharePoint 2013.  The solution outlined in this post is also illustrated in the following video:



Steve Fox wrote a great post a few months back on SharePoint 2013 and Windows Azure Media Services.  In it, Steve illustrated the use of Azure Media Services to deliver media within an app for SharePoint.  Steve introduced some powerful concepts that I want to take to the next level.  I envision a complete solution that allows users to contribute any media format, from any asset library in SharePoint, and send it through Azure Media Services for encoding and hosting (possibly around the globe using Azure’s Content Delivery Network).  But why Azure Media Services?  To understand why, it’s important to consider the video capabilities native to SharePoint 2013, their limitations, and past case studies on SharePoint as a media platform.

Video and SharePoint

Out of the box, SharePoint provides very basic video delivery capabilities.  Asset libraries allow users to upload videos, which are stored as SQL BLOBs in content databases (just like any other file in SharePoint).  Videos tend to be larger in size, which are both inefficient for BLOB storage and quickly grow content databases to maximum recommended capacity (200GB).  Videos are also subject to the maximum file upload size in SharePoint, which is 50MB by default with a hard limit of 2GB.  Videos that are uploaded into SharePoint can be consumed by “progressive download” in the exact same format and quality as the contributor uploaded.  This means that a 1080P .wmv uploaded to SharePoint will only be consumable as a 1080P .wmv file.  A progressive download also lacks intelligent fast-forward capabilities.  If a user only cares about the end of a long video, they have to download the entire video to get to the end.  Many enterprises with SharePoint (including Microsoft) have implemented solutions aimed addressing these challenges, including Remote Blob Storage (RBS), Blob Caching, Branch Caching, Bitrate Throttling, Encoding/Transcoding, DRM, and many more.

Academy is Microsoft’s internal social video platform built on SharePoint.  Academy sets a high standard for media delivery maturity, with customized media uploads, geographically distributed streaming, adjustable quality by device/connection, and advanced encoding workflows to accept almost any media format.  Academy is impressive, but represents years of effort, refinement, and lessons learned from digital asset and web content management.  Most organizations would find it very challenging to close the gap between media delivery native to SharePoint and what Microsoft has built internally with Academy…until now!

Academy (Microsoft’s internal social video platform built on SharePoint)

The landscape has changed significantly since Academy was first deployed at Microsoft several years ago.  Now, Windows Azure can provide the platform for media storage (Blob Storage), encoding/streaming (Media Services), and global distribution (Azure CDN) with very little CAPEX.  Additionally, the new SharePoint app model can help deliver these Azure capabilities, providing a highly customized media delivery experience to any SharePoint farm…even in SharePoint Online/Office 365.

NOTE: although advanced capabilities like encoding and remote storage aren’t native to SharePoint 2013, Microsoft did incorporate a number of media delivery patterns from Academy.  For example, Microsoft recognized that media delivery is much more than just a video…it includes social discussions, analytics, and supporting documentation (ex: slide deck, code, etc).  As such, the Document Set or “video set” is the container for videos in SharePoint 2013.  Downloads/Podcasting, thumbnail generation, and embed codes are other capabilities carried forward from Academy.


The Solution

Our app will exploit the best of both SharePoint 2013 and Windows Azure for media contribution, consumption, and management.  These capabilities will be delivered through a number of pages in our app and ribbon buttons in the host site.  I have detailed each of these solution components below.  The solution also leverages a SQL Azure database to keep track of videos sent through the app and app preferences/settings.


Upload.aspx (Empty)
Upload.aspx (Processing)


We want media contributors to have a familiar experience contributing videos through our app.  Since videos are traditionally uploaded through the ribbon on asset libraries, our solution will leverage the same ribbon to launch the upload page for our app.  The asset library ribbon button will launch the upload page inside the SharePoint dialog.  You can see this ribbon button and the upload dialog in the two screenshots above and the custom action xml to make it happen below:

Custom action for adding ribbon button to asset libraries

<?xml version=1.0encoding=utf-8?>
<Elements xmlns=>
  <CustomAction Id=a76f4430-c8b1-4317-b673-260429ca6dc1.UploadToAzureMediaSvc
                Title=Upload to Azure Media Services
        <CommandUIDefinition Location=Ribbon.Documents.New.Controls._children>
          <Button Id=Ribbon.Documents.New.UploadToAzureMediaSvcButton
                  Alt=Upload to Media Services
                  LabelText=Upload to Media Services
                  Image32by32=data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAAK…
                  Image16by16=data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK…/>
        <CommandUIHandler Command=Invoke_UploadToAzureMediaSvcButtonRequest


The upload page is where most of the magic happens, allowing users to send videos to Azure Media Services for encoding, thumbnail generation, and blob storage.  Uploading large videos, sending them to the cloud, and encoding could take significant time.  Most users won’t want to sit around waiting for this to complete.  For the proof of concept (POC), many of these processes are executed on a separate thread so the upload page respond once all the data posts.  Ideally, we would leverage an Azure Worker Process for this long running process (which is similar to a Windows Service).  However, our solution is implemented as an autohosted app for SharePoint for simplicity in deployment/tenancy.  Threading was easier to implement in that model for this POC.

Upload event for starting Azure Encoding thread

protected void btnOk_Click(object sender, EventArgs e)
    //get file bytes from the hdnImageBytes or the fileselect control
    byte[] mediaBytes = null;
    string fileName = “”;
    if (!String.IsNullOrEmpty(Page.Request[“hdnImageBytes”]))
        //get media from hdnImageBytes
        var base64MediaString = Page.Request[“hdnImageBytes”];
        fileName = Page.Request[“hdnFileName”];
        base64MediaString = base64MediaString.Substring(base64MediaString.IndexOf(‘,’) + 1);
        mediaBytes = Convert.FromBase64String(base64MediaString);
    if (mediaBytes != null && mediaBytes.Length > 0)
        //add database record for the media
        var spContext = Util.ContextUtil.Current;
        AzureMediaServicesJob amsJob = new AzureMediaServicesJob(ContextUtil.Current.ContextDetails);
        using (var clientContext = TokenHelper.GetClientContextWithContextToken(spContext.ContextDetails.HostWebUrl, spContext.ContextDetails.ContextTokenString, Request.Url.Authority))
            //get user information
            Web web = clientContext.Web;
            User currentUser = web.CurrentUser;

            using (AzureMediaModel model = new AzureMediaModel(ConnectionUtil.GetEntityConnectionString(clientContext)))
                //create the record
                Media newMedia = new Media();
                newMedia.Title = txtTitle.Text;
                newMedia.StatusId = 1;
                newMedia.AuthorLoginName = currentUser.LoginName;
                newMedia.AuthorEmail = currentUser.Email;
                amsJob.ItemID = newMedia.Id;

                //get default settings
                amsJob.ListUrl = Page.Request[“ListUrlDir”];
                amsJob.ListID = new Guid(Page.Request[“SelectedListId”]);
                amsJob.MediaBytes = mediaBytes;
                amsJob.MediaFileName = fileName;
                amsJob.IOPath = Server.MapPath(“~”);
        //set the itemID back on the form so it can check processing
        ScriptManager.RegisterStartupScript(updatePanel, updatePanel.GetType(), “checkStatus”, String.Format(“checkMediaStatus({0});”, amsJob.ItemID), true);

        //Start a new thread to perform the Media encoding and document set creation
        Thread thread = new Thread(ProcessMediaUtil.UploadMedia);
        thread.Name = String.Format(“EncodingTask{0}”, amsJob.ItemID.ToString());
        //TODO: notify user no file provided


The solution leverages a ProcessMediaUtil class to upload media to Azure Blob Storage, encode the videos to a common format, publish the encoded media, and generate thumbnails.

UploadMedia method on ProcessMediaUtil class

public static void UploadMedia(object azureMediaServicesJob)
    AzureMediaServicesJob amsJob = (AzureMediaServicesJob)azureMediaServicesJob;

    //add new listItem
    using (var clientContext = TokenHelper.GetClientContextWithContextToken(amsJob.ContextDetails.HostWebUrl, amsJob.ContextDetails.ContextTokenString, amsJob.ContextDetails.ServerUrl))
        using (AzureMediaModel model = new AzureMediaModel(Util.ConnectionUtil.GetEntityConnectionString(clientContext)))
                //get settings from database
                Settings appSettings = model.Settings.FirstOrDefault();

                // Initialize the Azure account information
                string connString = String.Format(“DefaultEndpointsProtocol={0};AccountName={1};AccountKey={2}”,
                    “https”, appSettings.StorageAccountName, appSettings.StorageAccountKey);
                CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(connString);
                mediaContext = new CloudMediaContext(appSettings.MediaAccountName, appSettings.MediaAccountKey);
                CloudBlobClient blobClient = cloudStorageAccount.CreateCloudBlobClient();

                //upload the asset to blob storage and publish
                var asset = UploadBlob(blobClient, amsJob.MediaFileName, amsJob.MediaBytes);
                string url = PublishAsset(asset, amsJob.MediaFileName);
                mediaContext = new CloudMediaContext(appSettings.MediaAccountName, appSettings.MediaAccountKey);

                //update the status
                Media mediaItem = model.Media.FirstOrDefault(i => i.Id == amsJob.ItemID);
                mediaItem.StatusId = 2;

                //Encode the asset
                IJob job = EncodeAsset(asset, appSettings, amsJob.MediaFileName);

                //refresh the context and publish the encoded asset
                mediaContext = new CloudMediaContext(appSettings.MediaAccountName, appSettings.MediaAccountKey);
                job = mediaContext.Jobs.Where(j => j.Id == job.Id).FirstOrDefault();
                var encodingTask = job.Tasks.Where(t => t.Name == “Encoding”).FirstOrDefault();
                var encodedAsset = encodingTask.OutputAssets.FirstOrDefault();
                url = String.Format(PublishAsset(encodedAsset, “SmoothStream-“ + asset.Name), amsJob.MediaFileName);

                //update the database record with the correct streaming url and the status
                mediaItem.MediaSvcUrl = url;
                mediaItem.StatusId = 3;

                //download the thumbnail bytes and publish video set
                byte[] thumbBytes = GetThumbnailBytes(blobClient, job);
                string targetDocSetUrl = PublishVideoSet(clientContext, amsJob, mediaItem, thumbBytes);

                //send email confirmation
                EmailProperties email = new EmailProperties();
                email.To = new List<String>() { mediaItem.AuthorEmail };
                email.Subject = String.Format(“Your video \”{0}\” is ready!”, mediaItem.Title);
                email.Body = String.Format(“<html><body><p>Your video \”{0}\” has finished processing and can be viewed at the following address:</p><p><a href=\”{1}\”>{1}</a></p></body></html>”, mediaItem.Title, targetDocSetUrl);
                Utility.SendEmail(clientContext, email);

                //update status…hard-code aspect ratio for now
                mediaItem.StatusId = 4;
                mediaItem.Width = 700;
                mediaItem.Height = 393;
                mediaItem.SharePointUrl = targetDocSetUrl;
            catch (Exception ex)
                //update the status
                Media mediaItem = model.Media.FirstOrDefault(i => i.Id == amsJob.ItemID);
                mediaItem.StatusId = 5;
                mediaItem.ErrorMessage = ex.Message;


Azure utility methods in ProcessMediaUtil class

private static IAsset UploadBlob(CloudBlobClient blobClient, string publishedName, byte[] fileBytes)
    var asset = mediaContext.Assets.Create(publishedName, AssetCreationOptions.None);
    var writePolicy = mediaContext.AccessPolicies.Create(“policy for copying”, TimeSpan.FromMinutes(30), AccessPermissions.Write | AccessPermissions.List);
    var destination = mediaContext.Locators.CreateSasLocator(asset, writePolicy, DateTime.UtcNow.AddMinutes(-5));
    var destContainer = blobClient.GetContainerReference(new Uri(destination.Path).Segments[1]);
    var destBlob = destContainer.GetBlockBlobReference(publishedName);
    destBlob.Properties.ContentType = “video/mp4”;
    return asset;

private static string PublishAsset(IAsset asset, string publishedName)
    var assetFile = asset.AssetFiles.Create(publishedName);
    asset = mediaContext.Assets.Where(a => a.Id == asset.Id).FirstOrDefault();
    var readPolicy = mediaContext.AccessPolicies.Create(“policy for access”, TimeSpan.FromDays(365 * 3), AccessPermissions.Read | AccessPermissions.List);
    var readLocator = mediaContext.Locators.CreateSasLocator(asset, readPolicy, DateTime.UtcNow.AddMinutes(-5));
    string[] parts = readLocator.Path.Split(‘?’);
    return parts[0] + “/{0}?” + parts[1];

private static IJob EncodeAsset(IAsset asset, Settings appSettings, string publishedName)
    var assetToEncode = mediaContext.Assets.Where(a => a.Id == asset.Id).FirstOrDefault();
    if (assetToEncode == null)
        throw new ArgumentException(“Could not find assetId: “ + asset.Id);
    IJob job = mediaContext.Jobs.Create(“Encoding “ + assetToEncode.Name + ” to “ + appSettings.EncodingOptions.DisplayName);

    //add encoding task
    IMediaProcessor latestWameMediaProcessor = (from p in mediaContext.MediaProcessors where p.Name == “Windows Azure Media Encoder” select p).ToList().OrderBy(wame => new Version(wame.Version)).LastOrDefault();
    ITask encodeTask = job.Tasks.AddNew(“Encoding”, latestWameMediaProcessor, appSettings.EncodingOptions.EncodingConfiguration, TaskOptions.None);
    encodeTask.OutputAssets.AddNew(“SmoothStream-“ + publishedName, AssetCreationOptions.None);

    //add thumbnail task
    ITask thumbTask = job.Tasks.AddNew(“Generate thumbnail”, latestWameMediaProcessor, “Thumbnails”, TaskOptions.None);
    thumbTask.OutputAssets.AddNew(“Thumb-“ + assetToEncode.Name + “.jpg”, AssetCreationOptions.None);

    //Submit the job and wait for it to complete
    job.StateChanged += new EventHandler<JobStateChangedEventArgs>((sender, jsc) =>
        //do nothing…could change status here, but we are waiting
    return job;

private static byte[] GetThumbnailBytes(CloudBlobClient blobClient, IJob job)
    var thumbTask = job.Tasks.Where(t => t.Name == “Generate thumbnail”).FirstOrDefault();
    var thumbAsset = mediaContext.Assets.Where(a => a.Id == thumbTask.OutputAssets[0].Id).FirstOrDefault();
    var thumbFile = thumbAsset.AssetFiles.FirstOrDefault();
    var writePolicy = mediaContext.AccessPolicies.Create(“policy for copying”, TimeSpan.FromMinutes(30), AccessPermissions.Write | AccessPermissions.List);
    var destination = mediaContext.Locators.CreateSasLocator(thumbAsset, writePolicy, DateTime.UtcNow.AddMinutes(-5));
    var destContainer = blobClient.GetContainerReference(new Uri(destination.Path).Segments[1]);
    var destBlob = destContainer.GetBlockBlobReference(thumbFile.Name);
    return destBlob.DownloadByteArray();


The ProcessMediaUtil will also create the “video set” in SharePoint once all the Azure jobs are complete.  The new video set will reference the Azure-hosted video via an embed code (which is stored in the hidden VideoSetEmbedCode column of the video set).

PublishVideoSet method on ProcessMediaUtil class

private static string PublishVideoSet(ClientContext clientContext, AzureMediaServicesJob amsJob, Media mediaItem, byte[] thumbBytes)
    //get the media list
    List mediaList = clientContext.Web.Lists.GetById(amsJob.ListID);
    clientContext.Load(mediaList, i => i.Fields);
    clientContext.Load(mediaList, i => i.ParentWebUrl);

    //create the Video document set
    ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation();
    itemCreateInfo.UnderlyingObjectType = FileSystemObjectType.Folder;
    itemCreateInfo.LeafName = mediaItem.Title;
    Microsoft.SharePoint.Client.ListItem newMediaItem = mediaList.AddItem(itemCreateInfo);
    newMediaItem[“Title”] = mediaItem.Title;
    newMediaItem[“ContentTypeId”] = “0x0120D520A80800E9538ABD5B77E14096B2460EC920FD5E”;

    //hard-code the video aspect ratio for now
    newMediaItem[“VideoSetEmbedCode”] = String.Format(“<iframe width=’700′ height=’400′ src='{0}/Pages/Player.aspx?Item={1}&SPHostUrl={2}’ frameborder=’0′ style=’overflow: hidden’ allowfullscreen></iframe>”,
        String.Format(“https://{0}”, amsJob.ContextDetails.ServerUrl), amsJob.ItemID.ToString(), amsJob.ContextDetails.HostWebUrl);

    //add subfolders folders for the app
    string targetDocSetUrl = amsJob.ListUrl + “/” + mediaItem.Title;
    Folder folder = clientContext.Web.GetFolderByServerRelativeUrl(targetDocSetUrl);
    clientContext.Load(folder, f => f.UniqueContentTypeOrder);

    //add the required subfolders for a Video
    var f1 = folder.Folders.Add(targetDocSetUrl + “/Additional Content”);
    var f2 = folder.Folders.Add(targetDocSetUrl + “/Preview Images”);

    //upload thumbnail
    FileCreationInformation fileInfo = new FileCreationInformation();
    fileInfo.Content = thumbBytes;
    fileInfo.Url = amsJob.ListUrl + “/” + mediaItem.Title + “/Preview Images/” + amsJob.MediaFileName + “_thumb.jpg”;
    Microsoft.SharePoint.Client.File previewImg = f2.Files.Add(fileInfo);
    clientContext.Load(previewImg, i => i.ServerRelativeUrl);
    newMediaItem[“AlternateThumbnailUrl”] = new Microsoft.SharePoint.Client.FieldUrlValue() { Url = previewImg.ServerRelativeUrl };

    return amsJob.ContextDetails.HostWebUrl + “/” + targetDocSetUrl;


Our solution will provide the user with status updates as the Azure job(s) process.  To do this, our app will host a REST/JSON status service.  This service is secured by the same context token our app leverages and can be called periodically from client-side script on our page.

StatusService for checking encoding status via REST/JSON

namespace AzureMediaManagerWeb.Services
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class StatusService
        [WebInvoke(Method = “POST”, BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
        public int GetStatus(int itemID, string contextToken, string hostWebUrl, string authority)
            int statusID = 0;
            using (var clientContext = TokenHelper.GetClientContextWithContextToken(hostWebUrl, contextToken, authority))
                using (AzureMediaModel model = new AzureMediaModel(Util.ConnectionUtil.GetEntityConnectionString(clientContext)))
                    statusID = model.Media.FirstOrDefault(i => i.Id == itemID).MediaStatus.Id;

            return statusID;


jQuery script for calling StatusService

function checkMediaStatus(id) {
    var json = JSON.parse(getCookie(‘SPContext’));
    var data = {
        itemID: id,
        contextToken: json.ContextTokenString,
        hostWebUrl: json.HostWebUrl,
        authority: json.ServerUrl
        cache: false,
        url: ‘../Services/StatusService.svc/GetStatus’,
        data: JSON.stringify(data),
        dataType: ‘json’,
        type: ‘POST’,
        contentType: ‘application/json; charset=utf-8’,
        success: function (result) {
            switch (result.d) {
                case 4:
                    $(‘#imgStatus4’).css(‘display’, ‘block’);
                    $(‘#divStatus4’).find(‘.divWaitingDots’).css(‘display’, ‘none’);
                    $(‘#btnOk’).click(function () {
                        return false;
                case 3:
                    $(‘#imgStatus3’).css(‘display’, ‘block’);
                    $(‘#divStatus3’).find(‘.divWaitingDots’).css(‘display’, ‘none’);
                case 2:
                    $(‘#imgStatus2’).css(‘display’, ‘block’);
                    $(‘#divStatus2’).find(‘.divWaitingDots’).css(‘display’, ‘none’);
                case 1:
                    $(‘#imgStatus1’).css(‘display’, ‘block’);
                    $(‘#divStatus5’).css(‘display’, ‘block’);
                    $(‘#divStatus1’).find(‘.divWaitingDots’).css(‘display’, ‘none’);

            //recursively call self
            if (result.d != 4)
                setTimeout(checkMediaStatus, 10000, id);
        error: function (result) {
            var status = $(‘#hdnStatus’).val();
            for (var i = 4; i > parseInt(status) ; i–) {
                $(‘#imgStatus’ + i).attr(‘src’, ‘../images/fail.png’);
                $(‘#imgStatus’ + i).css(‘display’, ‘block’);
                $(‘#divStatus’ + i).find(‘.divWaitingDots’).css(‘display’, ‘none’);



Player.aspx embedded in SharePoint video set page

In SharePoint 2013, videos can be contributed as a file, URL, or embed code (IFRAME to video).  You might be curious why the solution needs a player page instead of just providing SharePoint with a URL to the video hosted in Azure Media Services.  Unfortunately, SharePoint will not accept a parameterized video URL (ex: MyVideo.mp4?contextToken=xzy123) such as the one Azure Media Services will provide.  Even if it did, our solution might want to leverage a different media player that supports smooth streaming or advanced video analytics (ex: how long the user watched the video).  Instead, we will leverage a video embed code, which is ultimately an IFRAME pointing to a page hosting the video.  This is same way we would reference a YouTube video in a SharePoint asset library.  Azure Media Services does not provide a player page, so our app will deliver it.  In the screenshot above, you see the typical video set page displayed in SharePoint…the Player.aspx app page is being displayed in an IFRAME to display the video (the IFRAME is the same size as the video).  To make the player page dynamic, our embed codes will always pass a video id to the player page via URL parameter.  Our player page will use this video id to lookup the streaming URL stored in the app’s SQL Azure database.  We will also pass the host web URL in case the player page needs to get a context token from SharePoint.

Example of YouTube embed code markup

<iframe width=560height=315src=></iframe>


Example of embed code markup for the solution

<iframe width=700height=400src= hiddenallowfullscreen></iframe>


PageLoad of the Player.aspx to output a video element

public partial class Player : System.Web.UI.Page
    protected void Page_Load(object sender, EventArgs e)
        if (!String.IsNullOrEmpty(Page.Request[“Item”]))
            var spContext = Util.ContextUtil.Current;
            using (var clientContext = TokenHelper.GetClientContextWithContextToken(spContext.ContextDetails.HostWebUrl, spContext.ContextDetails.ContextTokenString, Request.Url.Authority))
                using (AzureMediaModel model = new AzureMediaModel(ConnectionUtil.GetEntityConnectionString(clientContext)))
                    //get item with the specified ID
                    int itemID = Convert.ToInt32(Page.Request[“Item”]);
                    Media mediaItem = model.Media.SingleOrDefault(i => i.Id == itemID);
                    if (mediaItem != null)
                        string mediaMarkup = @”<video id=’myVideo’ class=’pf-video’ height='{0}’ width='{1}’ controls=’controls’><source src='{2}’ type=’video/mp4;codecs=/””avc1.42E01E, mp4a.40.2/””‘ /></video>”;
                        divVideo.Controls.Add(new LiteralControl(String.Format(mediaMarkup, mediaItem.Height.ToString(), mediaItem.Width.ToString(), mediaItem.MediaSvcUrl)));


The result is a video set in SharePoint that looks exactly like any other.  With so many moving parts, I’ve provided a diagram to clear any confusion:

Diagram of the player page and embed code logic


Default.aspx page to display media processed by app

With videos being stored in Azure and the video set living in SharePoint, there exists the potential for orphaned items (“video sets” without corresponding videos and videos without corresponding “video sets”).  The default.aspx page allows users to identify potential orphan items or errors that may have occurred during processing.  It is the default page when a user enters the full-screen view of the app.  Nothing fancy here…just a GridView that displays information from our app database.


Settings.aspx for configuring app settings

In order for our app to integrate with Azure, it will need an account name and access key for Azure Blob Storage and Azure Media Services.  The settings page will allow an app administrator to configure these account settings, which will be stored in the app’s database.  The settings page will also allow an app administrator to specify additional app administrators and select the output format(s) for encoding.  Nothing interesting here, other than the multi-user people picker control.

Multi-user people picker for setting app admins

The settings page is security trimmed to app administrators.  Because we need at least one app administrator, the app will make use of the app installed remote event to capture the user that installs the app.

Handle App Installed in app project properties

App installed remote event to capture installer as adminisrator

public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
    SPRemoteEventResult result = new SPRemoteEventResult();
    using (ClientContext clientContext = TokenHelper.CreateAppEventClientContext(properties, false))
        if (clientContext != null)
            //get the current user and seed the database with his information
            PeopleManager pm = new PeopleManager(clientContext);
            var props = pm.GetMyProperties();

            using (AzureMediaModel model = new AzureMediaModel(ConnectionUtil.GetEntityConnectionString(clientContext)))
                Administrators primaryAdmin = new AzureMediaManagerWeb.Administrators();
                primaryAdmin.LoginName = props.AccountName;
                primaryAdmin.DisplayName = props.DisplayName;

    return result;


Final Thoughts

This solution might seem like a lot of work.  However, it addresses most of the serious limitations in native SharePoint video delivery that will only become more obvious as video contribution increases in a farm.  I have provided the code for the solution in the link below.  This is NOT a production ready solution.  However, it could be the start for taking media to the next level in your organization!

App for SharePoint Solution:

NOTE: if you download the solution and try to debug, Internet Explorer will not let you use the drag-drop upload in the debugging browser. This is because the debug browser is running elevated (due to running Visual Studio as an administrator) and Windows Explorer NOT running elevated. IE sees this as a security threat. The solution is to open a new browser window while debugging for uploads.
Comments (26)

  1. Thanks for the helpful post.

    But, in my case the custom video player page is not opening on click of video. Please give some guidelines/steps for the same.

  2. Hi,

    I am also facing the same issue. We have created a custom player page and given the source of that page in the iframe. The video gets uploaded in the library but the player page does not open.

    We also tried using the same iframe code by using embed code option given OOTB but it gave the error –

    "The embed code is invalid because the source of the embed content is not allowed.".

    Kindly help us with the same.

  3. Hey Vipul Jain 03 and SupriyaK…the solution outlined in this post is meant as a reference solution but is far from production ready.  First, the encoding should probably run on a worker role instead of the web request thread for more reliable uploading.  That said…do you see the media in the Azure Portal?  If so, do you see the status of the encoding job?  Is the VideoSetEmbedCode field on the video set getting set?

  4. umang says:

    this is so amazing, very common future, i hope microsoft incorporate this idea in sharepoint product itself.

  5. Lee says:

    Not sure if anyones checking this now but I've hit a problem with the sharepoint item generation code. The code is fine and it creates the video item, however when the item appears in search results the folder structure is shown (like in a documentset) instead of showing the player page. Any help or ideas would be great!!!

  6. Video backend says:

    Perhaps the most professional solution is the using of some video platform as backend, and embed the videos just like youtube or vimeo. This way enable statistics of the video usage.

    There are OpenSource solutions like Kaltura CE that offers a very professional features.

  7. Fred says:

    Hy, Thanks for the post.

    Same problem as Lee, when opening the item I see folder structure and not  player page.

    Did someone please find a solution ?

  8. Tommy says:

    Hi! I need to get this running on an on-premise SharePoint 2013. What changes do I need to do in order to achive that?

  9. Tommy – the app was built as a autohosted app, but could easily be re-written as a provider-hosted app.  Main conversion efforts will be around database management (this was written to leverage SQL Azure that is auto-deployed with the app) and in getting context with the TokenHelper.  For the later, you will likely convert the app to high-trust using S2S as is outlined in this post:…/fp179901.aspx

  10. Tommy says:

    Thank you for the reply Richard! I will first try to get it working on the O365 and then try to convert it to a provider-hosted app. I get the same error as Fred and others have mentioned. I just get 2 folders when clicking the thumbnail. I see one possible error. When I inspect the video in Azure Media service the url is a bit different than the one that gets stored in the database. In the databse the url is stored with the name I specified when uploading the file (i.e. clip.wmv), but in AMS it has the generated filename (i.e. clip_H264_4500kbps_AAC_und_ch2_128kbps.mp4)

  11. Stefan Bauer says:

    Nice tutorial actually I haven't looked that much into azure media service. Made some things clearer now.

    Under SharePoint 2010 I created a solution that lets you display YouTube and Vimeo Videos out of an asset library. I published this solution on Codeplex.

  12. Johan Skårman says:

    For everyone having trouble with folders displaying instead of the video, add the following when metadata is set:

    newMediaItem["HTML_x0020_File_x0020_Type"] = "Sharepoint.VideoSet";

  13. sharad patil says:

    Hello Richard,

    I am facing issue is, able to encode movie file but not able to encode images, during image file encoding geting below run time error .

    Status: Error Occurred

    Comments: Main Exception :Exception type: System.NullReferenceException Message : Object reference not set to an instance of an object. Stacktrace: at AzureMediaManagerWeb.Util.ProcessMediaUtil.GetThumbnailBytes(CloudBlobClient blobClient, IJob job) in c:UserssnpadminDownloadsAzureMediaManagerSevennewSevenAzureMediaManagerAzureMediaManagerWebUtilProcessMediaUtil.cs:line 87 at AzureMediaManagerWeb.Util.ProcessMediaUtil.UploadMedia(Object azureMediaServicesJob) in c:UserssnpadminDownloadsAzureMediaManagerSevennewSevenAzureMediaManagerAzureMediaManagerWebUtilProcessMediaUtil.cs:line 197

    Please help how can i accomplish,

  14. Mike says:

    Hi Richard,

    I liked your post and the details you have provided with code files. I am requesting you to please let me know the software requirements for running this solution. What version of Visual Studio, any other add-ons etc.

  15. Vishal N says:

    I am facing issue while encoding (window media video)file(video bytes).

    1) In my case ,I am converting  (window media video) file in video bytes and then uploading in azure while encoding it returns error "Windows Azure Media Encoder detected media errors on source file name .extension:File type isn't supported" and "Error Executing Task Unsupported Format".But in case of moving picture expert group – layer 4 file it works fine .

    Please help

  16. Jazz P says:

    Hi Richard, Vishal

    I am getting an error "Object reference not set to an instance of an object" While uploading image files.


  17. Vishal N says:

    Hi Jazz,

    Can you please provide the code?

  18. Jazz P says:

    Hello Vishal,

    Could you give me your personal id.

  19. Sachin says:

    Hi Richard,

    Well done you did a great job thanks.

    I am facing issues encoding did't work for .wmv files did you face this issue or is there any fixes for this?

  20. Mahesh says:

    I have a one issue with this app for the content type. When I uploaded a image file or audio file the content type is set to video. So please give me a content type ID for Images and Audio which is worked in your solution. I trued lots of combination for a content type Id but that not working.

  21. Mahesh Kulkarni says:

    Hello Richard,

    Your blog is great useful to me Thanks for that.

    I have one app for uploading images,audio and video in that all works fine but i am facing one issue regarding mp3 files not playing on Internet Explorer 11 on SharePoint Site It shows Encrypted code or unidentified Code.On same browser .wav extensions files are played but not mp3 files.

    I searched lot of related this and changes  setting in Internet Explorer 11 but its not working for me.

    Can you help me regarding this how to play mp3 files in Internet Explore 11.

    Thanks in Advance.

  22. sharepoint 2013 videos says:

    Thanks so much! Your instructions are very clear and helpful. I've been wondering how to do this for months and thrilled to find out how finally.

    <a href="…/">sharepoint 2013 videos</a>

  23. syed says:

    Jesus!! This is awesome. Thanks for the solution 🙂

  24. Iman says:

    Great info!

    Just a question, what if instead of using Azure we need to use an on-premise technology for security reasons. What would be the best choice for encoding to work with SP2013?

  25. Craig W. says:

    Very informative post. I have a couple of questions.

    What information does AZURE send back to SharePoint that will assign that particular content to a webpage (unique video ID etc.)?

    Does Azure's architecture have a multiplayer or tabbed player solution as well (like a filmstrip at the bottom or the side with other video content) as it would be good for a collection of videos?