Wszystko, co musisz wiedzieć, by zacząć programować Video on Demand w Azure

Jeśli czytałeś poprzedni wpis i chciałbyś zacząć programować tę usługę, to dziś właśnie postaram się to pokazać. Krok po kroku zobaczysz jak:

  • załadować plik multimedialny do usługi
  • wykonać konwersję materiału z Single Bitrate do Multiple Bitrate
  • opublikować strumień tak aby Twój player na stronie mógł odtworzyć jego zawartość

Na deser, jeśli dasz radę, zobaczymy jak :

  • sprawdzić, które pliki wymagają konwersji
  • utworzyć wiele zadań konwersji naraz

Przykładowy kod jest w C#, Azure Media Services ma jednak gotowe SDK do Java, PHP, Node.js. Jeśli żaden z nich Ci nie pasuje, REST API też jest.

Musisz poznać kilka pojęć zanim „wejdziemy” w kod. Są trywialne.

  • Asset – „zestaw plików” w usłudze Media Services zawierające materiał video, podpisy, ścieżki dźwiękowe ect. W Azure Media Services każdy, dodany plik to Asset.
  • Preset File – Definicja konwersji. Zwykły plik XML, dzięki któremu możesz sterować Media Services i opisać jaki efekt chcesz uzyskać. Przykład na moim github’ie.
  • Task – zadanie do wykonania dla encodera usługi AMS. Zdanie składa się z listy Assetów oraz Preset File, który steruje operacją. W danym momencie, jedna jednostka enodująca może przetwarzać jeden Asset.
  • Job – pozwala Ci uruchomić zadanie konwersji (Task) i kontrolować jego przebieg. Portal również pokaże Ci status wykonania zadań. Pamiętaj, portal zapamięta tylko 50k ostatnich Job’ów. Możesz je oczywiście zapisać do swojej bazy
  • Publish URL – adres URL, który podaje strumień Twojego materiału video. Możesz go zawsze sprawdzić w portalu. Domyślnie Twój asset nie ma takiego adresu. Musisz go przypisać
  • Access Policy – określa czy Twój strumień jest publicznie widoczny, jak długo i w jakim trybie (do odczytu, zapisu, ect). Domyślnie, portal ustawia widoczność na 100 lat. Przydatne, kiedy chcesz mieć pewność, że np. po 30 dniach nikt więcej nie obejrzy Twojego filmu.

To tyle książkowych definicji a teraz przechodzimy do działań.

Jeśli umiesz utworzyć Consol App w Visula Studio, to po dodaniu takie przestrzeni

 using Microsoft.WindowsAzure.MediaServices.Client; 

już możesz rozpocząć.

Najprostrzy „Hello World” dla Azure Media Services, który

  • łączy się do usługi
  • pobiera pierwsze 100 assetów i
  • wyświetla ich nazwę wygląda tak
static void Main(string[] args)
{
	_cachedCredentials = new MediaServicesCredentials(
                            _mediaServicesAccountName,
                            _mediaServicesAccountKey);
        // Used the chached credentials to create CloudMediaContext.
        _context = new CloudMediaContext(_cachedCredentials);

        var allAssets = _context.Assets.Take(100).ToList();
        foreach (var asset in allAssets)
        {
		Console.WriteLine(asset.Name, asset.Uri);
        }
        Console.ReadLine();
}

Maksymalnie możesz pobrać tylko 1000 assetów. Jeśli chcesz pobrać 1000 kolejnych, musisz wykorzystać metodę Skip:

_context.Assets.Skip(1000).Take(1000).ToList();

Jeśli masz identifikator swojego assetu, szybciej będzie tak:

static IAsset GetAsset(string assetId)
{
    // Use a LINQ Select query to get an asset.
    var assetInstance =
        from a in _context.Assets
        where a.Id == assetId
        select a;
    // Reference the asset as an IAsset.
    IAsset asset = assetInstance.FirstOrDefault();

    return asset;
}

Dodajmy pierwszy Asset do naszej usługi

Zajrzyj w poniższy kod i uważnie przeczytaj komentarze, pokazają najważniejsze wywołania.

