public static async Task <WebStringResult> ProcessAsync(IOwinContext context, string identifier, string profileName, long startPosition) { if (identifier == null) { throw new BadRequestException("InitStream: identifier is null"); } if (profileName == null) { throw new BadRequestException("InitStream: profileName is null"); } StreamItem streamItem = await StreamControl.GetStreamItemAsync(identifier); if (streamItem == null) { throw new BadRequestException(string.Format("StartStream: Unknown identifier: {0}", identifier)); } // Prefer getting profile by name EndPointProfile profile = ProfileManager.Profiles.Values.FirstOrDefault(p => p.Name == profileName); // If no ptofile with the specified name, see if there's one with a matching id if (profile == null && !ProfileManager.Profiles.TryGetValue(profileName, out profile)) { throw new BadRequestException(string.Format("StartStream: Unknown profile: {0}", profileName)); } streamItem.Profile = profile; // Seeking is not supported in live streams streamItem.StartPosition = streamItem.RequestedMediaItem is LiveTvMediaItem ? 0 : startPosition; Guid?userId = ResourceAccessUtils.GetUser(context); streamItem.TranscoderObject = new ProfileMediaItem(identifier, profile, streamItem.IsLive); await streamItem.TranscoderObject.Initialize(userId, streamItem.RequestedMediaItem, null); if (streamItem.TranscoderObject.TranscodingParameter is VideoTranscoding vt) { vt.HlsBaseUrl = string.Format("RetrieveStream?identifier={0}&hls=", identifier); } await StreamControl.StartStreamingAsync(identifier, startPosition); string filePostFix = "&file=media.ts"; if (profile.MediaTranscoding?.VideoTargets?.Any(t => t.Target.VideoContainerType == VideoContainer.Hls) ?? false) { filePostFix = "&file=manifest.m3u8"; //Must be added for some clients to work (Android mostly) } string url = GetBaseStreamUrl.GetBaseStreamURL(context) + "/MPExtended/StreamingService/stream/RetrieveStream?identifier=" + identifier + filePostFix; return(new WebStringResult { Result = url }); }
public static async Task <WebTranscodingInfo> ProcessAsync(IOwinContext context, string identifier, long?playerPosition) { if (identifier == null) { throw new BadRequestException("GetTranscodingInfo: identifier is null"); } StreamItem streamItem = await StreamControl.GetStreamItemAsync(identifier); if (streamItem == null) { throw new BadRequestException(string.Format("GetTranscodingInfo: Unknown identifier: {0}", identifier)); } return(new WebTranscodingInfo(streamItem.StreamContext as TranscodeContext)); }
public static async Task <WebStringResult> ProcessAsync(IOwinContext context, string identifier, string profileName, long startPosition, int audioId = -1, int subtitleId = -1) { if (identifier == null) { throw new BadRequestException("StartStreamWithStreamSelection: identifier is null"); } if (profileName == null) { throw new BadRequestException("StartStreamWithStreamSelection: profileName is null"); } 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("StartStreamWithStreamSelection: unknown profile: {0}", profileName)); } StreamItem streamItem = await StreamControl.GetStreamItemAsync(identifier); if (streamItem == null) { throw new BadRequestException(string.Format("StartStreamWithStreamSelection: unknown identifier: {0}", identifier)); } streamItem.Profile = profile; streamItem.StartPosition = startPosition; if (streamItem.RequestedMediaItem is LiveTvMediaItem) { streamItem.StartPosition = 0; } Guid?userId = ResourceAccessUtils.GetUser(context); streamItem.TranscoderObject = new ProfileMediaItem(identifier, profile, streamItem.IsLive); await streamItem.TranscoderObject.Initialize(userId, streamItem.RequestedMediaItem, audioId >= 0?audioId : (int?)null, subtitleId); if ((streamItem.TranscoderObject.TranscodingParameter is VideoTranscoding vt)) { vt.HlsBaseUrl = string.Format("RetrieveStream?identifier={0}&hls=", identifier); } await StreamControl.StartStreamingAsync(identifier, startPosition); string filePostFix = "&file=media.ts"; if (profile.MediaTranscoding != null && profile.MediaTranscoding.VideoTargets != null) { foreach (var target in profile.MediaTranscoding.VideoTargets) { if (target.Target.VideoContainerType == VideoContainer.Hls) { filePostFix = "&file=manifest.m3u8"; //Must be added for some clients to work (Android mostly) break; } } } string url = GetBaseStreamUrl.GetBaseStreamURL(context) + "/MPExtended/StreamingService/stream/RetrieveStream?identifier=" + identifier + filePostFix; return(new WebStringResult { Result = url }); }
public static async Task <bool> ProcessAsync(IOwinContext context, string identifier, string file, string hls) { Stream resourceStream = null; bool onlyHeaders = false; if (identifier == null) { throw new BadRequestException("RetrieveStream: Identifier is null"); } StreamItem streamItem = await StreamControl.GetStreamItemAsync(identifier); if (streamItem == null) { throw new BadRequestException("RetrieveStream: Identifier is not valid"); } if (!streamItem.IsActive) { Logger.Debug("RetrieveStream: Stream for {0} is no longer active", identifier); SetErrorStatus(context, "Stream is no longer active"); return(true); } long startPosition = streamItem.StartPosition; if (hls != null) { #region Handle segment/playlist request if (await SendSegmentAsync(hls, context, streamItem)) { return(true); } if (streamItem.ItemType != Common.WebMediaType.TV && streamItem.ItemType != Common.WebMediaType.Radio && MediaConverter.GetSegmentSequence(hls) >= 0) { long segmentRequest = MediaConverter.GetSegmentSequence(hls); if (await streamItem.RequestSegmentAsync(segmentRequest) == false) { Logger.Error("RetrieveStream: Request for segment file {0} canceled", hls); SetErrorStatus(context, "Request for segment file canceled"); return(true); } startPosition = segmentRequest * MediaConverter.HLSSegmentTimeInSeconds; } else { Logger.Error("RetrieveStream: Unable to find segment file {0}", hls); SetErrorStatus(context, "Unable to find segment file"); return(true); } #endregion } #region Init response // Grab the mimetype from the media item and set the Content Type header. if (streamItem.TranscoderObject.Mime == null) { throw new InternalServerException("RetrieveStream: Media item has bad mime type, re-import media item"); } context.Response.ContentType = streamItem.TranscoderObject.Mime; TransferMode mediaTransferMode = TransferMode.Interactive; if (streamItem.TranscoderObject.IsVideo || streamItem.TranscoderObject.IsAudio) { mediaTransferMode = TransferMode.Streaming; } StreamMode requestedStreamingMode = StreamMode.Normal; string byteRangesSpecifier = context.Request.Headers["Range"]; if (byteRangesSpecifier != null) { Logger.Debug("RetrieveStream: Requesting range {1} for mediaitem {0}", streamItem.RequestedMediaItem.MediaItemId, byteRangesSpecifier); requestedStreamingMode = StreamMode.ByteRange; } #endregion #region Process range request if (streamItem.StreamContext is TranscodeContext tc && (streamItem.TranscoderObject.IsTranscoding == false || (tc.Partial == false && tc.TargetFileSize > 0 && tc.TargetFileSize > streamItem.TranscoderObject.Metadata.Size))) { streamItem.TranscoderObject.Metadata.Size = tc.TargetFileSize; } IList <Range> ranges = null; Range timeRange = new Range(startPosition, 0); Range byteRange = null; if (requestedStreamingMode == StreamMode.ByteRange) { long lSize = GetStreamSize(streamItem.TranscoderObject); ranges = ParseByteRanges(byteRangesSpecifier, lSize); if (ranges == null || ranges.Count == 0) { //At least 1 range is needed context.Response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable; context.Response.ContentLength = 0; context.Response.ContentType = null; Logger.Debug("RetrieveStream: Sending headers: " + string.Join(";", context.Response.Headers.Select(x => x.Key + "=" + x.Value).ToArray())); return(true); } } if (streamItem.TranscoderObject.IsSegmented == false && streamItem.TranscoderObject.IsTranscoding == true && mediaTransferMode == TransferMode.Streaming) { if ((requestedStreamingMode == StreamMode.ByteRange) && (ranges == null || ranges.Count == 0)) { //At least 1 range is needed context.Response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable; context.Response.ContentLength = 0; context.Response.ContentType = null; Logger.Debug("RetrieveStream: Sending headers: " + string.Join(";", context.Response.Headers.Select(x => x.Key + "=" + x.Value).ToArray())); return(true); } } if (ranges != null && ranges.Count > 0) { //Use only last range if (requestedStreamingMode == StreamMode.ByteRange) { byteRange = ranges[ranges.Count - 1]; timeRange = ConvertToTimeRange(byteRange, streamItem.TranscoderObject); } } #endregion #region Handle ready file request // The initial request? if (resourceStream == null && streamItem.StreamContext != null && (streamItem.StartPosition == timeRange.From || file != null)) { resourceStream = streamItem.StreamContext.Stream; } if (resourceStream == null && streamItem.TranscoderObject.IsTranscoded == false) { var streamContext = await StreamControl.StartOriginalFileStreamingAsync(identifier); resourceStream = streamContext?.Stream; } #endregion #region Handle transcode bool partialResource = false; if (resourceStream == null) { Logger.Debug("RetrieveStream: Attempting to start streaming for mediaitem {0} in mode {1}", streamItem.RequestedMediaItem.MediaItemId, requestedStreamingMode.ToString()); var transcode = await StreamControl.StartStreamingAsync(identifier, timeRange.From); partialResource = transcode?.Partial ?? false; resourceStream = transcode?.Stream; //Send any HLS file originally requested if (hls != null && await SendSegmentAsync(hls, context, streamItem)) { return(true); } } if (!streamItem.TranscoderObject.IsStreamable) { Logger.Debug("RetrieveStream: Live transcoding of mediaitem {0} is not possible because of media container", streamItem.RequestedMediaItem.MediaItemId); } #endregion #region Finish and send response // HTTP/1.1 RFC2616 section 14.25 'If-Modified-Since' if (!string.IsNullOrEmpty(context.Request.Headers["If-Modified-Since"])) { DateTime lastRequest = DateTime.Parse(context.Request.Headers["If-Modified-Since"]); if (lastRequest.CompareTo(streamItem.TranscoderObject.LastUpdated) <= 0) { context.Response.StatusCode = (int)HttpStatusCode.NotModified; } } // HTTP/1.1 RFC2616 section 14.29 'Last-Modified' context.Response.Headers["Last-Modified"] = streamItem.TranscoderObject.LastUpdated.ToUniversalTime().ToString("r"); if (resourceStream == null) { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.Response.ReasonPhrase = "No resource stream found"; context.Response.ContentLength = 0; context.Response.ContentType = null; return(true); } using (await streamItem.RequestBusyLockAsync(SendDataCancellation.Token)) { // TODO: fix method onlyHeaders = context.Request.Method == "HEAD" || context.Response.StatusCode == (int)HttpStatusCode.NotModified; if (requestedStreamingMode == StreamMode.ByteRange) { if (ranges != null && ranges.Count > 0) { // We only support last range await SendByteRangeAsync(context, resourceStream, streamItem.TranscoderObject, streamItem.Profile, ranges[ranges.Count - 1], onlyHeaders, partialResource, mediaTransferMode); return(true); } } Logger.Debug("RetrieveStream: Sending file header only: {0}", onlyHeaders.ToString()); await SendWholeFileAsync(context, resourceStream, streamItem.TranscoderObject, streamItem.Profile, onlyHeaders, partialResource, mediaTransferMode); } #endregion return(true); }