AES token not working on iOS (iPhone). Asked to add proxy!

Sturla 176 Reputation points
2022-05-21T12:26:09.147+00:00

Hi, I´m not able to get AES to work on iPhone. But its working on windows

I found this issue here that talks about creating a proxy (?) to solve the known issue in Azure Media Player

AES and restricted token content does not playback using iOS and older Android devices. In order to achieve this scenario, a proxy must be added to your service.

What is it I have to do?

My setup is:

Razor page that gets the url with token

        private async Task GetStreamEventsAsync(Guid eventPageId)  
        {  
            var @event = await StreamEventsAppService.GetVideoWithUrlsAndTokenAsync(eventPageId);  
  
            LiveStreamUrl = @event.HlsUrl;  
            Token = $"Bearer={@event.Token}";  
        }  

the controller

        [HttpGet]  
        [Route("live-stream-video-token")]  
        public virtual Task<VideoDto> GetVideoWithUrlsAndTokenAsync(Guid eventPageId)  
        {  
            return _streamEventsAppService.GetVideoWithUrlsAndTokenAsync(eventPageId);  
        }  

With this to display it

   <video id="azuremediaplayer" class="azuremediaplayer amp-default-skin amp-big-play-centered"> </video>  
    <script>  
        var myOptions = {  
            autoplay: true,  
            controls: true,  
            width: "100%",  
            height: "800",  
            poster: "",  
            "logo": { "enabled": false },  
        };  
      
        var myPlayer = amp("azuremediaplayer", myOptions);  
        myPlayer.src([{ src: "@Model.LiveStreamUrl", type: "application/vnd.ms-sstr+xml", protectionInfo: [{ type: "AES", authenticationToken: "@Model.Token" }]}]);  
    </script>  

p.s
Just found this example https://github.com/AzureMediaServicesSamples/HLSSafariProxy and will try to figure it out and see if I can´t adopt that code.

UPDATE 1 :
With using this demo site https://mingfeiy.azurewebsites.net/ I was able to paste in the manifest url and my token and play this on my iPhone. So Now I just have to figure out the code and get to to work with mine

Since this was old code I had to update it like this

    [HttpGet]  
        [Route("proxy")]  
        public virtual async Task<HttpResponseMessage> GetProxyAsync(string playbackUrl, string token)  
        {  
            var collection = HttpUtility.ParseQueryString(token);  
            var authToken = collection.ToQueryString();  
            string armoredAuthToken = HttpUtility.UrlEncode(authToken);  
  
            var httpClient = new HttpClient();  
            httpClient.Timeout = TimeSpan.FromSeconds(30);  
  
            var response = new HttpResponseMessage();  
  
            try  
            {  
                var stream = await httpClient.GetStreamAsync(playbackUrl);  
                if (stream != null)  
                {  
                    using (var reader = new StreamReader(stream))  
                    {  
                        const string qualityLevelRegex = @"(QualityLevels\(\d+\))";  
                        const string fragmentsRegex = @"(Fragments\([\w\d=-]+,[\w\d=-]+\))";  
                        const string urlRegex = @"("")(https?:\/\/[\da-z\.-]+\.[a-z\.]{2,6}[\/\w \.-]*\/?[\?&][^&=]+=[^&=#]*)("")";  
  
                        var baseUrl = playbackUrl.Substring(0, playbackUrl.IndexOf(".ism", System.StringComparison.OrdinalIgnoreCase)) + ".ism";  
                        var content = reader.ReadToEnd();  
  
                        var newContent = Regex.Replace(content, urlRegex, string.Format(CultureInfo.InvariantCulture, "$1$2&token={0}$3", armoredAuthToken));  
                        var match = Regex.Match(playbackUrl, qualityLevelRegex);  
                        if (match.Success)  
                        {  
                            var qualityLevel = match.Groups[0].Value;  
                            newContent = Regex.Replace(newContent, fragmentsRegex, m => string.Format(CultureInfo.InvariantCulture, baseUrl + "/" + qualityLevel + "/" + m.Value));  
                        }  
  
                        response.Content = new StringContent(newContent, Encoding.UTF8, "application/vnd.apple.mpegurl");  
                    }  
                }  
            }  
            catch (Exception ex)  
            {  
  
            }  
            finally  
            {  
                httpClient.Dispose();  
            }  
            return response;  
        }  

That returns the following (I hope its correct) and then I´ll work on stitching it together tomorrow

