Browse Source

rework active recordings

Luke Pulverenti 7 years ago
parent
commit
afd94407f9

+ 15 - 0
Emby.Server.Implementations/Dto/DtoService.cs

@@ -411,6 +411,21 @@ namespace Emby.Server.Implementations.Dto
             {
                 liveTvManager.AddInfoToRecordingDto(item, dto, user);
             }
+            else
+            {
+                var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path);
+                if (activeRecording != null)
+                {
+                    dto.Type = "Recording";
+                    dto.CanDownload = false;
+                    if (!string.IsNullOrWhiteSpace(dto.SeriesName))
+                    {
+                        dto.EpisodeTitle = dto.Name;
+                        dto.Name = dto.SeriesName;
+                    }
+                    liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
+                }
+            }
 
             return dto;
         }

+ 96 - 12
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -830,6 +830,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             existingTimer.IsKids = updatedTimer.IsKids;
             existingTimer.IsNews = updatedTimer.IsNews;
             existingTimer.IsMovie = updatedTimer.IsMovie;
+            existingTimer.IsSeries = updatedTimer.IsSeries;
+            existingTimer.IsLive = updatedTimer.IsLive;
+            existingTimer.IsPremiere = updatedTimer.IsPremiere;
             existingTimer.IsProgramSeries = updatedTimer.IsProgramSeries;
             existingTimer.IsRepeat = updatedTimer.IsRepeat;
             existingTimer.IsSports = updatedTimer.IsSports;
@@ -861,7 +864,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
         public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
         {
-            return _activeRecordings.Values.ToList().Select(GetRecordingInfo).ToList();
+            return new List<RecordingInfo>();
+            //return _activeRecordings.Values.ToList().Select(GetRecordingInfo).ToList();
         }
 
         public string GetActiveRecordingPath(string id)
@@ -875,6 +879,28 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             return null;
         }
 
+        public IEnumerable<ActiveRecordingInfo> GetAllActiveRecordings()
+        {
+            return _activeRecordings.Values;
+        }
+
+        public ActiveRecordingInfo GetActiveRecordingInfo(string path)
+        {
+            if (string.IsNullOrWhiteSpace(path))
+            {
+                return null;
+            }
+
+            foreach (var recording in _activeRecordings.Values)
+            {
+                if (string.Equals(recording.Path, path, StringComparison.Ordinal))
+                {
+                    return recording;
+                }
+            }
+            return null;
+        }
+
         private RecordingInfo GetRecordingInfo(ActiveRecordingInfo info)
         {
             var timer = info.Timer;
@@ -1245,6 +1271,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             throw new FileNotFoundException();
         }
 
+        public async Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
+        {
+            var stream = new MediaSourceInfo
+            {
+                Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
+                Id = info.Id,
+                SupportsDirectPlay = false,
+                SupportsDirectStream = true,
+                SupportsTranscoding = true,
+                IsInfiniteStream = true,
+                RequiresOpening = false,
+                RequiresClosing = false,
+                Protocol = MediaBrowser.Model.MediaInfo.MediaProtocol.Http,
+                BufferMs = 0,
+                IgnoreDts = true,
+                IgnoreIndex = true
+            };
+
+            var isAudio = false;
+            await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
+
+            return new List<MediaSourceInfo>
+            {
+                stream
+            };
+        }
+
         public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
         {
             // Ignore the consumer id
@@ -1327,7 +1380,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 var activeRecordingInfo = new ActiveRecordingInfo
                 {
                     CancellationTokenSource = new CancellationTokenSource(),
-                    Timer = timer
+                    Timer = timer,
+                    Id = timer.Id
                 };
 
                 if (_activeRecordings.TryAdd(timer.Id, activeRecordingInfo))
@@ -1493,7 +1547,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
                 recordPath = EnsureFileUnique(recordPath, timer.Id);
 
-                _libraryManager.RegisterIgnoredPath(recordPath);
                 _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
                 _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(recordPath));
                 activeRecordingInfo.Path = recordPath;
@@ -1512,6 +1565,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                     _timerProvider.AddOrUpdate(timer, false);
 
                     SaveRecordingMetadata(timer, recordPath, seriesPath);
