aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/LiveTv
diff options
context:
space:
mode:
authorLuke <luke.pulverenti@gmail.com>2017-08-27 13:34:55 -0400
committerGitHub <noreply@github.com>2017-08-27 13:34:55 -0400
commitf3ee129bd9e88e8768221ba176bc8356f9b240e3 (patch)
tree72dc082f77629e8d70f764d175433b40ac4c02d0 /Emby.Server.Implementations/LiveTv
parent4f8684a16b1102056bd2b527dcbd585b78dd8000 (diff)
parentfa6bec94b59cf850246c5d0757b9279d080643d7 (diff)
Merge pull request #2847 from MediaBrowser/beta
Beta
Diffstat (limited to 'Emby.Server.Implementations/LiveTv')
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs165
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs31
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs51
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs228
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs22
-rw-r--r--Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs128
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs18
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs50
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs82
11 files changed, 399 insertions, 382 deletions
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 99b5558a2d..2e12f46bf9 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -208,7 +208,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
continue;
}
- if (virtualFolder.Locations.Count == 1)
+ if (virtualFolder.Locations.Length == 1)
{
// remove entire virtual folder
try
@@ -458,7 +458,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return GetEpgChannelFromTunerChannel(info, tunerChannel, epgChannels);
}
- private string GetMappedChannel(string channelId, List<NameValuePair> mappings)
+ private string GetMappedChannel(string channelId, NameValuePair[] mappings)
{
foreach (NameValuePair mapping in mappings)
{
@@ -472,10 +472,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private ChannelInfo GetEpgChannelFromTunerChannel(ListingsProviderInfo info, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
{
- return GetEpgChannelFromTunerChannel(info.ChannelMappings.ToList(), tunerChannel, epgChannels);
+ return GetEpgChannelFromTunerChannel(info.ChannelMappings, tunerChannel, epgChannels);
}
- public ChannelInfo GetEpgChannelFromTunerChannel(List<NameValuePair> mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
+ public ChannelInfo GetEpgChannelFromTunerChannel(NameValuePair[] mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
{
if (!string.IsNullOrWhiteSpace(tunerChannel.Id))
{
@@ -607,20 +607,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var timer = _timerProvider.GetTimer(timerId);
if (timer != null)
{
+ timer.Status = RecordingStatus.Cancelled;
+
if (string.IsNullOrWhiteSpace(timer.SeriesTimerId) || isSeriesCancelled)
{
_timerProvider.Delete(timer);
}
else
{
- timer.Status = RecordingStatus.Cancelled;
_timerProvider.AddOrUpdate(timer, false);
}
}
ActiveRecordingInfo activeRecordingInfo;
if (_activeRecordings.TryGetValue(timerId, out activeRecordingInfo))
- {
+ {
+ activeRecordingInfo.Timer = timer;
activeRecordingInfo.CancellationTokenSource.Cancel();
}
}
@@ -830,6 +832,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 +866,7 @@ 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>();
}
public string GetActiveRecordingPath(string id)
@@ -875,49 +880,31 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return null;
}
- private RecordingInfo GetRecordingInfo(ActiveRecordingInfo info)
- {
- var timer = info.Timer;
- var program = info.Program;
-
- var result = new RecordingInfo
- {
- ChannelId = timer.ChannelId,
- CommunityRating = timer.CommunityRating,
- DateLastUpdated = DateTime.UtcNow,
- EndDate = timer.EndDate,
- EpisodeTitle = timer.EpisodeTitle,
- Genres = timer.Genres,
- Id = "recording" + timer.Id,
- IsKids = timer.IsKids,
- IsMovie = timer.IsMovie,
- IsNews = timer.IsNews,
- IsRepeat = timer.IsRepeat,
- IsSeries = timer.IsProgramSeries,
- IsSports = timer.IsSports,
- Name = timer.Name,
- OfficialRating = timer.OfficialRating,
- OriginalAirDate = timer.OriginalAirDate,
- Overview = timer.Overview,
- ProgramId = timer.ProgramId,
- SeriesTimerId = timer.SeriesTimerId,
- StartDate = timer.StartDate,
- Status = RecordingStatus.InProgress,
- TimerId = timer.Id
- };
+ public IEnumerable<ActiveRecordingInfo> GetAllActiveRecordings()
+ {
+ return _activeRecordings.Values.Where(i => i.Timer.Status == RecordingStatus.InProgress && !i.CancellationTokenSource.IsCancellationRequested);
+ }
- if (program != null)
+ public ActiveRecordingInfo GetActiveRecordingInfo(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path))
{
- result.Audio = program.Audio;
- result.ImagePath = program.ImagePath;
- result.ImageUrl = program.ImageUrl;
- result.IsHD = program.IsHD;
- result.IsLive = program.IsLive;
- result.IsPremiere = program.IsPremiere;
- result.ShowId = program.ShowId;
+ return null;
}
- return result;
+ foreach (var recording in _activeRecordings.Values)
+ {
+ if (string.Equals(recording.Path, path, StringComparison.Ordinal) && !recording.CancellationTokenSource.IsCancellationRequested)
+ {
+ var timer = recording.Timer;
+ if (timer.Status != RecordingStatus.InProgress)
+ {
+ return null;
+ }
+ return recording;
+ }
+ }
+ return null;
}
public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
@@ -1245,6 +1232,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 +1341,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 +1508,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 +1526,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_timerProvider.AddOrUpdate(timer, false);
SaveRecordingMetadata(timer, recordPath, seriesPath);
+ TriggerRefresh(recordPath);
EnforceKeepUpTo(timer, seriesPath);
};
@@ -1543,7 +1558,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- _libraryManager.UnRegisterIgnoredPath(recordPath);
+ TriggerRefresh(recordPath);
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, true);
ActiveRecordingInfo removed;
@@ -1574,6 +1589,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
@@ -2591,7 +2644,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
list.Add(new VirtualFolderInfo
{
- Locations = new List<string> { defaultFolder },
+ Locations = new string[] { defaultFolder },
Name = defaultName
});
}
@@ -2601,7 +2654,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
list.Add(new VirtualFolderInfo
{
- Locations = new List<string> { customPath },
+ Locations = new string[] { customPath },
Name = "Recorded Movies",
CollectionType = CollectionType.Movies
});
@@ -2612,7 +2665,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
list.Add(new VirtualFolderInfo
{
- Locations = new List<string> { customPath },
+ Locations = new string[] { customPath },
Name = "Recorded Shows",
CollectionType = CollectionType.TvShows
});
@@ -2621,14 +2674,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)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index 94be4a02ef..b5de6ef01e 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/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;
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index e12acf1408..b7cfdea1b5 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -247,7 +247,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ProgramAudio audioType = ProgramAudio.Stereo;
bool repeat = programInfo.@new == null;
- string newID = programInfo.programID + "T" + startAt.Ticks + "C" + channelId;
+
+ var programId = programInfo.programID ?? string.Empty;
+
+ string newID = programId + "T" + startAt.Ticks + "C" + channelId;
if (programInfo.audioProperties != null)
{
@@ -300,7 +303,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Etag = programInfo.md5
};
- var showId = programInfo.programID ?? string.Empty;
+ var showId = programId;
if (!info.IsSeries)
{
@@ -339,11 +342,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (details.descriptions != null)
{
- if (details.descriptions.description1000 != null)
+ if (details.descriptions.description1000 != null && details.descriptions.description1000.Count > 0)
{
info.Overview = details.descriptions.description1000[0].description;
}
- else if (details.descriptions.description100 != null)
+ else if (details.descriptions.description100 != null && details.descriptions.description100.Count > 0)
{
info.Overview = details.descriptions.description100[0].description;
}
@@ -351,16 +354,24 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (info.IsSeries)
{
- info.SeriesId = programInfo.programID.Substring(0, 10);
+ info.SeriesId = programId.Substring(0, 10);
if (details.metadata != null)
{
- var gracenote = details.metadata.Find(x => x.Gracenote != null).Gracenote;
- info.SeasonNumber = gracenote.season;
-
- if (gracenote.episode > 0)
+ foreach (var metadataProgram in details.metadata)
{
- info.EpisodeNumber = gracenote.episode;
+ var gracenote = metadataProgram.Gracenote;
+ if (gracenote != null)
+ {
+ info.SeasonNumber = gracenote.season;
+
+ if (gracenote.episode > 0)
+ {
+ info.EpisodeNumber = gracenote.episode;
+ }
+
+ break;
+ }
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 619d2378df..eff2909fdb 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -15,6 +15,8 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.LiveTv
{
@@ -110,7 +112,7 @@ namespace Emby.Server.Implementations.LiveTv
PostPaddingSeconds = info.PostPaddingSeconds,
IsPostPaddingRequired = info.IsPostPaddingRequired,
IsPrePaddingRequired = info.IsPrePaddingRequired,
- Days = info.Days,
+ Days = info.Days.ToArray(),
Priority = info.Priority,
RecordAnyChannel = info.RecordAnyChannel,
RecordAnyTime = info.RecordAnyTime,
@@ -135,7 +137,7 @@ namespace Emby.Server.Implementations.LiveTv
dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
}
- dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days);
+ dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days.ToArray(info.Days.Count));
FillImages(dto, info.Name, info.SeriesId);
@@ -150,10 +152,7 @@ namespace Emby.Server.Implementations.LiveTv
Name = seriesName,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
- DtoOptions = new DtoOptions
- {
- Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
- }
+ DtoOptions = new DtoOptions(false)
}).FirstOrDefault();
@@ -196,10 +195,7 @@ namespace Emby.Server.Implementations.LiveTv
ExternalSeriesId = programSeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
- DtoOptions = new DtoOptions
- {
- Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
- }
+ DtoOptions = new DtoOptions(false)
}).FirstOrDefault();
@@ -248,10 +244,7 @@ namespace Emby.Server.Implementations.LiveTv
Name = seriesName,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
- DtoOptions = new DtoOptions
- {
- Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
- }
+ DtoOptions = new DtoOptions(false)
}).FirstOrDefault();
@@ -274,7 +267,7 @@ namespace Emby.Server.Implementations.LiveTv
{
try
{
- dto.ParentBackdropImageTags = new List<string>
+ dto.ParentBackdropImageTags = new string[]
{
_imageProcessor.GetImageCacheTag(librarySeries, image)
};
@@ -294,10 +287,7 @@ namespace Emby.Server.Implementations.LiveTv
Name = seriesName,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
- DtoOptions = new DtoOptions
- {
- Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
- }
+ DtoOptions = new DtoOptions(false)
}).FirstOrDefault() ?? _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -305,10 +295,7 @@ namespace Emby.Server.Implementations.LiveTv
ExternalSeriesId = programSeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
- DtoOptions = new DtoOptions
- {
- Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
- }
+ DtoOptions = new DtoOptions(false)
}).FirstOrDefault();
@@ -327,14 +314,14 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Count == 0)
+ if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Length == 0)
{
image = program.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
try
{
- dto.ParentBackdropImageTags = new List<string>
+ dto.ParentBackdropImageTags = new string[]
{
_imageProcessor.GetImageCacheTag(program, image)
};
@@ -349,24 +336,24 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- public DayPattern? GetDayPattern(List<DayOfWeek> days)
+ public DayPattern? GetDayPattern(DayOfWeek[] days)
{
DayPattern? pattern = null;
- if (days.Count > 0)
+ if (days.Length > 0)
{
- if (days.Count == 7)
+ if (days.Length == 7)
{
pattern = DayPattern.Daily;
}
- else if (days.Count == 2)
+ else if (days.Length == 2)
{
if (days.Contains(DayOfWeek.Saturday) && days.Contains(DayOfWeek.Sunday))
{
pattern = DayPattern.Weekends;
}
}
- else if (days.Count == 5)
+ else if (days.Length == 5)
{
if (days.Contains(DayOfWeek.Monday) && days.Contains(DayOfWeek.Tuesday) && days.Contains(DayOfWeek.Wednesday) && days.Contains(DayOfWeek.Thursday) && days.Contains(DayOfWeek.Friday))
{
@@ -384,7 +371,7 @@ namespace Emby.Server.Implementations.LiveTv
{
Name = info.Name,
Id = info.Id,
- Clients = info.Clients,
+ Clients = info.Clients.ToArray(),
ProgramName = info.ProgramName,
SourceType = info.SourceType,
Status = info.Status,
@@ -543,7 +530,7 @@ namespace Emby.Server.Implementations.LiveTv
PostPaddingSeconds = dto.PostPaddingSeconds,
IsPostPaddingRequired = dto.IsPostPaddingRequired,
IsPrePaddingRequired = dto.IsPrePaddingRequired,
- Days = dto.Days,
+ Days = dto.Days.ToList(),
Priority = dto.Priority,
RecordAnyChannel = dto.RecordAnyChannel,
RecordAnyTime = dto.RecordAnyTime,
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 3fbbc8390e..ac98d1043a 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/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);
@@ -558,7 +558,7 @@ namespace Emby.Server.Implementations.LiveTv
if (isNew)
{
- await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
+ _libraryManager.CreateItem(item, cancellationToken);
}
else if (forceUpdate)
{
@@ -875,7 +875,7 @@ namespace Emby.Server.Implementations.LiveTv
if (isNew)
{
- await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
+ _libraryManager.CreateItem(item, cancellationToken);
}
else if (dataChanged || info.DateLastUpdated > recording.DateLastSaved || statusChanged)
{
@@ -985,9 +985,8 @@ namespace Emby.Server.Implementations.LiveTv
var queryResult = _libraryManager.QueryItems(internalQuery);
- var returnList = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user)
+ var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user)
.ConfigureAwait(false));
- var returnArray = returnList.ToArray(returnList.Count);
var result = new QueryResult<BaseItemDto>
{
@@ -998,7 +997,7 @@ namespace Emby.Server.Implementations.LiveTv
return result;
}
- public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
+ public async Task<QueryResult<BaseItem>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
{
var user = _userManager.GetUserById(query.UserId);
@@ -1036,10 +1035,10 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- var programList = _libraryManager.QueryItems(internalQuery).Items.Cast<LiveTvProgram>().ToList();
- var totalCount = programList.Count;
+ var programList = _libraryManager.QueryItems(internalQuery).Items;
+ var totalCount = programList.Length;
- IOrderedEnumerable<LiveTvProgram> orderedPrograms = programList.OrderBy(i => i.StartDate.Date);
+ IOrderedEnumerable<LiveTvProgram> orderedPrograms = programList.Cast<LiveTvProgram>().OrderBy(i => i.StartDate.Date);
if (query.IsAiring ?? false)
{
@@ -1047,14 +1046,14 @@ namespace Emby.Server.Implementations.LiveTv
.ThenByDescending(i => GetRecommendationScore(i, user.Id, true));
}
- IEnumerable<LiveTvProgram> programs = orderedPrograms;
+ IEnumerable<BaseItem> programs = orderedPrograms;
if (query.Limit.HasValue)
{
programs = programs.Take(query.Limit.Value);
}
- var result = new QueryResult<LiveTvProgram>
+ var result = new QueryResult<BaseItem>
{
Items = programs.ToArray(),
TotalRecordCount = totalCount
@@ -1071,9 +1070,8 @@ namespace Emby.Server.Implementations.LiveTv
var user = _userManager.GetUserById(query.UserId);
- var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
+ var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
.ConfigureAwait(false));
- var returnArray = returnList.ToArray(returnList.Count);
var result = new QueryResult<BaseItemDto>
{
@@ -1223,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>();
@@ -1257,13 +1255,13 @@ namespace Emby.Server.Implementations.LiveTv
numComplete++;
double percent = numComplete;
- percent /= _services.Count;
+ percent /= _services.Length;
progress.Report(100 * percent);
}
- await CleanDatabaseInternal(newChannelIdList, new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
- await CleanDatabaseInternal(newProgramIdList, new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
+ await CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
+ await CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
@@ -1275,8 +1273,11 @@ namespace Emby.Server.Implementations.LiveTv
// Load these now which will prefetch metadata
var dtoOptions = new DtoOptions();
- dtoOptions.Fields.Remove(ItemFields.SyncInfo);
- dtoOptions.Fields.Remove(ItemFields.BasicSyncInfo);
+ var fields = dtoOptions.Fields.ToList();
+ fields.Remove(ItemFields.SyncInfo);
+ fields.Remove(ItemFields.BasicSyncInfo);
+ dtoOptions.Fields = fields.ToArray(fields.Count);
+
await GetRecordings(new RecordingQuery(), dtoOptions, cancellationToken).ConfigureAwait(false);
progress.Report(100);
@@ -1409,7 +1410,7 @@ namespace Emby.Server.Implementations.LiveTv
if (newPrograms.Count > 0)
{
- await _libraryManager.CreateItems(newPrograms, cancellationToken).ConfigureAwait(false);
+ _libraryManager.CreateItems(newPrograms, cancellationToken);
}
// TODO: Do this in bulk
@@ -1446,14 +1447,14 @@ namespace Emby.Server.Implementations.LiveTv
return new Tuple<List<Guid>, List<Guid>>(channels, programs);
}
- private async Task CleanDatabaseInternal(List<Guid> currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
+ private async Task CleanDatabaseInternal(Guid[] currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
{
var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
{
IncludeItemTypes = validTypes,
DtoOptions = new DtoOptions(false)
- }).ToList();
+ });
var numComplete = 0;
@@ -1543,7 +1544,7 @@ namespace Emby.Server.Implementations.LiveTv
var idList = await Task.WhenAll(recordingTasks).ConfigureAwait(false);
- await CleanDatabaseInternal(idList.ToList(), new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
+ await CleanDatabaseInternal(idList, new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
_lastRecordingRefreshTime = DateTime.UtcNow;
}
@@ -1560,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)
@@ -1576,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)
{
@@ -1631,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 },
@@ -1658,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>();
@@ -1701,11 +1702,9 @@ namespace Emby.Server.Implementations.LiveTv
DtoOptions = options
});
- var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
+ var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
.ConfigureAwait(false));
- var returnArray = returnList.ToArray(returnList.Count);
-
return new QueryResult<BaseItemDto>
{
Items = returnArray,
@@ -1723,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);
}
@@ -1841,7 +1836,7 @@ namespace Emby.Server.Implementations.LiveTv
};
}
- public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, List<ItemFields> fields, User user = null)
+ public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, ItemFields[] fields, User user = null)
{
var programTuples = new List<Tuple<BaseItemDto, string, string, string>>();
var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
@@ -1921,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;
@@ -1950,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).ToList();
+ 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;
@@ -1995,9 +2040,8 @@ namespace Emby.Server.Implementations.LiveTv
var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false);
- var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
+ var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
.ConfigureAwait(false));
- var returnArray = returnList.ToArray(returnList.Count);
return new QueryResult<BaseItemDto>
{
@@ -2100,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);
}
@@ -2350,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)
@@ -2369,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");
@@ -2479,7 +2516,7 @@ namespace Emby.Server.Implementations.LiveTv
var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
- info.Days = defaults.Item1.Days;
+ info.Days = defaults.Item1.Days.ToArray();
info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
@@ -2656,8 +2693,7 @@ namespace Emby.Server.Implementations.LiveTv
var series = recordings
.Where(i => i.IsSeries)
- .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase)
- .ToList();
+ .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase);
groups.AddRange(series.OrderByString(i => i.Key).Select(i => new BaseItemDto
{
@@ -2762,7 +2798,7 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- private async Task<IEnumerable<LiveTvServiceInfo>> GetServiceInfos(CancellationToken cancellationToken)
+ private async Task<LiveTvServiceInfo[]> GetServiceInfos(CancellationToken cancellationToken)
{
var tasks = Services.Select(i => GetServiceInfo(i, cancellationToken));
@@ -2806,7 +2842,7 @@ namespace Emby.Server.Implementations.LiveTv
return dto;
- }).ToList();
+ }).ToArray();
}
catch (Exception ex)
{
@@ -2822,25 +2858,24 @@ namespace Emby.Server.Implementations.LiveTv
public async Task<LiveTvInfo> GetLiveTvInfo(CancellationToken cancellationToken)
{
var services = await GetServiceInfos(CancellationToken.None).ConfigureAwait(false);
- var servicesList = services.ToList();
var info = new LiveTvInfo
{
- Services = servicesList.ToList(),
- IsEnabled = servicesList.Count > 0
+ Services = services,
+ IsEnabled = services.Length > 0
};
info.EnabledUsers = _userManager.Users
.Where(IsLiveTvEnabled)
.Select(i => i.Id.ToString("N"))
- .ToList();
+ .ToArray();
return info;
}
private bool IsLiveTvEnabled(User user)
{
- return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count > 0);
+ return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0);
}
public IEnumerable<User> GetEnabledUsers()
@@ -2880,10 +2915,13 @@ namespace Emby.Server.Implementations.LiveTv
private void RemoveFields(DtoOptions options)
{
- options.Fields.Remove(ItemFields.CanDelete);
- options.Fields.Remove(ItemFields.CanDownload);
- options.Fields.Remove(ItemFields.DisplayPreferencesId);
- options.Fields.Remove(ItemFields.Etag);
+ var fields = options.Fields.ToList();
+
+ fields.Remove(ItemFields.CanDelete);
+ fields.Remove(ItemFields.CanDownload);
+ fields.Remove(ItemFields.DisplayPreferencesId);
+ fields.Remove(ItemFields.Etag);
+ options.Fields = fields.ToArray(fields.Count);
}
public async Task<Folder> GetInternalLiveTvFolder(CancellationToken cancellationToken)
@@ -2911,12 +2949,14 @@ namespace Emby.Server.Implementations.LiveTv
var config = GetConfiguration();
- var index = config.TunerHosts.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
+ var list = config.TunerHosts.ToList();
+ var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
{
info.Id = Guid.NewGuid().ToString("N");
- config.TunerHosts.Add(info);
+ list.Add(info);
+ config.TunerHosts = list.ToArray(list.Count);
}
else
{
@@ -2948,12 +2988,14 @@ namespace Emby.Server.Implementations.LiveTv
var config = GetConfiguration();
- var index = config.ListingProviders.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
+ var list = config.ListingProviders.ToList();
+ var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
{
info.Id = Guid.NewGuid().ToString("N");
- config.ListingProviders.Add(info);
+ list.Add(info);
+ config.ListingProviders = list.ToArray(list.Count);
info.EnableNewProgramIds = true;
}
else
@@ -2972,7 +3014,7 @@ namespace Emby.Server.Implementations.LiveTv
{
var config = GetConfiguration();
- config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
+ config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
_config.SaveConfiguration("livetv", config);
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
@@ -3004,7 +3046,7 @@ namespace Emby.Server.Implementations.LiveTv
var providerChannels = await GetChannelsFromListingsProviderData(providerId, CancellationToken.None)
.ConfigureAwait(false);
- var mappings = listingsProviderInfo.ChannelMappings.ToList();
+ var mappings = listingsProviderInfo.ChannelMappings;
var tunerChannelMappings =
tunerChannels.Select(i => GetTunerChannelMapping(i, mappings, providerChannels)).ToList();
@@ -3014,7 +3056,7 @@ namespace Emby.Server.Implementations.LiveTv
return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase));
}
- public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, List<NameValuePair> mappings, List<ChannelInfo> epgChannels)
+ public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> epgChannels)
{
var result = new TunerChannelMapping
{
@@ -3078,7 +3120,7 @@ namespace Emby.Server.Implementations.LiveTv
if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase))
{
var config = GetConfiguration();
- if (config.TunerHosts.Count > 0 &&
+ if (config.TunerHosts.Length > 0 &&
config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0)
{
return Task.FromResult(new MBRegistrationRecord
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index 5436a12b8d..0e52f874d4 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/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)
diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
index 5582d8f35e..cad28c809c 100644
--- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
+++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv
public bool IsHidden
{
- get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count == 0; }
+ get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0; }
}
public bool IsEnabled
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 2c12f4ca15..eac8455791 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -25,12 +25,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
private readonly IEnvironmentInfo _environment;
+ private readonly INetworkManager _networkManager;
- public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
+ public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment, INetworkManager networkManager) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_environment = environment;
+ _networkManager = networkManager;
}
public override string Type
@@ -38,7 +40,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
get { return "m3u"; }
}
- public string Name
+ public virtual string Name
{
get { return "M3U Tuner"; }
}
@@ -99,72 +101,84 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
- var m3uchannels = channels.Cast<M3UChannel>();
- var channel = m3uchannels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase));
+ var channel = channels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase));
if (channel != null)
{
- var path = channel.Path;
- MediaProtocol protocol = MediaProtocol.File;
- if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- protocol = MediaProtocol.Http;
- }
- else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase))
- {
- protocol = MediaProtocol.Rtmp;
- }
- else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase))
- {
- protocol = MediaProtocol.Rtsp;
- }
- else if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
- {
- protocol = MediaProtocol.Udp;
- }
- else if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
- {
- protocol = MediaProtocol.Rtmp;
- }
+ return new List<MediaSourceInfo> { CreateMediaSourceInfo(info, channel) };
+ }
+ return new List<MediaSourceInfo>();
+ }
+
+ protected virtual MediaSourceInfo CreateMediaSourceInfo(TunerHostInfo info, ChannelInfo channel)
+ {
+ var path = channel.Path;
+ MediaProtocol protocol = MediaProtocol.File;
+ if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Http;
+ }
+ else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Rtmp;
+ }
+ else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Rtsp;
+ }
+ else if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Udp;
+ }
+ else if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Rtmp;
+ }
+
+ Uri uri;
+ var isRemote = true;
+ if (Uri.TryCreate(path, UriKind.Absolute, out uri))
+ {
+ isRemote = !_networkManager.IsInLocalNetwork(uri.Host);
+ }
- var mediaSource = new MediaSourceInfo
+ var mediaSource = new MediaSourceInfo
+ {
+ Path = path,
+ Protocol = protocol,
+ MediaStreams = new List<MediaStream>
{
- Path = channel.Path,
- Protocol = protocol,
- MediaStreams = new List<MediaStream>
+ new MediaStream
{
- new MediaStream
- {
- Type = MediaStreamType.Video,
- // Set the index to -1 because we don't know the exact index of the video stream within the container
- Index = -1,
- IsInterlaced = true
- },
- new MediaStream
- {
- Type = MediaStreamType.Audio,
- // Set the index to -1 because we don't know the exact index of the audio stream within the container
- Index = -1
-
- }
+ Type = MediaStreamType.Video,
+ // Set the index to -1 because we don't know the exact index of the video stream within the container
+ Index = -1,
+ IsInterlaced = true
},
- RequiresOpening = true,
- RequiresClosing = true,
- RequiresLooping = info.EnableStreamLooping,
+ new MediaStream
+ {
+ Type = MediaStreamType.Audio,
+ // Set the index to -1 because we don't know the exact index of the audio stream within the container
+ Index = -1
- ReadAtNativeFramerate = false,
+ }
+ },
+ RequiresOpening = true,
+ RequiresClosing = true,
+ RequiresLooping = info.EnableStreamLooping,
+ EnableMpDecimate = info.EnableMpDecimate,
- Id = channel.Path.GetMD5().ToString("N"),
- IsInfiniteStream = true,
- IsRemote = true,
+ ReadAtNativeFramerate = false,
- IgnoreDts = true
- };
+ Id = channel.Path.GetMD5().ToString("N"),
+ IsInfiniteStream = true,
+ IsRemote = isRemote,
- mediaSource.InferTotalBitrate();
+ IgnoreDts = true
+ };
- return new List<MediaSourceInfo> { mediaSource };
- }
- return new List<MediaSourceInfo>();
+ mediaSource.InferTotalBitrate();
+
+ return mediaSource;
}
protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 113e691b6f..ca744b615e 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
_appHost = appHost;
}
- public async Task<List<M3UChannel>> Parse(string url, string channelIdPrefix, string tunerHostId, CancellationToken cancellationToken)
+ public async Task<List<ChannelInfo>> Parse(string url, string channelIdPrefix, string tunerHostId, CancellationToken cancellationToken)
{
// Read the file and display it line by line.
using (var reader = new StreamReader(await GetListingsStream(url, cancellationToken).ConfigureAwait(false)))
@@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
- public List<M3UChannel> ParseString(string text, string channelIdPrefix, string tunerHostId)
+ public List<ChannelInfo> ParseString(string text, string channelIdPrefix, string tunerHostId)
{
// Read the file and display it line by line.
using (var reader = new StringReader(text))
@@ -66,9 +66,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
const string ExtInfPrefix = "#EXTINF:";
- private List<M3UChannel> GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId)
+ private List<ChannelInfo> GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId)
{
- var channels = new List<M3UChannel>();
+ var channels = new List<ChannelInfo>();
string line;
string extInf = "";
@@ -111,9 +111,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return channels;
}
- private M3UChannel GetChannelnfo(string extInf, string tunerHostId, string mediaUrl)
+ private ChannelInfo GetChannelnfo(string extInf, string tunerHostId, string mediaUrl)
{
- var channel = new M3UChannel();
+ var channel = new ChannelInfo();
channel.TunerHostId = tunerHostId;
extInf = extInf.Trim();
@@ -335,10 +335,4 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return dict;
}
}
-
-
- public class M3UChannel : ChannelInfo
- {
- public string Path { get; set; }
- }
} \ No newline at end of file
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
index cf50e60921..45a0c348e1 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
@@ -24,8 +24,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task CopyUntilCancelled(Stream source, Action onStarted, CancellationToken cancellationToken)
{
- byte[] buffer = new byte[BufferSize];
-
if (source == null)
{
throw new ArgumentNullException("source");
@@ -35,25 +33,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
cancellationToken.ThrowIfCancellationRequested();
+ byte[] buffer = new byte[BufferSize];
+
var bytesRead = source.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
- var allStreams = _outputStreams.ToList();
-
- //if (allStreams.Count == 1)
- //{
- // allStreams[0].Value.Write(buffer, 0, bytesRead);
- //}
- //else
+ foreach (var stream in _outputStreams)
{
- byte[] copy = new byte[bytesRead];
- Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
-
- foreach (var stream in allStreams)
- {
- stream.Value.Queue(copy, 0, copy.Length);
- }
+ stream.Value.Queue(buffer, 0, bytesRead);
}
if (onStarted != null)
@@ -73,27 +61,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{
- var result = new QueueStream(stream, _logger)
- {
- OnFinished = OnFinished
- };
-
- _outputStreams.TryAdd(result.Id, result);
+ var queueStream = new QueueStream(stream, _logger);
- result.Start(cancellationToken);
-
- return result.TaskCompletion.Task;
- }
+ _outputStreams.TryAdd(queueStream.Id, queueStream);
- public void RemoveOutputStream(QueueStream stream)
- {
- QueueStream removed;
- _outputStreams.TryRemove(stream.Id, out removed);
- }
+ try
+ {
+ queueStream.Start(cancellationToken);
+ }
+ finally
+ {
+ _outputStreams.TryRemove(queueStream.Id, out queueStream);
+ GC.Collect();
+ }
- private void OnFinished(QueueStream queueStream)
- {
- RemoveOutputStream(queueStream);
+ return Task.FromResult(true);
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
index 61bc390b40..07a4daa87d 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
@@ -13,10 +13,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class QueueStream
{
private readonly Stream _outputStream;
- private readonly ConcurrentQueue<Tuple<byte[], int, int>> _queue = new ConcurrentQueue<Tuple<byte[], int, int>>();
- public TaskCompletionSource<bool> TaskCompletion { get; private set; }
+ private readonly BlockingCollection<Tuple<byte[], int, int>> _queue = new BlockingCollection<Tuple<byte[], int, int>>();
- public Action<QueueStream> OnFinished { get; set; }
private readonly ILogger _logger;
public Guid Id = Guid.NewGuid();
@@ -24,94 +22,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
_outputStream = outputStream;
_logger = logger;
- TaskCompletion = new TaskCompletionSource<bool>();
}
public void Queue(byte[] bytes, int offset, int count)
{
- _queue.Enqueue(new Tuple<byte[], int, int>(bytes, offset, count));
+ _queue.Add(new Tuple<byte[], int, int>(bytes, offset, count));
}
public void Start(CancellationToken cancellationToken)
{
- Task.Run(() => StartInternal(cancellationToken));
- }
-
- private Tuple<byte[], int, int> Dequeue()
- {
- Tuple<byte[], int, int> result;
- if (_queue.TryDequeue(out result))
- {
- return result;
- }
-
- return null;
- }
-
- private void OnClosed()
- {
- GC.Collect();
- if (OnFinished != null)
- {
- OnFinished(this);
- }
- }
-
- public void Write(byte[] bytes, int offset, int count)
- {
- //return _outputStream.WriteAsync(bytes, offset, count, cancellationToken);
-
- try
+ while (true)
{
- _outputStream.Write(bytes, offset, count);
- }
- catch (OperationCanceledException)
- {
- _logger.Debug("QueueStream cancelled");
- TaskCompletion.TrySetCanceled();
- OnClosed();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error in QueueStream", ex);
- TaskCompletion.TrySetException(ex);
- OnClosed();
- }
- }
-
- private async Task StartInternal(CancellationToken cancellationToken)
- {
- try
- {
- while (true)
+ foreach (var result in _queue.GetConsumingEnumerable())
{
cancellationToken.ThrowIfCancellationRequested();
- var result = Dequeue();
- if (result != null)
- {
- _outputStream.Write(result.Item1, result.Item2, result.Item3);
- }
- else
- {
- await Task.Delay(50, cancellationToken).ConfigureAwait(false);
- }
+ _outputStream.Write(result.Item1, result.Item2, result.Item3);
}
}
- catch (OperationCanceledException)
- {
- _logger.Debug("QueueStream cancelled");
- TaskCompletion.TrySetCanceled();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error in QueueStream", ex);
- TaskCompletion.TrySetException(ex);
- }
- finally
- {
- OnClosed();
- }
}
}
}