Merge pull request #2545 from MediaBrowser/dev

Dev
This commit is contained in:
Luke 2017-03-26 12:27:41 -04:00 committed by GitHub
commit 5063c2c310
19 changed files with 227 additions and 482 deletions

View File

@ -16,12 +16,15 @@ namespace Emby.Common.Implementations.Net
internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket
{ {
#region Fields
private Socket _Socket; private Socket _Socket;
private int _LocalPort; private int _LocalPort;
#endregion
private SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
{
SocketFlags = SocketFlags.None
};
private TaskCompletionSource<SocketReceiveResult> _currentReceiveTaskCompletionSource;
public UdpSocket(Socket socket, int localPort, IPAddress ip) public UdpSocket(Socket socket, int localPort, IPAddress ip)
{ {
@ -32,6 +35,32 @@ namespace Emby.Common.Implementations.Net
LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); LocalIPAddress = NetworkManager.ToIpAddressInfo(ip);
_Socket.Bind(new IPEndPoint(ip, _LocalPort)); _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) public UdpSocket(Socket socket, IpEndPointInfo endPoint)
@ -40,6 +69,8 @@ namespace Emby.Common.Implementations.Net
_Socket = socket; _Socket = socket;
_Socket.Connect(NetworkManager.ToIPEndPoint(endPoint)); _Socket.Connect(NetworkManager.ToIPEndPoint(endPoint));
InitReceiveSocketAsyncEventArgs();
} }
public IpAddressInfo LocalIPAddress public IpAddressInfo LocalIPAddress
@ -57,12 +88,12 @@ namespace Emby.Common.Implementations.Net
var tcs = new TaskCompletionSource<SocketReceiveResult>(); var tcs = new TaskCompletionSource<SocketReceiveResult>();
EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0);
var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
state.TaskCompletionSource = tcs;
cancellationToken.Register(() => tcs.TrySetCanceled()); cancellationToken.Register(() => tcs.TrySetCanceled());
#if NETSTANDARD1_6 #if NETSTANDARD1_6
var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
state.TaskCompletionSource = tcs;
_Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer), SocketFlags.None, state.RemoteEndPoint) _Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer), SocketFlags.None, state.RemoteEndPoint)
.ContinueWith((task, asyncState) => .ContinueWith((task, asyncState) =>
{ {
@ -74,7 +105,15 @@ namespace Emby.Common.Implementations.Net
} }
}, state); }, state);
#else #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 #endif
return tcs.Task; return tcs.Task;

View File

@ -498,11 +498,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId)) 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)) if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
{ {
mappedTunerChannelId = tunerChannel.TunerChannelId; mappedTunerChannelId = tunerChannelId;
} }
var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase)); 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) public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken)
{ {
var existingTimer = _timerProvider.GetAll() var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ?
.FirstOrDefault(i => string.Equals(timer.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase)); null :
_timerProvider.GetTimerByProgramId(timer.ProgramId);
if (existingTimer != null) if (existingTimer != null)
{ {
@ -724,10 +731,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return true; return true;
} }
//if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId)) if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId))
//{ {
// return true; return true;
//} }
return false; return false;
}) })
@ -740,7 +747,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
timer.SeriesTimerId = info.Id; timer.SeriesTimerId = info.Id;
timer.IsManual = true; timer.IsManual = true;
_timerProvider.AddOrUpdate(timer); _timerProvider.AddOrUpdate(timer, false);
} }
await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(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); var existingTimer = _timerProvider.GetTimer(timer.Id);
if (existingTimer == null)
{
existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId)
? null
: _timerProvider.GetTimerByProgramId(timer.ProgramId);
}
if (existingTimer == null) if (existingTimer == null)
{ {
if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
@ -2354,9 +2368,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
else 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; 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); UpdateExistingTimerWithNewMetadata(existingTimer, timer);

View File

@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks); var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
var inputModifiers = "-fflags +genpts -async 1 -vsync -1"; 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; long startTimeTicks = 0;
//if (mediaSource.DateLiveStreamOpened.HasValue) //if (mediaSource.DateLiveStreamOpened.HasValue)
@ -193,7 +193,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn"; 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; return inputModifiers + " " + commandLineArgs;
} }

