aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/LiveTv
diff options
context:
space:
mode:
authorLuke <luke.pulverenti@gmail.com>2017-05-31 15:40:34 -0400
committerGitHub <noreply@github.com>2017-05-31 15:40:34 -0400
commit91176d9ccc1dde8155c10411c70e62a9f4b059d5 (patch)
tree21365f5a8dd09534a53d9f88d2a7a3116f3f3f98 /Emby.Server.Implementations/LiveTv
parentc37c9a75073b1b9caa3af2c3bc62abd837bd630e (diff)
parent4e10daf646e0788409f2bc52ef70effa2616e3f3 (diff)
Merge pull request #2677 from MediaBrowser/beta
Beta
Diffstat (limited to 'Emby.Server.Implementations/LiveTv')
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs33
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs47
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs32
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs8
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs30
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs66
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs23
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs88
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs53
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs193
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs59
18 files changed, 356 insertions, 303 deletions
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index ef440899cb..dcfaaa9d70 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -3,9 +3,10 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
-using MediaBrowser.Common.IO;
+
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Logging;
@@ -29,7 +30,35 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return targetFile;
}
- public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ public Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ {
+ if (directStreamProvider != null)
+ {
+ return RecordFromDirectStreamProvider(directStreamProvider, targetFile, duration, onStarted, cancellationToken);
+ }
+
+ return RecordFromMediaSource(mediaSource, targetFile, duration, onStarted, cancellationToken);
+ }
+
+ private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ {
+ using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ {
+ onStarted();
+
+ _logger.Info("Copying recording stream to file {0}", targetFile);
+
+ // The media source if infinite so we need to handle stopping ourselves
+ var durationToken = new CancellationTokenSource(duration);
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+
+ await directStreamProvider.CopyToAsync(output, cancellationToken).ConfigureAwait(false);
+ }
+
+ _logger.Info("Recording completed to file {0}", targetFile);
+ }
+
+ private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
var httpRequestOptions = new HttpRequestOptions
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index f1b3f41b42..6ce2b88f12 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -28,8 +28,9 @@ using System.Xml;
using MediaBrowser.Model.IO;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
+
using MediaBrowser.Controller;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
@@ -1232,7 +1233,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
RequiresClosing = false,
Protocol = MediaBrowser.Model.MediaInfo.MediaProtocol.Http,
BufferMs = 0,
- IgnoreDts = true
+ IgnoreDts = true,
+ IgnoreIndex = true
};
var isAudio = false;
@@ -1517,7 +1519,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
EnforceKeepUpTo(timer, seriesPath);
};
- await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
+ await recorder.Record(liveStreamInfo.Item1 as IDirectStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
recordingStatus = RecordingStatus.Completed;
_logger.Info("Recording completed: {0}", recordPath);
@@ -1634,15 +1636,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- var episodesToDelete = (await librarySeries.GetItems(new InternalItemsQuery
+ var episodesToDelete = (librarySeries.GetItems(new InternalItemsQuery
{
SortBy = new[] { ItemSortBy.DateCreated },
SortOrder = SortOrder.Descending,
IsVirtualItem = false,
IsFolder = false,
- Recursive = true
+ Recursive = true,
+ DtoOptions = new DtoOptions(true)
- }).ConfigureAwait(false))
+ }))
.Items
.Where(i => i.LocationType == LocationType.FileSystem && _fileSystem.FileExists(i.Path))
.Skip(seriesTimer.KeepUpTo - 1)
@@ -1759,20 +1762,30 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var config = GetConfiguration();
- if (config.EnableRecordingEncoding)
- {
- var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false);
+ var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false);
- if (regInfo.IsValid)
+ if (regInfo.IsValid)
+ {
+ if (config.EnableRecordingEncoding)
{
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient, _processFactory, _config);
}
+
+ return new DirectRecorder(_logger, _httpClient, _fileSystem);
+
+ //var options = new LiveTvOptions
+ //{
+ // EnableOriginalAudioWithEncodedRecordings = true,
+ // RecordedVideoCodec = "copy",
+ // RecordingEncodingFormat = "ts"
+ //};
+ //return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, options, _httpClient, _processFactory, _config);
}
- return new DirectRecorder(_logger, _httpClient, _fileSystem);
+ throw new InvalidOperationException("Emby DVR Requires an active Emby Premiere subscription.");
}
- private async void OnSuccessfulRecording(TimerInfo timer, string path)
+ private void OnSuccessfulRecording(TimerInfo timer, string path)
{
//if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize)
//{
@@ -1967,7 +1980,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
Limit = 1,
- ExternalId = timer.ProgramId
+ ExternalId = timer.ProgramId,
+ DtoOptions = new DtoOptions(true)
}).FirstOrDefault() as LiveTvProgram;
@@ -2501,16 +2515,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue)
{
- var result = _libraryManager.GetItemsResult(new InternalItemsQuery
+ var result = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Episode).Name },
ParentIndexNumber = program.SeasonNumber.Value,
IndexNumber = program.EpisodeNumber.Value,
AncestorIds = seriesIds,
- IsVirtualItem = false
+ IsVirtualItem = false,
+ Limit = 1
});
- if (result.TotalRecordCount > 0)
+ if (result.Count > 0)
{
return true;
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 790e6c27d1..6173068cc6 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -8,7 +8,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
-using MediaBrowser.Common.IO;
+
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
@@ -21,6 +21,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Library;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
@@ -64,6 +65,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return "mkv";
}
+ if (string.Equals(format, "ts", StringComparison.OrdinalIgnoreCase))
+ {
+ return "ts";
+ }
return "mp4";
}
@@ -90,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return Path.ChangeExtension(targetFile, "." + extension);
}
- public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
//var durationToken = new CancellationTokenSource(duration);
//cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
@@ -177,6 +182,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
videoArgs = "-codec:v:0 copy";
}
+ videoArgs += " -fflags +genpts";
+
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
var flags = new List<string>();
@@ -188,28 +195,37 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
flags.Add("+ignidx");
}
+ if (mediaSource.GenPtsInput)
+ {
+ flags.Add("+genpts");
+ }
- var inputModifiers = "-async 1 -vsync -1";
+ var inputModifier = "-async 1 -vsync -1";
if (flags.Count > 0)
{
- inputModifiers += " -fflags " + string.Join("", flags.ToArray());
+ inputModifier += " -fflags " + string.Join("", flags.ToArray());
}
if (!string.IsNullOrWhiteSpace(GetEncodingOptions().HardwareAccelerationType))
{
- inputModifiers += " -hwaccel auto";
+ inputModifier += " -hwaccel auto";
}
if (mediaSource.ReadAtNativeFramerate)
{
- inputModifiers += " -re";
+ inputModifier += " -re";
+ }
+
+ if (mediaSource.RequiresLooping)
+ {
+ inputModifier += " -stream_loop -1";
}
var analyzeDurationSeconds = 5;
var analyzeDuration = " -analyzeduration " +
(analyzeDurationSeconds * 1000000).ToString(CultureInfo.InvariantCulture);
- inputModifiers += analyzeDuration;
+ inputModifier += analyzeDuration;
var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn";
@@ -228,7 +244,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
durationParam,
outputParam);
- return inputModifiers + " " + commandLineArgs;
+ return inputModifier + " " + commandLineArgs;
}
private string GetAudioArgs(MediaSourceInfo mediaSource)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
index 3b5e60c4a7..e639a312c0 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
@@ -10,13 +11,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
/// <summary>
/// Records the specified media source.
/// </summary>
- /// <param name="mediaSource">The media source.</param>
- /// <param name="targetFile">The target file.</param>
- /// <param name="duration">The duration.</param>
- /// <param name="onStarted">The on started.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
+ Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index 953cb8e415..4ba2269a61 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -4,7 +4,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using MediaBrowser.Common.IO;
+
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index 17de93a3c4..94be4a02ef 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
else
{
- name += " " + info.StartDate.ToString("yyyy-MM-dd") + " " + info.Id;
+ name += " " + info.StartDate.ToString("yyyy-MM-dd");
}
return name;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
index 7bf6bf1ca2..843ba7e42c 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
@@ -2,8 +2,6 @@
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 2eec3df8a5..380b248001 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -8,7 +8,7 @@ using System.Collections.Concurrent;
using System.Globalization;
using System.Linq;
using System.Threading;
-using MediaBrowser.Common.IO;
+
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index a36cb124db..da6759b34d 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -167,10 +167,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
var programEntry = programDict[schedule.programID];
- var data = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
- data = data.OrderByDescending(GetSizeOrder).ToList();
+ var allImages = (images[imageIndex].data ?? new List<ScheduleDirect.ImageData>()).OrderByDescending(GetSizeOrder).ToList();
+ var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase)).ToList();
+
+ programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, "Logo", true, 600) ??
+ GetProgramImage(ApiUrl, allImages, "Logo", true, 600);
- programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 600);
//programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false);
//programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 43b055098a..5c50721922 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -149,7 +149,11 @@ namespace Emby.Server.Implementations.LiveTv
IncludeItemTypes = new string[] { typeof(Series).Name },
Name = seriesName,
Limit = 1,
- ImageTypes = new ImageType[] { ImageType.Thumb }
+ ImageTypes = new ImageType[] { ImageType.Thumb },
+ DtoOptions = new DtoOptions
+ {
+ Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
+ }
}).FirstOrDefault();
@@ -191,7 +195,11 @@ namespace Emby.Server.Implementations.LiveTv
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
ExternalSeriesId = programSeriesId,
Limit = 1,
- ImageTypes = new ImageType[] { ImageType.Primary }
+ ImageTypes = new ImageType[] { ImageType.Primary },
+ DtoOptions = new DtoOptions
+ {
+ Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
+ }
}).FirstOrDefault();
@@ -239,7 +247,11 @@ namespace Emby.Server.Implementations.LiveTv
IncludeItemTypes = new string[] { typeof(Series).Name },
Name = seriesName,
Limit = 1,
- ImageTypes = new ImageType[] { ImageType.Thumb }
+ ImageTypes = new ImageType[] { ImageType.Thumb },
+ DtoOptions = new DtoOptions
+ {
+ Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
+ }
}).FirstOrDefault();
@@ -281,14 +293,22 @@ namespace Emby.Server.Implementations.LiveTv
IncludeItemTypes = new string[] { typeof(Series).Name },
Name = seriesName,
Limit = 1,
- ImageTypes = new ImageType[] { ImageType.Primary }
+ ImageTypes = new ImageType[] { ImageType.Primary },
+ DtoOptions = new DtoOptions
+ {
+ Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
+ }
}).FirstOrDefault() ?? _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
ExternalSeriesId = programSeriesId,
Limit = 1,
- ImageTypes = new ImageType[] { ImageType.Primary }
+ ImageTypes = new ImageType[] { ImageType.Primary },
+ DtoOptions = new DtoOptions
+ {
+ Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
+ }
}).FirstOrDefault();
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index fa86ac36d0..c2f0575604 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -26,7 +26,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Common.Events;
-using MediaBrowser.Common.IO;
+
using MediaBrowser.Common.Security;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
@@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, CancellationToken cancellationToken)
+ public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
{
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
@@ -192,7 +192,8 @@ namespace Emby.Server.Implementations.LiveTv
IsFavorite = query.IsFavorite,
IsLiked = query.IsLiked,
StartIndex = query.StartIndex,
- Limit = query.Limit
+ Limit = query.Limit,
+ DtoOptions = dtoOptions
};
internalQuery.OrderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending)));
@@ -249,7 +250,7 @@ namespace Emby.Server.Implementations.LiveTv
{
Id = id
- }, cancellationToken).ConfigureAwait(false);
+ }, new DtoOptions(), cancellationToken).ConfigureAwait(false);
return result.Items.FirstOrDefault();
}
@@ -477,7 +478,8 @@ 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
+ // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
+ //mediaSource.SupportsDirectPlay = false;
mediaSource.SupportsDirectStream = false;
mediaSource.SupportsTranscoding = true;
foreach (var stream in mediaSource.MediaStreams)
@@ -864,13 +866,6 @@ namespace Emby.Server.Implementations.LiveTv
return item.Id;
}
-
-
- private string GetExternalSeriesIdLegacy(BaseItem item)
- {
- return item.GetProviderId("ProviderExternalSeriesId");
- }
-
public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
{
var program = GetInternalProgram(id);
@@ -881,11 +876,6 @@ namespace Emby.Server.Implementations.LiveTv
var externalSeriesId = program.ExternalSeriesId;
- if (string.IsNullOrWhiteSpace(externalSeriesId))
- {
- externalSeriesId = GetExternalSeriesIdLegacy(program);
- }
-
list.Add(new Tuple<BaseItemDto, string, string, string>(dto, program.ServiceName, GetItemExternalId(program), externalSeriesId));
await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
@@ -905,6 +895,8 @@ namespace Emby.Server.Implementations.LiveTv
query.SortBy = new[] { ItemSortBy.StartDate };
}
+ RemoveFields(options);
+
var internalQuery = new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
@@ -964,8 +956,6 @@ namespace Emby.Server.Implementations.LiveTv
var queryResult = _libraryManager.QueryItems(internalQuery);
- RemoveFields(options);
-
var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user).ConfigureAwait(false)).ToArray();
var result = new QueryResult<BaseItemDto>
@@ -1044,12 +1034,12 @@ namespace Emby.Server.Implementations.LiveTv
public async Task<QueryResult<BaseItemDto>> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
{
+ RemoveFields(options);
+
var internalResult = await GetRecommendedProgramsInternal(query, options, cancellationToken).ConfigureAwait(false);
var user = _userManager.GetUserById(query.UserId);
- RemoveFields(options);
-
var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
var result = new QueryResult<BaseItemDto>
@@ -1332,7 +1322,8 @@ namespace Emby.Server.Implementations.LiveTv
{
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
- ChannelIds = new string[] { currentChannel.Id.ToString("N") }
+ ChannelIds = new string[] { currentChannel.Id.ToString("N") },
+ DtoOptions = new DtoOptions(true)
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
@@ -1435,7 +1426,8 @@ namespace Emby.Server.Implementations.LiveTv
{
var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
{
- IncludeItemTypes = validTypes
+ IncludeItemTypes = validTypes,
+ DtoOptions = new DtoOptions(false)
}).ToList();
@@ -1662,6 +1654,8 @@ namespace Emby.Server.Implementations.LiveTv
includeItemTypes.Add(typeof(Series).Name);
+ RemoveFields(options);
+
var internalResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{
Recursive = true,
@@ -1671,11 +1665,10 @@ namespace Emby.Server.Implementations.LiveTv
SortOrder = SortOrder.Descending,
EnableTotalRecordCount = query.EnableTotalRecordCount,
IncludeItemTypes = includeItemTypes.ToArray(),
- ExcludeItemTypes = excludeItemTypes.ToArray()
+ ExcludeItemTypes = excludeItemTypes.ToArray(),
+ DtoOptions = options
});
- RemoveFields(options);
-
var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
return new QueryResult<BaseItemDto>
@@ -1685,7 +1678,7 @@ namespace Emby.Server.Implementations.LiveTv
};
}
- public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken)
+ public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken)
{
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
if (user != null && !IsLiveTvEnabled(user))
@@ -1695,14 +1688,15 @@ namespace Emby.Server.Implementations.LiveTv
if (_services.Count == 1 && !(query.IsInProgress ?? false) && (!query.IsLibraryItem.HasValue || query.IsLibraryItem.Value))
{
- return GetEmbyRecordings(query, new DtoOptions(), user);
+ return GetEmbyRecordings(query, options, user);
}
await RefreshRecordings(cancellationToken).ConfigureAwait(false);
var internalQuery = new InternalItemsQuery(user)
{
- IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }
+ IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name },
+ DtoOptions = options
};
if (!string.IsNullOrEmpty(query.ChannelId))
@@ -1871,11 +1865,6 @@ namespace Emby.Server.Implementations.LiveTv
var externalSeriesId = program.ExternalSeriesId;
- if (string.IsNullOrWhiteSpace(externalSeriesId))
- {
- externalSeriesId = GetExternalSeriesIdLegacy(program);
- }
-
programTuples.Add(new Tuple<BaseItemDto, string, string, string>(dto, serviceName, GetItemExternalId(program), externalSeriesId));
}
@@ -1952,10 +1941,10 @@ namespace Emby.Server.Implementations.LiveTv
{
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
- var internalResult = await GetInternalRecordings(query, cancellationToken).ConfigureAwait(false);
-
RemoveFields(options);
+ var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false);
+
var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
return new QueryResult<BaseItemDto>
@@ -2298,7 +2287,8 @@ namespace Emby.Server.Implementations.LiveTv
MinEndDate = now,
Limit = channelIds.Length,
SortBy = new[] { "StartDate" },
- TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") }
+ TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") },
+ DtoOptions = options
}).ToList() : new List<BaseItem>();
@@ -2600,7 +2590,7 @@ namespace Emby.Server.Implementations.LiveTv
{
UserId = query.UserId
- }, cancellationToken).ConfigureAwait(false);
+ }, new DtoOptions(), cancellationToken).ConfigureAwait(false);
var recordings = recordingResult.Items.OfType<ILiveTvRecording>().ToList();
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index d724a9fbc4..504f9a6ee6 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -421,7 +421,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
SupportsDirectStream = true,
SupportsTranscoding = true,
IsInfiniteStream = true,
- IgnoreDts = true
+ IgnoreDts = true,
+ //IgnoreIndex = true,
+ ReadAtNativeFramerate = true
};
mediaSource.InferTotalBitrate();
@@ -505,12 +507,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{
- return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
+ return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
}
// The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet
- var enableHttpStream = _environment.OperatingSystem == OperatingSystem.OSX ||
- _environment.OperatingSystem == OperatingSystem.BSD;
+ var enableHttpStream = _environment.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX
+ || _environment.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.BSD;
enableHttpStream = true;
if (enableHttpStream)
{
@@ -519,17 +521,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var httpUrl = GetApiUrl(info, true) + "/auto/v" + hdhrId;
// If raw was used, the tuner doesn't support params
- if (!string.IsNullOrWhiteSpace(profile)
- && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
{
httpUrl += "?transcode=" + profile;
}
mediaSource.Path = httpUrl;
- return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
+ return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment);
}
- return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
+ return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
}
public async Task Validate(TunerHostInfo info)
@@ -596,10 +597,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// Need a way to set the Receive timeout on the socket otherwise this might never timeout?
try
{
- await udpClient.SendAsync(discBytes, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken);
+ await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken);
+ var receiveBuffer = new byte[8192];
+
while (!cancellationToken.IsCancellationRequested)
{
- var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ var response = await udpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
var deviceIp = response.RemoteEndPoint.IpAddress.Address;
// check to make sure we have enough bytes received to be a valid message and make sure the 2nd byte is the discover reply byte
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs
index 2798805fa0..5db842dec7 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs
@@ -2,6 +2,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using Emby.Server.Implementations.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
@@ -10,6 +11,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.System;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
@@ -17,24 +19,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
- private readonly IServerApplicationPaths _appPaths;
private readonly IServerApplicationHost _appHost;
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
- private readonly MulticastStream _multicastStream;
- public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
- : base(mediaSource)
+ private readonly string _tempFilePath;
+
+ public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment)
+ : base(mediaSource, environment, fileSystem)
{
- _fileSystem = fileSystem;
_httpClient = httpClient;
_logger = logger;
- _appPaths = appPaths;
_appHost = appHost;
OriginalStreamId = originalStreamId;
- _multicastStream = new MulticastStream(_logger);
+
+ _tempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts");
}
protected override async Task OpenInternal(CancellationToken openCancellationToken)
@@ -57,9 +57,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
OpenedMediaSource.Protocol = MediaProtocol.Http;
- OpenedMediaSource.SupportsDirectPlay = false;
- OpenedMediaSource.SupportsDirectStream = true;
- OpenedMediaSource.SupportsTranscoding = true;
+ //OpenedMediaSource.SupportsDirectPlay = false;
+ //OpenedMediaSource.SupportsDirectStream = true;
+ //OpenedMediaSource.SupportsTranscoding = true;
await taskCompletionSource.Task.ConfigureAwait(false);
@@ -74,9 +74,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return _liveStreamTaskCompletionSource.Task;
}
- private async Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
+ private Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
- await Task.Run(async () =>
+ return Task.Run(async () =>
{
var isFirstAttempt = true;
@@ -101,13 +101,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
_logger.Info("Beginning multicastStream.CopyUntilCancelled");
- Action onStarted = null;
- if (isFirstAttempt)
+ FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
+ using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous))
{
- onStarted = () => openTaskCompletionSource.TrySetResult(true);
- }
+ ResolveAfterDelay(3000, openTaskCompletionSource);
- await _multicastStream.CopyUntilCancelled(response.Content, onStarted, cancellationToken).ConfigureAwait(false);
+ //await response.Content.CopyToAsync(fileStream, 81920, cancellationToken).ConfigureAwait(false);
+ await AsyncStreamCopier.CopyStream(response.Content, fileStream, 81920, 4, cancellationToken).ConfigureAwait(false);
+ }
}
}
}
@@ -131,13 +132,60 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
_liveStreamTaskCompletionSource.TrySetResult(true);
+ await DeleteTempFile(_tempFilePath).ConfigureAwait(false);
+ });
+ }
- }).ConfigureAwait(false);
+ private void ResolveAfterDelay(int delayMs, TaskCompletionSource<bool> openTaskCompletionSource)
+ {
+ Task.Run(async () =>
+ {
+ await Task.Delay(delayMs).ConfigureAwait(false);
+ openTaskCompletionSource.TrySetResult(true);
+ });
}
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{
- return _multicastStream.CopyToAsync(stream);
+ return CopyFileTo(_tempFilePath, false, stream, cancellationToken);
+ }
+
+ protected async Task CopyFileTo(string path, bool allowEndOfFile, Stream outputStream, CancellationToken cancellationToken)
+ {
+ var eofCount = 0;
+
+ long startPosition = -25000;
+ if (startPosition < 0)
+ {
+ var length = FileSystem.GetFileInfo(path).Length;
+ startPosition = Math.Max(length - startPosition, 0);
+ }
+
+ using (var inputStream = GetInputStream(path, startPosition, true))
+ {
+ if (startPosition > 0)
+ {
+ inputStream.Position = startPosition;
+ }
+
+ while (eofCount < 20 || !allowEndOfFile)
+ {
+ var bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 4, cancellationToken).ConfigureAwait(false);
+
+ //var position = fs.Position;
+ //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
+
+ if (bytesRead == 0)
+ {
+ eofCount++;
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ eofCount = 0;
+ }
+ }
+ }
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index 2c678d9f8d..41b058baf7 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -46,10 +46,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public class HdHomerunChannelCommands : IHdHomerunChannelCommands
{
private string _channel;
+ private string _profile;
- public HdHomerunChannelCommands(string channel)
+ public HdHomerunChannelCommands(string channel, string profile)
{
_channel = channel;
+ _profile = profile;
}
public IEnumerable<Tuple<string, string>> GetCommands()
@@ -57,7 +59,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var commands = new List<Tuple<string, string>>();
if (!String.IsNullOrEmpty(_channel))
- commands.Add(Tuple.Create("vchannel", _channel));
+ {
+ if (!string.IsNullOrWhiteSpace(_profile) && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
+ {
+ commands.Add(Tuple.Create("vchannel", String.Format("{0} transcode={1}", _channel, _profile)));
+ }
+ else
+ {
+ commands.Add(Tuple.Create("vchannel", _channel));
+ }
+ }
return commands;
}
@@ -103,8 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var ipEndPoint = new IpEndPointInfo(remoteIp, HdHomeRunPort);
var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
- await socket.SendAsync(lockkeyMsg, lockkeyMsg.Length, ipEndPoint, cancellationToken);
- var response = await socket.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ await socket.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken);
+
+ var receiveBuffer = new byte[8192];
+ var response = await socket.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
+
string returnVal;
ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal);
@@ -117,6 +131,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
{
+ var receiveBuffer = new byte[8192];
+
if (!_lockkey.HasValue)
{
var rand = new Random();
@@ -133,8 +149,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_activeTuner = i;
var lockKeyString = String.Format("{0:d}", _lockkey.Value);
var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
- await tcpClient.SendAsync(lockkeyMsg, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
- var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ await tcpClient.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
+ var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
string returnVal;
// parse response to make sure it worked
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
@@ -144,8 +160,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
foreach(Tuple<string,string> command in commandList)
{
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, _lockkey.Value);
- await tcpClient.SendAsync(channelMsg, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
- response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
+ response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
{
@@ -158,8 +174,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var targetValue = String.Format("rtp://{0}:{1}", localIp, localPort);
var targetMsg = CreateSetMessage(i, "target", targetValue, _lockkey.Value);
- await tcpClient.SendAsync(targetMsg, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
- response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ await tcpClient.SendToAsync(targetMsg, 0, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
+ response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
{
@@ -180,11 +196,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
{
var commandList = commands.GetCommands();
+ var receiveBuffer = new byte[8192];
+
foreach (Tuple<string, string> command in commandList)
{
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey.Value);
- await tcpClient.SendAsync(channelMsg, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false);
- var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false);
+ var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
string returnVal;
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
@@ -209,12 +227,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private async Task ReleaseLockkey(ISocket tcpClient)
{
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", _lockkey);
- await tcpClient.SendAsync(releaseTarget, releaseTarget.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
- await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false);
+ await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
+
+ var receiveBuffer = new byte[8192];
+
+ await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false);
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", _lockkey);
_lockkey = null;
- await tcpClient.SendAsync(releaseKeyMsg, releaseKeyMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
- await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false);
+ await tcpClient.SendToAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
+ await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false);
}
private static byte[] CreateGetMessage(int tuner, string name)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index e1572ea3ff..2989177c0f 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Emby.Server.Implementations.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
@@ -14,39 +15,35 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net;
+using MediaBrowser.Model.System;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
{
private readonly ILogger _logger;
- private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
- private readonly IServerApplicationPaths _appPaths;
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
- private readonly MulticastStream _multicastStream;
private readonly IHdHomerunChannelCommands _channelCommands;
private readonly int _numTuners;
private readonly INetworkManager _networkManager;
- public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager)
- : base(mediaSource)
+ private readonly string _tempFilePath;
+
+ public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment)
+ : base(mediaSource, environment, fileSystem)
{
- _fileSystem = fileSystem;
- _httpClient = httpClient;
_logger = logger;
- _appPaths = appPaths;
_appHost = appHost;
_socketFactory = socketFactory;
_networkManager = networkManager;
OriginalStreamId = originalStreamId;
- _multicastStream = new MulticastStream(_logger);
_channelCommands = channelCommands;
_numTuners = numTuners;
+ _tempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts");
}
protected override async Task OpenInternal(CancellationToken openCancellationToken)
@@ -70,9 +67,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
OpenedMediaSource.Protocol = MediaProtocol.Http;
- OpenedMediaSource.SupportsDirectPlay = false;
- OpenedMediaSource.SupportsDirectStream = true;
- OpenedMediaSource.SupportsTranscoding = true;
+ //OpenedMediaSource.SupportsDirectPlay = false;
+ //OpenedMediaSource.SupportsDirectStream = true;
+ //OpenedMediaSource.SupportsTranscoding = true;
await taskCompletionSource.Task.ConfigureAwait(false);
@@ -87,9 +84,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return _liveStreamTaskCompletionSource.Task;
}
- private async Task StartStreaming(string remoteIp, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
+ private Task StartStreaming(string remoteIp, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
- await Task.Run(async () =>
+ return Task.Run(async () =>
{
var isFirstAttempt = true;
using (var udpClient = _socketFactory.CreateUdpSocket(localPort))
@@ -124,13 +121,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (!cancellationToken.IsCancellationRequested)
{
- Action onStarted = null;
- if (isFirstAttempt)
+ FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
+ using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous))
{
- onStarted = () => openTaskCompletionSource.TrySetResult(true);
+ await CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken).ConfigureAwait(false);
}
-
- await _multicastStream.CopyUntilCancelled(new UdpClientStream(udpClient), onStarted, cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException ex)
@@ -159,135 +154,109 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- }).ConfigureAwait(false);
+ await DeleteTempFile(_tempFilePath).ConfigureAwait(false);
+ });
}
- public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
+ private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
{
- return _multicastStream.CopyToAsync(stream);
+ Task.Run(() =>
+ {
+ openTaskCompletionSource.TrySetResult(true);
+ });
}
- }
-
- // This handles the ReadAsync function only of a Stream object
- // This is used to wrap a UDP socket into a stream for MulticastStream which only uses ReadAsync
- public class UdpClientStream : Stream
- {
- private static int RtpHeaderBytes = 12;
- private static int PacketSize = 1316;
- private readonly ISocket _udpClient;
- bool disposed;
- public UdpClientStream(ISocket udpClient) : base()
+ public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{
- _udpClient = udpClient;
+ return CopyFileTo(_tempFilePath, false, stream, cancellationToken);
}
- public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ protected async Task CopyFileTo(string path, bool allowEndOfFile, Stream outputStream, CancellationToken cancellationToken)
{
- if (buffer == null)
- throw new ArgumentNullException("buffer");
+ var eofCount = 0;
- if (offset + count < 0)
- throw new ArgumentOutOfRangeException("offset + count must not be negative", "offset+count");
+ long startPosition = -25000;
+ if (startPosition < 0)
+ {
+ var length = FileSystem.GetFileInfo(path).Length;
+ startPosition = Math.Max(length - startPosition, 0);
+ }
- if (offset + count > buffer.Length)
- throw new ArgumentException("offset + count must not be greater than the length of buffer", "offset+count");
+ using (var inputStream = GetInputStream(path, startPosition, true))
+ {
+ if (startPosition > 0)
+ {
+ inputStream.Position = startPosition;
+ }
- if (disposed)
- throw new ObjectDisposedException(typeof(UdpClientStream).ToString());
+ while (eofCount < 20 || !allowEndOfFile)
+ {
+ var bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 4, cancellationToken).ConfigureAwait(false);
- // This will always receive a 1328 packet size (PacketSize + RtpHeaderSize)
- // The RTP header will be stripped so see how many reads we need to make to fill the buffer.
- int numReads = count / PacketSize;
- int totalBytesRead = 0;
+ //var position = fs.Position;
+ //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
- for (int i = 0; i < numReads; ++i)
- {
- var data = await _udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
-
- var bytesRead = data.ReceivedBytes - RtpHeaderBytes;
-
- // remove rtp header
- Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, buffer, offset, bytesRead);
- offset += bytesRead;
- totalBytesRead += bytesRead;
+ if (bytesRead == 0)
+ {
+ eofCount++;
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ eofCount = 0;
+ }
+ }
}
- return totalBytesRead;
}
- protected override void Dispose(bool disposing)
+ private static int RtpHeaderBytes = 12;
+ private Task CopyTo(ISocket udpClient, Stream outputStream, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
- disposed = true;
+ return CopyStream(_socketFactory.CreateNetworkStream(udpClient, false), outputStream, 81920, 4, openTaskCompletionSource, cancellationToken);
}
- public override bool CanRead
+ private Task CopyStream(Stream source, Stream target, int bufferSize, int bufferCount, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
- get
- {
- throw new NotImplementedException();
- }
- }
+ var copier = new AsyncStreamCopier(source, target, 0, cancellationToken, false, bufferSize, bufferCount);
+ copier.IndividualReadOffset = RtpHeaderBytes;
- public override bool CanSeek
- {
- get
- {
- throw new NotImplementedException();
- }
- }
+ var taskCompletion = new TaskCompletionSource<long>();
- public override bool CanWrite
- {
- get
- {
- throw new NotImplementedException();
- }
- }
+ copier.TaskCompletionSource = taskCompletion;
- public override long Length
- {
- get
- {
- throw new NotImplementedException();
- }
- }
+ var result = copier.BeginCopy(StreamCopyCallback, copier);
- public override long Position
- {
- get
+ if (openTaskCompletionSource != null)
{
- throw new NotImplementedException();
+ Resolve(openTaskCompletionSource);
+ openTaskCompletionSource = null;
}
- set
+ if (result.CompletedSynchronously)
{
- throw new NotImplementedException();
+ StreamCopyCallback(result);
}
- }
- public override void Flush()
- {
- throw new NotImplementedException();
- }
+ cancellationToken.Register(() => taskCompletion.TrySetCanceled());
- public override int Read(byte[] buffer, int offset, int count)
- {
- throw new NotImplementedException();
+ return taskCompletion.Task;
}
- public override long Seek(long offset, SeekOrigin origin)
+ private void StreamCopyCallback(IAsyncResult result)
{
- throw new NotImplementedException();
- }
+ var copier = (AsyncStreamCopier)result.AsyncState;
+ var taskCompletion = copier.TaskCompletionSource;
- public override void SetLength(long value)
- {
- throw new NotImplementedException();
+ try
+ {
+ copier.EndCopy(result);
+ taskCompletion.TrySetResult(0);
+ }
+ catch (Exception ex)
+ {
+ taskCompletion.TrySetException(ex);
+ }
}
- public override void Write(byte[] buffer, int offset, int count)
- {
- throw new NotImplementedException();
- }
}
} \ No newline at end of file
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 8cf1106f0f..12b7901f93 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -11,7 +11,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.IO;
+
using MediaBrowser.Model.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
@@ -19,6 +19,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.System;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
@@ -27,13 +28,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
+ private readonly IEnvironmentInfo _environment;
- public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost)
+ public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment)
: base(config, logger, jsonSerializer, mediaEncoder)
{
_fileSystem = fileSystem;
_httpClient = httpClient;
_appHost = appHost;
+ _environment = environment;
}
public override string Type
@@ -73,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false);
- var liveStream = new LiveStream(sources.First());
+ var liveStream = new LiveStream(sources.First(), _environment, _fileSystem);
return liveStream;
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 8c4b9bf606..4c1190e0ee 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -8,7 +8,6 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.IO;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
index 02ebbcf16d..e650086d34 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
@@ -15,7 +15,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
private readonly ConcurrentDictionary<Guid,QueueStream> _outputStreams = new ConcurrentDictionary<Guid, QueueStream>();
private const int BufferSize = 81920;
- private CancellationToken _cancellationToken;
private readonly ILogger _logger;
public MulticastStream(ILogger logger)
@@ -25,8 +24,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task CopyUntilCancelled(Stream source, Action onStarted, CancellationToken cancellationToken)
{
- _cancellationToken = cancellationToken;
-
byte[] buffer = new byte[BufferSize];
if (source == null)
@@ -72,59 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
- private static int RtpHeaderBytes = 12;
- public async Task CopyUntilCancelled(ISocket udpClient, Action onStarted, CancellationToken cancellationToken)
- {
- _cancellationToken = cancellationToken;
-
- while (!cancellationToken.IsCancellationRequested)
- {
- var receiveToken = cancellationToken;
-
- // On the first connection attempt, put a timeout to avoid being stuck indefinitely in the event of failure
- if (onStarted != null)
- {
- receiveToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(5000).Token, cancellationToken).Token;
- }
-
- var data = await udpClient.ReceiveAsync(receiveToken).ConfigureAwait(false);
- var bytesRead = data.ReceivedBytes - RtpHeaderBytes;
-
- if (bytesRead > 0)
- {
- var allStreams = _outputStreams.ToList();
-
- if (allStreams.Count == 1)
- {
- await allStreams[0].Value.WriteAsync(data.Buffer, 0, bytesRead).ConfigureAwait(false);
- }
- else
- {
- byte[] copy = new byte[bytesRead];
- Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead);
-
- foreach (var stream in allStreams)
- {
- stream.Value.Queue(copy, 0, copy.Length);
- }
- }
-
- if (onStarted != null)
- {
- var onStartedCopy = onStarted;
- onStarted = null;
- Task.Run(onStartedCopy);
- }
- }
-
- else
- {
- await Task.Delay(100).ConfigureAwait(false);
- }
- }
- }
-
- public Task CopyToAsync(Stream stream)
+ public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{
var result = new QueueStream(stream, _logger)
{
@@ -133,7 +78,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
_outputStreams.TryAdd(result.Id, result);
- result.Start(_cancellationToken);
+ result.Start(cancellationToken);
return result.TaskCompletion.Task;
}