So you want to build your own YouTube?

Here is a reference architecture for building out a user-contributed video gallery using Windows Azure Media Services.

Not a lot of time to blog this week, perhaps I’ll have time in the future to really work this scenario over with full examples including the web and worker roles, azure tables and such.

So there’s the presentation and below is some code to provide that basic interaction with Media Services.

Note, when using a browser client to PUT directly to storage, you will need a client framework such as Flash or Silverlight and a crossdomain.xml / clientaccesspolicy.xml in the $root folder of your storage account to avoid CORS issues with the simpler XMLHttpRequest.send().

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Configuration;

/// Microsoft.WindowsAzure.MediaServices.Client
///
/// Reference:
///  C:\Program Files (x86)\Microsoft WCF Data Services\5.0\bin\.NETFramework\Microsoft.Data.Edm.dll
///  C:\Program Files (x86)\Microsoft WCF Data Services\5.0\bin\.NETFramework\Microsoft.Data.OData.dll
///  C:\Program Files (x86)\Microsoft WCF Data Services\5.0\bin\.NETFramework\Microsoft.Data.Services.dll
///  C:\Program Files (x86)\Microsoft WCF Data Services\5.0\bin\.NETFramework\Microsoft.Data.Services.Client.dll
///  C:\Program Files (x86)\Microsoft WCF Data Services\5.0\bin\.NETFramework\System.Spatial.dll
/// Download from: http://www.microsoft.com/en-us/download/details.aspx?id=29306
///
///  C:\Program Files (x86)\Microsoft SDKs\Windows Azure Media Services\Services\v1.0\Microsoft.WindowsAzure.MediaServices.Client.dll
/// Download from: http://www.microsoft.com/en-us/download/details.aspx?id=30153
///
///  C:\Program Files\Windows Azure SDK\v1.6\bin\Microsoft.WindowsAzure.StorageClient.dll
/// Download from WebPI: http://go.microsoft.com/fwlink/?linkid=255386
///                      On the Products tab select All, then find and install the
///                      Windows Azure SDK 1.6 for Visual Studio 2010 (November 2011).
///
/// General Support:
///  Windows Azure Media Services Forums: http://social.msdn.microsoft.com/Forums/da-dk/MediaServices/threads
///
using Microsoft.WindowsAzure.MediaServices.Client;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure;

namespace WindowsAzureMediaServices.Helper
{
    public class MediaHelper
    {
        private static string thumbnailToken = "_thumbnail";
        private static string smoothToken = "_smooth";
        private static string windowsAzureMediaEncoderName = "Windows Azure Media Encoder";

        ///<summary>
        /// Call this once per thread to get the context for any further calls. Keep the object and use it for all calls from that thread.
        /// </summary>
        /// context object to be passed to other helper functions for this thread.
        public static CloudMediaContext GetContext(HelperConfigInfo info)
        {
            if (!info.IsInitialized)
                throw new ArgumentNullException("info");

            CloudMediaContext context = null;

            if (!String.IsNullOrEmpty(info.scope))
            {
                context = new CloudMediaContext(new Uri(info.apiServer), info.wamsAccountName, info.wamsAccountKey, info.scope, info.acsBaseAddress);
            }
            else if (!String.IsNullOrEmpty(info.apiServer))
            {
                context = new CloudMediaContext(new Uri(info.apiServer), info.wamsAccountName, info.wamsAccountKey);
            }
            else
            {
                context = new CloudMediaContext(info.wamsAccountName, info.wamsAccountKey);
            }
            return context;
        }