View File

@ -166,5 +166,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase)); 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));
}
} }
} }

View File

@ -1078,6 +1078,8 @@ namespace Emby.Server.Implementations.LiveTv
var channel = GetInternalChannel(program.ChannelId); var channel = GetInternalChannel(program.ChannelId);
if (channel != null)
{
var channelUserdata = _userDataManager.GetUserData(userId, channel); var channelUserdata = _userDataManager.GetUserData(userId, channel);
if (channelUserdata.Likes ?? false) if (channelUserdata.Likes ?? false)
@ -1098,6 +1100,7 @@ namespace Emby.Server.Implementations.LiveTv
{ {
score += channelUserdata.PlayCount; score += channelUserdata.PlayCount;
} }
}
return score; return score;
} }

View File

@ -275,6 +275,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public int HD { get; set; } 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) private MediaSourceInfo GetMediaSource(TunerHostInfo info, string channelId, ChannelInfo channelInfo, string profile)
{ {
int? width = null; int? width = null;
@ -362,14 +372,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
nal = "0"; nal = "0";
} }
var url = GetApiUrl(info, true) + "/auto/v" + channelId; var url = GetApiUrl(info, false);
// If raw was used, the tuner doesn't support params
if (!string.IsNullOrWhiteSpace(profile)
&& !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
{
url += "?transcode=" + profile;
}
var id = profile; var id = profile;
if (string.IsNullOrWhiteSpace(id)) if (string.IsNullOrWhiteSpace(id))
@ -378,92 +381,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
id += "_" + url.GetMD5().ToString("N"); 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 var mediaSource = new MediaSourceInfo
{ {
Path = url, Path = url,
@ -527,7 +444,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (isLegacyTuner) if (isLegacyTuner)
{ {
list.Add(GetLegacyMediaSource(info, hdhrId, channelInfo)); list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
} }
else else
{ {
@ -579,20 +496,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var hdhomerunChannel = channelInfo as HdHomerunChannelInfo; var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner) var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
{
var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo);
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
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);
} }
else else
{ {
var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile); //return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
//var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); 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);
} }
} }

View File

@ -120,8 +120,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// send url to start streaming // send url to start streaming
await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, cancellationToken).ConfigureAwait(false); 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); _logger.Info("Opened HDHR UDP stream from {0}", remoteAddress);
if (!cancellationToken.IsCancellationRequested) if (!cancellationToken.IsCancellationRequested)
@ -132,8 +130,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
onStarted = () => openTaskCompletionSource.TrySetResult(true); onStarted = () => openTaskCompletionSource.TrySetResult(true);
} }
var stream = new UdpClientStream(udpClient); await _multicastStream.CopyUntilCancelled(udpClient, onStarted, cancellationToken).ConfigureAwait(false);
await _multicastStream.CopyUntilCancelled(stream, onStarted, cancellationToken).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException ex) catch (OperationCanceledException ex)
@ -158,7 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
await hdHomerunManager.StopStreaming().ConfigureAwait(false); await hdHomerunManager.StopStreaming().ConfigureAwait(false);
udpClient.Dispose();
_liveStreamTaskCompletionSource.TrySetResult(true); _liveStreamTaskCompletionSource.TrySetResult(true);
} }
} }
@ -171,127 +167,4 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return _multicastStream.CopyToAsync(stream); 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();
}
}
} }

View File

