commit
5063c2c310
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("3.2.8.12")]
|
[assembly: AssemblyVersion("3.2.8.13")]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user