diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index b85245ba1..45cb42ecd 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -16,12 +16,15 @@ namespace Emby.Common.Implementations.Net internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket { - - #region Fields - private Socket _Socket; private int _LocalPort; - #endregion + + private SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() + { + SocketFlags = SocketFlags.None + }; + + private TaskCompletionSource _currentReceiveTaskCompletionSource; public UdpSocket(Socket socket, int localPort, IPAddress ip) { @@ -32,6 +35,32 @@ namespace Emby.Common.Implementations.Net LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); _Socket.Bind(new IPEndPoint(ip, _LocalPort)); + + InitReceiveSocketAsyncEventArgs(); + } + + private void InitReceiveSocketAsyncEventArgs() + { + var buffer = new byte[8192]; + _receiveSocketAsyncEventArgs.SetBuffer(buffer, 0, buffer.Length); + _receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed; + } + + private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) + { + var tcs = _currentReceiveTaskCompletionSource; + if (tcs != null) + { + _currentReceiveTaskCompletionSource = null; + + tcs.TrySetResult(new SocketReceiveResult + { + Buffer = e.Buffer, + ReceivedBytes = e.BytesTransferred, + RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint), + LocalIPAddress = LocalIPAddress + }); + } } public UdpSocket(Socket socket, IpEndPointInfo endPoint) @@ -40,6 +69,8 @@ namespace Emby.Common.Implementations.Net _Socket = socket; _Socket.Connect(NetworkManager.ToIPEndPoint(endPoint)); + + InitReceiveSocketAsyncEventArgs(); } public IpAddressInfo LocalIPAddress @@ -57,12 +88,12 @@ namespace Emby.Common.Implementations.Net var tcs = new TaskCompletionSource(); EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); - var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); - state.TaskCompletionSource = tcs; - cancellationToken.Register(() => tcs.TrySetCanceled()); #if NETSTANDARD1_6 + var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); + state.TaskCompletionSource = tcs; + _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer), SocketFlags.None, state.RemoteEndPoint) .ContinueWith((task, asyncState) => { @@ -74,7 +105,15 @@ namespace Emby.Common.Implementations.Net } }, state); #else - _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state); + //var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); + //state.TaskCompletionSource = tcs; + + //_Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state); + + _receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint; + _currentReceiveTaskCompletionSource = tcs; + + var isPending = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs); #endif return tcs.Task; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 2d9ea5853..368053b9d 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -498,11 +498,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId)) { - var mappedTunerChannelId = GetMappedChannel(tunerChannel.TunerChannelId, mappings); + var tunerChannelId = tunerChannel.TunerChannelId; + if (tunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1) + { + tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I'); + } + + var mappedTunerChannelId = GetMappedChannel(tunerChannelId, mappings); if (string.IsNullOrWhiteSpace(mappedTunerChannelId)) { - mappedTunerChannelId = tunerChannel.TunerChannelId; + mappedTunerChannelId = tunerChannelId; } var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase)); @@ -644,8 +650,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public Task CreateTimer(TimerInfo timer, CancellationToken cancellationToken) { - var existingTimer = _timerProvider.GetAll() - .FirstOrDefault(i => string.Equals(timer.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase)); + var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ? + null : + _timerProvider.GetTimerByProgramId(timer.ProgramId); if (existingTimer != null) { @@ -724,10 +731,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return true; } - //if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId)) - //{ - // return true; - //} + if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId)) + { + return true; + } return false; }) @@ -740,7 +747,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV timer.SeriesTimerId = info.Id; timer.IsManual = true; - _timerProvider.AddOrUpdate(timer); + _timerProvider.AddOrUpdate(timer, false); } await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false); @@ -2340,6 +2347,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var existingTimer = _timerProvider.GetTimer(timer.Id); + if (existingTimer == null) + { + existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) + ? null + : _timerProvider.GetTimerByProgramId(timer.ProgramId); + } + if (existingTimer == null) { if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) @@ -2354,9 +2368,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } else { - // Only update if not currently active + // Only update if not currently active - test both new timer and existing in case Id's are different + // Id's could be different if the timer was created manually prior to series timer creation ActiveRecordingInfo activeRecordingInfo; - if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo)) + if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo) && !_activeRecordings.TryGetValue(existingTimer.Id, out activeRecordingInfo)) { UpdateExistingTimerWithNewMetadata(existingTimer, timer); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 0567bdfd9..6cc5b6920 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks); var inputModifiers = "-fflags +genpts -async 1 -vsync -1"; - var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4} -y \"{1}\""; + var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4}{6} -y \"{1}\""; long startTimeTicks = 0; //if (mediaSource.DateLiveStreamOpened.HasValue) @@ -193,7 +193,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn"; - commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam); + var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ? + " -f mp4 -movflags frag_keyframe+empty_moov" : + string.Empty; + + commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam, outputParam); return inputModifiers + " " + commandLineArgs; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 35868d318..2eec3df8a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -166,5 +166,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase)); } + + public TimerInfo GetTimerByProgramId(string programId) + { + return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase)); + } } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 8406d44a7..b9e73b62e 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1078,25 +1078,28 @@ namespace Emby.Server.Implementations.LiveTv var channel = GetInternalChannel(program.ChannelId); - var channelUserdata = _userDataManager.GetUserData(userId, channel); + if (channel != null) + { + var channelUserdata = _userDataManager.GetUserData(userId, channel); - if (channelUserdata.Likes ?? false) - { - score += 2; - } - else if (!(channelUserdata.Likes ?? true)) - { - score -= 2; - } + if (channelUserdata.Likes ?? false) + { + score += 2; + } + else if (!(channelUserdata.Likes ?? true)) + { + score -= 2; + } - if (channelUserdata.IsFavorite) - { - score += 3; - } + if (channelUserdata.IsFavorite) + { + score += 3; + } - if (factorChannelWatchCount) - { - score += channelUserdata.PlayCount; + if (factorChannelWatchCount) + { + score += channelUserdata.PlayCount; + } } return score; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 02a6bf85d..17c57712e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -275,6 +275,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public int HD { get; set; } } + protected EncodingOptions GetEncodingOptions() + { + return Config.GetConfiguration("encoding"); + } + + private string GetHdHrIdFromChannelId(string channelId) + { + return channelId.Split('_')[1]; + } + private MediaSourceInfo GetMediaSource(TunerHostInfo info, string channelId, ChannelInfo channelInfo, string profile) { int? width = null; @@ -362,14 +372,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun nal = "0"; } - var url = GetApiUrl(info, true) + "/auto/v" + channelId; - - // If raw was used, the tuner doesn't support params - if (!string.IsNullOrWhiteSpace(profile) - && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase)) - { - url += "?transcode=" + profile; - } + var url = GetApiUrl(info, false); var id = profile; if (string.IsNullOrWhiteSpace(id)) @@ -378,92 +381,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } id += "_" + url.GetMD5().ToString("N"); - var mediaSource = new MediaSourceInfo - { - Path = url, - Protocol = MediaProtocol.Http, - MediaStreams = new List - { - 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 = isInterlaced, - Codec = videoCodec, - Width = width, - Height = height, - BitRate = videoBitrate, - NalLengthSize = nal - - }, - 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, - Codec = audioCodec, - BitRate = audioBitrate - } - }, - RequiresOpening = true, - RequiresClosing = false, - BufferMs = 0, - Container = "ts", - Id = id, - SupportsDirectPlay = false, - SupportsDirectStream = true, - SupportsTranscoding = true, - IsInfiniteStream = true - }; - - mediaSource.InferTotalBitrate(); - - return mediaSource; - } - - protected EncodingOptions GetEncodingOptions() - { - return Config.GetConfiguration("encoding"); - } - - private string GetHdHrIdFromChannelId(string channelId) - { - return channelId.Split('_')[1]; - } - - private MediaSourceInfo GetLegacyMediaSource(TunerHostInfo info, string channelId, ChannelInfo channel) - { - int? width = null; - int? height = null; - bool isInterlaced = true; - string videoCodec = null; - string audioCodec = null; - - int? videoBitrate = null; - int? audioBitrate = null; - - if (channel != null) - { - if (string.IsNullOrWhiteSpace(videoCodec)) - { - videoCodec = channel.VideoCodec; - } - audioCodec = channel.AudioCodec; - } - - // normalize - if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase)) - { - videoCodec = "mpeg2video"; - } - - string nal = null; - - var url = GetApiUrl(info, false); - var id = channelId; - id += "_" + url.GetMD5().ToString("N"); - var mediaSource = new MediaSourceInfo { Path = url, @@ -527,7 +444,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (isLegacyTuner) { - list.Add(GetLegacyMediaSource(info, hdhrId, channelInfo)); + list.Add(GetMediaSource(info, hdhrId, channelInfo, "native")); } else { @@ -579,20 +496,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var hdhomerunChannel = channelInfo as HdHomerunChannelInfo; + var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile); + var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); + if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner) { - var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo); - var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); } else { - var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile); - //var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - - return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); - //return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); + //return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); + return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 867e07e87..92000a1b3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -120,8 +120,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun // send url to start streaming await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, cancellationToken).ConfigureAwait(false); - var timeoutToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(5000).Token, cancellationToken).Token; - var response = await udpClient.ReceiveAsync(timeoutToken).ConfigureAwait(false); _logger.Info("Opened HDHR UDP stream from {0}", remoteAddress); if (!cancellationToken.IsCancellationRequested) @@ -132,8 +130,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun onStarted = () => openTaskCompletionSource.TrySetResult(true); } - var stream = new UdpClientStream(udpClient); - await _multicastStream.CopyUntilCancelled(stream, onStarted, cancellationToken).ConfigureAwait(false); + await _multicastStream.CopyUntilCancelled(udpClient, onStarted, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException ex) @@ -158,7 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } await hdHomerunManager.StopStreaming().ConfigureAwait(false); - udpClient.Dispose(); _liveStreamTaskCompletionSource.TrySetResult(true); } } @@ -171,127 +167,4 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return _multicastStream.CopyToAsync(stream); } } - - // 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() - { - _udpClient = udpClient; - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (buffer == null) - throw new ArgumentNullException("buffer"); - - if (offset + count < 0) - throw new ArgumentOutOfRangeException("offset + count must not be negative", "offset+count"); - - if (offset + count > buffer.Length) - throw new ArgumentException("offset + count must not be greater than the length of buffer", "offset+count"); - - if (disposed) - throw new ObjectDisposedException(typeof(UdpClientStream).ToString()); - - // 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; - - 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; - } - return totalBytesRead; - } - - protected override void Dispose(bool disposing) - { - disposed = true; - } - - public override bool CanRead - { - get - { - throw new NotImplementedException(); - } - } - - public override bool CanSeek - { - get - { - throw new NotImplementedException(); - } - } - - public override bool CanWrite - { - get - { - throw new NotImplementedException(); - } - } - - public override long Length - { - get - { - throw new NotImplementedException(); - } - } - - public override long Position - { - get - { - throw new NotImplementedException(); - } - - set - { - throw new NotImplementedException(); - } - } - - public override void Flush() - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - 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/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index fe677f6fa..8c4b9bf60 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -144,11 +144,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts channel.TunerChannelId = string.IsNullOrWhiteSpace(tvgId) ? channelId : tvgId; - if (!string.IsNullOrWhiteSpace(channel.TunerChannelId) && channel.TunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1) - { - channel.TunerChannelId = channel.TunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I'); - } - var channelIdValues = new List(); if (!string.IsNullOrWhiteSpace(channelId)) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs index 90ff36441..e3d0d1eba 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; namespace Emby.Server.Implementations.LiveTv.TunerHosts { @@ -40,7 +41,52 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var allStreams = _outputStreams.ToList(); foreach (var stream in allStreams) { - stream.Value.Queue(copy); + 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); + } + } + } + + 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) + { + byte[] copy = new byte[bytesRead]; + Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead); + + var allStreams = _outputStreams.ToList(); + foreach (var stream in allStreams) + { + //stream.Value.Queue(data.Buffer, RtpHeaderBytes, bytesRead); + stream.Value.Queue(copy, 0, copy.Length); } if (onStarted != null) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs index 7b48ce21a..27dd288a7 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs @@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public class QueueStream { private readonly Stream _outputStream; - private readonly ConcurrentQueue _queue = new ConcurrentQueue(); + private readonly ConcurrentQueue> _queue = new ConcurrentQueue>(); private CancellationToken _cancellationToken; public TaskCompletionSource TaskCompletion { get; private set; } @@ -28,9 +28,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts TaskCompletion = new TaskCompletionSource(); } - public void Queue(byte[] bytes) + public void Queue(byte[] bytes, int offset, int count) { - _queue.Enqueue(bytes); + _queue.Enqueue(new Tuple(bytes, offset, count)); } public void Start(CancellationToken cancellationToken) @@ -39,12 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Task.Run(() => StartInternal()); } - private byte[] Dequeue() + private Tuple Dequeue() { - byte[] bytes; - if (_queue.TryDequeue(out bytes)) + Tuple result; + if (_queue.TryDequeue(out result)) { - return bytes; + return result; } return null; @@ -58,10 +58,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { while (true) { - var bytes = Dequeue(); - if (bytes != null) + var result = Dequeue(); + if (result != null) { - await _outputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + await _outputStream.WriteAsync(result.Item1, result.Item2, result.Item3, cancellationToken).ConfigureAwait(false); } else { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index eb69a8f7b..5eaab4110 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1583,6 +1583,15 @@ namespace MediaBrowser.Controller.MediaEncoding MediaSourceInfo mediaSource, string requestedUrl) { + if (state == null) + { + throw new ArgumentNullException("state"); + } + if (mediaSource == null) + { + throw new ArgumentNullException("mediaSource"); + } + state.MediaPath = mediaSource.Path; state.InputProtocol = mediaSource.Protocol; state.InputContainer = mediaSource.Container; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 580f5c615..97f2c57eb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -647,9 +647,9 @@ namespace MediaBrowser.MediaEncoding.Encoder var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - if (videoStream != null) + if (videoStream != null && !videoStream.IsInterlaced) { - var isInterlaced = await DetectInterlaced(mediaInfo, videoStream, inputPath, probeSizeArgument).ConfigureAwait(false); + var isInterlaced = DetectInterlaced(mediaInfo, videoStream); if (isInterlaced) { @@ -672,7 +672,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - private async Task DetectInterlaced(MediaSourceInfo video, MediaStream videoStream, string inputPath, string probeSizeArgument) + private bool DetectInterlaced(MediaSourceInfo video, MediaStream videoStream) { var formats = (video.Container ?? string.Empty).Split(',').ToList(); var enableInterlacedDection = formats.Contains("vob", StringComparer.OrdinalIgnoreCase) || @@ -698,165 +698,9 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - if (video.Protocol != MediaProtocol.File) - { - return false; - } - - var args = "{0} -i {1} -map 0:v:{2} -an -filter:v idet -frames:v 500 -an -f null /dev/null"; - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both or ffmpeg may hang due to deadlocks. See comments below. - RedirectStandardError = true, - FileName = FFMpegPath, - Arguments = string.Format(args, probeSizeArgument, inputPath, videoStream.Index.ToString(CultureInfo.InvariantCulture)).Trim(), - - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true - }); - - _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - var idetFoundInterlaced = false; - - using (var processWrapper = new ProcessWrapper(process, this, _logger)) - { - try - { - StartProcess(processWrapper); - } - catch (Exception ex) - { - _logger.ErrorException("Error starting ffprobe", ex); - - throw; - } - - try - { - //process.BeginOutputReadLine(); - - using (var reader = new StreamReader(process.StandardError.BaseStream)) - { - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - - if (line.StartsWith("[Parsed_idet", StringComparison.OrdinalIgnoreCase)) - { - var idetResult = AnalyzeIdetResult(line); - - if (idetResult.HasValue) - { - if (!idetResult.Value) - { - return false; - } - - idetFoundInterlaced = true; - } - } - } - } - - } - catch - { - StopProcess(processWrapper, 100); - - throw; - } - } - - return idetFoundInterlaced; - } - - private bool? AnalyzeIdetResult(string line) - { - // As you can see, the filter only guessed one frame as progressive. - // Results like this are pretty typical. So if less than 30% of the detections are in the "Undetermined" category, then I only consider the video to be interlaced if at least 65% of the identified frames are in either the TFF or BFF category. - // In this case (310 + 311)/(622) = 99.8% which is well over the 65% metric. I may refine that number with more testing but I honestly do not believe I will need to. - // http://awel.domblogger.net/videoTranscode/interlace.html - var index = line.IndexOf("detection:", StringComparison.OrdinalIgnoreCase); - - if (index == -1) - { - return null; - } - - line = line.Substring(index).Trim(); - var parts = line.Split(' ').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => i.Trim()).ToList(); - - if (parts.Count < 2) - { - return null; - } - double tff = 0; - double bff = 0; - double progressive = 0; - double undetermined = 0; - double total = 0; - - for (var i = 0; i < parts.Count - 1; i++) - { - var part = parts[i]; - - if (string.Equals(part, "tff:", StringComparison.OrdinalIgnoreCase)) - { - tff = GetNextPart(parts, i); - total += tff; - } - else if (string.Equals(part, "bff:", StringComparison.OrdinalIgnoreCase)) - { - bff = GetNextPart(parts, i); - total += tff; - } - else if (string.Equals(part, "progressive:", StringComparison.OrdinalIgnoreCase)) - { - progressive = GetNextPart(parts, i); - total += progressive; - } - else if (string.Equals(part, "undetermined:", StringComparison.OrdinalIgnoreCase)) - { - undetermined = GetNextPart(parts, i); - total += undetermined; - } - } - - if (total == 0) - { - return null; - } - - if ((undetermined / total) >= .3) - { - return false; - } - - if (((tff + bff) / total) >= .4) - { - return true; - } - return false; } - private int GetNextPart(List parts, int index) - { - var next = parts[index + 1]; - - int value; - if (int.TryParse(next, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) - { - return value; - } - return 0; - } - /// /// The us culture /// diff --git a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs index f32dd178f..eef273250 100644 --- a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs +++ b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs @@ -264,6 +264,8 @@ namespace MediaBrowser.MediaEncoding.Probing /// The loro_surmixlev. public string loro_surmixlev { get; set; } + public string field_order { get; set; } + /// /// Gets or sets the disposition. /// diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 7927ddb6a..8b20dca1b 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -508,6 +508,11 @@ namespace MediaBrowser.MediaEncoding.Probing stream.IsAVC = false; } + if (!string.IsNullOrWhiteSpace(streamInfo.field_order) && !string.Equals(streamInfo.field_order, "progressive", StringComparison.OrdinalIgnoreCase)) + { + stream.IsInterlaced = true; + } + // Filter out junk if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) { diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 6a0fdede3..91ea977f7 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Model.LiveTv TunerHosts = new List(); ListingProviders = new List(); MediaLocationsCreated = new string[] { }; - RecordingEncodingFormat = "mp4"; + RecordingEncodingFormat = "mkv"; RecordingPostProcessorArguments = "\"{path}\""; EnableRecordingEncoding = true; } diff --git a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj index 4d9f3e7c4..bb67a9e36 100644 --- a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj +++ b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj @@ -386,14 +386,8 @@ Resources\dashboard-ui\livetvstatus.html - - Resources\dashboard-ui\livetvtunerprovider-hdhomerun.html - - - Resources\dashboard-ui\livetvtunerprovider-m3u.html - - - Resources\dashboard-ui\livetvtunerprovider-satip.html + + Resources\dashboard-ui\livetvtuner.html Resources\dashboard-ui\log.html @@ -560,12 +554,6 @@ Resources\dashboard-ui\wizardlibrary.html - - Resources\dashboard-ui\wizardlivetvguide.html - - - Resources\dashboard-ui\wizardlivetvtuner.html - Resources\dashboard-ui\wizardsettings.html @@ -800,6 +788,9 @@ Resources\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js + + Resources\dashboard-ui\bower_components\emby-webcomponents\staticbackdrops.js + Resources\dashboard-ui\bower_components\emby-webcomponents\thememediaplayer.js @@ -1253,6 +1244,9 @@ Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\empty.png + + Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingbutton.js + Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css @@ -1736,9 +1730,15 @@ Resources\dashboard-ui\components\iap.js + + Resources\dashboard-ui\components\maintabsmanager.js + Resources\dashboard-ui\components\remotecontrol.js + + Resources\dashboard-ui\components\tunerpicker.js + Resources\dashboard-ui\components\viewcontainer-lite.js @@ -2141,8 +2141,8 @@ Resources\dashboard-ui\dashboard\librarysettings.js - - Resources\dashboard-ui\dashboard\livetvtunerprovider-satip.js + + Resources\dashboard-ui\dashboard\livetvtuner.js Resources\dashboard-ui\dashboard\logpage.js @@ -2171,6 +2171,12 @@ Resources\dashboard-ui\legacy\selectmenu.js + + Resources\dashboard-ui\offline\offline.html + + + Resources\dashboard-ui\offline\offline.js + Resources\dashboard-ui\scripts\addpluginpage.js @@ -2189,9 +2195,6 @@ Resources\dashboard-ui\scripts\channels.js - - Resources\dashboard-ui\scripts\channelslatest.js - Resources\dashboard-ui\scripts\connectlogin.js @@ -2306,12 +2309,6 @@ Resources\dashboard-ui\scripts\livetvsuggested.js - - Resources\dashboard-ui\scripts\livetvtunerprovider-hdhomerun.js - - - Resources\dashboard-ui\scripts\livetvtunerprovider-m3u.js - Resources\dashboard-ui\scripts\localsync.js @@ -2516,12 +2513,6 @@ Resources\dashboard-ui\scripts\wizardcontroller.js - - Resources\dashboard-ui\scripts\wizardlivetvguide.js - - - Resources\dashboard-ui\scripts\wizardlivetvtuner.js - Resources\dashboard-ui\scripts\wizardsettings.js @@ -2576,6 +2567,9 @@ Resources\dashboard-ui\strings\es.json + + Resources\dashboard-ui\strings\fa.json + Resources\dashboard-ui\strings\fi.json diff --git a/MediaBrowser.Server.Mac/MacAppHost.cs b/MediaBrowser.Server.Mac/MacAppHost.cs index f84e96126..304472529 100644 --- a/MediaBrowser.Server.Mac/MacAppHost.cs +++ b/MediaBrowser.Server.Mac/MacAppHost.cs @@ -87,10 +87,6 @@ namespace MediaBrowser.Server.Mac throw new NotImplementedException(); } - protected override void EnableLoopbackInternal(string appName) - { - } - public override bool SupportsRunningAsService { get diff --git a/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs b/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs index daf2b90e6..7aeff5ac8 100644 --- a/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs +++ b/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs @@ -1,13 +1,14 @@ using Emby.Common.Implementations.IO; using MediaBrowser.Model.Logging; using Mono.Unix.Native; +using MediaBrowser.Model.System; namespace Emby.Server.Mac.Native { public class MonoFileSystem : ManagedFileSystem { - public MonoFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, string tempPath) - : base(logger, supportsAsyncFileStreams, enableManagedInvalidFileNameChars, true, tempPath) + public MonoFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath) + : base(logger, environmentInfo, tempPath) { } diff --git a/SharedVersion.cs b/SharedVersion.cs index 4bb1f4610..33259a0bc 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.12")] +[assembly: AssemblyVersion("3.2.8.13")]