Advertisement
eugene-gubenkov

Azure Media Services - HLS proxy

Nov 24th, 2016
4,114
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 5.97 KB | None | 0 0
  1.     /// <summary>
  2.     /// It's controller that will serve as proxy for iOS video playback via Azure Media Services.
  3.     /// Such playback does not work out-f-the-box.
  4.     ///
  5.     /// In a nutshell, this "service" will change underlying playlist and substitute it modified version where
  6.     /// auth token is embedded into the URI, so that the iOS device can just query the URI w/o Auth header, as
  7.     /// auth token will the in query string and AMS can authorize the request.
  8.     ///
  9.     /// Here are the links that describes the problem and the solution.
  10.     /// https://azure.microsoft.com/en-us/blog/how-to-make-token-authorized-aes-encrypted-hls-stream-working-in-safari/
  11.     ///
  12.     /// The code is almost w/o changes taken from here.
  13.     /// https://github.com/AzureMediaServicesSamples/HLSSafariProxy/blob/master/HLSProxy/HLSManifestProxyDemo/Controllers/ManifestProxyController.cs
  14.     /// </summary>
  15.     [AllowAnonymous]
  16.     [RoutePrefix("AzureMediaServicesManifestProxy")]
  17.     public class AzureMediaServicesManifestProxyController : ApiController
  18.     {
  19.         private static LoggingService _log = LoggingService.GetCurrentClassInstance();
  20.  
  21.         /// <summary>
  22.         /// GET /AzureMediaservicesManifestProxy/TopLevel(playbackUrl,token)
  23.         /// </summary>
  24.         [Route("TopLevel")]
  25.         public HttpResponseMessage GetTopLevelHslPlaylist(string playbackUrl, string token)
  26.         {
  27.             _log.Info($"TopLevel({playbackUrl}, {token})");
  28.  
  29.             if (playbackUrl.ToLowerInvariant().EndsWith("manifest"))
  30.             {
  31.                 playbackUrl += "(format=m3u8-aapl)";
  32.             }
  33.  
  34.             string secondLevelProxyUrl = GetBaseUrl() + "/AzureMediaServicesManifestProxy/SecondLevel";
  35.  
  36.             var modifiedTopLeveLManifest = GetTopLevelManifestForToken(playbackUrl, token, secondLevelProxyUrl);
  37.  
  38.             var response = this.Request.CreateResponse();
  39.  
  40.             response.Content = new StringContent(modifiedTopLeveLManifest, Encoding.UTF8, "application/vnd.apple.mpegurl");
  41.  
  42.             response.Headers.Add("X-Content-Type-Options", "nosniff");
  43.             response.Headers.Add("Cache-Control", "max-age=259200");
  44.  
  45.             //_log.Info($"TopLevelResult: {modifiedTopLeveLManifest}");
  46.  
  47.             return response;
  48.         }
  49.  
  50.         /// <summary>
  51.         /// GET /AzureMediaservicesManifestProxy/SecondLevel(playbackUrl,token)
  52.         /// </summary>
  53.         [Route("SecondLevel")]
  54.         public HttpResponseMessage GetSecondLevelHslPlaylist(string playbackUrl, string token)
  55.         {
  56.             _log.Info($"SecondLevel({playbackUrl}, {token})");
  57.  
  58.             // get rid of "Bearer=" or "Bearer " prefixes
  59.             if (token.StartsWith("Bearer", StringComparison.OrdinalIgnoreCase))
  60.             {
  61.                 token = token.Substring("Bearer".Length + 1).Trim();
  62.             }
  63.  
  64.             string encodedToken = HttpUtility.UrlEncode(token);
  65.  
  66.             const string qualityLevelRegex = @"(QualityLevels\(\d+\))";
  67.             const string fragmentsRegex = @"(Fragments\([\w\d=-]+,[\w\d=-]+\))";
  68.             const string urlRegex = @"("")(https?:\/\/[\da-z\.-]+\.[a-z\.]{2,6}[\/\w \.-]*\/?[\?&][^&=]+=[^&=#]*)("")";
  69.  
  70.             string baseUrl = playbackUrl.Substring(0, playbackUrl.IndexOf(".ism", System.StringComparison.OrdinalIgnoreCase)) + ".ism";
  71.             string content = GetRawContent(playbackUrl);
  72.  
  73.             string newContent = Regex.Replace(content, urlRegex, string.Format(CultureInfo.InvariantCulture, "$1$2&token={0}$3", encodedToken));
  74.             Match match = Regex.Match(playbackUrl, qualityLevelRegex);
  75.             if (match.Success)
  76.             {
  77.                 var qualityLevel = match.Groups[0].Value;
  78.                 newContent = Regex.Replace(newContent, fragmentsRegex, m => string.Format(CultureInfo.InvariantCulture, baseUrl + "/" + qualityLevel + "/" + m.Value));
  79.             }
  80.  
  81.             HttpResponseMessage response = this.Request.CreateResponse();
  82.  
  83.             response.Content = new StringContent(newContent, Encoding.UTF8, "application/vnd.apple.mpegurl");
  84.  
  85.             //_log.Info($"SecondLevelResult: {newContent}");
  86.  
  87.             return response;
  88.         }
  89.  
  90.         #region Private methods
  91.         private string GetTopLevelManifestForToken(string topLeveLManifestUrl, string token, string secondLevelManifestProxyBaseUrl)
  92.         {
  93.             const string qualityLevelRegex = @"(QualityLevels\(\d+\)/Manifest\(.+\))";
  94.  
  95.             string topLevelManifestContent = GetRawContent(topLeveLManifestUrl);
  96.  
  97.             string topLevelManifestBaseUrl = topLeveLManifestUrl.Substring(0, topLeveLManifestUrl.IndexOf(".ism", System.StringComparison.OrdinalIgnoreCase)) + ".ism";
  98.             string urlEncodedTopLeveLManifestBaseUrl = HttpUtility.UrlEncode(topLevelManifestBaseUrl);
  99.             string urlEncodedToken = HttpUtility.UrlEncode(token);
  100.  
  101.             MatchEvaluator encodingReplacer = (Match m) => $"{secondLevelManifestProxyBaseUrl}?playbackUrl={urlEncodedTopLeveLManifestBaseUrl}{HttpUtility.UrlEncode("/" + m.Value)}&token={urlEncodedToken}";
  102.  
  103.             string newContent = Regex.Replace(topLevelManifestContent, qualityLevelRegex, encodingReplacer);
  104.  
  105.             return newContent;
  106.         }
  107.  
  108.         private string GetBaseUrl()
  109.         {
  110.             Uri requestUri = Request.RequestUri;
  111.             string baseUrl = Request.RequestUri.GetLeftPart(UriPartial.Authority);
  112.  
  113.             return baseUrl;
  114.         }
  115.  
  116.         private string GetRawContent(string uri)
  117.         {
  118.             var httpRequest = (HttpWebRequest)WebRequest.Create(new Uri(uri));
  119.             httpRequest.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
  120.             httpRequest.Timeout = 30000;
  121.  
  122.             using (WebResponse httpResponse = httpRequest.GetResponse())
  123.             using (Stream stream = httpResponse.GetResponseStream())
  124.             using (var reader = new StreamReader(stream))
  125.             {
  126.                 return reader.ReadToEnd();
  127.             }
  128.         }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement