private async Task FragmentHandler(IOwinContext ctx, ParsedRequest req, Channel channel)
        {
            var ct = ctx.Request.CallCancelled;

            using (var subscription = HLSChannelSink.GetSubscription(this, channel, ctx, req.Session)) {
                subscription.Stopped.ThrowIfCancellationRequested();
                using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ct, subscription.Stopped)) {
                    cts.CancelAfter(10000);
                    var segments = await subscription.Segmenter.GetSegmentsAsync(cts.Token).ConfigureAwait(false);

                    var segment = segments.FirstOrDefault(s => s.Index == req.FragmentNumber);
                    if (segment.Index == 0)
                    {
                        ctx.Response.StatusCode = (int)HttpStatusCode.NotFound;
                    }
                    else
                    {
                        ctx.Response.StatusCode = (int)HttpStatusCode.OK;
                        ctx.Response.Headers.Add("Access-Control-Allow-Origin", new string[] { "*" });
                        ctx.Response.ContentType   = "video/MP2T";
                        ctx.Response.ContentLength = segment.Data.LongLength;
                        await ctx.Response.WriteAsync(segment.Data, cts.Token).ConfigureAwait(false);
                    }
                }
            }
        }
        private async Task PlayListHandler(IOwinContext ctx, ParsedRequest req, Channel channel)
        {
            var ct      = ctx.Request.CallCancelled;
            var session = req.Session;

            if (String.IsNullOrWhiteSpace(session))
            {
                var source = new IPEndPoint(IPAddress.Parse(ctx.Request.RemoteIpAddress), ctx.Request.RemotePort ?? 0).ToString();
                using (var md5 = System.Security.Cryptography.MD5.Create()) {
                    session =
                        md5
                        .ComputeHash(System.Text.Encoding.ASCII.GetBytes(source))
                        .Aggregate(new System.Text.StringBuilder(), (builder, v) => builder.Append(v.ToString("X2")))
                        .ToString();
                }
                var location = new UriBuilder(ctx.Request.Uri);
                if (String.IsNullOrEmpty(location.Query))
                {
                    location.Query = $"session={session}";
                }
                else
                {
                    location.Query = location.Query.Substring(1) + $"&session={session}";
                }
                ctx.Response.Redirect(location.Uri.ToString());
                return;
            }
            var pls = new M3U8PlayList(ctx.Request.Query.Get("scheme"), channel);

            ctx.Response.StatusCode = (int)HttpStatusCode.OK;
            ctx.Response.Headers.Add("Cache-Control", new string [] { "private" });
            ctx.Response.Headers.Add("Cache-Disposition", new string [] { "inline" });
            ctx.Response.Headers.Add("Access-Control-Allow-Origin", new string[] { "*" });
            ctx.Response.ContentType = pls.MIMEType;
            using (var subscription = HLSChannelSink.GetSubscription(this, channel, ctx, session)) {
                subscription.Stopped.ThrowIfCancellationRequested();
                byte[] body;
                try {
                    var baseuri = new Uri(
                        new Uri(ctx.Request.Uri.GetComponents(UriComponents.SchemeAndServer | UriComponents.UserInfo, UriFormat.UriEscaped)),
                        "hls/");
                    var acinfo = ctx.GetAccessControlInfo();
                    using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ct, subscription.Stopped)) {
                        cts.CancelAfter(10000);
                        if (acinfo.AuthorizationRequired)
                        {
                            var parameters = new Dictionary <string, string>()
                            {
                                { "auth", acinfo.AuthenticationKey.GetToken() },
                                { "session", subscription.SessionId },
                            };
                            body = await pls.CreatePlayListAsync(baseuri, parameters, subscription.Segmenter, cts.Token).ConfigureAwait(false);
                        }
                        else
                        {
                            var parameters = new Dictionary <string, string>()
                            {
                                { "session", subscription.SessionId },
                            };
                            body = await pls.CreatePlayListAsync(baseuri, parameters, subscription.Segmenter, cts.Token).ConfigureAwait(false);
                        }
                    }
                }
                catch (OperationCanceledException) {
                    ctx.Response.StatusCode = (int)HttpStatusCode.GatewayTimeout;
                    return;
                }
                ctx.Response.StatusCode = (int)HttpStatusCode.OK;
                await ctx.Response.WriteAsync(body, ct).ConfigureAwait(false);
            }
        }