        ///<summary>
        /// Gets the base uri with SAS token for uploads
        /// </summary>
        ///
        ///
        /// Append the filename to the path using a UriBuilder  http://server/path?SasToken
        public static Uri GetUploadUri(HelperConfigInfo info, Guid idForThisMedia)
        {
            if (info == null)
                throw new ArgumentNullException("info");
            if(!info.IsInitialized)
                throw new ArgumentException("make sure info is initialized");
            if (idForThisMedia == Guid.Empty)
                throw new ArgumentException("Provide a non-empty Guid for idForThisMedia");

            CloudMediaContext context = GetContext(info);
            if (context == null)
                throw new ApplicationException("failed to get context");

            // Check if this asset exists:
            IAsset duplicate = GetAssetByName(context, idForThisMedia.ToString());
            if (duplicate != null)
            {
                throw new ArgumentException("idForThisMedia is being re-used.");
            }

            Uri uploadUri = null;

            //
            // Create an empty asset
            //
            IAsset inputAsset = context.Assets.CreateEmptyAsset(idForThisMedia.ToString(), AssetCreationOptions.None);

            //
            // Get a SAS url:
            //
            IAccessPolicy writePolicy = context.AccessPolicies.Create("Policy For Copying", TimeSpan.FromDays(info.accessPolicyDurationInDays), AccessPermissions.Write | AccessPermissions.List);
            ILocator destinationLocator = context.Locators.CreateSasLocator(inputAsset, writePolicy, DateTime.UtcNow.AddMinutes(info.locatorStartTimeInMinutes));

            uploadUri = new Uri(destinationLocator.Path);

            return uploadUri;
        }

        ///<summary>
        /// Disables all SAS locators on the asset
        /// </summary>
        ///
        ///
        ///
        public static void DisableUploadUri(HelperConfigInfo info, Guid idForThisMedia)
        {
            if (info == null)
                throw new ArgumentNullException("info");
            if (!info.IsInitialized)
                throw new ArgumentException("make sure info is initialized");
            if (idForThisMedia == Guid.Empty)
                throw new ArgumentException("Provide a non-empty Guid for idForThisMedia");

            CloudMediaContext context = GetContext(info);
            if (context == null)
                throw new ApplicationException("failed to get context");

            // Check if this asset exists:
            IAsset asset = GetAssetByName(context, idForThisMedia.ToString());
            if (asset != null)
            {
                foreach (var locator in asset.Locators)
                {
                    if (locator.Type == LocatorType.Sas)
                    {
                        context.Locators.Revoke(locator);
                    }
                }
            }
        }

        public static void RunJob(HelperConfigInfo info, Guid idForThisMedia)
        {
            if (info == null)
                throw new ArgumentNullException("info");
            if (!info.IsInitialized)
                throw new ArgumentException("make sure info is initialized");
            if (idForThisMedia == Guid.Empty)
                throw new ArgumentException("Provide a non-empty Guid for idForThisMedia");

            CloudMediaContext context = GetContext(info);
            if (context == null)
                throw new ApplicationException("failed to get context");

            //
            // Get Input Asset
            //
            IAsset inputAsset = GetAssetByName(context, idForThisMedia.ToString());
            if (inputAsset == null)
                throw new ApplicationException("failed to get asset");

            //
            // Publish it
            //
            //This is required to search for and add files that were added by the StorageClient copy/upload calls.
            inputAsset.Publish(); //May throw if there are no files.

            //
            // Check file-count, should be 1
            //
            inputAsset = GetAssetByName(context, idForThisMedia.ToString());
            if (inputAsset.Files.Count() != 1)
                throw new ApplicationException("Asset should have one file, idForThisMedia:" + idForThisMedia); //This will throw at the thread callback level, catch at app level.

            //
            // Create a job, name it idForThisMedia
            //
            IJob job = context.Jobs.Create(idForThisMedia.ToString());

            //
            // Get the media processor
            //
            IMediaProcessor windowsAzureMediaEncoder = (from a in context.MediaProcessors
                                                        where a.Name == windowsAzureMediaEncoderName
                                                        select a).First();

            //
            // Add the Encode task.
            //
            ITask encodeTask = job.Tasks.AddNew(idForThisMedia.ToString() + smoothToken,
                                                    windowsAzureMediaEncoder,
                                                    info.windowsAzureMediaEncoder_EncodeConfig,
                                                    TaskCreationOptions.None);
            encodeTask.InputMediaAssets.Add(inputAsset);
            encodeTask.OutputMediaAssets.AddNew(idForThisMedia.ToString() + smoothToken, true, AssetCreationOptions.None);

            //
            // Add the Thumbnail task.
            //
            ITask thumbnailTask = job.Tasks.AddNew(idForThisMedia.ToString() + thumbnailToken,
                                                    windowsAzureMediaEncoder,
                                                    info.windowsAzureMediaEncoder_ThumbnailConfig,
                                                    TaskCreationOptions.None);
            thumbnailTask.InputMediaAssets.Add(inputAsset);
            thumbnailTask.OutputMediaAssets.AddNew(idForThisMedia.ToString() + thumbnailToken, true, AssetCreationOptions.None);

            //
            // Add this job to the job queue
            //
            job.Submit();

            //
            // The ouput assets are not actually created util the job is submitted.
            // At that point, they can be refreshed and updated.
            //
            IAsset smoothAsset = job.OutputMediaAssets[0];
            smoothAsset = RefreshAsset(context, smoothAsset.Id);
            smoothAsset.Name = idForThisMedia.ToString() + smoothToken;
            context.Assets.Update(smoothAsset);
            IAsset thumbnailAsset = job.OutputMediaAssets[1];
            thumbnailAsset = RefreshAsset(context, thumbnailAsset.Id);
            thumbnailAsset.Name = idForThisMedia.ToString() + thumbnailToken;
            context.Assets.Update(thumbnailAsset);
        }