+                    TriggerRefresh(recordPath);
                     EnforceKeepUpTo(timer, seriesPath);
                 };
 
@@ -1543,7 +1597,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 }
             }
 
-            _libraryManager.UnRegisterIgnoredPath(recordPath);
+            TriggerRefresh(recordPath);
             _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true);
 
             ActiveRecordingInfo removed;
@@ -1574,6 +1628,44 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             OnRecordingStatusChanged();
         }
 
+        private void TriggerRefresh(string path)
+        {
+            var item = GetAffectedBaseItem(_fileSystem.GetDirectoryName(path));
+
+            if (item != null)
+            {
+                item.ChangedExternally();
+            }
+        }
+
+        private BaseItem GetAffectedBaseItem(string path)
+        {
+            BaseItem item = null;
+
+            while (item == null && !string.IsNullOrEmpty(path))
+            {
+                item = _libraryManager.FindByPath(path, null);
+
+                path = _fileSystem.GetDirectoryName(path);
+            }
+
+            if (item != null)
+            {
+                // If the item has been deleted find the first valid parent that still exists
+                while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
+                {
+                    item = item.GetParent();
+
+                    if (item == null)
+                    {
+                        break;
+                    }
+                }
+            }
+
+            return item;
+        }
+
         private void OnRecordingStatusChanged()
         {
             EventHelper.FireEventIfNotNull(RecordingStatusChanged, this, new RecordingStatusChangedEventArgs
@@ -2621,14 +2713,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             return list;
         }
 
-        class ActiveRecordingInfo
-        {
-            public string Path { get; set; }
-            public TimerInfo Timer { get; set; }
-            public ProgramInfo Program { get; set; }
-            public CancellationTokenSource CancellationTokenSource { get; set; }
-        }
-
         private const int TunerDiscoveryDurationMs = 3000;
 
         public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)

+ 4 - 0
Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs

@@ -58,6 +58,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             timerInfo.OriginalAirDate = programInfo.OriginalAirDate;
             timerInfo.IsProgramSeries = programInfo.IsSeries;
 
+            timerInfo.IsSeries = programInfo.IsSeries;
+            timerInfo.IsLive = programInfo.IsLive;
+            timerInfo.IsPremiere = programInfo.IsPremiere;
+
             timerInfo.HomePageUrl = programInfo.HomePageUrl;
             timerInfo.CommunityRating = programInfo.CommunityRating;
             timerInfo.Overview = programInfo.Overview;

+ 84 - 45
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.LiveTv
 
         private readonly LiveTvDtoService _tvDtoService;
 
-        private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
+        private ILiveTvService[] _services = new ILiveTvService[] { };
 
         private readonly SemaphoreSlim _refreshRecordingsLock = new SemaphoreSlim(1, 1);
 
@@ -124,7 +124,7 @@ namespace Emby.Server.Implementations.LiveTv
         /// <param name="listingProviders">The listing providers.</param>
         public void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders)
         {
-            _services.AddRange(services);
+            _services = services.ToArray();
             _tunerHosts.AddRange(tunerHosts);
             _listingProviders.AddRange(listingProviders);
 
@@ -1221,9 +1221,9 @@ namespace Emby.Server.Implementations.LiveTv
             await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
 
             var numComplete = 0;
-            double progressPerService = _services.Count == 0
+            double progressPerService = _services.Length == 0
                 ? 0
-                : 1 / _services.Count;
+                : 1 / _services.Length;
 
             var newChannelIdList = new List<Guid>();
             var newProgramIdList = new List<Guid>();
@@ -1255,7 +1255,7 @@ namespace Emby.Server.Implementations.LiveTv
 
                 numComplete++;
                 double percent = numComplete;
-                percent /= _services.Count;
+                percent /= _services.Length;
 
                 progress.Report(100 * percent);
             }
@@ -1561,11 +1561,6 @@ namespace Emby.Server.Implementations.LiveTv
                 return new QueryResult<BaseItem>();
             }
 
-            if ((query.IsInProgress ?? false))
-            {
-                return new QueryResult<BaseItem>();
-            }
-
             var folderIds = EmbyTV.EmbyTV.Current.GetRecordingFolders()
                 .SelectMany(i => i.Locations)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
