فهرست منبع

Merge pull request #2545 from MediaBrowser/dev

Dev
Luke 8 سال پیش
والد
کامیت
5063c2c310

+ 47 - 8
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<SocketReceiveResult> _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<SocketReceiveResult>();
 
             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<Byte>(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;

+ 26 - 11
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<string> 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);
 

+ 6 - 2
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;
         }

+ 5 - 0
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));
+        }
     }
 }

+ 19 - 16
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 (channelUserdata.Likes ?? false)
-            {
-                score += 2;
-            }
-            else if (!(channelUserdata.Likes ?? true))
+            if (channel != null)
             {
-                score -= 2;
-            }
+                var channelUserdata = _userDataManager.GetUserData(userId, channel);
 
-            if (channelUserdata.IsFavorite)
-            {
-                score += 3;
-            }
+                if (channelUserdata.Likes ?? false)
+                {
+                    score += 2;
+                }
+                else if (!(channelUserdata.Likes ?? true))
+                {
+                    score -= 2;
+                }
 
-            if (factorChannelWatchCount)
-            {
-                score += channelUserdata.PlayCount;
+                if (channelUserdata.IsFavorite)
+                {
+                    score += 3;
+                }
+
+                if (factorChannelWatchCount)
+                {
+                    score += channelUserdata.PlayCount;
+                }
             }
 
             return score;

+ 17 - 103
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<EncodingOptions>("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<MediaStream>
-                        {
-                            new MediaStream
-                            {
-                                Type = MediaStreamType.Video,
-                                // Set the index to -1 because we don't know the exact index of the video stream within the container
-                                Index = -1,
-                                IsInterlaced = 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<EncodingOptions>("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);
             }
         }
 

+ 1 - 128
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<int> 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();
-        }
-    }
 }

+ 0 - 5
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<string>();
             if (!string.IsNullOrWhiteSpace(channelId))
             {

+ 47 - 1
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)

+ 10 - 10
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<byte[]> _queue = new ConcurrentQueue<byte[]>();
+        private readonly ConcurrentQueue<Tuple<byte[],int,int>> _queue = new ConcurrentQueue<Tuple<byte[], int, int>>();
         private CancellationToken _cancellationToken;
         public TaskCompletionSource<bool> TaskCompletion { get; private set; }
 
@@ -28,9 +28,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             TaskCompletion = new TaskCompletionSource<bool>();
         }
 