        ///<summary>
        /// Creates or returns an existing SAS Url for the thumbnail.jpg. Usable immediately.
        /// </summary>
        ///
        ///
        /// Usable URL or null
        public static Uri GetThumbnailUri(HelperConfigInfo info, Guid idForThisMedia)
        {
            if (info == null)
                throw new ArgumentNullException("info");
            if (!info.IsInitialized)
                throw new ArgumentException("make sure info is initialized");
            if (idForThisMedia == Guid.Empty)
                throw new ArgumentException("Provide a non-empty Guid for idForThisMedia");

            CloudMediaContext context = GetContext(info);
            if (context == null)
                throw new ApplicationException("failed to get context");

            Uri thumbnailUri = null;

            //
            // Get the asset
            //
            IAsset asset = GetAssetByName(context, idForThisMedia + thumbnailToken);
            if (asset == null)
                throw new ApplicationException("Could not find asset using idForThisMedia: " + idForThisMedia);

            //
            // Find the .jpg in the asset
            //
            IFileInfo jpgFile = (from f in asset.Files
                                 where f.Name.EndsWith("2.jpg")
                                 select f).FirstOrDefault();
            if (jpgFile == null)
                throw new ApplicationException("Could not find a .jpg file in the asset.id: " + asset.Id);

            //
            // Look for an existing locator
            //
            ILocator locator = null;
            var locators = from rows in asset.Locators where rows.Type == LocatorType.Sas orderby rows.ExpirationDateTime select rows;
            if (locators != null && locators.Count() > 0)
            {
                //Get the one that expires last:
                locator = locators.LastOrDefault();
            }

            //
            // Check the existing locator
            //
            //TODO: Test this logic
            if (locator != null)
            {
                //Check if it will expire within _locatorRecreationThresholdDays
                if (locator.ExpirationDateTime = maxLocators)
                    {
                        var earliest = locators.FirstOrDefault();
                        if (earliest != null)
                        {
                            context.Locators.Revoke(earliest);
                        }
                    }
                }
            }

            //
            // Create a locator if required.
            //
            if (locator == null)
            {
                var accessPolicyTimeout = TimeSpan.FromDays(info.accessPolicyDurationInDays);
                IAccessPolicy readPolicy = context.AccessPolicies.Create("Read Policy " + idForThisMedia + thumbnailToken, accessPolicyTimeout, AccessPermissions.Read);
                var startTime = DateTime.UtcNow.AddMinutes(info.locatorStartTimeInMinutes);
                locator = context.Locators.CreateSasLocator(asset, readPolicy, startTime);
            }