@@ -1577,13 +1572,10 @@ namespace Emby.Server.Implementations.LiveTv
 
             var excludeItemTypes = new List<string>();
 
-            if (!query.IsInProgress.HasValue)
-            {
-                folderIds.Add(internalLiveTvFolderId);
+            folderIds.Add(internalLiveTvFolderId);
 
-                excludeItemTypes.Add(typeof(LiveTvChannel).Name);
-                excludeItemTypes.Add(typeof(LiveTvProgram).Name);
-            }
+            excludeItemTypes.Add(typeof(LiveTvChannel).Name);
+            excludeItemTypes.Add(typeof(LiveTvProgram).Name);
 
             if (folderIds.Count == 0)
             {
@@ -1632,6 +1624,19 @@ namespace Emby.Server.Implementations.LiveTv
                 }
             }
 
+            if ((query.IsInProgress ?? false))
+            {
+                // TODO: filter
+                var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
+                var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
+
+                return new QueryResult<BaseItem>
+                {
+                    Items = items,
+                    TotalRecordCount = items.Length
+                };
+            }
+
             return _libraryManager.GetItemsResult(new InternalItemsQuery(user)
             {
                 MediaTypes = new[] { MediaType.Video },
@@ -1659,11 +1664,6 @@ namespace Emby.Server.Implementations.LiveTv
                 return new QueryResult<BaseItemDto>();
             }
 
-            if (_services.Count > 1)
-            {
-                return new QueryResult<BaseItemDto>();
-            }
-
             if (user == null || (query.IsInProgress ?? false))
             {
                 return new QueryResult<BaseItemDto>();
@@ -1722,13 +1722,9 @@ namespace Emby.Server.Implementations.LiveTv
 
             var folder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
 
-            if (_services.Count == 1 && (!query.IsInProgress.HasValue || !query.IsInProgress.Value) && (!query.IsLibraryItem.HasValue || query.IsLibraryItem.Value))
+            // TODO: Figure out how to merge emby recordings + service recordings
+            if (_services.Length == 1)
             {
-                if (!query.IsInProgress.HasValue)
-                {
-                    await RefreshRecordings(folder.Id, cancellationToken).ConfigureAwait(false);
-                }
-
                 return GetEmbyRecordings(query, options, folder.Id, user);
             }
 
@@ -1920,6 +1916,11 @@ namespace Emby.Server.Implementations.LiveTv
             await AddRecordingInfo(programTuples, CancellationToken.None).ConfigureAwait(false);
         }
 
+        public ActiveRecordingInfo GetActiveRecordingInfo(string path)
+        {
+            return EmbyTV.EmbyTV.Current.GetActiveRecordingInfo(path);
+        }
+
         public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, User user = null)
         {
             var recording = (ILiveTvRecording)item;
@@ -1949,27 +1950,72 @@ namespace Emby.Server.Implementations.LiveTv
             dto.IsKids = info.IsKids;
             dto.IsPremiere = info.IsPremiere;
 
-            dto.CanDelete = user == null
-                ? recording.CanDelete()
-                : recording.CanDelete(user);
-
-            if (dto.MediaSources == null)
+            if (info.Status == RecordingStatus.InProgress && info.EndDate.HasValue)
             {
-                dto.MediaSources = recording.GetMediaSources(true);
+                var now = DateTime.UtcNow.Ticks;
+                var start = info.StartDate.Ticks;
+                var end = info.EndDate.Value.Ticks;
+
+                var pct = now - start;
+                pct /= end;
+                pct *= 100;
+                dto.CompletionPercentage = pct;
             }
 
-            if (dto.MediaStreams == null)
+            if (channel != null)
             {
-                dto.MediaStreams = dto.MediaSources.SelectMany(i => i.MediaStreams).ToArray();
+                dto.ChannelName = channel.Name;
+
+                if (channel.HasImage(ImageType.Primary))
+                {
+                    dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
+                }
             }
+        }
 