{  
  "version": "1.1",  
  "content": {  
    "headers": [  
      {  
        "key": "Content-Type",  
        "value": [  
          "application/vnd.apple.mpegurl; charset=utf-8"  
        ]  
      }  
    ]  
  },  
  "statusCode": 200,  
  "reasonPhrase": "OK",  
  "headers": [],  
  "trailingHeaders": [],  
  "requestMessage": null,  
  "isSuccessStatusCode": true  
}  

UPDATE 2
Since I had problems updating the code above to work with modern .net I searched the internet and found these sources.

  1. HLSSafariProxy the original code (as above)
  2. AmsHLSProxy based on the original but for 2017 style Azure functions
  3. HLS proxy example I found
  4. HLS Proxy service for AES encrypted HLS stream This is the newest one (last updated 2020)
Azure Media Services
Azure Media Services
A group of Azure services that includes encoding, format conversion, on-demand streaming, content protection, and live streaming services.
302 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Sturla 176 Reputation points
    2022-05-22T17:34:26.883+00:00

    Based on this code HLS Proxy service for AES encrypted HLS stream I found, I got this to work.

    I combined the two controllers (load/proxy) into one that I think is easier to work with

    Here below is most of the code and should be enough for someone to figure this out. BUT I think you should have an updated example with this kind of code somewhere. Please share with us if you do.

    Controller

     [RemoteService]
        [Area("app")]
        [ControllerName("Manifest")]
        [Route("api/app/manifest")]
        public class ManifestLoadController : Controller
        {
            private const string ManifestProxyUrlTemplate = "http://{0}:{1}/api/app/manifest/manifestproxy";
            private readonly ITopLevelManifestRetriever _topLevelManifestRetriever;
            private readonly ITokenManifestInjector _tokenManifestInjector;
    
            public ManifestLoadController(ITopLevelManifestRetriever topLevelManifestRetriever, ITokenManifestInjector tokenManifestInjector)
            {
                _topLevelManifestRetriever = topLevelManifestRetriever;
                _tokenManifestInjector = tokenManifestInjector;
            }
    
            [AllowAnonymous]
            [HttpGet]
            [Route("manifestload")]
            public virtual IActionResult GetLoad(string playbackUrl, string webtoken)
            {
                if (playbackUrl == null || webtoken == null)
                    return BadRequest("playbackUrl or webtoken cannot be empty");
    
                if (playbackUrl.Contains("&", StringComparison.OrdinalIgnoreCase))
                {
                    playbackUrl = playbackUrl.Remove(playbackUrl.IndexOf("&"));
                }
    
                var token = webtoken;
                var modifiedTopLeveLManifest = _topLevelManifestRetriever.GetTopLevelManifestForToken(GetManifestProxyUrl(Request), playbackUrl, token);
                var response = new ContentResult
                {
                    Content = modifiedTopLeveLManifest,
                    ContentType = @"application/vnd.apple.mpegurl"
                };
                Response.Headers.Add("Access-Control-Allow-Origin", "*");
                Response.Headers.Add("X-Content-Type-Options", "nosniff");
                Response.Headers.Add("Cache-Control", "max-age=259200");
    
                return response;
            }
    
            [AllowAnonymous]
            [HttpGet]
            [Produces("application/vnd.apple.mpegurl")]
            [Route("manifestproxy")]
            public IActionResult GetProxy(string playbackUrl, string token)
            {
                var collection = HttpUtility.ParseQueryString(token);
                var authToken = collection[0];
                var armoredAuthToken = HttpUtility.UrlEncode(authToken);
    
                var httpRequest = (HttpWebRequest)WebRequest.Create(new Uri(playbackUrl));
                httpRequest.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
                httpRequest.Timeout = 30000;
                var httpResponse = httpRequest.GetResponse();
    
                //var response = this.Request.ReadFormAsync().Result;
                var response = new ContentResult();
                try
                {
                    var stream = httpResponse.GetResponseStream();
                    if (stream != null)
                    {
                        using (var reader = new StreamReader(stream))
                        {
                            var content = reader.ReadToEnd();
                            response.Content =
                                _tokenManifestInjector.InjetTokenToManifestChunks(playbackUrl, armoredAuthToken, content);
                        }
                    }
                }
                finally
                {
                    httpResponse.Close();
                }
                Response.Headers.Add("Access-Control-Allow-Origin", "*");
                return response;
            }
    
            private static string GetManifestProxyUrl(HttpRequest request)
            {
                var hostPortion = request.Host.Host;
                var port = request.Host.Port.GetValueOrDefault(80);
                var manifestProxyUrl = string.Format(ManifestProxyUrlTemplate, hostPortion, port);
    
                return manifestProxyUrl;
            }
        }
    

    cshtml view

    <video id="azuremediaplayer" class="azuremediaplayer amp-default-skin amp-big-play-centered"> </video>
    
    <script>
        var myOptions = {
            autoplay: true,
            controls: true,
            width: "100%",
            height: "300",
            poster: "",
            "logo": { "enabled": false },
            //preload: true,
            //poster: url
        };
    
        var myPlayer = amp("appleplayer", myOptions);
        myPlayer.src([{ src: "@Model.ManifestUrl", type: "application/vnd.ms-sstr+xml", streamingFormats: ["DASH", "SMOOTH"], protectionInfo: [{ type: "AES", authenticationToken: "@Model.Token" }] }, { src: "@Html.Raw(@Model.LiveStreamUrl)", type: "application/vnd.apple.mpegurl", disableUrlRewriter: true, protectionInfo: [{ type: "AES" }] }]);
    
    </script>
    
            public async Task OnGet(Guid Id)
            {
                var @event = GetTokenFromService(id);
    
                if (OnApple)
                {
                    LiveStreamUrl = $"https://{Request.Host}/api/app/manifest/manifestload?playbackUrl={@event.HlsUrl}&webtoken={@event.Token}";
                    Token = @event.Token;
                    ManifestUrl = @event.HlsUrl;
                }
                else
                {
                    LiveStreamUrl = @event.DashUrl;
                    Token = $"Bearer={@event.Token}";
                    ManifestUrl = @event.DashUrl;
                    DebugUrl = $"https://ampdemo.azureedge.net/?url={LiveStreamUrl}&playready=true&widevine=true&token={Token}";
                }
    }
    

    For browser detection I´m using https://www.nuget.org/packages/Shyjus.BrowserDetector/

    1 person found this answer helpful.

  2. Sturla 176 Reputation points
    2022-08-27T18:59:34.073+00:00

    Hi again!

    I thought I had this (streaming events like this https://www.ibeinni.is/Event/ViewEvent/8cf1dc7a-b7e3-4c2f-a709-4a9503f18dc6?couponCode=815563&drm=x9) ) working on iPhone but few days ago I found out it only works on Safari on a mac but NOT on iPhone.

    If I try to add the manifest and token to the http://mingfeiy.azurewebsites.net/ testing web it works on Safari in mac but not on my iPhone.

    Shouldn´t this work on iPhone if it works in Safari on a mac?

    Here is my manifest
    https://beinnip-euno.streaming.media.azure.net/4dd929ef-29dc-438b-9a84-ae5fe718322a/7324bef4-d61c-492b-a3a6-ad8470967ea5.ism/manifest(format=m3u8-cmaf,encryption=cbc)&aes=true

    Token
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cm46bWljcm9zb2Z0OmF6dXJlOm1lZGlhc2VydmljZXM6Y29udGVudGtleWlkZW50aWZpZXIiOiI1MjI5YjkzOC1kYWFlLTQwNWUtOGMzMy00YjViZTU0ZmRjZmEiLCJ1cm46bWljcm9zb2Z0OmF6dXJlOm1lZGlhc2VydmljZXM6bWF4dXNlcyI6IjUiLCJuYmYiOjE2NjE2MjU1MTcsImV4cCI6MTY2MTYyNjQxNywiaXNzIjoiaUJlaW5uaUlzc3VlciIsImF1ZCI6ImlCZWlubmlBdWRpZW5jZSJ9.ccBZUOh_6NfiD74AOz3iSry8S-TFc0hxmiN9fTeMqrs

    You can create a new token by going to this url https://www.ibeinni.is/Event/ViewEvent/8cf1dc7a-b7e3-4c2f-a709-4a9503f18dc6?couponCode=815563&drm=x9 (you need to register and login.. takes 1 min)) and then you see the manifest url and token at the bottom

    235399-doesnt-work-on-iphone.png

    And btw I can't find any information about this 0x50300000 error anywhere (and I can´t seem to get it to be in English..sorry about that)

    I really hope you can assist my since this is the last peace in the puzzle I´m missing.

    0 comments No comments