static public IAsset CreateAssetAndUploadSingleFile(
                     AssetCreationOptions assetCreationOptions, 
                     string singleFilePath)
{
  //Name of the asset, after you have uploaded it to the service
  var assetName = "UploadSingleFile_" + DateTime.UtcNow.ToString();

  //Create the asset
  var asset = _context.Assets.Create(assetName, assetCreationOptions);

  var fileName = Path.GetFileName(singleFilePath);

  //create simple asset
  var assetFile = asset.AssetFiles.Create(fileName);
       
  Console.WriteLine("Created assetFile {0}", assetFile.Name);

  //create simple policy for this asset
  var accessPolicy = _context.AccessPolicies.Create(
                                   assetName, 
                                   TimeSpan.FromDays(30),
                                   AccessPermissions.Write | AccessPermissions.List
                                   );
  //create a simple locater, your asset will be available 
  var locator = _context.Locators.CreateLocator(
                                  LocatorType.Sas, 
                                  asset, 
                                  accessPolicy);

  Console.WriteLine("Upload {0}", assetFile.Name);
            
  //upload the file to asset
  assetFile.Upload(singleFilePath);
  Console.WriteLine("Done uploading {0}", assetFile.Name);

  //for now, we dont need a locator or policy,
  // we just wanted to upload the asset to Media Services
  locator.Delete();
  accessPolicy.Delete();

  return asset;
}

A jak „przenumerować” wszystkie Assety? Microsoft pokazuje to tak, tu na przykładzie Job’ów.

