Пример #1
0
        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
            });
        }
Пример #2
0
        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));
        }
Пример #3
0
        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
            });
        }
Пример #4
0
        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);
        }