protected static Range ConvertToTimeRange(Range byteRange, ProfileMediaItem item) { if (byteRange.Length <= 0.0) { return(new Range(0, Convert.ToInt64(item.Metadata.Duration))); } double startSeconds = 0; double endSeconds = 0; if (item.IsTranscoding == true) { long length = GetStreamSize(item); double factor = Convert.ToDouble(item.Metadata.Duration) / Convert.ToDouble(length); startSeconds = Convert.ToDouble(byteRange.From) * factor; endSeconds = Convert.ToDouble(byteRange.To) * factor; } else { double bitrate = 0; if (item.IsSegmented == false) { bitrate = Convert.ToDouble(item.Metadata.Bitrate) * 1024; //Bitrate in bits/s } if (bitrate > 0) { startSeconds = Convert.ToDouble(byteRange.From) / (bitrate / 8.0); endSeconds = Convert.ToDouble(byteRange.To) / (bitrate / 8.0); } } return(new Range(Convert.ToInt64(startSeconds), Convert.ToInt64(endSeconds))); }
protected static Range ConvertToByteRange(Range timeRange, ProfileMediaItem item) { if (timeRange.Length <= 0.0) { return(new Range(0, item.Metadata.Size ?? 0)); } long startByte = 0; long endByte = 0; if (item.IsTranscoding == true) { long length = GetStreamSize(item); double factor = Convert.ToDouble(length) / Convert.ToDouble(item.Metadata.Duration); startByte = Convert.ToInt64(Convert.ToDouble(timeRange.From) * factor); endByte = Convert.ToInt64(Convert.ToDouble(timeRange.To) * factor); } else { double bitrate = 0; if (item.IsSegmented == false) { bitrate = Convert.ToDouble(item.Metadata.Bitrate) * 1024; //Bitrate in bits/s } startByte = Convert.ToInt64((bitrate * timeRange.From) / 8.0); endByte = Convert.ToInt64((bitrate * timeRange.To) / 8.0); } return(new Range(startByte, endByte)); }
protected static Range ConvertToFileRange(Range requestedByteRange, ProfileMediaItem item, long length) { long toRange = requestedByteRange.To; long fromRange = requestedByteRange.From; if (toRange <= 0 || toRange > length) { toRange = length; } if (item.IsSegmented == false && item.IsTranscoding == true) { if (item.Metadata.Size > 0 && (toRange > item.Metadata.Size || fromRange > item.Metadata.Size)) { fromRange = Convert.ToInt64((Convert.ToDouble(fromRange) / Convert.ToDouble(length)) * Convert.ToDouble(item.Metadata.Size)); toRange = Convert.ToInt64((Convert.ToDouble(toRange) / Convert.ToDouble(length)) * Convert.ToDouble(item.Metadata.Size)); } } return(new Range(fromRange, toRange)); }
internal static long GetStreamSize(ProfileMediaItem item) { long length = item?.Metadata?.Size ?? 0; if (item.IsTranscoding == true || item.IsLive == true || length <= 0) //if (length <= 0) { if (item.IsAudio) { return(TRANSCODED_AUDIO_STREAM_MAX); } else if (item.IsImage) { return(TRANSCODED_IMAGE_STREAM_MAX); } else if (item.IsVideo) { return(TRANSCODED_VIDEO_STREAM_MAX); } return(TRANSCODED_VIDEO_STREAM_MAX); } return(length); }
protected static async Task SendByteRangeAsync(IOwinContext context, Stream resourceStream, ProfileMediaItem item, EndPointProfile profile, Range range, bool onlyHeaders, bool partialResource, TransferMode mediaTransferMode) { if (range.From > 0 && range.From == range.To) { context.Response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable; context.Response.ContentLength = 0; context.Response.ContentType = null; return; } long length = range.Length; if (item.IsSegmented == false && item.IsTranscoding == true) { length = GetStreamSize(item); } else { length = resourceStream.Length; } Range fileRange = ConvertToFileRange(range, item, length); if (fileRange.From < 0 || length <= fileRange.From) { context.Response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable; context.Response.ContentLength = 0; context.Response.ContentType = null; return; } if (partialResource == false && await WaitForMinimumFileSizeAsync(resourceStream, fileRange.From) == false) { context.Response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable; context.Response.ContentLength = 0; context.Response.ContentType = null; return; } if (range.From > length || range.To > length) { range = fileRange; } context.Response.StatusCode = (int)HttpStatusCode.PartialContent; if (item.IsLive || range.Length == 0 || (mediaTransferMode == TransferMode.Streaming && context.Request.Protocol == "HTTP/1.1" && profile.Settings.Communication.AllowChunckedTransfer)) { context.Response.Headers["Content-Range"] = $"bytes {range.From}-"; context.Response.ContentLength = null; } else if (length <= 0) { context.Response.Headers["Content-Range"] = $"bytes {range.From}-{range.To - 1}"; context.Response.ContentLength = range.Length; } else { context.Response.Headers["Content-Range"] = $"bytes {range.From}-{range.To - 1}/{length}"; context.Response.ContentLength = range.Length; } if (item.IsLive == false) { context.Response.Headers["X-Content-Duration"] = Convert.ToDouble(item.Metadata.Duration).ToString("0.00", CultureInfo.InvariantCulture); context.Response.Headers["Content-Duration"] = Convert.ToDouble(item.Metadata.Duration).ToString("0.00", CultureInfo.InvariantCulture); } await SendAsync(context, resourceStream, item, profile, onlyHeaders, partialResource, fileRange); }
protected static async Task SendAsync(IOwinContext context, Stream resourceStream, ProfileMediaItem item, EndPointProfile profile, bool onlyHeaders, bool partialResource, Range byteRange) { if (onlyHeaders) { return; } bool clientDisconnected = false; Guid streamID = item.StartStreaming(); if (streamID == Guid.Empty) { Logger.Error("BaseSendData: Unable to start stream"); return; } try { if (context.Response.ContentLength == 0) { //Not allowed to have a content length of zero context.Response.ContentLength = null; } Logger.Debug("BaseSendData: Sending chunked: {0}", context.Response.ContentLength == null); string clientID = context.Request.RemoteIpAddress; int bufferSize = profile.Settings.Communication.DefaultBufferSize; if (bufferSize <= 0) { bufferSize = 1500; } byte[] buffer = new byte[bufferSize]; int bytesRead; long count = 0; bool isStream = false; long waitForSize = 0; if (byteRange.Length == 0 || (byteRange.Length > 0 && byteRange.Length >= profile.Settings.Communication.InitialBufferSize)) { waitForSize = profile.Settings.Communication.InitialBufferSize; } if (partialResource == false) { if (waitForSize < byteRange.From) { waitForSize = byteRange.From; } } if (await WaitForMinimumFileSizeAsync(resourceStream, waitForSize) == false) { Logger.Error("BaseSendData: Unable to send stream because of invalid length: {0} ({1} required)", resourceStream.Length, waitForSize); return; } long start = 0; if (partialResource == false) { start = byteRange.From; } if (resourceStream.CanSeek) { resourceStream.Seek(start, SeekOrigin.Begin); } long length = byteRange.Length; if (length <= 0 || item.IsLive || (item.IsSegmented == false && item.IsTranscoding == true)) { isStream = true; } int emptyCount = 0; while (item.IsStreamActive(streamID)) { if (isStream) { if (resourceStream.CanSeek) { length = resourceStream.Length - count; } else { length = bufferSize; //Keep stream alive } } bytesRead = await resourceStream.ReadAsync(buffer, 0, length > bufferSize?bufferSize : (int)length); count += bytesRead; if (bytesRead > 0) { emptyCount = 0; try { //Send fetched bytes await context.Response.WriteAsync(buffer, 0, bytesRead, SendDataCancellation.Token); } catch (Exception) { // Client disconnected Logger.Debug("BaseSendData: Connection lost after {0} bytes", count); clientDisconnected = true; break; } length -= bytesRead; if (isStream == false && length <= 0) { //All bytes in the requested range sent break; } } else { emptyCount++; if (emptyCount > 2) { Logger.Debug("BaseSendData: Buffer underrun delay"); await Task.Delay(100); } if (emptyCount > 10) { //Stream is not getting any bigger break; } } if (resourceStream.CanSeek) { if (item.IsTranscoding == false && resourceStream.Position == resourceStream.Length) { //No more data will be available break; } } } } finally { item.StopStreaming(streamID); if (clientDisconnected || item.IsSegmented == false) { if (clientDisconnected == false) { //Everything sent to client so presume watched if (item.IsLive == false) { Guid? userId = ResourceAccessUtils.GetUser(context); IMediaLibrary library = ServiceRegistration.Get <IMediaLibrary>(); if (library != null && userId.HasValue) { library.NotifyUserPlayback(userId.Value, item.MediaItemId, 100, true); } } } } Logger.Debug("BaseSendData: Sending complete"); } }
protected static async Task SendWholeFileAsync(IOwinContext context, Stream resourceStream, ProfileMediaItem item, EndPointProfile profile, bool onlyHeaders, bool partialResource, TransferMode mediaTransferMode) { if (await WaitForMinimumFileSizeAsync(resourceStream, 1) == false) { context.Response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable; context.Response.ContentLength = 0; context.Response.ContentType = null; Logger.Debug("BaseSendData: Sending headers: " + string.Join(";", context.Response.Headers.Select(x => x.Key + "=" + x.Value).ToArray())); return; } long length = GetStreamSize(item); if (resourceStream.CanSeek == true && (item.IsTranscoding == false || item.IsSegmented == true)) { length = resourceStream.Length; } if (resourceStream.CanSeek == false && context.Request.Protocol == "HTTP/1.1" && profile.Settings.Communication.AllowChunckedTransfer) { context.Response.StatusCode = (int)HttpStatusCode.PartialContent; context.Response.ContentLength = null; } else { context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.ContentLength = length; } Range byteRange = new Range(0, context.Response.ContentLength ?? 0); await SendAsync(context, resourceStream, item, profile, onlyHeaders, partialResource, byteRange); }
public static Task <WebResolution> ProcessAsync(IOwinContext context, WebMediaType type, int?provider, string itemId, int?offset, string profileName) { if (itemId == null) { throw new BadRequestException("GetStreamSize: itemId is null"); } if (profileName == null) { throw new BadRequestException("GetStreamSize: profileName is null"); } ISet <Guid> necessaryMIATypes = new HashSet <Guid>(); necessaryMIATypes.Add(MediaAspect.ASPECT_ID); necessaryMIATypes.Add(ProviderResourceAspect.ASPECT_ID); necessaryMIATypes.Add(ImporterAspect.ASPECT_ID); ISet <Guid> optionalMIATypes = new HashSet <Guid>(); optionalMIATypes.Add(VideoAspect.ASPECT_ID); optionalMIATypes.Add(VideoStreamAspect.ASPECT_ID); optionalMIATypes.Add(VideoAudioStreamAspect.ASPECT_ID); optionalMIATypes.Add(ImageAspect.ASPECT_ID); var item = MediaLibraryAccess.GetMediaItemById(context, itemId, necessaryMIATypes, optionalMIATypes); if (item == null) { throw new NotFoundException(String.Format("GetStreamSize: No MediaItem found with id: {0}", itemId)); } EndPointProfile profile = null; List <EndPointProfile> namedProfiles = ProfileManager.Profiles.Where(x => x.Value.Name == profileName).Select(namedProfile => namedProfile.Value).ToList(); if (namedProfiles.Count > 0) { profile = namedProfiles[0]; } else if (ProfileManager.Profiles.ContainsKey(profileName)) { profile = ProfileManager.Profiles[profileName]; } if (profile == null) { throw new BadRequestException(string.Format("GetStreamSize: unknown profile: {0}", profileName)); } var target = new ProfileMediaItem(Guid.NewGuid().ToString(), profile, false); var output = new WebResolution(); if (target.IsImage) { output.Height = target.Image.Height ?? 0; output.Width = target.Image.Width ?? 0; } else if (target.IsVideo) { output.Height = target.Image.Height ?? 0; output.Width = target.Image.Width ?? 0; } return(Task.FromResult(output)); }