internal static WebTVSeasonBasic TVSeasonBasic(IOwinContext context, MediaItem item, Guid?showId = null) { Guid? user = ResourceAccessUtils.GetUser(context); ISet <Guid> necessaryMIATypespisodes = new HashSet <Guid>(); necessaryMIATypespisodes.Add(MediaAspect.ASPECT_ID); necessaryMIATypespisodes.Add(EpisodeAspect.ASPECT_ID); IFilter unwatchedEpisodeFilter = BooleanCombinationFilter.CombineFilters(BooleanOperator.And, new RelationshipFilter(EpisodeAspect.ROLE_EPISODE, SeasonAspect.ROLE_SEASON, item.MediaItemId), new RelationalUserDataFilter(user.Value, UserDataKeysKnown.KEY_PLAY_PERCENTAGE, RelationalOperator.LT, UserDataKeysKnown.GetSortablePlayPercentageString(100), true)); int unwatchedCount = MediaLibraryAccess.CountMediaItems(context, necessaryMIATypespisodes, unwatchedEpisodeFilter); GetShowId(item, ref showId); var mediaAspect = item.GetAspect(MediaAspect.Metadata); var seasonAspect = item.GetAspect(SeasonAspect.Metadata); var importerAspect = item.GetAspect(ImporterAspect.Metadata); DateTime?firstAired = mediaAspect.GetAttributeValue <DateTime?>(MediaAspect.ATTR_RECORDINGTIME); return(new WebTVSeasonBasic { Title = mediaAspect.GetAttributeValue <string>(MediaAspect.ATTR_TITLE), Id = item.MediaItemId.ToString(), ShowId = showId.HasValue ? showId.Value.ToString() : null, SeasonNumber = seasonAspect.GetAttributeValue <int>(SeasonAspect.ATTR_SEASON), EpisodeCount = seasonAspect.GetAttributeValue <int>(SeasonAspect.ATTR_AVAILABLE_EPISODES), UnwatchedEpisodeCount = unwatchedCount, DateAdded = importerAspect.GetAttributeValue <DateTime>(ImporterAspect.ATTR_DATEADDED), Year = firstAired.HasValue ? firstAired.Value.Year : 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 }); }
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"); } }
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 }); }