-        public void Queue(byte[] bytes)
+        public void Queue(byte[] bytes, int offset, int count)
         {
-            _queue.Enqueue(bytes);
+            _queue.Enqueue(new Tuple<byte[], int, int>(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<byte[], int, int> Dequeue()
         {
-            byte[] bytes;
-            if (_queue.TryDequeue(out bytes))
+            Tuple<byte[], int, int> 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
                     {

+ 9 - 0
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;

+ 3 - 159
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<bool> 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<string> parts, int index)
-        {
-            var next = parts[index + 1];
-
-            int value;
-            if (int.TryParse(next, NumberStyles.Any, CultureInfo.InvariantCulture, out value))
-            {
-                return value;
-            }
-            return 0;
-        }
-
         /// <summary>
         /// The us culture
         /// </summary>

+ 2 - 0
MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs

@@ -264,6 +264,8 @@ namespace MediaBrowser.MediaEncoding.Probing
         /// <value>The loro_surmixlev.</value>
         public string loro_surmixlev { get; set; }
 
+        public string field_order { get; set; }
+
         /// <summary>
         /// Gets or sets the disposition.
         /// </summary>

+ 5 - 0
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)
             {

+ 1 - 1
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

@@ -34,7 +34,7 @@ namespace MediaBrowser.Model.LiveTv
             TunerHosts = new List<TunerHostInfo>();
             ListingProviders = new List<ListingsProviderInfo>();
             MediaLocationsCreated = new string[] { };
-            RecordingEncodingFormat = "mp4";
+            RecordingEncodingFormat = "mkv";
             RecordingPostProcessorArguments = "\"{path}\"";
             EnableRecordingEncoding = true;
         }

+ 25 - 31
MediaBrowser.Server.Mac/Emby.Server.Mac.csproj

@@ -386,14 +386,8 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvstatus.html">
       <Link>Resources\dashboard-ui\livetvstatus.html</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvtunerprovider-hdhomerun.html">
-      <Link>Resources\dashboard-ui\livetvtunerprovider-hdhomerun.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvtunerprovider-m3u.html">
-      <Link>Resources\dashboard-ui\livetvtunerprovider-m3u.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvtunerprovider-satip.html">
-      <Link>Resources\dashboard-ui\livetvtunerprovider-satip.html</Link>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvtuner.html">
+      <Link>Resources\dashboard-ui\livetvtuner.html</Link>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\log.html">
       <Link>Resources\dashboard-ui\log.html</Link>
@@ -560,12 +554,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardlibrary.html">
       <Link>Resources\dashboard-ui\wizardlibrary.html</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardlivetvguide.html">
-      <Link>Resources\dashboard-ui\wizardlivetvguide.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardlivetvtuner.html">
-      <Link>Resources\dashboard-ui\wizardlivetvtuner.html</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardsettings.html">
       <Link>Resources\dashboard-ui\wizardsettings.html</Link>
     </BundleResource>
@@ -800,6 +788,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\staticbackdrops.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\staticbackdrops.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\thememediaplayer.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\thememediaplayer.js</Link>
     </BundleResource>
@@ -1253,6 +1244,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\empty.png">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\empty.png</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingbutton.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingbutton.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css</Link>
     </BundleResource>
@@ -1736,9 +1730,15 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\iap.js">
       <Link>Resources\dashboard-ui\components\iap.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\maintabsmanager.js">
+      <Link>Resources\dashboard-ui\components\maintabsmanager.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\remotecontrol.js">
       <Link>Resources\dashboard-ui\components\remotecontrol.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\tunerpicker.js">
+      <Link>Resources\dashboard-ui\components\tunerpicker.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\viewcontainer-lite.js">
       <Link>Resources\dashboard-ui\components\viewcontainer-lite.js</Link>
     </BundleResource>
@@ -2141,8 +2141,8 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\librarysettings.js">
       <Link>Resources\dashboard-ui\dashboard\librarysettings.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\livetvtunerprovider-satip.js">
-      <Link>Resources\dashboard-ui\dashboard\livetvtunerprovider-satip.js</Link>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\livetvtuner.js">
+      <Link>Resources\dashboard-ui\dashboard\livetvtuner.js</Link>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\logpage.js">
       <Link>Resources\dashboard-ui\dashboard\logpage.js</Link>
@@ -2171,6 +2171,12 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\selectmenu.js">
       <Link>Resources\dashboard-ui\legacy\selectmenu.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\offline\offline.html">
+      <Link>Resources\dashboard-ui\offline\offline.html</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\offline\offline.js">
+      <Link>Resources\dashboard-ui\offline\offline.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\addpluginpage.js">
       <Link>Resources\dashboard-ui\scripts\addpluginpage.js</Link>
     </BundleResource>
@@ -2189,9 +2195,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\channels.js">
       <Link>Resources\dashboard-ui\scripts\channels.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\channelslatest.js">
-      <Link>Resources\dashboard-ui\scripts\channelslatest.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\connectlogin.js">
       <Link>Resources\dashboard-ui\scripts\connectlogin.js</Link>
     </BundleResource>
@@ -2306,12 +2309,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvsuggested.js">
       <Link>Resources\dashboard-ui\scripts\livetvsuggested.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvtunerprovider-hdhomerun.js">
-      <Link>Resources\dashboard-ui\scripts\livetvtunerprovider-hdhomerun.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvtunerprovider-m3u.js">
-      <Link>Resources\dashboard-ui\scripts\livetvtunerprovider-m3u.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\localsync.js">
       <Link>Resources\dashboard-ui\scripts\localsync.js</Link>
     </BundleResource>
@@ -2516,12 +2513,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardcontroller.js">
       <Link>Resources\dashboard-ui\scripts\wizardcontroller.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardlivetvguide.js">
-      <Link>Resources\dashboard-ui\scripts\wizardlivetvguide.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardlivetvtuner.js">
-      <Link>Resources\dashboard-ui\scripts\wizardlivetvtuner.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardsettings.js">
       <Link>Resources\dashboard-ui\scripts\wizardsettings.js</Link>
     </BundleResource>
@@ -2576,6 +2567,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\es.json">
       <Link>Resources\dashboard-ui\strings\es.json</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\fa.json">
+      <Link>Resources\dashboard-ui\strings\fa.json</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\fi.json">
       <Link>Resources\dashboard-ui\strings\fi.json</Link>
     </BundleResource>

+ 0 - 4
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

+ 3 - 2
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)
         {
         }
 

+ 1 - 1
SharedVersion.cs

@@ -1,3 +1,3 @@
 using System.Reflection;
 
-[assembly: AssemblyVersion("3.2.8.12")]
+[assembly: AssemblyVersion("3.2.8.13")]