@ -144,11 +144,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
channel.TunerChannelId = string.IsNullOrWhiteSpace(tvgId) ? channelId : tvgId; 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>(); var channelIdValues = new List<string>();
if (!string.IsNullOrWhiteSpace(channelId)) if (!string.IsNullOrWhiteSpace(channelId))
{ {

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
namespace Emby.Server.Implementations.LiveTv.TunerHosts namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
@ -40,7 +41,52 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var allStreams = _outputStreams.ToList(); var allStreams = _outputStreams.ToList();
foreach (var stream in allStreams) 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) if (onStarted != null)

View File

@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class QueueStream public class QueueStream
{ {
private readonly Stream _outputStream; 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; private CancellationToken _cancellationToken;
public TaskCompletionSource<bool> TaskCompletion { get; private set; } public TaskCompletionSource<bool> TaskCompletion { get; private set; }
@ -28,9 +28,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
TaskCompletion = new TaskCompletionSource<bool>(); 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) public void Start(CancellationToken cancellationToken)
@ -39,12 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Task.Run(() => StartInternal()); Task.Run(() => StartInternal());
} }
private byte[] Dequeue() private Tuple<byte[], int, int> Dequeue()
{ {
byte[] bytes; Tuple<byte[], int, int> result;
if (_queue.TryDequeue(out bytes)) if (_queue.TryDequeue(out result))
{ {
return bytes; return result;
} }
return null; return null;
@ -58,10 +58,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
while (true) while (true)
{ {
var bytes = Dequeue(); var result = Dequeue();
if (bytes != null) 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 else
{ {

View File

@ -1583,6 +1583,15 @@ namespace MediaBrowser.Controller.MediaEncoding
MediaSourceInfo mediaSource, MediaSourceInfo mediaSource,
string requestedUrl) string requestedUrl)
{ {
if (state == null)
{
throw new ArgumentNullException("state");
}
if (mediaSource == null)
{
throw new ArgumentNullException("mediaSource");
}
state.MediaPath = mediaSource.Path; state.MediaPath = mediaSource.Path;
state.InputProtocol = mediaSource.Protocol; state.InputProtocol = mediaSource.Protocol;
state.InputContainer = mediaSource.Container; state.InputContainer = mediaSource.Container;

View File

@ -647,9 +647,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); 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) 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 formats = (video.Container ?? string.Empty).Split(',').ToList();
var enableInterlacedDection = formats.Contains("vob", StringComparer.OrdinalIgnoreCase) || var enableInterlacedDection = formats.Contains("vob", StringComparer.OrdinalIgnoreCase) ||
@ -698,165 +698,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
} }
if (video.Protocol != MediaProtocol.File)
{
return false; 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> /// <summary>
/// The us culture /// The us culture
/// </summary> /// </summary>

View File

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

View File

@ -508,6 +508,11 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.IsAVC = false; stream.IsAVC = false;
} }
if (!string.IsNullOrWhiteSpace(streamInfo.field_order) && !string.Equals(streamInfo.field_order, "progressive", StringComparison.OrdinalIgnoreCase))
{
stream.IsInterlaced = true;
}
// Filter out junk // Filter out junk
if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1)
{ {

View File

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

View File

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

View File

@ -87,10 +87,6 @@ namespace MediaBrowser.Server.Mac
throw new NotImplementedException(); throw new NotImplementedException();
} }
protected override void EnableLoopbackInternal(string appName)
{
}
public override bool SupportsRunningAsService public override bool SupportsRunningAsService
{ {
get get

View File

@ -1,13 +1,14 @@
using Emby.Common.Implementations.IO; using Emby.Common.Implementations.IO;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using Mono.Unix.Native; using Mono.Unix.Native;
using MediaBrowser.Model.System;
namespace Emby.Server.Mac.Native namespace Emby.Server.Mac.Native
{ {
public class MonoFileSystem : ManagedFileSystem public class MonoFileSystem : ManagedFileSystem
{ {
public MonoFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, string tempPath) public MonoFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath)
: base(logger, supportsAsyncFileStreams, enableManagedInvalidFileNameChars, true, tempPath) : base(logger, environmentInfo, tempPath)
{ {
} }

View File

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