            //
            // Build Uri
            //
            if (locator != null)
            {
                UriBuilder ub = new UriBuilder(locator.Path);
                ub.Path += "/" + jpgFile.Name;
                thumbnailUri = ub.Uri;
            }

            return thumbnailUri;
        }

        ///<summary>
        /// Creates or returns an existing Origin Url for the smooth asset. Usable after 30 seconds.
        /// </summary>
        ///
        ///
        /// Usable URL or null
        public static Uri GetSmoothStreamingUri(HelperConfigInfo info, Guid idForThisMedia)
        {

            if (info == null)
                throw new ArgumentNullException("info");
            if (!info.IsInitialized)
                throw new ArgumentException("make sure info is initialized");
            if (idForThisMedia == Guid.Empty)
                throw new ArgumentException("Provide a non-empty Guid for idForThisMedia");

            CloudMediaContext context = GetContext(info);
            if (context == null)
                throw new ApplicationException("failed to get context");

            Uri smoothUri = null;

            //
            // Get the asset
            //
            IAsset asset = GetAssetByName(context, idForThisMedia + smoothToken);
            if (asset == null)
                throw new ApplicationException("Could not find asset using idForThisMedia: " + idForThisMedia);

            //
            // Find the .ism in the asset
            //
            IFileInfo ismFile = (from f in asset.Files
                                 where f.Name.EndsWith(".ism")
                                 select f).FirstOrDefault();
            if (ismFile == null)
                throw new ApplicationException("Could not find a .ism file in the asset.id: " + asset.Id);

            //
            // Look for an existing locator
            //
            ILocator locator = null;
            var locators = from rows in asset.Locators where rows.Type == LocatorType.Origin orderby rows.ExpirationDateTime select rows;
            if (locators != null && locators.Count() > 0)
            {
                //Get the one that expires last:
                locator = locators.LastOrDefault();
            }

            //
            // Check the existing locator
            //
            //TODO: Test this logic
            if (locator != null)
            {
                //Check if it will expire within _locatorRecreationThresholdDays
                if (locator.ExpirationDateTime = maxLocators)
                    {
                        var earliest = locators.FirstOrDefault();
                        if (earliest != null)
                        {
                            context.Locators.Revoke(earliest);
                        }
                    }
                }
            }

            //
            // Create a locator if required.
            //
            if (locator == null)
            {
                var accessPolicyTimeout = TimeSpan.FromDays(info.accessPolicyDurationInDays);
                IAccessPolicy readPolicy = context.AccessPolicies.Create("Read Policy " + idForThisMedia + smoothToken, accessPolicyTimeout, AccessPermissions.Read);
                var startTime = DateTime.UtcNow.AddMinutes(info.locatorStartTimeInMinutes);
                locator = context.Locators.CreateWindowsAzureCdnLocator(asset, readPolicy, startTime);
            }

            //
            // Build Uri
            //
            if (locator != null)
            {
                UriBuilder ub = new UriBuilder(locator.Path);
                ub.Path +=  ismFile.Name + "/manifest";
                smoothUri = ub.Uri;
            }