-            if (info.Status == RecordingStatus.InProgress && info.EndDate.HasValue)
+        public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User user = null)
+        {
+            var service = EmbyTV.EmbyTV.Current;
+
+            var info = activeRecordingInfo.Timer;
+
+            var channel = string.IsNullOrWhiteSpace(info.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, info.ChannelId));
+
+            dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
+                ? null
+                : _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
+
+            dto.TimerId = string.IsNullOrEmpty(info.Id)
+                ? null
+                : _tvDtoService.GetInternalTimerId(service.Name, info.Id).ToString("N");
+
+            var startDate = info.StartDate;
+            var endDate = info.EndDate;
+
+            dto.StartDate = startDate;
+            dto.EndDate = endDate;
+            dto.Status = info.Status.ToString();
+            dto.IsRepeat = info.IsRepeat;
+            dto.EpisodeTitle = info.EpisodeTitle;
+            dto.IsMovie = info.IsMovie;
+            dto.IsSeries = info.IsSeries;
+            dto.IsSports = info.IsSports;
+            dto.IsLive = info.IsLive;
+            dto.IsNews = info.IsNews;
+            dto.IsKids = info.IsKids;
+            dto.IsPremiere = info.IsPremiere;
+
+            if (info.Status == RecordingStatus.InProgress)
             {
+                startDate = info.StartDate.AddSeconds(0 - info.PrePaddingSeconds);
+                endDate = info.EndDate.AddSeconds(info.PostPaddingSeconds);
+
                 var now = DateTime.UtcNow.Ticks;
-                var start = info.StartDate.Ticks;
-                var end = info.EndDate.Value.Ticks;
+                var start = startDate.Ticks;
+                var end = endDate.Ticks;
 
                 var pct = now - start;
+
                 pct /= end;
                 pct *= 100;
                 dto.CompletionPercentage = pct;
@@ -2098,7 +2144,6 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (service is EmbyTV.EmbyTV)
             {
-                // We can't trust that we'll be able to direct stream it through emby server,  no matter what the provider says
                 return service.DeleteRecordingAsync(GetItemExternalId(recording), CancellationToken.None);
             }
 
@@ -2348,7 +2393,6 @@ namespace Emby.Server.Implementations.LiveTv
             var currentChannelsDict = new Dictionary<string, BaseItemDto>();
 
             var addCurrentProgram = options.AddCurrentProgram;
-            var addMediaSources = options.Fields.Contains(ItemFields.MediaSources);
             var addServiceName = options.Fields.Contains(ItemFields.ServiceName);
 
             foreach (var tuple in tuples)
@@ -2367,11 +2411,6 @@ namespace Emby.Server.Implementations.LiveTv
 
                 currentChannelsDict[dto.Id] = dto;
 
-                if (addMediaSources)
-                {
-                    dto.MediaSources = channel.GetMediaSources(true);
-                }
-
                 if (addCurrentProgram)
                 {
                     var channelIdString = channel.Id.ToString("N");

+ 16 - 6
Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs

@@ -43,9 +43,11 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (baseItem.SourceType == SourceType.LiveTV)
             {
-                if (string.IsNullOrWhiteSpace(baseItem.Path))
+                var activeRecordingInfo = _liveTvManager.GetActiveRecordingInfo(item.Path);
+
+                if (string.IsNullOrWhiteSpace(baseItem.Path) || activeRecordingInfo != null)
                 {
-                    return GetMediaSourcesInternal(item, cancellationToken);
+                    return GetMediaSourcesInternal(item, activeRecordingInfo, cancellationToken);
                 }
             }
 
@@ -56,7 +58,7 @@ namespace Emby.Server.Implementations.LiveTv
         private const char StreamIdDelimeter = '_';
         private const string StreamIdDelimeterString = "_";
 
-        private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(IHasMediaSources item, CancellationToken cancellationToken)
+        private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(IHasMediaSources item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
         {
             IEnumerable<MediaSourceInfo> sources;
 
@@ -67,12 +69,20 @@ namespace Emby.Server.Implementations.LiveTv
                 if (item is ILiveTvRecording)
                 {
                     sources = await _liveTvManager.GetRecordingMediaSources(item, cancellationToken)
-                                .ConfigureAwait(false);
+                        .ConfigureAwait(false);
                 }
                 else
                 {
-                    sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken)
-                                .ConfigureAwait(false);
+                    if (activeRecordingInfo != null)
+                    {
+                        sources = await EmbyTV.EmbyTV.Current.GetRecordingStreamMediaSources(activeRecordingInfo, cancellationToken)
+                            .ConfigureAwait(false);
+                    }
+                    else
+                    {
+                        sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken)
+                            .ConfigureAwait(false);
+                    }
                 }
             }
             catch (NotImplementedException)

+ 1 - 1
MediaBrowser.Controller/Entities/IHasMediaSources.cs

@@ -4,7 +4,7 @@ using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public interface IHasMediaSources : IHasUserData
+    public interface IHasMediaSources : IHasMetadata
     {
         /// <summary>
         /// Gets the media sources.

+ 38 - 1
MediaBrowser.Controller/Entities/Video.cs

@@ -160,7 +160,7 @@ namespace MediaBrowser.Controller.Entities
 
         public string[] GetPlayableStreamFileNames()
         {
-            return GetPlayableStreamFiles().Select(System.IO.Path.GetFileName).ToArray(); 
+            return GetPlayableStreamFiles().Select(System.IO.Path.GetFileName).ToArray();
         }
 
         /// <summary>
@@ -234,6 +234,35 @@ namespace MediaBrowser.Controller.Entities
             return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
         }
 
+        [IgnoreDataMember]
+        public override SourceType SourceType
+        {
+            get
+            {
+                if (IsActiveRecording())
+                {
+                    return SourceType.LiveTV;
+                }
+
+                return base.SourceType;
+            }
+        }
+
+        protected bool IsActiveRecording()
+        {
+            return LiveTvManager.GetActiveRecordingInfo(Path) != null;
+        }
+
+        public override bool CanDelete()
+        {
+            if (IsActiveRecording())
+            {
+                return false;
+            }
+
+            return base.CanDelete();
+        }
+
         [IgnoreDataMember]
         protected virtual bool EnableDefaultVideoUserDataKeys
         {
@@ -616,6 +645,14 @@ namespace MediaBrowser.Controller.Entities
             var list = GetAllVideosForMediaSources();
             var result = list.Select(i => GetVersionInfo(enablePathSubstitution, i.Item1, i.Item2)).ToList();
 
+            if (IsActiveRecording())
+            {
+                foreach (var mediaSource in result)
+                {
+                    mediaSource.Type = MediaSourceType.Placeholder;
+                }
+            }
+
             return result.OrderBy(i =>
             {
                 if (i.VideoType == VideoType.VideoFile)

+ 4 - 0
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -384,5 +384,9 @@ namespace MediaBrowser.Controller.LiveTv
 
         string GetEmbyTvActiveRecordingPath(string id);
         Task<LiveStream> GetEmbyTvLiveStream(string id);
+
+        ActiveRecordingInfo GetActiveRecordingInfo(string path);
+
+        void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User user = null);
     }
 }

+ 9 - 0
MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs

@@ -36,4 +36,13 @@ namespace MediaBrowser.Controller.LiveTv
         DateTime? EndDate { get; set; }
         DateTime DateCreated { get; set; }
     }
+
+    public class ActiveRecordingInfo
+    {
+        public string Id { get; set; }
+        public string Path { get; set; }
+        public TimerInfo Timer { get; set; }
+        public ProgramInfo Program { get; set; }
+        public CancellationTokenSource CancellationTokenSource { get; set; }
+    }
 }

+ 3 - 0
MediaBrowser.Controller/LiveTv/TimerInfo.cs

@@ -109,6 +109,9 @@ namespace MediaBrowser.Controller.LiveTv
         public bool IsKids { get; set; }
         public bool IsSports { get; set; }
         public bool IsNews { get; set; }
+        public bool IsSeries { get; set; }
+        public bool IsLive { get; set; }
+        public bool IsPremiere { get; set; }
         public int? ProductionYear { get; set; }
         public string EpisodeTitle { get; set; }
         public DateTime? OriginalAirDate { get; set; }