private async Task ReadBodyAsync(HttpRequest request, MediaSegment mediaSegment, bool expire) { int count = 0; while (true) { var(buffer, endOfStream) = await ReadBufferAsync(request); var cacheKey = mediaSegment.GetChunkKey(count++); var options = new MemoryCacheEntryOptions { SlidingExpiration = expire ? TimeSpan.FromSeconds(60) : (TimeSpan?)null, Priority = expire ? CacheItemPriority.Normal : CacheItemPriority.NeverRemove, Size = BUFFER_SIZE }; options.RegisterPostEvictionCallback(RemoveCacheEntry); _cache.Set <MediaBuffer>(cacheKey, buffer, options); _logger.LogDebug($"Got buffer of size: {buffer.Length} for {cacheKey} "); mediaSegment.AddBuffer(buffer.Length, endOfStream); if (endOfStream) { break; } } }
private async Task SendResponse(HttpResponse response, MediaSegment mediaSegment) { response.StatusCode = 200; var length = mediaSegment.Length; if (mediaSegment.Complete) { response.ContentLength = mediaSegment.Length; _logger.LogWarning($"Sending full response for {mediaSegment.Path} length:{mediaSegment.Length} "); } else { _logger.LogWarning($"Chunked transfer encoding for {mediaSegment.Path}"); } response.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue { Public = true, NoCache = true, NoStore = true }; foreach (var index in mediaSegment.GetBufferIndex()) { var cacheKey = mediaSegment.GetChunkKey(index); var buffer = _cache.Get <MediaBuffer>(cacheKey); if (buffer == null) { throw new InvalidOperationException($"Missing cache entry for {cacheKey}"); } await response.Body.WriteAsync(buffer.Memory); await response.Body.FlushAsync(); } }
public MediaSearchEventArgs(Media media, MediaSegment segment, string mediaName, TimeSpan tCIn, TimeSpan duration) { Media = media; MediaSegment = segment; MediaName = mediaName; TCIn = tCIn; Duration = duration; }
private void MakeTimelineInfo(IXmlNode segmentTimeline, uint bitrate, ulong timescale, string repID, uint startNumber, ulong presentationTimeOffset, string mediaFormat, ref MediaStream stream) { var timeline = segmentTimeline.ChildNodes.Where(x => x.NodeName == "S"); uint segmentNumber = startNumber; ulong time = 0; //Set time equal to the @t attribute of first element, otherwise it is 0. //This is to replace the value of $Time$ in the media segment url var tAttribute = timeline.First().Attributes.GetNamedItem("t"); if (tAttribute != null) { time = ulong.Parse(tAttribute.InnerText); } foreach (var t in timeline) { ulong duration = ulong.Parse(t.Attributes.GetNamedItem("d").InnerText); var repeatAttribute = t.Attributes.GetNamedItem("r"); long repeats = 0; if (repeatAttribute != null) { repeats = long.Parse(repeatAttribute.InnerText); } for (int i = 0; i < repeats + 1; i++) { MediaSegment segment = new MediaSegment(); segment.Duration = duration; segment.Timestamp = time - presentationTimeOffset; segment.Number = segmentNumber; //Construct the mediaURL string mediaSegmentUrl; ExpandDASHUrlSegmentTemplate(mediaFormat, manifest.BaseUrl, bitrate, repID, segmentNumber, time, out mediaSegmentUrl); segment.SegmentUrl = mediaSegmentUrl; stream.PushBackSegment(segment); time += duration; segmentNumber++; } } double msStreamDuration = (double)time * (1000) / timescale; #if DEBUG Logger.Log("Found " + stream.Segments.Count() + " segments - Duration: " + msStreamDuration.ToString() + " miliseconds"); #endif if (msStreamDuration > manifest.MediaPresentationDuration.TotalMilliseconds) { manifest.MediaPresentationDuration = TimeSpan.FromMilliseconds(msStreamDuration); } }
private MediaBuffer GetMediaBuffer(MediaSegment mediaSegment, int index) { var cacheKey = mediaSegment.GetChunkKey(index); var buffer = _cache.Get <MediaBuffer>(cacheKey); if (buffer == null) { throw new InvalidOperationException($"Missing cache entry for {cacheKey}"); } return(buffer); }
private async void SearchSegmentIfOutsideBuffer() { MediaSegment segment = ((LiveSourceBufferManager)GetVideoBuffer()).GetNextSegment(); HttpResponseMessage response = await Downloader.SendHeadRequestAsync(new Uri(segment.SegmentUrl)); if (response.StatusCode == HttpStatusCode.NotFound) { #if DEBUG Logger.Log("Trying to go to Live Edge"); #endif GoToLiveEdge(); } }
private async Task SendResponse(HttpRequest request, HttpResponse response, MediaSegment mediaSegment) { response.Headers.Add("Accept-Ranges", "bytes"); if (mediaSegment.Complete) { var length = mediaSegment.Length; response.ContentLength = mediaSegment.Length; _logger.LogInformation($"Sending complete segment for {mediaSegment.Path} length:{mediaSegment.Length} "); } else { _logger.LogWarning($"Chunked transfer encoding for {mediaSegment.Path}"); } response.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue { Public = true, NoCache = true, NoStore = true }; // check for byte range. var values = request.Headers["Range"]; if (values.Count > 0) { var rangeHeader = RangeHeaderValue.Parse(values[0]); response.StatusCode = 206; var range = rangeHeader.Ranges.Single(); // only one range is supported. var offset = range.From ?? 0; var length = range.To.HasValue ? range.To.Value - offset : long.MaxValue; length = mediaSegment.Complete ? Math.Min(length, mediaSegment.Length) : length; response.Headers.ContentLength = length; var lengthString = mediaSegment.Complete ? mediaSegment.Length.ToString() : "*"; response.Headers.Add("Content-Range", $"bytes {range.From}-{range.To}/{lengthString}"); foreach (var buffer in GetRangeBuffers(mediaSegment, (int)offset, (int)length)) { await SendBufferAsync(response, buffer, !mediaSegment.Complete); } } else { response.StatusCode = 200; foreach (var index in mediaSegment.GetBufferIndex()) { var buffer = GetMediaBuffer(mediaSegment, index); await SendBufferAsync(response, buffer.Memory, flush : !mediaSegment.Complete); } } }
public async Task InvokeAsync(HttpContext context) { _logger.LogDebug( $"{context.Request.Method} {context.Request.Path} type: {context.Request.ContentType} size: {context.Request.Headers.ContentLength} Encoding:{context.Request.Headers["Transfer-Encoding"]}"); var options = new MemoryCacheEntryOptions(); var path = context.Request.Path.Value; string contentType = context.Request.ContentType; var expire = false; if (path.Contains("/chunk")) { contentType = CmafSegmentContentType; options.SetSlidingExpiration(TimeSpan.FromSeconds(60)); expire = true; } else if (path.Contains("/init")) { contentType = CmafSegmentContentType; options.Priority = CacheItemPriority.NeverRemove; } else if (path.EndsWith(".mpd")) { contentType = ManifestContentType; options.Priority = CacheItemPriority.NeverRemove; } else if (path.EndsWith(".m3u8")) { contentType = HlsContentType; options.Priority = CacheItemPriority.NeverRemove; } var mediaBuffer = new MediaSegment(context.Request.Path.Value, contentType, _logger); _cache.Set <MediaSegment>(context.Request.Path, mediaBuffer, options); try { await ReadBodyAsync(context.Request, mediaBuffer, expire); } catch (Exception ex) { _logger.LogError(ex, $"Failed to read body for {context.Request.Path}"); _cache.Remove(context.Request.Path); } _logger.LogInformation($"completed {context.Request.Path} size:{mediaBuffer.Length} type:{contentType} "); context.Response.StatusCode = 201; }
internal MediaSegment GetNextSegment() { DetermineStartingSegmentIndex(); bool isEndOfStream = false; if (manifest.HasPresentationDuration) { if (isFirstSegment) { var segmentInfo = stream.SegmentInformation; var presentationDuration = (ulong)manifest.MediaPresentationDuration.TotalMilliseconds * segmentInfo.Timescale / 1000; var segmentDuration = segmentInfo.Duration; totalSegmentCount = (int)presentationDuration / (int)segmentDuration; } } MediaSegment segment; if (stream.CanGenerateSegmentsDynamically && !isEndOfStream) { //create new segment based on media segment info given segment = new MediaSegment(); var segmentInfo = stream.SegmentInformation; var format = segmentInfo.UrlTemplate; var bitrate = segmentInfo.Bitrate; var repID = segmentInfo.RepresentationID; var number = segmentInfo.StartNumber + (ulong)nextSegmentIndex; var time = segmentInfo.StartTimestamp + (ulong)nextSegmentIndex; string mediaUrl; DashManifestParser.ExpandDASHUrlSegmentTemplate(format, manifest.BaseUrl, bitrate, repID, number, time, out mediaUrl); segment.Duration = segmentInfo.Duration; segment.Number = number; segment.Timestamp = time; segment.SegmentUrl = mediaUrl; } else //populated by a list of segments { segment = stream.Segments.ElementAt(nextSegmentIndex); } nextSegmentNumber = segment.Number + 1; return(segment); }
private async Task DownloadAsync(MediaSegment segment, Stream outStream, int totalSegmentCount, Reference <long> overallDownloaded, Reference <int> downloadedSegmentCount) { using var client = new HttpClient(); using var response = await client.GetAsync(segment.Uri, HttpCompletionOption.ResponseHeadersRead); using var originalStream = await response.Content.ReadAsStreamAsync(); using var stream = await _grabResult.WrapStreamAsync(originalStream); var contentLength = response.Content.Headers.ContentLength; if (contentLength == null) { await stream.CopyToAsync(outStream); return; } var avgSegmentSize = downloadedSegmentCount > 0 ? overallDownloaded / (double)downloadedSegmentCount : contentLength.Value; var downloaded = 0L; var totalBytes = avgSegmentSize * totalSegmentCount; const int BUFFER_LENGTH = 4096; var buffer = new byte[BUFFER_LENGTH]; while (true) { var read = await stream.ReadAsync(buffer, 0, buffer.Length); if (read == 0) { break; } await outStream.WriteAsync(buffer, 0, read); downloaded += read; _viewModel.DownloadProgress = (avgSegmentSize * downloadedSegmentCount + downloaded) / totalBytes; } overallDownloaded.Value += contentLength.Value; downloadedSegmentCount.Value++; }
private IEnumerable <ReadOnlyMemory <byte> > GetRangeBuffers(MediaSegment segment, int offset, int length) { var curOffset = 0; foreach (var index in segment.GetBufferIndex()) { var buffer = GetMediaBuffer(segment, index); if (offset > curOffset + buffer.Length) { curOffset += buffer.Length; continue; } var curLength = Math.Min(length, buffer.Length); yield return(buffer.Memory.Slice(offset - curOffset, curLength)); length -= curLength; if (length == 0) { break; } } }
private void MakeNumberBaseSegmentInfo(uint bitrate, UInt64 timescale, UInt64 segmentDuration, string repID, uint startNumber, string mediaFormat, ref MediaStream stream) { var hnsPresentationDuration = (Convert.ToUInt64(manifest.MediaPresentationDuration.TotalMilliseconds) * Convert.ToUInt64(10000)); var hnsFragmentDuration = ((segmentDuration * 10000000) / timescale); UInt64 count = (uint)(hnsPresentationDuration / hnsFragmentDuration); UInt64 remain = (uint)(hnsPresentationDuration - (hnsFragmentDuration * count)); if (manifest.IsLive && count == 0) { if (manifest.TimeShiftBufferDepth.TotalSeconds > 0 && hnsFragmentDuration > 0) { var hnsTimeShiftBufferDepth = (ulong)(manifest.TimeShiftBufferDepth.TotalSeconds * 10000000); count = hnsTimeShiftBufferDepth / hnsFragmentDuration; } else { count = LiveSegmentCount; } } UInt64 time = 0; UInt64 segmentNumber = startNumber; if (manifest.IsLive) { CalculateNumberIdentifierForLive(hnsFragmentDuration, ref segmentNumber); } //Since we calculated the latest segment number, we will add to the front of the list for (UInt64 i = 0; i < count; i++) { MediaSegment segment = new MediaSegment(); segment.Timestamp = time; segment.Duration = segmentDuration; segment.Number = segmentNumber; //Construct the mediaURL string mediaSegmentUrl; ExpandDASHUrlSegmentTemplate(mediaFormat, manifest.BaseUrl, bitrate, repID, segmentNumber, time, out mediaSegmentUrl); segment.SegmentUrl = mediaSegmentUrl; stream.PushBackSegment(segment); segmentNumber++; time += segmentDuration; } if (remain > 0) { MediaSegment segment = new MediaSegment(); segment.Timestamp = time; segment.Duration = remain / timescale; segment.Number = segmentNumber; string mediaSegmentUrl; ExpandDASHUrlSegmentTemplate(mediaFormat, manifest.BaseUrl, bitrate, repID, segmentNumber, time, out mediaSegmentUrl); segment.SegmentUrl = mediaSegmentUrl; stream.PushBackSegment(segment); #if DEBUG Logger.Log("Adding final segment to stream with MimeType:" + stream.MimeType + ". Segment{ Timestamp: " + time + "Duration: " + segmentDuration + "Number: " + startNumber + "}" + "\nURL: " + segment.SegmentUrl); #endif time += segmentDuration; } }
internal static ObservableSynchronizedCollection<MediaSegment> DbMediaSegmentsRead(this PersistentMedia media) { if (Connect()) { Guid mediaGuid = media.MediaGuid; ObservableSynchronizedCollection<MediaSegment> segments = null; MediaSegment newMediaSegment; MySqlCommand cmd = new MySqlCommand("SELECT * FROM tas.MediaSegments where MediaGuid = @MediaGuid;", connection); cmd.Parameters.Add("@MediaGuid", MySqlDbType.Binary).Value = mediaGuid.ToByteArray(); lock (connection) { if (_mediaSegments == null) _mediaSegments = new Hashtable(); segments = (ObservableSynchronizedCollection<MediaSegment>)_mediaSegments[mediaGuid]; if (segments == null) { segments = new ObservableSynchronizedCollection<MediaSegment>(); using (MySqlDataReader dataReader = cmd.ExecuteReader()) { while (dataReader.Read()) { newMediaSegment = new MediaSegment(mediaGuid) { idMediaSegment = dataReader.GetUInt64("idMediaSegment"), SegmentName = (dataReader.IsDBNull(dataReader.GetOrdinal("SegmentName")) ? string.Empty : dataReader.GetString("SegmentName")), TCIn = dataReader.IsDBNull(dataReader.GetOrdinal("TCIn")) ? default(TimeSpan) : dataReader.GetTimeSpan("TCIn"), TCOut = dataReader.IsDBNull(dataReader.GetOrdinal("TCOut")) ? default(TimeSpan) : dataReader.GetTimeSpan("TCOut"), }; segments.Add(newMediaSegment); } dataReader.Close(); } _mediaSegments.Add(mediaGuid, segments); } } return segments; } else return null; }
protected override async void SendMediaSegment(TimeSpan now) { try { var end = now; if (!isInitialized || Appending || isEndOfStream) { return; } //if the next segment is out of range from the manifest, then //try to redownload the manifest and pick up where we left off. //this is the case where a manifest reload didn't come in time, //we don't have a mediaPresentationDuration present, and we cannot generate //segments dynamically.the new manifest does not contain the segment, //then it is the end of stream. if (manifest.HasPresentationDuration && nextSegmentIndex > totalSegmentCount) { //nextSegmentIndex = await GetNewSegmentIndexFromNewStream(); //if (nextSegmentIndex == -1) //{ isEndOfStream = true; return; //} } foreach (var range in sb.Buffered) { if (range.Start <= end + MaxGap && range.End > end) { end = range.End; } else if (range.Start > end) { break; } } var stream = base.stream; MediaSegment segment = GetNextSegment(); if (now + MaxBuffering <= end) { return; } string url = segment.SegmentUrl; internalAppending = true; #if DEBUG Logger.Log("Loading segment url " + url); #endif Uri mediaSegmentUrl = null; try { mediaSegmentUrl = new Uri(url); } catch (Exception e) { #if DEBUG Logger.Log("malformed media segment: " + url + Logger.Display(e)); #endif } bool firstDownloadSucceded = await TryDownloadBuffer(mediaSegmentUrl); if (firstDownloadSucceded && isFirstSegment) { var bufferedRange = sb.Buffered; TimeSpan startingTimestamp = bufferedRange.First().Start; TimeSpan endingTimestamp = bufferedRange.Last().End; if (sb.Buffered.Count != 0) { sb.Remove(TimeSpan.Zero, endingTimestamp); #if DEBUG Logger.Log("Removing buffered Range from 0 to " + endingTimestamp); #endif } sb.TimestampOffset = -startingTimestamp; #if DEBUG Logger.Log("Setting Timestamp to " + sb.TimestampOffset); #endif sb.AppendBuffer(activeDownload.Result); isFirstSegment = false; } else if (!firstDownloadSucceded) { bool searchSucceeded = await SearchForSegment(); if (!searchSucceeded) { #if DEBUG Logger.Log("Calling end of stream"); #endif // Failed - should send EndOfStream sb.Abort(); isEndOfStream = true; } } } catch (Exception e) { #if DEBUG Logger.Log("Error when sending media segment: " + e.Message); #endif } }