            return smoothUri;
        }

        ///<summary>
        /// Query for a fresh reference to a job during threaded operations
        /// </summary>
        ///
        ///
        /// The job or null
        public static IJob GetJobByName(CloudMediaContext context, string jobName)
        {
            // Use a Linq select query to get an updated reference by Id.
            IJob theJob = (from j in context.Jobs
                           where j.Name == jobName
                           select j).FirstOrDefault();
            return theJob;
        }

        ///<summary>
        /// Deletes the associated assets.
        /// </summary>
        ///
        ///
        /// "Deleted" or "Input=DeleteResult, Smooth=DeleteResult, Thumbnail=DeleteResult"
        public static string DeleteAllRelatedAssets(CloudMediaContext context, Guid idForThisMedia)
        {
            string result = string.Empty;

            //
            // Validate input params:
            //
            if (context == null)
                throw new ArgumentNullException("context");
            if (idForThisMedia == Guid.Empty)
                throw new ArgumentException("Provide a non-empty Guid for idForThisMedia");

            String inputResult = DeleteAssetByName(context, idForThisMedia.ToString());
            String smoothResult = DeleteAssetByName(context, idForThisMedia + smoothToken);
            String thumbnailResult = DeleteAssetByName(context, idForThisMedia + thumbnailToken);

            if (inputResult == "Deleted" &&
                smoothResult == "Deleted" &&
                thumbnailResult == "Deleted")
            {
                //Worked
                result = "Deleted";
            }
            else
            {
                //Something failed, give full results:
                result = "Input=" + inputResult + ", Smooth=" + smoothResult + ", Thumbnail=" + thumbnailResult;
            }

            return result;
        }

        #region Internal Helper functions

        ///<summary>
        /// Deletes the asset and the associated locators.
        /// </summary>
        ///
        ///
        /// Deleted or error.
        internal static string DeleteAssetByName(CloudMediaContext context, string name)
        {
            IAsset asset = GetAssetByName(context, name);

            if (asset == null)
                return "NotFound";

            try
            {
                foreach (ILocator locator in asset.Locators)
                {
                    context.Locators.Revoke(locator);
                }
                int numContentKeys = asset.ContentKeys.Count();
                for (int i = 0; i < numContentKeys; i++)
                {
                    asset.ContentKeys.RemoveAt(i);
                }
                context.Assets.Delete(asset);
            }
            catch (Exception e)
            {
                return "Failed: " + e.Message;
            }

            return "Deleted";
        }

        ///<summary>
        /// Query for a fresh reference to an asset during threaded operations.
        /// </summary>
        ///
        ///
        /// The asset or null.
        internal static IAsset GetAssetByName(CloudMediaContext context, string name)
        {
            // Use a Linq select query to get an updated reference by Id.
            IAsset theAsset = (from a in context.Assets
                               where a.Name == name
                               select a).FirstOrDefault();
            return theAsset;
        }

        ///<summary>
        /// Query for a fresh reference to an asset during threaded operations.
        /// </summary>
        ///
        ///
        /// The asset or null.
        internal static IAsset RefreshAsset(CloudMediaContext context, string assetId)
        {
            // Use a Linq select query to get an updated reference by Id.
            IAsset theAsset = (from a in context.Assets
                              where a.Id == assetId
                              select a).FirstOrDefault();
            return theAsset;
        }

        #endregion

    }
}

Here is a bit of code which uses the above in a command line app.
The command line app consolidates the client, upload web-role, the RunJob worker and the Publishing worker.
There are no cross domain hurdles when using a full .Net client for the PUT to storage — as noted above, you need another strategy in-browser.

Dictionary<Guid, string> files = new Dictionary<Guid, string>();
files.Add(Guid.NewGuid(), @"D:\MS\CONTENT\1.MOV");  
files.Add(Guid.NewGuid(), @"D:\MS\CONTENT\2.MOV");
files.Add(Guid.NewGuid(), @"D:\MS\CONTENT\3.MOV");

foreach (var file in files)
{
    Console.WriteLine(file.ToString()); //Track these guids in your content management system.
}

