commit
5063c2c310
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1078,6 +1078,8 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
var channel = GetInternalChannel(program.ChannelId);
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
var channelUserdata = _userDataManager.GetUserData(userId, channel);
|
||||
|
||||
if (channelUserdata.Likes ?? false)
|
||||
|
@ -1098,6 +1100,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
score += channelUserdata.PlayCount;
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
|
||||
{
|
||||
var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo);
|
||||
var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -87,10 +87,6 @@ namespace MediaBrowser.Server.Mac
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override void EnableLoopbackInternal(string appName)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool SupportsRunningAsService
|
||||
{
|
||||
get
|
||||
|
|
|
@ -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,3 +1,3 @@
|
|||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("3.2.8.12")]
|
||||
[assembly: AssemblyVersion("3.2.8.13")]
|
||||
|
|
Loading…
Reference in New Issue
Block a user