static void ProcessJobs()
{
    try
    {

        int skipSize = 0;
        int batchSize = 1000;
        int currentBatch = 0;

        while (true)
        {
            // Loop through all Jobs (1000 at a time) in the Media Services account
            IQueryable _jobsCollectionQuery = _context.Jobs.Skip(skipSize).Take(batchSize);
            foreach (IJob job in _jobsCollectionQuery)
            {
                currentBatch++;
                Console.WriteLine("Processing Job Id:" + job.Id);
            }

            if (currentBatch == batchSize)
            {
                skipSize += batchSize;
                currentBatch = 0;
            }
            else
            {
                break;
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

Moja koncepcja jest nieco inna, poniżej metoda, której używam by przekodować wszystkie assety, które w Media Services nie są przekodowane.

private static void DoEncodingOfAllItems()
{
  int GlobalCounter = 0;

  while (true){
  //number of assets enumerated
  GlobalCounter++;
  //get the another _maxTake=1000 assets
  //and choose those who need encoding
  //for me, I choose those, which are not encoded
  //and published are that are not in my database
  IEnumerable _list = ShowItemsToTranscode(_maxTake, 0);

  if (_list != null){
    int counter = 0;
    int _totalToTranscode = _list.Count();
    while (true) {
    //_maxNumberOfBatches - maximum number of Jobs
    //which can run at the same time
    //in my project, this reflects the numer of
    //encoding units in Azure Media Services
      if (counter * _maxNumberOfBatches < _totalToTranscode){
      var _listToTranscode = _list.Take(_maxNumberOfBatches).ToList();

      if (_listToTranscode.Count() != 0){
        //Mark in the database assets, which will be encoded
        MarkItemsToEncode(_listToTranscode);
        //Take the list of Assets,
        //pack them into Job and do transcoding
        DoTranscodingAndMark(_listToTranscode);
        counter++;
      }
     else { if (_listToTranscode.Count() == 0) break; }
    }
    else break;
   }
  }
  else break;
 }
}

Żeby kod powyżej zadziałał, potrzebujesz jeszcze dwóch kawałków.

Pierwszy kawałek spakuje assety w paczki po np. 10 plików

private static List<IAsset> DoTranscodingInBatch(
                              IEnumerable<IAsset> _list, 
                              int numberOfItems) 
{
  int counter = 0;
  List<IAsset> resultItems = new List<IAsset>();
  List<IAsset> _listOfJobs = new List<IAsset>();
  foreach (IAsset a in _list){
    if (counter < numberOfItems){
      _listOfJobs.Add(a);
      counter++;
    } 
    else {
      if (counter == numberOfItems) {
        resultItems.AddRange(CreateEncodingJob(_listOfJobs, _encodeFormat, GetTitle));
        _listOfJobs = new List<IAsset>();
        counter = 0;
       }
    }
  }
  if (counter > 0) 
    resultItems.AddRange(CreateEncodingJob(_listOfJobs, _encodeFormat, GetTitle));
  return resultItems;
}

Ten kawałek kodu, utworzy Job, który już wykona konwersję i zapisze do folderu efekt encodowania.

// transformName this is a simple function to convert an asset name 
// into final name
static public List<IAsset> CreateEncodingJob(
                           IEnumerable<IAsset> assets, 
                           string preset, 
                           Func<string,string> transformName)
{
  // Declare a new job.
  IJob job = _context.Jobs.Create(assets.First().Name + " encoding job");

  // It is cruciall to take the right Media Encoder. You have 3 options at least.
  // Unless you need an advanced workflow to transcode film, use thie encoder
  var mediaProcessors = 
              _context.MediaProcessors.Where(p => p.Name.Contains("Media Encoder Standard")).ToList();

  // Make sure you will get the latest verion of Your Media encoder
  var latestMediaProcessor =
              mediaProcessors.OrderBy(mp => new Version(mp.Version)).LastOrDefault();
 
  foreach (var asset in assets){
    // Create a task with the encoding details, using a string preset.
    // Get the preset file with details how to encode assets
    preset = File.ReadAllText(_presetFile);
    ITask task = job.Tasks.AddNew(asset.Name + " encoding task",
                    latestMediaProcessor,
                    preset,
                    TaskOptions.None);

    // Specify the input asset to be encoded.
    task.InputAssets.Add(asset);
    // Add an output asset to contain the results of the job. 
    // This output is specified as AssetCreationOptions.None, which 
    // means the output asset is not encrypted. 
    task.OutputAssets.AddNew(transformName(asset.Name), AssetCreationOptions.None);
                
  }

  // Use the following event handler to check job progress.  
  job.StateChanged += new
                    EventHandler<JobStateChangedEventArgs>(StateChanged);

  // Launch the job.
  job.Submit();

  // Optionally log job details. This displays basic job details
  // to the console and saves them to a JobDetails-{JobId}.txt file 
  // in your output folder.
  LogJobDetails(job.Id);

  // Check job execution and wait for job to finish. 
  Task progressJobTask = 
       job.GetExecutionProgressTask(CancellationToken.None);

  // progressJobTask.Wait();

  // If job state is Error the event handling 
  // method for job progress should log errors.  Here we check 
  // for error state and exit if needed.
  if (job.State == JobState.Error){
    throw new Exception("\nExiting method due to job error.");
  }

  return job.OutputMediaAssets.ToList<IAsset>();
}

Skoro już mamy wszystkie assety skonwertowane, to teraz do bazy dodamy tylko publiczne adresy (adresy strumienia) dla każdego assetu.

Całą robotę za nas „wykona” metoda GetStreamingOriginalLocator, która dla danego assetu tworzy tzw. Publish URL i zwraca go do bazy.

private static void MarkItemsWhenDone(IEnumerable<IAsset> resultItems){
  Dictionary<string, string> _locators = new Dictionary<string, string>();
  foreach (IAsset a in resultItems){
    _locators.Add(a.Name, GetStreamingOriginLocatorURL(a));
  }
  MarkTranscodedAssets(_locators);
}

Aby pobrać ten publiczny adres (strumień), który dostarcza zawartość video, wystarczy z assetu pobrać plik z rozszerzeniem .ism.

static public string GetStreamingOriginLocatorURL(IAsset assetToStream){
  // Get a reference to the streaming manifest file from the  
  // collection of files in the asset. 
  var theManifest =
    from f in assetToStream.AssetFiles
    where f.Name.EndsWith(".ism")
    select f;
 
  // Cast the reference to a true IAssetFile type. 
  IAssetFile manifestFile = theManifest.First();

  // Create a 30-day readonly access policy. 
  IAccessPolicy policy = _context.AccessPolicies.Create(
                "Streaming policy",
                TimeSpan.FromDays(30*1000),
                AccessPermissions.Read);

  // Create a locator to the streaming content on an origin. 
  ILocator originLocator = _context.Locators.CreateLocator(
                LocatorType.OnDemandOrigin,
                assetToStream,
                policy,
                DateTime.UtcNow.AddMinutes(-5));

  // Create a full URL to the manifest file. 
  int end = originLocator.Path.IndexOf("net/");
  return originLocator.Path;
}

Publish URL dla Assetu zawsze przyjmuje poniższą konwencję.

https://<nazwa Azure Media Services>.streaming.mediaservices.windows.net/<GUID>/<nazwa assetu>.ism

Nie jest to idealne rozwiązanie bo po nazwie assetu nie można ustalić właśnie Publish URL. Dlatego ja w swoim rozwiązaniu trzymam w bazie mapowanie <nazwa assetu> – <GUID> i wystawiam proste API (oparte o AppService), które mi tę wartość publish url zwraca.

I już. Koniec.

Miało być mniej słów, dużo kodu i było. Mam nadzieję, że to oszczędzi Ci kilku godzin pracy. Powodzenia.

A jeśli już koniecznie chcesz zobaczyć orginalne referencje do dokumentacji, zapraszam Cię tutaj.

  1. Endcoding and Processing Media in Azure Media Services
  2. Azure Media Services Presets References
  3. How to scale Azure Media Services with .Net 
  4. REST API Reference
Ten wpis został opublikowany w kategorii How2Code, LessonsLearned i oznaczony tagami , , , , , , . Dodaj zakładkę do bezpośredniego odnośnika.