System.Threading.Tasks.Parallel.ForEach(files, file =>
{
    try
    {

        //Get the upload Url
        Uri uploadUrl = MediaHelper.GetUploadUri(info, file.Key);

        //Use it.  In this test, we just upload a file using a PUT.
        //But you could pass it down to an app so that it can handle the upload.
        if (!DoSomethingWithUploadUrl(info, uploadUrl, file.Value))
            return;

        //Disable the upload Url:
        MediaHelper.DisableUploadUri(info, file.Key);

        //Now we verify the asset and kick off the encode:
        MediaHelper.RunJob(info, file.Key);

        //Wait for the job:
        bool success = false;
        {
            CloudMediaContext threadContext = MediaHelper.GetContext(info);
            success = CheckJobProgress(threadContext, file.Key.ToString());
        }

        if (success)
        {
            Uri jpgUrl = MediaHelper.GetThumbnailUri(info, file.Key);
            Console.WriteLine("Jpg: " + jpgUrl.ToString());
            DoSomethingWithJpgUrl(jpgUrl);

            Uri smoothUrl = MediaHelper.GetSmoothStreamingUri(info, file.Key);
            Console.WriteLine("Smooth: " + smoothUrl.ToString());
            System.Threading.Thread.Sleep(30000); //Wait 30s for the streaming servers to get the locator table updates.
            DoSomethingWithStreamingUrl(smoothUrl);
        }
        else
        {
            Console.WriteLine("Main thread simple test failed.");
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
});

And for completeness, here are the functions used in the above:

        #region TestApp Specific
        
        //Derive from WebClient so that you can override base class settings:
        public class WebUpload : WebClient
        {
            protected override WebRequest GetWebRequest(Uri address)
            {
                WebRequest request = (WebRequest)base.GetWebRequest(address);
                // Perform any customizations on the request.
                request.Timeout = 600000; //Set upload timeout to 10min
                return request;
            }

        }
        private static bool DoSomethingWithUploadUrl(HelperConfigInfo info, Uri uploadUrl, string mediaFile)
        {
            string fileName = Path.GetFileName(mediaFile);

            UriBuilder ub = new UriBuilder(uploadUrl);
            ub.Path += "/" + fileName;

            Uri fullUploadUrl = ub.Uri;

            bool uploadOK = false;
            WebUpload wc = new WebUpload();
            try
            {
                Console.WriteLine(mediaFile + " upload start " + DateTime.Now);
                byte[] responce = wc.UploadFile(fullUploadUrl, "PUT", mediaFile);
                Console.WriteLine(mediaFile + " upload done " + DateTime.Now);
                UTF8Encoding enc = new UTF8Encoding();
                string resp = enc.GetString(responce);
                Console.WriteLine(resp);
                uploadOK = true;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            return uploadOK;
        }


        private static void DoSomethingWithJpgUrl(Uri jpgUrl)
        {
            WebClient wc = new WebClient();
            byte[] responceData = null;
            try
            {
                responceData = wc.DownloadData(jpgUrl);
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to download manifest: " + e.Message);
            }
            if (responceData == null || responceData.Length == 0)
            {
                Console.WriteLine("Failed to download manifest: no data.");
            }
            else
            {
                FileStream fs = new FileStream("c:\\temp\\output\\mainThreadUserMediaGuid.jpg", FileMode.Create);
                BinaryWriter br = new BinaryWriter(fs);
                br.Write(responceData);
                br.Close();
                fs.Close();
            }
        }


        private static void DoSomethingWithStreamingUrl(Uri smoothUrl)
        {
            WebClient wc = new WebClient();
            byte[] responceData = null;
            try
            {
                responceData = wc.DownloadData(smoothUrl);
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to download manifest: " + e.Message);
            }
            if (responceData == null || responceData.Length == 0)
            {
                Console.WriteLine("Failed to download manifest: no data.");
            }
            else
            {
                FileStream fs = new FileStream("c:\\temp\\output\\mainThreadUserMediaGuid.ismc", FileMode.Create);
                BinaryWriter br = new BinaryWriter(fs);
                br.Write(responceData);
                br.Close();
                fs.Close();
            }
        }
        #endregion


        /// <summary>
        /// Expected polling interval in milliseconds.  Adjust this interval as needed based on estimated job completion times. 
        /// </summary>
        const int _JobProgressInterval = 20000;

        /// <summary>
        /// Check the job progress and wait for completion or failure.  If job does not exist, this will wait in the queued state: for async logic.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="jobName"></param>
        /// <returns></returns>
        public static bool CheckJobProgress(CloudMediaContext context, string jobName)
        {
            // Flag to indicate when job state is finished. 
            bool jobCompleted = false;
            bool success = false;
            JobState state = JobState.Queued;

            double loops = 0;
            while (!jobCompleted)
            {
                // Get state:
                IJob job = MediaHelper.GetJobByName(context, jobName);
                if (job == null)
                {
                    state = JobState.Queued; //Force to Queued
                    Console.WriteLine("Job not found, force to Queued State");
                }
                else
                {
                    state = job.State;
                }

                // Report:
                Console.WriteLine(jobName + " " + state + " elapse time: " + loops * _JobProgressInterval / 1000.0);
                loops++;

                // Check:
                switch (state)
                {
                    case JobState.Finished:
                        jobCompleted = true;
                        success = true;
                        break;
                    case JobState.Queued:
                    case JobState.Scheduled:
                    case JobState.Processing:
                        //Do nothing.
                        break;
                    case JobState.Error:
                        jobCompleted = true;

                        // Dig into the main MediaServices.Client for error handling:
                        if (job != null)
                        {
                            foreach (var task in job.Tasks)
                            {
                                var ed = task.ErrorDetails;
                                if (ed != null)
                                {
                                    foreach (var item in ed)
                                    {
                                        Console.WriteLine(String.Format("Job failed while building the asset. Error code: {0} Message: {1}", item.Code, item.Message));
                                    }
                                }
                            }
                        }
                        break;
                    default:
                        // Not normal to be here!
                        jobCompleted = true;
                        break;
                }

                // Wait for the specified job interval before checking state again.
                if (!jobCompleted)
                    System.Threading.Thread.Sleep(_JobProgressInterval);
            }
            return success;
        }

This entry was posted in Windows Azure Media Services and tagged . Bookmark the permalink.

8 Responses to So you want to build your own YouTube?

  1. Pingback: Windows Azure Media Services So you want to build your own YouTube? | Encoding video | Scoop.it

  2. Wilson Raynor says:

    How can you allow video files to convert/encode at the sametime on Azure? Currently, we are allowing users to upload multiple files at the same time (anywhere from 20-100 15mb files). If we have 4 users doing this, one user has to wait for the users infront of them’s video to finish before theirs starts. Needless to say, if we have 100 users doing this, it takes days before they can view their files.

    • ndrouin says:

      Hi Wilson,
      Short answer: encoding reserved units.
      Go to the management portal and find your media service. Up top, there is a ‘Scale’ tab which has a slider entitled ‘Encoding’. For each of these ‘units’ we are reserving a slot for only your jobs in the system. This costs money: 99$/month is the current rate (prorated daily using the high-water mark of reserved or concurrent jobs). So if you buy one, you will always have at least one of your jobs running (a second could have made its way through the general queue). If you buy 5, you will always have 5 in ‘processing’. There is a nifty ‘jobs’ tab now in the portal, you can immediately see the effect of using that slider. Note that the portal user interface is hosted in one datacenter, and your account may be across the world: there is some time as the requests make their way through the system and we do have internal caps as to total available reserved units.

  3. Saji Saseendran says:

    Does Azure Media Service provide an option to find the resolution and duration of an asset without using a player instance to find these properties? We are uploading a video file and saving the name, description etc to a SQL table and would like to save duration and resolution properties of the orginal asset in the table. We tried Shell properties and doesnt seem to work on Windows 2008 R2 server.

    • ndrouin says:

      No, not for input assets.
      For assets which come out of the ‘Windows Azure Media Encoder’ there is an encoding metadata xml which has all these properties.
      If your file is an MP4, it is pretty easy to write parser for the MP4 format and pull out the information you need from the moov box. (It’s easier than you think: less than 100 lines of code, I would estimate.)
      Assets which are uploaded into the system are not inspected. When we do an encode, or a packaging or encryption operation, we need to pay royalties on your behalf for some of the codecs and container formats that are used. In those cases, we only keep track of information that is required to support/document this.

  4. Ash says:

    Any chance you could post the full code base somewhere?

Comments are closed.