aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Common.Implementations/IO/LnkShortcutHandler.cs (renamed from MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs)3
-rw-r--r--Emby.Common.Implementations/IO/ManagedFileSystem.cs15
-rw-r--r--Emby.Common.Implementations/Net/NetAcceptSocket.cs43
-rw-r--r--Emby.Common.Implementations/Net/SocketFactory.cs40
-rw-r--r--Emby.Common.Implementations/Net/UdpSocket.cs206
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs4
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs2
-rw-r--r--Emby.Drawing/ImageProcessor.cs20
-rw-r--r--Emby.Server.Core/ApplicationHost.cs56
-rw-r--r--Emby.Server.Core/HttpServerFactory.cs4
-rw-r--r--Emby.Server.Core/IO/LibraryMonitor.cs11
-rw-r--r--Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs32
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs79
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs4
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj8
-rw-r--r--Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs46
-rw-r--r--Emby.Server.Implementations/HttpServer/FileWriter.cs191
-rw-r--r--Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs17
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs8
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs42
-rw-r--r--Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs6
-rw-r--r--Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs8
-rw-r--r--Emby.Server.Implementations/HttpServer/SwaggerService.cs47
-rw-r--r--Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs2
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs27
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs153
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs8
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs100
-rw-r--r--Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs29
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs158
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs256
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs13
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs17
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs69
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs57
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs1
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs32
-rw-r--r--Emby.Server.Implementations/Security/PluginSecurityManager.cs2
-rw-r--r--Emby.Server.Implementations/Services/ResponseHelper.cs82
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs29
-rw-r--r--Emby.Server.Implementations/Udp/UdpServer.cs26
-rw-r--r--Emby.Server.Implementations/packages.config2
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs66
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs146
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs40
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs41
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs8
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs12
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs68
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs6
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs184
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs6
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs30
-rw-r--r--MediaBrowser.Api/StartupWizardService.cs76
-rw-r--r--MediaBrowser.Api/TvShowsService.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs7
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicGenre.cs7
-rw-r--r--MediaBrowser.Controller/Entities/GameGenre.cs7
-rw-r--r--MediaBrowser.Controller/Entities/Genre.cs7
-rw-r--r--MediaBrowser.Controller/Entities/IHasImages.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Person.cs7
-rw-r--r--MediaBrowser.Controller/Entities/Studio.cs7
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Year.cs7
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs11
-rw-r--r--MediaBrowser.Controller/LiveTv/ITunerHost.cs2
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj1
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs237
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs11
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs10
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs (renamed from MediaBrowser.MediaEncoding/Encoder/JobLogger.cs)47
-rw-r--r--MediaBrowser.Controller/Net/StaticResultOptions.cs13
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs1
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs11
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs162
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs135
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj1
-rw-r--r--MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs19
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs2
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs1
-rw-r--r--MediaBrowser.Model/Dlna/ProfileConditionValue.cs3
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs63
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs66
-rw-r--r--MediaBrowser.Model/Dlna/TranscodingProfile.cs6
-rw-r--r--MediaBrowser.Model/IO/IFileSystem.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvOptions.cs5
-rw-r--r--MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs2
-rw-r--r--MediaBrowser.Model/Net/IAcceptSocket.cs3
-rw-r--r--MediaBrowser.Model/Net/ISocket.cs1
-rw-r--r--MediaBrowser.Model/Net/ISocketFactory.cs2
-rw-r--r--MediaBrowser.Model/Querying/NextUpQuery.cs3
-rw-r--r--MediaBrowser.Model/Services/IAsyncStreamWriter.cs5
-rw-r--r--MediaBrowser.Model/Services/IRequest.cs6
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs17
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs17
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs21
-rw-r--r--MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs120
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs2
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs2
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs41
-rw-r--r--MediaBrowser.Server.Mac/Emby.Server.Mac.csproj56
-rw-r--r--MediaBrowser.Server.Mac/MacAppHost.cs40
-rw-r--r--MediaBrowser.Server.Mac/Main.cs7
-rw-r--r--MediaBrowser.Server.Mac/Native/MonoFileSystem.cs5
-rw-r--r--MediaBrowser.Server.Mono/Native/MonoFileSystem.cs5
-rw-r--r--MediaBrowser.Server.Mono/Program.cs7
-rw-r--r--MediaBrowser.ServerApplication/MainStartup.cs8
-rw-r--r--MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj3
-rw-r--r--MediaBrowser.ServerApplication/Native/LoopUtil.cs (renamed from MediaBrowser.ServerApplication/Native/LoopbackUtil.cs)9
-rw-r--r--MediaBrowser.ServerApplication/WindowsAppHost.cs11
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs5
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec2
-rw-r--r--RSSDP/SsdpCommunicationsServer.cs10
-rw-r--r--SocketHttpListener.Portable/Net/EndPointListener.cs6
-rw-r--r--SocketHttpListener.Portable/Net/EndPointManager.cs3
-rw-r--r--SocketHttpListener.Portable/Net/HttpConnection.cs16
-rw-r--r--SocketHttpListener.Portable/Net/HttpListener.cs12
-rw-r--r--SocketHttpListener.Portable/Net/HttpListenerContext.cs6
-rw-r--r--SocketHttpListener.Portable/Net/HttpListenerRequest.cs2
-rw-r--r--SocketHttpListener.Portable/Net/HttpListenerResponse.cs14
-rw-r--r--SocketHttpListener.Portable/Net/ResponseStream.cs84
133 files changed, 2259 insertions, 1855 deletions
diff --git a/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs b/Emby.Common.Implementations/IO/LnkShortcutHandler.cs
index b4a87b9b4..5d5f46057 100644
--- a/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs
+++ b/Emby.Common.Implementations/IO/LnkShortcutHandler.cs
@@ -6,7 +6,7 @@ using System.Security;
using System.Text;
using MediaBrowser.Model.IO;
-namespace MediaBrowser.ServerApplication.Native
+namespace Emby.Common.Implementations.IO
{
public class LnkShortcutHandler :IShortcutHandler
{
@@ -35,7 +35,6 @@ namespace MediaBrowser.ServerApplication.Native
/// <summary>
/// Class NativeMethods
/// </summary>
- [SuppressUnmanagedCodeSecurity]
public static class NativeMethods
{
/// <summary>
diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs
index 3fe20f659..0c1c02cd5 100644
--- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.System;
namespace Emby.Common.Implementations.IO
{
@@ -18,17 +19,21 @@ namespace Emby.Common.Implementations.IO
private readonly bool _supportsAsyncFileStreams;
private char[] _invalidFileNameChars;
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
- private bool EnableFileSystemRequestConcat = true;
+ private bool EnableFileSystemRequestConcat;
private string _tempPath;
- public ManagedFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, bool enableFileSystemRequestConcat, string tempPath)
+ public ManagedFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath)
{
Logger = logger;
- _supportsAsyncFileStreams = supportsAsyncFileStreams;
+ _supportsAsyncFileStreams = true;
_tempPath = tempPath;
- EnableFileSystemRequestConcat = enableFileSystemRequestConcat;
- SetInvalidFileNameChars(enableManagedInvalidFileNameChars);
+
+ // On Linux, this needs to be true or symbolic links are ignored
+ EnableFileSystemRequestConcat = environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows &&
+ environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.OSX;
+
+ SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows);
}
public void AddShortcutHandler(IShortcutHandler handler)
diff --git a/Emby.Common.Implementations/Net/NetAcceptSocket.cs b/Emby.Common.Implementations/Net/NetAcceptSocket.cs
index 731ad1b74..3721709e6 100644
--- a/Emby.Common.Implementations/Net/NetAcceptSocket.cs
+++ b/Emby.Common.Implementations/Net/NetAcceptSocket.cs
@@ -2,6 +2,7 @@
using System.Net;
using System.Net.Sockets;
using System.Threading;
+using System.Threading.Tasks;
using Emby.Common.Implementations.Networking;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Logging;
@@ -59,7 +60,7 @@ namespace Emby.Common.Implementations.Net
#if NET46
Socket.Close();
#else
- Socket.Dispose();
+ Socket.Dispose();
#endif
}
@@ -96,6 +97,46 @@ namespace Emby.Common.Implementations.Net
_acceptor.StartAccept();
}
+#if NET46
+ public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken)
+ {
+ var options = TransmitFileOptions.UseKernelApc;
+
+ var completionSource = new TaskCompletionSource<bool>();
+
+ var result = Socket.BeginSendFile(path, preBuffer, postBuffer, options, new AsyncCallback(FileSendCallback), new Tuple<Socket, string, TaskCompletionSource<bool>>(Socket, path, completionSource));
+
+ return completionSource.Task;
+ }
+
+ private void FileSendCallback(IAsyncResult ar)
+ {
+ // Retrieve the socket from the state object.
+ Tuple<Socket, string, TaskCompletionSource<bool>> data = (Tuple<Socket, string, TaskCompletionSource<bool>>)ar.AsyncState;
+
+ var client = data.Item1;
+ var path = data.Item2;
+ var taskCompletion = data.Item3;
+
+ // Complete sending the data to the remote device.
+ try {
+ client.EndSendFile(ar);
+ taskCompletion.TrySetResult(true);
+}
+ catch(SocketException ex){
+ _logger.Info("Socket.SendFile failed for {0}. error code {1}", path, ex.SocketErrorCode);
+ taskCompletion.TrySetException(ex);
+}catch(Exception ex){
+ taskCompletion.TrySetException(ex);
+}
+ }
+#else
+ public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+#endif
+
public void Dispose()
{
Socket.Dispose();
diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs
index 523f4da85..1fd367afb 100644
--- a/Emby.Common.Implementations/Net/SocketFactory.cs
+++ b/Emby.Common.Implementations/Net/SocketFactory.cs
@@ -52,6 +52,18 @@ namespace Emby.Common.Implementations.Net
{
throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex);
}
+ catch (ArgumentException ex)
+ {
+ if (dualMode)
+ {
+ // Mono for BSD incorrectly throws ArgumentException instead of SocketException
+ throw new SocketCreateException("AddressFamilyNotSupported", ex);
+ }
+ else
+ {
+ throw;
+ }
+ }
}
public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort)
@@ -59,6 +71,7 @@ namespace Emby.Common.Implementations.Net
if (remotePort < 0) throw new ArgumentException("remotePort cannot be less than zero.", "remotePort");
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
+
try
{
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
@@ -96,10 +109,31 @@ namespace Emby.Common.Implementations.Net
}
}
+ public ISocket CreateUdpBroadcastSocket(int localPort)
+ {
+ if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
+
+ var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
+ try
+ {
+ retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
+
+ return new UdpSocket(retVal, localPort, IPAddress.Any);
+ }
+ catch
+ {
+ if (retVal != null)
+ retVal.Dispose();
+
+ throw;
+ }
+ }
+
/// <summary>
- /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
- /// </summary>
- /// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
+ /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
+ /// </summary>
+ /// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
{
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs
index 8e2a1da6f..f9181eb6a 100644
--- a/Emby.Common.Implementations/Net/UdpSocket.cs
+++ b/Emby.Common.Implementations/Net/UdpSocket.cs
@@ -16,12 +16,23 @@ namespace Emby.Common.Implementations.Net
internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket
{
-
- #region Fields
-
private Socket _Socket;
private int _LocalPort;
- #endregion
+
+ private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
+ {
+ SocketFlags = SocketFlags.None
+ };
+
+ private readonly SocketAsyncEventArgs _sendSocketAsyncEventArgs = new SocketAsyncEventArgs()
+ {
+ SocketFlags = SocketFlags.None
+ };
+
+ private TaskCompletionSource<SocketReceiveResult> _currentReceiveTaskCompletionSource;
+ private TaskCompletionSource<int> _currentSendTaskCompletionSource;
+
+ private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
public UdpSocket(Socket socket, int localPort, IPAddress ip)
{
@@ -32,6 +43,61 @@ namespace Emby.Common.Implementations.Net
LocalIPAddress = NetworkManager.ToIpAddressInfo(ip);
_Socket.Bind(new IPEndPoint(ip, _LocalPort));
+
+ InitReceiveSocketAsyncEventArgs();
+ }
+
+ private void InitReceiveSocketAsyncEventArgs()
+ {
+ var receiveBuffer = new byte[8192];
+ _receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
+ _receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed;
+
+ var sendBuffer = new byte[8192];
+ _sendSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length);
+ _sendSocketAsyncEventArgs.Completed += _sendSocketAsyncEventArgs_Completed;
+ }
+
+ private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
+ {
+ var tcs = _currentReceiveTaskCompletionSource;
+ if (tcs != null)
+ {
+ _currentReceiveTaskCompletionSource = null;
+
+ if (e.SocketError == SocketError.Success)
+ {
+ tcs.TrySetResult(new SocketReceiveResult
+ {
+ Buffer = e.Buffer,
+ ReceivedBytes = e.BytesTransferred,
+ RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint),
+ LocalIPAddress = LocalIPAddress
+ });
+ }
+ else
+ {
+ tcs.TrySetException(new Exception("SocketError: " + e.SocketError));
+ }
+ }
+ }
+
+ private void _sendSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
+ {
+ var tcs = _currentSendTaskCompletionSource;
+ if (tcs != null)
+ {
+ _currentSendTaskCompletionSource = null;
+
+ if (e.SocketError == SocketError.Success)
+ {
+ tcs.TrySetResult(e.BytesTransferred);
+ }
+ else
+ {
+ tcs.TrySetException(new Exception("SocketError: " + e.SocketError));
+ }
+ }
}
public UdpSocket(Socket socket, IpEndPointInfo endPoint)
@@ -40,6 +106,8 @@ namespace Emby.Common.Implementations.Net
_Socket = socket;
_Socket.Connect(NetworkManager.ToIPEndPoint(endPoint));
+
+ InitReceiveSocketAsyncEventArgs();
}
public IpAddressInfo LocalIPAddress
@@ -48,32 +116,33 @@ namespace Emby.Common.Implementations.Net
private set;
}
- #region ISocket Members
-
public Task<SocketReceiveResult> ReceiveAsync(CancellationToken cancellationToken)
{
ThrowIfDisposed();
-
var tcs = new TaskCompletionSource<SocketReceiveResult>();
-
EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0);
+
var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
state.TaskCompletionSource = tcs;
-#if NETSTANDARD1_6
- _Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer), SocketFlags.None, state.RemoteEndPoint)
- .ContinueWith((task, asyncState) =>
+ cancellationToken.Register(() => tcs.TrySetCanceled());
+
+ _receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint;
+ _currentReceiveTaskCompletionSource = tcs;
+
+ try
+ {
+ var willRaiseEvent = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs);
+
+ if (!willRaiseEvent)
{
- if (task.Status != TaskStatus.Faulted)
- {
- var receiveState = asyncState as AsyncReceiveState;
- receiveState.RemoteEndPoint = task.Result.RemoteEndPoint;
- ProcessResponse(receiveState, () => task.Result.ReceivedBytes, LocalIPAddress);
- }
- }, state);
-#else
- _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state);
-#endif
+ _receiveSocketAsyncEventArgs_Completed(this, _receiveSocketAsyncEventArgs);
+ }
+ }
+ catch (Exception ex)
+ {
+ tcs.TrySetException(ex);
+ }
return tcs.Task;
}
@@ -129,61 +198,69 @@ namespace Emby.Common.Implementations.Net
taskSource.TrySetException(ex);
}
- //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port));
-
return taskSource.Task;
#endif
- }
+ //ThrowIfDisposed();
- #endregion
+ //if (buffer == null) throw new ArgumentNullException("messageData");
+ //if (endPoint == null) throw new ArgumentNullException("endPoint");
- #region Overrides
+ //cancellationToken.ThrowIfCancellationRequested();
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- var socket = _Socket;
- if (socket != null)
- socket.Dispose();
- }
- }
+ //var tcs = new TaskCompletionSource<int>();
+
+ //cancellationToken.Register(() => tcs.TrySetCanceled());
+
+ //_sendSocketAsyncEventArgs.SetBuffer(buffer, 0, size);
+ //_sendSocketAsyncEventArgs.RemoteEndPoint = NetworkManager.ToIPEndPoint(endPoint);
+ //_currentSendTaskCompletionSource = tcs;
- #endregion
+ //var willRaiseEvent = _Socket.SendAsync(_sendSocketAsyncEventArgs);
- #region Private Methods
+ //if (!willRaiseEvent)
+ //{
+ // _sendSocketAsyncEventArgs_Completed(this, _sendSocketAsyncEventArgs);
+ //}
- private static void ProcessResponse(AsyncReceiveState state, Func<int> receiveData, IpAddressInfo localIpAddress)
+ //return tcs.Task;
+ }
+
+ public async Task SendWithLockAsync(byte[] buffer, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken)
{
- try
- {
- var bytesRead = receiveData();
+ ThrowIfDisposed();
- var ipEndPoint = state.RemoteEndPoint as IPEndPoint;
- state.TaskCompletionSource.SetResult(
- new SocketReceiveResult
- {
- Buffer = state.Buffer,
- ReceivedBytes = bytesRead,
- RemoteEndPoint = ToIpEndPointInfo(ipEndPoint),
- LocalIPAddress = localIpAddress
- }
- );
- }
- catch (ObjectDisposedException)
+ //await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
{
- state.TaskCompletionSource.SetCanceled();
+ await SendAsync(buffer, size, endPoint, cancellationToken).ConfigureAwait(false);
}
- catch (SocketException se)
+ finally
{
- if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown)
- state.TaskCompletionSource.SetException(se);
- else
- state.TaskCompletionSource.SetCanceled();
+ //_sendLock.Release();
}
- catch (Exception ex)
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
{
- state.TaskCompletionSource.SetException(ex);
+ var socket = _Socket;
+ if (socket != null)
+ socket.Dispose();
+
+ _sendLock.Dispose();
+
+ var tcs = _currentReceiveTaskCompletionSource;
+ if (tcs != null)
+ {
+ tcs.TrySetCanceled();
+ }
+ var sendTcs = _currentSendTaskCompletionSource;
+ if (sendTcs != null)
+ {
+ sendTcs.TrySetCanceled();
+ }
}
}
@@ -227,10 +304,6 @@ namespace Emby.Common.Implementations.Net
#endif
}
- #endregion
-
- #region Private Classes
-
private class AsyncReceiveState
{
public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint)
@@ -247,8 +320,5 @@ namespace Emby.Common.Implementations.Net
public TaskCompletionSource<SocketReceiveResult> TaskCompletionSource { get; set; }
}
-
- #endregion
-
}
}
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index d048dcde1..c7eae91dd 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -208,7 +208,7 @@ namespace Emby.Dlna.Didl
var targetHeight = streamInfo.TargetHeight;
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
- streamInfo.VideoCodec,
+ streamInfo.TargetVideoCodec,
streamInfo.TargetAudioCodec,
targetWidth,
targetHeight,
@@ -352,7 +352,7 @@ namespace Emby.Dlna.Didl
var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
streamInfo.TargetAudioCodec,
- streamInfo.VideoCodec,
+ streamInfo.TargetVideoCodec,
streamInfo.TargetAudioBitrate,
targetWidth,
targetHeight,
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index 3c07e95db..b73332c4b 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -542,7 +542,7 @@ namespace Emby.Dlna.PlayTo
{
var list = new ContentFeatureBuilder(profile)
.BuildVideoHeader(streamInfo.Container,
- streamInfo.VideoCodec,
+ streamInfo.TargetVideoCodec,
streamInfo.TargetAudioCodec,
streamInfo.TargetWidth,
streamInfo.TargetHeight,
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index a15f75c9a..5b04ceea2 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -238,7 +238,7 @@ namespace Emby.Drawing
var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
- var imageProcessingLockTaken = false;
+ //var imageProcessingLockTaken = false;
try
{
@@ -253,9 +253,9 @@ namespace Emby.Drawing
var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
- await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
+ //await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
- imageProcessingLockTaken = true;
+ //imageProcessingLockTaken = true;
_imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
CopyFile(tmpPath, cacheFilePath);
@@ -273,13 +273,13 @@ namespace Emby.Drawing
// Just spit out the original file if all the options are default
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
- finally
- {
- if (imageProcessingLockTaken)
- {
- _imageProcessingSemaphore.Release();
- }
- }
+ //finally
+ //{
+ // if (imageProcessingLockTaken)
+ // {
+ // _imageProcessingSemaphore.Release();
+ // }
+ //}
}
private void CopyFile(string src, string destination)
diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs
index 4425d1a0b..50c572b8c 100644
--- a/Emby.Server.Core/ApplicationHost.cs
+++ b/Emby.Server.Core/ApplicationHost.cs
@@ -61,7 +61,7 @@ using System.Threading;
using System.Threading.Tasks;
using Emby.Common.Implementations;
using Emby.Common.Implementations.Archiving;
-using Emby.Common.Implementations.Networking;
+using Emby.Common.Implementations.IO;
using Emby.Common.Implementations.Reflection;
using Emby.Common.Implementations.Serialization;
using Emby.Common.Implementations.TextEncoding;
@@ -93,7 +93,7 @@ using Emby.Server.Implementations.Social;
using Emby.Server.Implementations.Channels;
using Emby.Server.Implementations.Collections;
using Emby.Server.Implementations.Dto;
-using Emby.Server.Implementations.EntryPoints;
+using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.FileOrganization;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.HttpServer.Security;
@@ -107,7 +107,6 @@ using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations;
using Emby.Server.Implementations.ServerManager;
using Emby.Server.Implementations.Session;
-using Emby.Server.Implementations.Social;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
using MediaBrowser.Model.Activity;
@@ -294,6 +293,13 @@ namespace Emby.Server.Core
ImageEncoder = imageEncoder;
SetBaseExceptionMessage();
+
+ if (environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
+ {
+ fileSystem.AddShortcutHandler(new LnkShortcutHandler());
+ }
+
+ fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
}
private Version _version;
@@ -606,7 +612,7 @@ namespace Emby.Server.Core
CertificatePath = GetCertificatePath(true);
Certificate = GetCertificate(CertificatePath);
- HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, SupportsDualModeSockets);
+ HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, FileSystemManager, SupportsDualModeSockets);
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
RegisterSingleInstance(HttpServer, false);
progress.Report(10);
@@ -796,17 +802,25 @@ namespace Emby.Server.Core
info.FFMpegFilename = "ffmpeg";
info.FFProbeFilename = "ffprobe";
info.ArchiveType = "7z";
- info.Version = "20160215";
+ info.Version = "20170308";
info.DownloadUrls = GetLinuxDownloadUrls();
}
else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
{
info.FFMpegFilename = "ffmpeg.exe";
info.FFProbeFilename = "ffprobe.exe";
- info.Version = "20160410";
+ info.Version = "20170308";
info.ArchiveType = "7z";
info.DownloadUrls = GetWindowsDownloadUrls();
}
+ else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
+ {
+ info.FFMpegFilename = "ffmpeg";
+ info.FFProbeFilename = "ffprobe";
+ info.ArchiveType = "7z";
+ info.Version = "20170308";
+ info.DownloadUrls = GetMacDownloadUrls();
+ }
else
{
// No version available - user requirement
@@ -816,6 +830,20 @@ namespace Emby.Server.Core
return info;
}
+ private string[] GetMacDownloadUrls()
+ {
+ switch (EnvironmentInfo.SystemArchitecture)
+ {
+ case Architecture.X64:
+ return new[]
+ {
+ "https://embydata.com/downloads/ffmpeg/osx/ffmpeg-x64-20170308.7z"
+ };
+ }
+
+ return new string[] { };
+ }
+
private string[] GetWindowsDownloadUrls()
{
switch (EnvironmentInfo.SystemArchitecture)
@@ -823,12 +851,12 @@ namespace Emby.Server.Core
case Architecture.X64:
return new[]
{
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win64.7z"
+ "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win64.7z"
};
case Architecture.X86:
return new[]
{
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win32.7z"
+ "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win32.7z"
};
}
@@ -842,12 +870,12 @@ namespace Emby.Server.Core
case Architecture.X64:
return new[]
{
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z"
+ "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-64bit-static.7z"
};
case Architecture.X86:
return new[]
{
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-32bit-static.7z"
+ "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-32bit-static.7z"
};
}
@@ -1714,14 +1742,8 @@ namespace Emby.Server.Core
((IProcess)sender).Dispose();
}
- public void EnableLoopback(string appName)
- {
- EnableLoopbackInternal(appName);
- }
-
- protected virtual void EnableLoopbackInternal(string appName)
+ public virtual void EnableLoopback(string appName)
{
-
}
private void RegisterModules()
diff --git a/Emby.Server.Core/HttpServerFactory.cs b/Emby.Server.Core/HttpServerFactory.cs
index deed3c6f3..dfd435c33 100644
--- a/Emby.Server.Core/HttpServerFactory.cs
+++ b/Emby.Server.Core/HttpServerFactory.cs
@@ -45,6 +45,7 @@ namespace Emby.Server.Core
IXmlSerializer xml,
IEnvironmentInfo environment,
ICertificate certificate,
+ IFileSystem fileSystem,
bool enableDualModeSockets)
{
var logger = logManager.GetLogger("HttpServer");
@@ -65,7 +66,8 @@ namespace Emby.Server.Core
certificate,
new StreamFactory(),
GetParseFn,
- enableDualModeSockets);
+ enableDualModeSockets,
+ fileSystem);
}
private static Func<string, object> GetParseFn(Type propertyType)
diff --git a/Emby.Server.Core/IO/LibraryMonitor.cs b/Emby.Server.Core/IO/LibraryMonitor.cs
index 4df9b930e..e1e3186c3 100644
--- a/Emby.Server.Core/IO/LibraryMonitor.cs
+++ b/Emby.Server.Core/IO/LibraryMonitor.cs
@@ -421,17 +421,6 @@ namespace Emby.Server.Core.IO
var path = e.FullPath;
- // For deletes, use the parent path
- if (e.ChangeType == WatcherChangeTypes.Deleted)
- {
- var parentPath = Path.GetDirectoryName(path);
-
- if (!string.IsNullOrWhiteSpace(parentPath))
- {
- path = parentPath;
- }
- }
-
ReportFileSystemChanged(path);
}
catch (Exception ex)
diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
index 2819a249f..0096f2284 100644
--- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
+++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities.Audio;
@@ -22,13 +23,15 @@ namespace Emby.Server.Implementations.Data
private readonly IItemRepository _itemRepo;
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
+ private readonly IApplicationPaths _appPaths;
- public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem)
+ public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
{
_libraryManager = libraryManager;
_itemRepo = itemRepo;
_logger = logger;
_fileSystem = fileSystem;
+ _appPaths = appPaths;
}
public string Name
@@ -150,13 +153,27 @@ namespace Emby.Server.Implementations.Data
try
{
- if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
+ var isPathInLibrary = false;
+
+ if (allLibraryPaths.Any(i => path.StartsWith(i, StringComparison.Ordinal)) ||
+ allLibraryPaths.Contains(path, StringComparer.Ordinal) ||
+ path.StartsWith(_appPaths.ProgramDataPath, StringComparison.Ordinal))
{
- continue;
+ isPathInLibrary = true;
+
+ if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
+ {
+ continue;
+ }
}
var libraryItem = _libraryManager.GetItemById(item.Item1);
+ if (libraryItem == null)
+ {
+ continue;
+ }
+
if (libraryItem.IsTopParent)
{
continue;
@@ -180,7 +197,14 @@ namespace Emby.Server.Implementations.Data
continue;
}
- _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
+ if (isPathInLibrary)
+ {
+ _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
+ }
+ else
+ {
+ _logger.Info("Deleting item from database {0} because path is no longer in the server library. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
+ }
await libraryItem.OnFileDeleted().ConfigureAwait(false);
}
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index e65ebeb04..c5ba6c892 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -3204,6 +3204,40 @@ namespace Emby.Server.Implementations.Data
}
}
+ private bool IsAlphaNumeric(string str)
+ {
+ if (string.IsNullOrWhiteSpace(str))
+ return false;
+
+ for (int i = 0; i < str.Length; i++)
+ {
+ if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i]))))
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool IsValidType(string value)
+ {
+ return IsAlphaNumeric(value);
+ }
+
+ private bool IsValidMediaType(string value)
+ {
+ return IsAlphaNumeric(value);
+ }
+
+ private bool IsValidId(string value)
+ {
+ return IsAlphaNumeric(value);
+ }
+
+ private bool IsValidPersonType(string value)
+ {
+ return IsAlphaNumeric(value);
+ }
+
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "")
{
if (query.IsResumable ?? false)
@@ -3423,9 +3457,9 @@ namespace Emby.Server.Implementations.Data
statement.TryBind("@ChannelId", query.ChannelIds[0]);
}
}
- if (query.ChannelIds.Length > 1)
+ else if (query.ChannelIds.Length > 1)
{
- var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i + "'").ToArray());
+ var inClause = string.Join(",", query.ChannelIds.Where(IsValidId).Select(i => "'" + i + "'").ToArray());
whereClauses.Add(string.Format("ChannelId in ({0})", inClause));
}
@@ -4157,17 +4191,18 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))");
}
}
- if (query.MediaTypes.Length == 1)
+ var queryMediaTypes = query.MediaTypes.Where(IsValidMediaType).ToArray();
+ if (queryMediaTypes.Length == 1)
{
whereClauses.Add("MediaType=@MediaTypes");
if (statement != null)
{
- statement.TryBind("@MediaTypes", query.MediaTypes[0]);
+ statement.TryBind("@MediaTypes", queryMediaTypes[0]);
}
}
- if (query.MediaTypes.Length > 1)
+ else if (queryMediaTypes.Length > 1)
{
- var val = string.Join(",", query.MediaTypes.Select(i => "'" + i + "'").ToArray());
+ var val = string.Join(",", queryMediaTypes.Select(i => "'" + i + "'").ToArray());
whereClauses.Add("MediaType in (" + val + ")");
}
@@ -4273,7 +4308,9 @@ namespace Emby.Server.Implementations.Data
//var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0;
var enableItemsByName = query.IncludeItemsByName ?? false;
- if (query.TopParentIds.Length == 1)
+ var queryTopParentIds = query.TopParentIds.Where(IsValidId).ToArray();
+
+ if (queryTopParentIds.Length == 1)
{
if (enableItemsByName)
{
@@ -4289,12 +4326,12 @@ namespace Emby.Server.Implementations.Data
}
if (statement != null)
{
- statement.TryBind("@TopParentId", query.TopParentIds[0]);
+ statement.TryBind("@TopParentId", queryTopParentIds[0]);
}
}
- if (query.TopParentIds.Length > 1)
+ else if (queryTopParentIds.Length > 1)
{
- var val = string.Join(",", query.TopParentIds.Select(i => "'" + i + "'").ToArray());
+ var val = string.Join(",", queryTopParentIds.Select(i => "'" + i + "'").ToArray());
if (enableItemsByName)
{
@@ -4544,7 +4581,7 @@ namespace Emby.Server.Implementations.Data
return result;
}
- return new[] { value };
+ return new[] { value }.Where(IsValidType);
}
public async Task DeleteItem(Guid id, CancellationToken cancellationToken)
@@ -4696,31 +4733,35 @@ namespace Emby.Server.Implementations.Data
statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidParamValue());
}
}
- if (query.PersonTypes.Count == 1)
+ var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
+
+ if (queryPersonTypes.Count == 1)
{
whereClauses.Add("PersonType=@PersonType");
if (statement != null)
{
- statement.TryBind("@PersonType", query.PersonTypes[0]);
+ statement.TryBind("@PersonType", queryPersonTypes[0]);
}
}
- if (query.PersonTypes.Count > 1)
+ else if (queryPersonTypes.Count > 1)
{
- var val = string.Join(",", query.PersonTypes.Select(i => "'" + i + "'").ToArray());
+ var val = string.Join(",", queryPersonTypes.Select(i => "'" + i + "'").ToArray());
whereClauses.Add("PersonType in (" + val + ")");
}
- if (query.ExcludePersonTypes.Count == 1)
+ var queryExcludePersonTypes = query.ExcludePersonTypes.Where(IsValidPersonType).ToList();
+
+ if (queryExcludePersonTypes.Count == 1)
{
whereClauses.Add("PersonType<>@PersonType");
if (statement != null)
{
- statement.TryBind("@PersonType", query.ExcludePersonTypes[0]);
+ statement.TryBind("@PersonType", queryExcludePersonTypes[0]);
}
}
- if (query.ExcludePersonTypes.Count > 1)
+ else if (queryExcludePersonTypes.Count > 1)
{
- var val = string.Join(",", query.ExcludePersonTypes.Select(i => "'" + i + "'").ToArray());
+ var val = string.Join(",", queryExcludePersonTypes.Select(i => "'" + i + "'").ToArray());
whereClauses.Add("PersonType not in (" + val + ")");
}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 147abd171..d477008a5 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -491,7 +491,7 @@ namespace Emby.Server.Implementations.Dto
}
}
- //if (!(item is LiveTvProgram) || fields.Contains(ItemFields.PlayAccess))
+ if (!(item is LiveTvProgram) || fields.Contains(ItemFields.PlayAccess))
{
dto.PlayAccess = item.GetPlayAccess(user);
}
@@ -1639,7 +1639,7 @@ namespace Emby.Server.Implementations.Dto
var width = size.Width;
var height = size.Height;
- if (width == 0 || height == 0)
+ if (width.Equals(0) || height.Equals(0))
{
return null;
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index c704d0e4e..670acd37f 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -84,7 +84,7 @@
<Compile Include="FileOrganization\NameUtils.cs" />
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
<Compile Include="FileOrganization\TvFolderOrganizer.cs" />
- <Compile Include="HttpServer\GetSwaggerResource.cs" />
+ <Compile Include="HttpServer\FileWriter.cs" />
<Compile Include="HttpServer\HttpListenerHost.cs" />
<Compile Include="HttpServer\HttpResultFactory.cs" />
<Compile Include="HttpServer\LoggerUtils.cs" />
@@ -102,7 +102,6 @@
<Compile Include="HttpServer\SocketSharp\WebSocketSharpRequest.cs" />
<Compile Include="HttpServer\SocketSharp\WebSocketSharpResponse.cs" />
<Compile Include="HttpServer\StreamWriter.cs" />
- <Compile Include="HttpServer\SwaggerService.cs" />
<Compile Include="Images\BaseDynamicImageProvider.cs" />
<Compile Include="IO\FileRefresher.cs" />
<Compile Include="IO\MbLinkShortcutHandler.cs" />
@@ -170,7 +169,6 @@
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
<Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunManager.cs" />
- <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHttpStream.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunUdpStream.cs" />
@@ -302,8 +300,8 @@
<HintPath>..\packages\Emby.XmlTv.1.0.7\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
<Private>True</Private>
</Reference>
- <Reference Include="MediaBrowser.Naming, Version=1.0.6201.24431, Culture=neutral, processorArchitecture=MSIL">
- <HintPath>..\packages\MediaBrowser.Naming.1.0.4\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
+ <Reference Include="MediaBrowser.Naming, Version=1.0.6279.25941, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\MediaBrowser.Naming.1.0.5\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SQLitePCL.pretty, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
diff --git a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
index f841b8b6b..0a9c67285 100644
--- a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
+++ b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
@@ -677,20 +677,7 @@ namespace Emby.Server.Implementations.FileOrganization
var newPath = GetSeasonFolderPath(series, seasonNumber.Value, options);
- // MAX_PATH - trailing <NULL> charachter - drive component: 260 - 1 - 3 = 256
- // Usually newPath would include the drive component, but use 256 to be sure
- var maxFilenameLength = 256 - newPath.Length;
-
- if (!newPath.EndsWith(@"\"))
- {
- // Remove 1 for missing backslash combining path and filename
- maxFilenameLength--;
- }
-
- // Remove additional 4 chars to prevent PathTooLongException for downloaded subtitles (eg. filename.ext.eng.srt)
- maxFilenameLength -= 4;
-
- var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options, maxFilenameLength);
+ var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options);
if (string.IsNullOrEmpty(episodeFileName))
{
@@ -742,7 +729,7 @@ namespace Emby.Server.Implementations.FileOrganization
return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName));
}
- private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options, int? maxLength)
+ private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options)
{
seriesName = _fileSystem.GetValidFilename(seriesName).Trim();
@@ -786,32 +773,15 @@ namespace Emby.Server.Implementations.FileOrganization
.Replace("%0e", episodeNumber.ToString("00", _usCulture))
.Replace("%00e", episodeNumber.ToString("000", _usCulture));
- if (maxLength.HasValue && result.Contains("%#"))
- {
- // Substract 3 for the temp token length (%#1, %#2 or %#3)
- int maxRemainingTitleLength = maxLength.Value - result.Length + 3;
- string shortenedEpisodeTitle = string.Empty;
-
- if (maxRemainingTitleLength > 5)
- {
- // A title with fewer than 5 letters wouldn't be of much value
- shortenedEpisodeTitle = episodeTitle.Substring(0, Math.Min(maxRemainingTitleLength, episodeTitle.Length));
- }
-
- result = result.Replace("%#1", shortenedEpisodeTitle)
- .Replace("%#2", shortenedEpisodeTitle.Replace(" ", "."))
- .Replace("%#3", shortenedEpisodeTitle.Replace(" ", "_"));
- }
-
- if (maxLength.HasValue && result.Length > maxLength.Value)
+ if (result.Contains("%#"))
{
- // There may be cases where reducing the title length may still not be sufficient to
- // stay below maxLength
- var msg = string.Format("Unable to generate an episode file name shorter than {0} characters to constrain to the max path limit", maxLength);
- throw new Exception(msg);
+ result = result.Replace("%#1", episodeTitle)
+ .Replace("%#2", episodeTitle.Replace(" ", "."))
+ .Replace("%#3", episodeTitle.Replace(" ", "_"));
}
- return result;
+ // Finally, call GetValidFilename again in case user customized the episode expression with any invalid filename characters
+ return _fileSystem.GetValidFilename(result).Trim();
}
private bool IsSameEpisode(string sourcePath, string newPath)
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs
new file mode 100644
index 000000000..dbaf97b1e
--- /dev/null
+++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Services;
+
+namespace Emby.Server.Implementations.HttpServer
+{
+ public class FileWriter : IHttpResult
+ {
+ private ILogger Logger { get; set; }
+
+ private string RangeHeader { get; set; }
+ private bool IsHeadRequest { get; set; }
+
+ private long RangeStart { get; set; }
+ private long RangeEnd { get; set; }
+ private long RangeLength { get; set; }
+ private long TotalContentLength { get; set; }
+
+ public Action OnComplete { get; set; }
+ public Action OnError { get; set; }
+ private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+ public List<Cookie> Cookies { get; private set; }
+
+ public FileShareMode FileShare { get; set; }
+
+ /// <summary>
+ /// The _options
+ /// </summary>
+ private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
+ /// <summary>
+ /// Gets the options.
+ /// </summary>
+ /// <value>The options.</value>
+ public IDictionary<string, string> Headers
+ {
+ get { return _options; }
+ }
+
+ public string Path { get; set; }
+
+ public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem)
+ {
+ if (string.IsNullOrEmpty(contentType))
+ {
+ throw new ArgumentNullException("contentType");
+ }
+
+ Path = path;
+ Logger = logger;
+ RangeHeader = rangeHeader;
+
+ Headers["Content-Type"] = contentType;
+
+ TotalContentLength = fileSystem.GetFileInfo(path).Length;
+
+ if (string.IsNullOrWhiteSpace(rangeHeader))
+ {
+ Headers["Content-Length"] = TotalContentLength.ToString(UsCulture);
+ StatusCode = HttpStatusCode.OK;
+ }
+ else
+ {
+ Headers["Accept-Ranges"] = "bytes";
+ StatusCode = HttpStatusCode.PartialContent;
+ SetRangeValues();
+ }
+
+ FileShare = FileShareMode.Read;
+ Cookies = new List<Cookie>();
+ }
+
+ /// <summary>
+ /// Sets the range values.
+ /// </summary>
+ private void SetRangeValues()
+ {
+ var requestedRange = RequestedRanges[0];
+
+ // If the requested range is "0-", we can optimize by just doing a stream copy
+ if (!requestedRange.Value.HasValue)
+ {
+ RangeEnd = TotalContentLength - 1;
+ }
+ else
+ {
+ RangeEnd = requestedRange.Value.Value;
+ }
+
+ RangeStart = requestedRange.Key;
+ RangeLength = 1 + RangeEnd - RangeStart;
+
+ // Content-Length is the length of what we're serving, not the original content
+ Headers["Content-Length"] = RangeLength.ToString(UsCulture);
+ Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
+ }
+
+ /// <summary>
+ /// The _requested ranges
+ /// </summary>
+ private List<KeyValuePair<long, long?>> _requestedRanges;
+ /// <summary>
+ /// Gets the requested ranges.
+ /// </summary>
+ /// <value>The requested ranges.</value>
+ protected List<KeyValuePair<long, long?>> RequestedRanges
+ {
+ get
+ {
+ if (_requestedRanges == null)
+ {
+ _requestedRanges = new List<KeyValuePair<long, long?>>();
+
+ // Example: bytes=0-,32-63
+ var ranges = RangeHeader.Split('=')[1].Split(',');
+
+ foreach (var range in ranges)
+ {
+ var vals = range.Split('-');
+
+ long start = 0;
+ long? end = null;
+
+ if (!string.IsNullOrEmpty(vals[0]))
+ {
+ start = long.Parse(vals[0], UsCulture);
+ }
+ if (!string.IsNullOrEmpty(vals[1]))
+ {
+ end = long.Parse(vals[1], UsCulture);
+ }
+
+ _requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
+ }
+ }
+
+ return _requestedRanges;
+ }
+ }
+
+ public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken)
+ {
+ try
+ {
+ // Headers only
+ if (IsHeadRequest)
+ {
+ return;
+ }
+
+ if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1))
+ {
+ Logger.Info("Transmit file {0}", Path);
+ await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
+ return;
+ }
+
+ await response.TransmitFile(Path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ if (OnComplete != null)
+ {
+ OnComplete();
+ }
+ }
+ }
+
+ public string ContentType { get; set; }
+
+ public IRequest RequestContext { get; set; }
+
+ public object Response { get; set; }
+
+ public int Status { get; set; }
+
+ public HttpStatusCode StatusCode
+ {
+ get { return (HttpStatusCode)Status; }
+ set { Status = (int)value; }
+ }
+
+ public string StatusDescription { get; set; }
+
+ }
+}
diff --git a/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs b/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs
deleted file mode 100644
index 819ede1ab..000000000
--- a/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using MediaBrowser.Model.Services;
-
-namespace Emby.Server.Implementations.HttpServer
-{
- /// <summary>
- /// Class GetDashboardResource
- /// </summary>
- [Route("/swagger-ui/{ResourceName*}", "GET")]
- public class GetSwaggerResource
- {
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- public string ResourceName { get; set; }
- }
-} \ No newline at end of file
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index b5a3c2992..6d15cc619 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -52,6 +52,7 @@ namespace Emby.Server.Implementations.HttpServer
private readonly ISocketFactory _socketFactory;
private readonly ICryptoProvider _cryptoProvider;
+ private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private readonly IXmlSerializer _xmlSerializer;
private readonly ICertificate _certificate;
@@ -70,8 +71,7 @@ namespace Emby.Server.Implementations.HttpServer
ILogger logger,
IServerConfigurationManager config,
string serviceName,
- string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets)
- : base()
+ string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem)
{
Instance = this;
@@ -89,6 +89,7 @@ namespace Emby.Server.Implementations.HttpServer
_streamFactory = streamFactory;
_funcParseFn = funcParseFn;
_enableDualModeSockets = enableDualModeSockets;
+ _fileSystem = fileSystem;
_config = config;
_logger = logger;
@@ -226,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer
_cryptoProvider,
_streamFactory,
_enableDualModeSockets,
- GetRequest);
+ GetRequest,
+ _fileSystem);
}
private IHttpRequest GetRequest(HttpListenerContext httpContext)
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index 6bfd83110..310161d41 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -474,10 +474,6 @@ namespace Emby.Server.Implementations.HttpServer
{
throw new ArgumentNullException("cacheKey");
}
- if (options.ContentFactory == null)
- {
- throw new ArgumentNullException("factoryFn");
- }
var key = cacheKey.ToString("N");
@@ -560,30 +556,44 @@ namespace Emby.Server.Implementations.HttpServer
{
var rangeHeader = requestContext.Headers.Get("Range");
- var stream = await factoryFn().ConfigureAwait(false);
+ if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path))
+ {
+ return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
+ {
+ OnComplete = options.OnComplete,
+ OnError = options.OnError,
+ FileShare = options.FileShare
+ };
+ }
if (!string.IsNullOrEmpty(rangeHeader))
{
+ var stream = await factoryFn().ConfigureAwait(false);
+
return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger)
{
OnComplete = options.OnComplete
};
}
+ else
+ {
+ var stream = await factoryFn().ConfigureAwait(false);
- responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
+ responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
- if (isHeadRequest)
- {
- stream.Dispose();
+ if (isHeadRequest)
+ {
+ stream.Dispose();
- return GetHttpResult(new byte[] { }, contentType, true);
- }
+ return GetHttpResult(new byte[] { }, contentType, true);
+ }
- return new StreamWriter(stream, contentType, _logger)
- {
- OnComplete = options.OnComplete,
- OnError = options.OnError
- };
+ return new StreamWriter(stream, contentType, _logger)
+ {
+ OnComplete = options.OnComplete,
+ OnError = options.OnError
+ };
+ }
}
using (var stream = await factoryFn().ConfigureAwait(false))
diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
index 652fc4f83..b11b2fe88 100644
--- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
+++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
@@ -27,10 +27,11 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
private readonly ISocketFactory _socketFactory;
private readonly ICryptoProvider _cryptoProvider;
private readonly IStreamFactory _streamFactory;
+ private readonly IFileSystem _fileSystem;
private readonly Func<HttpListenerContext, IHttpRequest> _httpRequestFactory;
private readonly bool _enableDualMode;
- public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory)
+ public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory, IFileSystem fileSystem)
{
_logger = logger;
_certificate = certificate;
@@ -42,6 +43,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
_streamFactory = streamFactory;
_enableDualMode = enableDualMode;
_httpRequestFactory = httpRequestFactory;
+ _fileSystem = fileSystem;
}
public Action<Exception, IRequest, bool> ErrorHandler { get; set; }
@@ -54,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
public void Start(IEnumerable<string> urlPrefixes)
{
if (_listener == null)
- _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider);
+ _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem);
_listener.EnableDualMode = _enableDualMode;
diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
index 36f795411..fd30b227f 100644
--- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
+++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
@@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using SocketHttpListener.Net;
using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
@@ -189,5 +192,10 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
public void ClearCookies()
{
}
+
+ public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
+ {
+ return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken);
+ }
}
}
diff --git a/Emby.Server.Implementations/HttpServer/SwaggerService.cs b/Emby.Server.Implementations/HttpServer/SwaggerService.cs
deleted file mode 100644
index d41946645..000000000
--- a/Emby.Server.Implementations/HttpServer/SwaggerService.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Net;
-using System.IO;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Services;
-
-namespace Emby.Server.Implementations.HttpServer
-{
- public class SwaggerService : IService, IRequiresRequest
- {
- private readonly IServerApplicationPaths _appPaths;
- private readonly IFileSystem _fileSystem;
-
- public SwaggerService(IServerApplicationPaths appPaths, IFileSystem fileSystem, IHttpResultFactory resultFactory)
- {
- _appPaths = appPaths;
- _fileSystem = fileSystem;
- _resultFactory = resultFactory;
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetSwaggerResource request)
- {
- var swaggerDirectory = Path.Combine(_appPaths.ApplicationResourcesPath, "swagger-ui");
-
- var requestedFile = Path.Combine(swaggerDirectory, request.ResourceName.Replace('/', _fileSystem.DirectorySeparatorChar));
-
- return _resultFactory.GetStaticFileResult(Request, requestedFile).Result;
- }
-
- /// <summary>
- /// Gets or sets the result factory.
- /// </summary>
- /// <value>The result factory.</value>
- private readonly IHttpResultFactory _resultFactory;
-
- /// <summary>
- /// Gets or sets the request context.
- /// </summary>
- /// <value>The request context.</value>
- public IRequest Request { get; set; }
- }
-}
diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
index 7a36691df..38908c2bd 100644
--- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
@@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Images
{
return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
}
- if (item is Playlist || item is MusicGenre)
+ if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre)
{
return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index f7706db47..026486efc 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -514,6 +514,11 @@ namespace Emby.Server.Implementations.Library
public Guid GetNewItemId(string key, Type type)
{
+ return GetNewItemIdInternal(key, type, false);
+ }
+
+ private Guid GetNewItemIdInternal(string key, Type type, bool forceCaseInsensitive)
+ {
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentNullException("key");
@@ -531,7 +536,7 @@ namespace Emby.Server.Implementations.Library
.Replace("/", "\\");
}
- if (!ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
+ if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
{
key = key.ToLower();
}
@@ -865,7 +870,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Person}.</returns>
public Person GetPerson(string name)
{
- return CreateItemByName<Person>(Person.GetPath(name), name);
+ return CreateItemByName<Person>(Person.GetPath, name);
}
/// <summary>
@@ -875,7 +880,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Studio}.</returns>
public Studio GetStudio(string name)
{
- return CreateItemByName<Studio>(Studio.GetPath(name), name);
+ return CreateItemByName<Studio>(Studio.GetPath, name);
}
/// <summary>
@@ -885,7 +890,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Genre}.</returns>
public Genre GetGenre(string name)
{
- return CreateItemByName<Genre>(Genre.GetPath(name), name);
+ return CreateItemByName<Genre>(Genre.GetPath, name);
}
/// <summary>
@@ -895,7 +900,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{MusicGenre}.</returns>
public MusicGenre GetMusicGenre(string name)
{
- return CreateItemByName<MusicGenre>(MusicGenre.GetPath(name), name);
+ return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name);
}
/// <summary>
@@ -905,7 +910,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{GameGenre}.</returns>
public GameGenre GetGameGenre(string name)
{
- return CreateItemByName<GameGenre>(GameGenre.GetPath(name), name);
+ return CreateItemByName<GameGenre>(GameGenre.GetPath, name);
}
/// <summary>
@@ -923,7 +928,7 @@ namespace Emby.Server.Implementations.Library
var name = value.ToString(CultureInfo.InvariantCulture);
- return CreateItemByName<Year>(Year.GetPath(name), name);
+ return CreateItemByName<Year>(Year.GetPath, name);
}
/// <summary>
@@ -933,10 +938,10 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Genre}.</returns>
public MusicArtist GetArtist(string name)
{
- return CreateItemByName<MusicArtist>(MusicArtist.GetPath(name), name);
+ return CreateItemByName<MusicArtist>(MusicArtist.GetPath, name);
}
- private T CreateItemByName<T>(string path, string name)
+ private T CreateItemByName<T>(Func<string,string> getPathFn, string name)
where T : BaseItem, new()
{
if (typeof(T) == typeof(MusicArtist))
@@ -957,7 +962,9 @@ namespace Emby.Server.Implementations.Library
}
}
- var id = GetNewItemId(path, typeof(T));
+ var path = getPathFn(name);
+ var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
+ var id = GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
var item = GetItemById(id) as T;
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index c1bd8fe91..ccd4c3631 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -369,6 +369,8 @@ namespace Emby.Server.Implementations.Library
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+ enableAutoClose = false;
+
try
{
var tuple = GetProvider(request.OpenToken);
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 926d82f94..0ea1b38b1 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -442,6 +442,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
result = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false);
+ foreach (var channel in result)
+ {
+ _logger.Info("Found epg channel in {0} {1} {2} {3}", provider.Name, info.ListingsId, channel.Name, channel.Id);
+ }
+
_epgChannels.AddOrUpdate(info.Id, result, (k, v) => result);
}
@@ -493,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));
@@ -639,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)
{
@@ -710,7 +722,34 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new InvalidOperationException("SeriesId for program not found");
}
+ // If any timers have already been manually created, make sure they don't get cancelled
+ var existingTimers = (await GetTimersAsync(CancellationToken.None).ConfigureAwait(false))
+ .Where(i =>
+ {
+ if (string.Equals(i.ProgramId, info.ProgramId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.ProgramId))
+ {
+ return true;
+ }
+
+ if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId))
+ {
+ return true;
+ }
+
+ return false;
+ })
+ .ToList();
+
_seriesTimerProvider.Add(info);
+
+ foreach (var timer in existingTimers)
+ {
+ timer.SeriesTimerId = info.Id;
+ timer.IsManual = true;
+
+ _timerProvider.AddOrUpdate(timer, false);
+ }
+
await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false);
return info.Id;
@@ -991,6 +1030,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (epgChannel == null)
{
+ _logger.Debug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
programs = new List<ProgramInfo>();
}
else
@@ -1276,6 +1316,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
+ var registration = await _liveTvManager.GetRegistrationInfo("dvr").ConfigureAwait(false);
+ if (!registration.IsValid)
+ {
+ _logger.Warn("Emby Premiere required to use Emby DVR.");
+ OnTimerOutOfDate(timer);
+ return;
+ }
+
var activeRecordingInfo = new ActiveRecordingInfo
{
CancellationTokenSource = new CancellationTokenSource(),
@@ -2301,6 +2349,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (existingTimer == null)
{
+ existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId)
+ ? null
+ : _timerProvider.GetTimerByProgramId(timer.ProgramId);
+ }
+
+ if (existingTimer == null)
+ {
if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
{
timer.Status = RecordingStatus.Cancelled;
@@ -2313,12 +2368,16 @@ 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);
+ // Needed by ShouldCancelTimerForSeriesTimer
+ timer.IsManual = existingTimer.IsManual;
+
if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
{
existingTimer.Status = RecordingStatus.Cancelled;
@@ -2516,7 +2575,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
list.Add(new VirtualFolderInfo
{
Locations = new List<string> { customPath },
- Name = "Recorded Series",
+ Name = "Recorded Shows",
CollectionType = CollectionType.TvShows
});
}
@@ -2531,6 +2590,86 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public ProgramInfo Program { get; set; }
public CancellationTokenSource CancellationTokenSource { get; set; }
}
+
+ private const int TunerDiscoveryDurationMs = 3000;
+
+ public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
+ {
+ var list = new List<TunerHostInfo>();
+
+ var configuredDeviceIds = GetConfiguration().TunerHosts
+ .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId))
+ .Select(i => i.DeviceId)
+ .ToList();
+
+ foreach (var host in _liveTvManager.TunerHosts)
+ {
+ var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
+
+ if (newDevicesOnly)
+ {
+ discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase))
+ .ToList();
+ }
+ list.AddRange(discoveredDevices);
+ }
+
+ return list;
+ }
+
+ public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken)
+ {
+ foreach (var host in _liveTvManager.TunerHosts)
+ {
+ await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken)
+ {
+ var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
+
+ var configuredDevices = GetConfiguration().TunerHosts
+ .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ foreach (var device in discoveredDevices)
+ {
+ var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase));
+
+ if (configuredDevice != null)
+ {
+ if (!string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.Info("Tuner url has changed from {0} to {1}", configuredDevice.Url, device.Url);
+
+ configuredDevice.Url = device.Url;
+ await _liveTvManager.SaveTunerHost(configuredDevice).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+
+ private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDuationMs, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var discoveredDevices = await host.DiscoverDevices(discoveryDuationMs, cancellationToken).ConfigureAwait(false);
+
+ foreach (var device in discoveredDevices)
+ {
+ _logger.Info("Discovered tuner device {0} at {1}", host.Name, device.Url);
+ }
+
+ return discoveredDevices;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error discovering tuner devices", ex);
+
+ return new List<TunerHostInfo>();
+ }
+ }
}
public static class ConfigurationExtension
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 0567bdfd9..6cc5b6920 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
var inputModifiers = "-fflags +genpts -async 1 -vsync -1";
- var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4} -y \"{1}\"";
+ var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4}{6} -y \"{1}\"";
long startTimeTicks = 0;
//if (mediaSource.DateLiveStreamOpened.HasValue)
@@ -193,7 +193,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn";
- commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam);
+ var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
+ " -f mp4 -movflags frag_keyframe+empty_moov" :
+ string.Empty;
+
+ commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam, outputParam);
return inputModifiers + " " + commandLineArgs;
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 35868d318..2eec3df8a 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -166,5 +166,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
}
+
+ public TimerInfo GetTimerByProgramId(string programId)
+ {
+ return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
+ }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index c22bb1171..21c4006a6 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -205,6 +205,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
programInfo.ShowId = uniqueString.GetMD5().ToString("N");
+
+ // If we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped
+ if (programInfo.IsSeries && !programInfo.IsRepeat)
+ {
+ if ((programInfo.EpisodeNumber ?? 0) == 0)
+ {
+ programInfo.ShowId = programInfo.ShowId + programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture);
+ }
+ }
}
// Construct an id from the channel and start date
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 618cd1d45..b9e73b62e 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -150,6 +150,21 @@ namespace Emby.Server.Implementations.LiveTv
get { return _listingProviders; }
}
+ public List<NameIdPair> GetTunerHostTypes()
+ {
+ return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
+ {
+ Name = i.Name,
+ Id = i.Type
+
+ }).ToList();
+ }
+
+ public Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
+ {
+ return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken);
+ }
+
void service_DataSourceChanged(object sender, EventArgs e)
{
if (!_isDisposed)
@@ -1063,25 +1078,28 @@ namespace Emby.Server.Implementations.LiveTv
var channel = GetInternalChannel(program.ChannelId);
- var channelUserdata = _userDataManager.GetUserData(userId, channel);
-
- if (channelUserdata.Likes ?? false)
- {
- score += 2;
- }
- else if (!(channelUserdata.Likes ?? true))
+ if (channel != null)
{
- score -= 2;
- }
+ var channelUserdata = _userDataManager.GetUserData(userId, channel);
- if (channelUserdata.IsFavorite)
- {
- score += 3;
- }
+ if (channelUserdata.Likes ?? false)
+ {
+ score += 2;
+ }
+ else if (!(channelUserdata.Likes ?? true))
+ {
+ score -= 2;
+ }
- if (factorChannelWatchCount)
- {
- score += channelUserdata.PlayCount;
+ if (channelUserdata.IsFavorite)
+ {
+ score += 3;
+ }
+
+ if (factorChannelWatchCount)
+ {
+ score += channelUserdata.PlayCount;
+ }
}
return score;
@@ -1180,6 +1198,8 @@ namespace Emby.Server.Implementations.LiveTv
{
EmbyTV.EmbyTV.Current.CreateRecordingFolders();
+ await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
+
var numComplete = 0;
double progressPerService = _services.Count == 0
? 0
@@ -2748,7 +2768,7 @@ namespace Emby.Server.Implementations.LiveTv
private bool IsLiveTvEnabled(User user)
{
- return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count(i => i.IsEnabled) > 0);
+ return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count > 0);
}
public IEnumerable<User> GetEnabledUsers()
@@ -2986,7 +3006,7 @@ namespace Emby.Server.Implementations.LiveTv
if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase))
{
var config = GetConfiguration();
- if (config.TunerHosts.Count(i => i.IsEnabled) > 0 &&
+ if (config.TunerHosts.Count > 0 &&
config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0)
{
return Task.FromResult(new MBRegistrationRecord
@@ -3000,50 +3020,6 @@ namespace Emby.Server.Implementations.LiveTv
return _security.GetRegistrationStatus(feature);
}
- public List<NameValuePair> GetSatIniMappings()
- {
- return new List<NameValuePair>();
- //var names = GetType().Assembly.GetManifestResourceNames().Where(i => i.IndexOf("SatIp.ini", StringComparison.OrdinalIgnoreCase) != -1).ToList();
-
- //return names.Select(GetSatIniMappings).Where(i => i != null).DistinctBy(i => i.Value.Split('|')[0]).ToList();
- }
-
- public NameValuePair GetSatIniMappings(string resource)
- {
- return new NameValuePair();
- //using (var stream = GetType().Assembly.GetManifestResourceStream(resource))
- //{
- // using (var reader = new StreamReader(stream))
- // {
- // var parser = new StreamIniDataParser();
- // IniData data = parser.ReadData(reader);
-
- // var satType1 = data["SATTYPE"]["1"];
- // var satType2 = data["SATTYPE"]["2"];
-
- // if (string.IsNullOrWhiteSpace(satType2))
- // {
- // return null;
- // }
-
- // var srch = "SatIp.ini.";
- // var filename = Path.GetFileName(resource);
-
- // return new NameValuePair
- // {
- // Name = satType1 + " " + satType2,
- // Value = satType2 + "|" + filename.Substring(filename.IndexOf(srch) + srch.Length)
- // };
- // }
- //}
- }
-
- public Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken)
- {
- return Task.FromResult(new List<ChannelInfo>());
- //return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken);
- }
-
public Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken)
{
var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
index f2806292d..5582d8f35 100644
--- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
+++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv
public bool IsHidden
{
- get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count(i => i.IsEnabled) == 0; }
+ get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count == 0; }
}
public bool IsEnabled
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 5ac3812b0..74b8a7764 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
var list = result.ToList();
- Logger.Debug("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
+ Logger.Info("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
if (!string.IsNullOrWhiteSpace(key) && list.Count > 0)
{
@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected virtual List<TunerHostInfo> GetTunerHosts()
{
return GetConfiguration().TunerHosts
- .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
+ .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
.ToList();
}
@@ -135,8 +135,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
// Check to make sure the tuner is available
// If there's only one tuner, don't bother with the check and just let the tuner be the one to throw an error
- if (hostsWithChannel.Count > 1 &&
- !await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false))
+ if (hostsWithChannel.Count > 1 && !await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false))
{
Logger.Error("Tuner is not currently available");
continue;
@@ -208,6 +207,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
foreach (var host in hostsWithChannel)
{
+ if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
try
{
var liveStream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
@@ -243,7 +247,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken);
- protected abstract bool IsValidChannelId(string channelId);
+ protected virtual string ChannelIdPrefix
+ {
+ get
+ {
+ return Type + "_";
+ }
+ }
+ protected virtual bool IsValidChannelId(string channelId)
+ {
+ if (string.IsNullOrWhiteSpace(channelId))
+ {
+ throw new ArgumentNullException("channelId");
+ }
+
+ return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
+ }
protected LiveTvOptions GetConfiguration()
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
deleted file mode 100644
index 336469c50..000000000
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
+++ /dev/null
@@ -1,158 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.LiveTv;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Linq;
-using System.Threading;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Serialization;
-
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
-{
- public class HdHomerunDiscovery : IServerEntryPoint
- {
- private readonly IDeviceDiscovery _deviceDiscovery;
- private readonly IServerConfigurationManager _config;
- private readonly ILogger _logger;
- private readonly ILiveTvManager _liveTvManager;
- private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
- private readonly IHttpClient _httpClient;
- private readonly IJsonSerializer _json;
-
- public HdHomerunDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json)
- {
- _deviceDiscovery = deviceDiscovery;
- _config = config;
- _logger = logger;
- _liveTvManager = liveTvManager;
- _httpClient = httpClient;
- _json = json;
- }
-
- public void Run()
- {
- _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
- }
-
- void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
- {
- string server = null;
- var info = e.Argument;
-
- if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
- {
- string location;
- if (info.Headers.TryGetValue("Location", out location))
- {
- //_logger.Debug("HdHomerun found at {0}", location);
-
- // Just get the beginning of the url
- Uri uri;
- if (Uri.TryCreate(location, UriKind.Absolute, out uri))
- {
- var apiUrl = location.Replace(uri.LocalPath, String.Empty, StringComparison.OrdinalIgnoreCase)
- .TrimEnd('/');
-
- //_logger.Debug("HdHomerun api url: {0}", apiUrl);
- AddDevice(apiUrl);
- }
- }
- }
- }
-
- private async void AddDevice(string url)
- {
- await _semaphore.WaitAsync().ConfigureAwait(false);
-
- try
- {
- var options = GetConfiguration();
-
- if (options.TunerHosts.Any(i =>
- string.Equals(i.Type, HdHomerunHost.DeviceType, StringComparison.OrdinalIgnoreCase) &&
- UriEquals(i.Url, url)))
- {
- return;
- }
-
- // Strip off the port
- url = new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped).TrimEnd('/');
-
- // Test it by pulling down the lineup
- using (var stream = await _httpClient.Get(new HttpRequestOptions
- {
- Url = string.Format("{0}/discover.json", url),
- CancellationToken = CancellationToken.None,
- BufferContent = false
- }))
- {
- var response = _json.DeserializeFromStream<HdHomerunHost.DiscoverResponse>(stream);
-
- var existing = GetConfiguration().TunerHosts
- .FirstOrDefault(i => string.Equals(i.Type, HdHomerunHost.DeviceType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.DeviceId, response.DeviceID, StringComparison.OrdinalIgnoreCase));
-
- if (existing == null)
- {
- await _liveTvManager.SaveTunerHost(new TunerHostInfo
- {
- Type = HdHomerunHost.DeviceType,
- Url = url,
- DeviceId = response.DeviceID
-
- }).ConfigureAwait(false);
- }
- else
- {
- if (!string.Equals(existing.Url, url, StringComparison.OrdinalIgnoreCase))
- {
- existing.Url = url;
- await _liveTvManager.SaveTunerHost(existing).ConfigureAwait(false);
- }
- }
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error saving device", ex);
- }
- finally
- {
- _semaphore.Release();
- }
- }
-
- private bool UriEquals(string savedUri, string location)
- {
- return string.Equals(NormalizeUrl(location), NormalizeUrl(savedUri), StringComparison.OrdinalIgnoreCase);
- }
-
- private string NormalizeUrl(string url)
- {
- if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- url = "http://" + url;
- }
-
- url = url.TrimEnd('/');
-
- // Strip off the port
- return new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped);
- }
-
- private LiveTvOptions GetConfiguration()
- {
- return _config.GetConfiguration<LiveTvOptions>("livetv");
- }
-
- public void Dispose()
- {
- }
- }
-}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 62385e172..8fa1bbe23 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -20,6 +20,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Net;
+using MediaBrowser.Model.System;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
@@ -30,8 +31,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
+ private readonly IEnvironmentInfo _environment;
- public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager)
+ public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment)
: base(config, logger, jsonSerializer, mediaEncoder)
{
_httpClient = httpClient;
@@ -39,6 +41,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_appHost = appHost;
_socketFactory = socketFactory;
_networkManager = networkManager;
+ _environment = environment;
}
public string Name
@@ -56,7 +59,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
get { return "hdhomerun"; }
}
- private const string ChannelIdPrefix = "hdhr_";
+ protected override string ChannelIdPrefix
+ {
+ get
+ {
+ return "hdhr_";
+ }
+ }
private string GetChannelId(TunerHostInfo info, Channels i)
{
@@ -125,7 +134,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
DiscoverResponse response;
if (_modelCache.TryGetValue(info.Url, out response))
{
- return response;
+ if ((DateTime.UtcNow - response.DateQueried).TotalHours <= 12)
+ {
+ return response;
+ }
}
}
@@ -135,8 +147,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
CancellationToken = cancellationToken,
- CacheLength = TimeSpan.FromDays(1),
- CacheMode = CacheMode.Unconditional,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
BufferContent = false
@@ -215,7 +225,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var list = new List<LiveTvTunerInfo>();
foreach (var host in GetConfiguration().TunerHosts
- .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)))
+ .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)))
{
try
{
@@ -268,6 +278,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;
@@ -355,14 +375,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))
@@ -374,92 +387,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
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,
Protocol = MediaProtocol.Udp,
MediaStreams = new List<MediaStream>
{
@@ -520,7 +447,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (isLegacyTuner)
{
- list.Add(GetLegacyMediaSource(info, hdhrId, channelInfo));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
}
else
{
@@ -559,26 +486,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return list;
}
- protected override bool IsValidChannelId(string channelId)
- {
- if (string.IsNullOrWhiteSpace(channelId))
- {
- throw new ArgumentNullException("channelId");
- }
-
- return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
- }
-
protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
{
var profile = streamId.Split('_')[0];
Logger.Info("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelId, streamId, profile);
- if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase))
- {
- throw new ArgumentException("Channel not found");
- }
var hdhrId = GetHdHrIdFromChannelId(channelId);
var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
@@ -586,30 +499,40 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
- if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
- {
- var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo);
- var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ 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
+
+ // The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet
+ var enableHttpStream = _environment.OperatingSystem == OperatingSystem.OSX ||
+ _environment.OperatingSystem == OperatingSystem.BSD;
+ enableHttpStream = true;
+ if (enableHttpStream)
{
- var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
- //var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ mediaSource.Protocol = MediaProtocol.Http;
+
+ var httpUrl = GetApiUrl(info, true) + "/auto/v" + hdhrId;
+
+ // If raw was used, the tuner doesn't support params
+ if (!string.IsNullOrWhiteSpace(profile)
+ && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
+ {
+ httpUrl += "?transcode=" + profile;
+ }
+ mediaSource.Path = httpUrl;
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 HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
}
public async Task Validate(TunerHostInfo info)
{
- if (!info.IsEnabled)
- {
- return;
- }
-
lock (_modelCache)
{
_modelCache.Clear();
@@ -651,6 +574,83 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public string BaseURL { get; set; }
public string LineupURL { get; set; }
public int TunerCount { get; set; }
+
+ public DateTime DateQueried { get; set; }
+
+ public DiscoverResponse()
+ {
+ DateQueried = DateTime.UtcNow;
+ }
+ }
+
+ public async Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
+ {
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token;
+ var list = new List<TunerHostInfo>();
+
+ // Create udp broadcast discovery message
+ byte[] discBytes = { 0, 2, 0, 12, 1, 4, 255, 255, 255, 255, 2, 4, 255, 255, 255, 255, 115, 204, 125, 143 };
+ using (var udpClient = _socketFactory.CreateUdpBroadcastSocket(0))
+ {
+ // Need a way to set the Receive timeout on the socket otherwise this might never timeout?
+ try
+ {
+ await udpClient.SendAsync(discBytes, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken);
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ var deviceIp = response.RemoteEndPoint.IpAddress.Address;
+
+ // check to make sure we have enough bytes received to be a valid message and make sure the 2nd byte is the discover reply byte
+ if (response.ReceivedBytes > 13 && response.Buffer[1] == 3)
+ {
+ var deviceAddress = "http://" + deviceIp;
+
+ var info = await TryGetTunerHostInfo(deviceAddress, cancellationToken).ConfigureAwait(false);
+
+ if (info != null)
+ {
+ list.Add(info);
+ }
+ }
+ }
+
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch
+ {
+ // Socket timeout indicates all messages have been received.
+ }
+ }
+
+ return list;
+ }
+
+ private async Task<TunerHostInfo> TryGetTunerHostInfo(string url, CancellationToken cancellationToken)
+ {
+ var hostInfo = new TunerHostInfo
+ {
+ Type = Type,
+ Url = url
+ };
+
+ try
+ {
+ var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
+
+ hostInfo.DeviceId = modelInfo.DeviceID;
+ hostInfo.FriendlyName = modelInfo.FriendlyName;
+
+ return hostInfo;
+ }
+ catch
+ {
+ // logged at lower levels
+ }
+
+ return null;
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index a881d0ea1..e1572ea3f 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -120,7 +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 response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
_logger.Info("Opened HDHR UDP stream from {0}", remoteAddress);
if (!cancellationToken.IsCancellationRequested)
@@ -131,12 +130,13 @@ 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(new UdpClientStream(udpClient), onStarted, cancellationToken).ConfigureAwait(false);
}
}
- catch (OperationCanceledException)
+ catch (OperationCanceledException ex)
{
+ _logger.Info("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
+ openTaskCompletionSource.TrySetException(ex);
break;
}
catch (Exception ex)
@@ -155,7 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
await hdHomerunManager.StopStreaming().ConfigureAwait(false);
- udpClient.Dispose();
_liveStreamTaskCompletionSource.TrySetResult(true);
}
}
@@ -207,7 +206,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
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;
@@ -291,4 +290,4 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
throw new NotImplementedException();
}
}
-}
+} \ No newline at end of file
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index a939cec7b..4ec70f802 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -46,8 +46,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
get { return "M3U Tuner"; }
}
- private const string ChannelIdPrefix = "m3u_";
-
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
{
var result = await new M3uParser(Logger, _fileSystem, _httpClient, _appHost).Parse(info.Url, ChannelIdPrefix, info.Id, !info.EnableTvgId, cancellationToken).ConfigureAwait(false);
@@ -87,16 +85,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
- protected override bool IsValidChannelId(string channelId)
- {
- if (string.IsNullOrWhiteSpace(channelId))
- {
- throw new ArgumentNullException("channelId");
- }
-
- return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
- }
-
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
{
var urlHash = info.Url.GetMD5().ToString("N");
@@ -176,5 +164,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
return Task.FromResult(true);
}
+
+ public Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(new List<TunerHostInfo>());
+ }
}
} \ No newline at end of file
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
index 90ff36441..281632590 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
@@ -7,6 +7,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
@@ -34,13 +35,73 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (bytesRead > 0)
{
- byte[] copy = new byte[bytesRead];
- Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
+ var allStreams = _outputStreams.ToList();
+
+ if (allStreams.Count == 1)
+ {
+ await allStreams[0].Value.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
+ }
+ else
+ {
+ byte[] copy = new byte[bytesRead];
+ Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
+
+ foreach (var stream in allStreams)
+ {
+ 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)
+ {
var allStreams = _outputStreams.ToList();
- foreach (var stream in allStreams)
+
+ if (allStreams.Count == 1)
+ {
+ await allStreams[0].Value.WriteAsync(data.Buffer, 0, bytesRead).ConfigureAwait(false);
+ }
+ else
{
- stream.Value.Queue(copy);
+ byte[] copy = new byte[bytesRead];
+ Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead);
+
+ foreach (var stream in allStreams)
+ {
+ stream.Value.Queue(copy, 0, copy.Length);
+ }
}
if (onStarted != null)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
index 7b48ce21a..543d2e373 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
@@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class QueueStream
{
private readonly Stream _outputStream;
- private readonly ConcurrentQueue<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,17 +39,49 @@ 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;
}
+ private void OnClosed()
+ {
+ GC.Collect();
+ if (OnFinished != null)
+ {
+ OnFinished(this);
+ }
+ }
+
+ public async Task WriteAsync(byte[] bytes, int offset, int count)
+ {
+ //return _outputStream.WriteAsync(bytes, offset, count, cancellationToken);
+ var cancellationToken = _cancellationToken;
+
+ try
+ {
+ await _outputStream.WriteAsync(bytes, offset, count, cancellationToken).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ _logger.Debug("QueueStream cancelled");
+ TaskCompletion.TrySetCanceled();
+ OnClosed();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error in QueueStream", ex);
+ TaskCompletion.TrySetException(ex);
+ OnClosed();
+ }
+ }
+
private async Task StartInternal()
{
var cancellationToken = _cancellationToken;
@@ -58,10 +90,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
{
@@ -81,10 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
finally
{
- if (OnFinished != null)
- {
- OnFinished(this);
- }
+ OnClosed();
}
}
}
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 120f445c2..64a41acef 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -408,6 +408,7 @@ namespace Emby.Server.Implementations.Localization
new LocalizatonOption{ Name="Italian", Value="it"},
new LocalizatonOption{ Name="Kazakh", Value="kk"},
new LocalizatonOption{ Name="Norwegian Bokmål", Value="nb"},
+ new LocalizatonOption{ Name="Persian", Value="fa"},
new LocalizatonOption{ Name="Polish", Value="pl"},
new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"},
new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},
diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
index ef7d6dba8..9514c12ca 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
@@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Library;
@@ -101,4 +102,35 @@ namespace Emby.Server.Implementations.Playlists
//}
}
+ public class GenreImageProvider : BaseDynamicImageProvider<Genre>
+ {
+ private readonly ILibraryManager _libraryManager;
+
+ public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
+ {
+ _libraryManager = libraryManager;
+ }
+
+ protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
+ {
+ var items = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Genres = new[] { item.Name },
+ IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name },
+ SortBy = new[] { ItemSortBy.Random },
+ Limit = 4,
+ Recursive = true,
+ ImageTypes = new[] { ImageType.Primary }
+
+ }).ToList();
+
+ return Task.FromResult(GetFinalItems(items));
+ }
+
+ //protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ //{
+ // return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
+ //}
+ }
+
}
diff --git a/Emby.Server.Implementations/Security/PluginSecurityManager.cs b/Emby.Server.Implementations/Security/PluginSecurityManager.cs
index a26df7625..d42fae3ad 100644
--- a/Emby.Server.Implementations/Security/PluginSecurityManager.cs
+++ b/Emby.Server.Implementations/Security/PluginSecurityManager.cs
@@ -301,10 +301,12 @@ namespace Emby.Server.Implementations.Security
if (reg.registered)
{
+ _logger.Info("Registered for feature {0}", feature);
LicenseFile.AddRegCheck(feature, reg.expDate);
}
else
{
+ _logger.Info("Not registered for feature {0}", feature);
LicenseFile.RemoveRegCheck(feature);
}
diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs
index d4ce1cabf..3c2af60db 100644
--- a/Emby.Server.Implementations/Services/ResponseHelper.cs
+++ b/Emby.Server.Implementations/Services/ResponseHelper.cs
@@ -12,45 +12,6 @@ namespace Emby.Server.Implementations.Services
{
public static class ResponseHelper
{
- private static async Task<bool> WriteToOutputStream(IResponse response, object result)
- {
- var asyncStreamWriter = result as IAsyncStreamWriter;
- if (asyncStreamWriter != null)
- {
- await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false);
- return true;
- }
-
- var streamWriter = result as IStreamWriter;
- if (streamWriter != null)
- {
- streamWriter.WriteTo(response.OutputStream);
- return true;
- }
-
- var stream = result as Stream;
- if (stream != null)
- {
- using (stream)
- {
- await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false);
- return true;
- }
- }
-
- var bytes = result as byte[];
- if (bytes != null)
- {
- response.ContentType = "application/octet-stream";
- response.SetContentLength(bytes.Length);
-
- await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
- return true;
- }
-
- return false;
- }
-
public static Task WriteToResponse(IResponse httpRes, IRequest httpReq, object result)
{
if (result == null)
@@ -141,16 +102,51 @@ namespace Emby.Server.Implementations.Services
response.ContentType += "; charset=utf-8";
}
- var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false);
- if (writeToOutputStreamResult)
+ var asyncStreamWriter = result as IAsyncStreamWriter;
+ if (asyncStreamWriter != null)
{
+ await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false);
+ return;
+ }
+
+ var streamWriter = result as IStreamWriter;
+ if (streamWriter != null)
+ {
+ streamWriter.WriteTo(response.OutputStream);
+ return;
+ }
+
+ var fileWriter = result as FileWriter;
+ if (fileWriter != null)
+ {
+ await fileWriter.WriteToAsync(response, CancellationToken.None).ConfigureAwait(false);
+ return;
+ }
+
+ var stream = result as Stream;
+ if (stream != null)
+ {
+ using (stream)
+ {
+ await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false);
+ return;
+ }
+ }
+
+ var bytes = result as byte[];
+ if (bytes != null)
+ {
+ response.ContentType = "application/octet-stream";
+ response.SetContentLength(bytes.Length);
+
+ await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
return;
}
var responseText = result as string;
if (responseText != null)
{
- var bytes = Encoding.UTF8.GetBytes(responseText);
+ bytes = Encoding.UTF8.GetBytes(responseText);
response.SetContentLength(bytes.Length);
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
return;
@@ -163,7 +159,7 @@ namespace Emby.Server.Implementations.Services
{
var contentType = request.ResponseContentType;
var serializer = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
-
+
using (var ms = new MemoryStream())
{
serializer(result, ms);
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 9dfaa102a..b5e64bc23 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.TV
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items);
- return GetResult(episodes, null, request);
+ return GetResult(episodes, request);
}
public QueryResult<BaseItem> GetNextUp(NextUpQuery request, List<Folder> parentsFolders)
@@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.TV
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items);
- return GetResult(episodes, null, request);
+ return GetResult(episodes, request);
}
public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<string> seriesKeys)
@@ -163,8 +163,7 @@ namespace Emby.Server.Implementations.TV
return false;
})
.Select(i => i.Item2())
- .Where(i => i != null)
- .Take(request.Limit ?? int.MaxValue);
+ .Where(i => i != null);
}
private string GetUniqueSeriesKey(BaseItem series)
@@ -232,24 +231,30 @@ namespace Emby.Server.Implementations.TV
return new Tuple<DateTime, Func<Episode>>(DateTime.MinValue, getEpisode);
}
- private QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, int? totalRecordLimit, NextUpQuery query)
+ private QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query)
{
- var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();
- var totalCount = itemsArray.Length;
+ int totalCount = 0;
- if (query.Limit.HasValue)
+ if (query.EnableTotalRecordCount)
{
- itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray();
+ var list = items.ToList();
+ totalCount = list.Count;
+ items = list;
}
- else if (query.StartIndex.HasValue)
+
+ if (query.StartIndex.HasValue)
+ {
+ items = items.Skip(query.StartIndex.Value);
+ }
+ if (query.Limit.HasValue)
{
- itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray();
+ items = items.Take(query.Limit.Value);
}
return new QueryResult<BaseItem>
{
TotalRecordCount = totalCount,
- Items = itemsArray
+ Items = items.ToArray()
};
}
}
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index bb303d8fa..21ef3cab6 100644
--- a/Emby.Server.Implementations/Udp/UdpServer.cs
+++ b/Emby.Server.Implementations/Udp/UdpServer.cs
@@ -204,19 +204,6 @@ namespace Emby.Server.Implementations.Udp
}
/// <summary>
- /// Stops this instance.
- /// </summary>
- public void Stop()
- {
- _isDisposed = true;
-
- if (_udpClient != null)
- {
- _udpClient.Dispose();
- }
- }
-
- /// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
@@ -224,7 +211,12 @@ namespace Emby.Server.Implementations.Udp
{
if (dispose)
{
- Stop();
+ _isDisposed = true;
+
+ if (_udpClient != null)
+ {
+ _udpClient.Dispose();
+ }
}
}
@@ -247,10 +239,14 @@ namespace Emby.Server.Implementations.Udp
try
{
- await _udpClient.SendAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false);
+ await _udpClient.SendWithLockAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false);
_logger.Info("Udp message sent to {0}", remoteEndPoint);
}
+ catch (OperationCanceledException)
+ {
+
+ }
catch (Exception ex)
{
_logger.ErrorException("Error sending message to {0}", ex, remoteEndPoint);
diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config
index 7e638e171..ccabbc27b 100644
--- a/Emby.Server.Implementations/packages.config
+++ b/Emby.Server.Implementations/packages.config
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Emby.XmlTv" version="1.0.7" targetFramework="portable45-net45+win8" />
- <package id="MediaBrowser.Naming" version="1.0.4" targetFramework="portable45-net45+win8" />
+ <package id="MediaBrowser.Naming" version="1.0.5" targetFramework="portable45-net45+win8" />
<package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" />
<package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="portable45-net45+win8" />
<package id="UniversalDetector" version="1.0.1" targetFramework="portable45-net45+win8" />
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index dc4e57155..2d8744f70 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -582,13 +582,13 @@ namespace MediaBrowser.Api.LiveTv
}
[Route("/LiveTv/ListingProviders/Default", "GET")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class GetDefaultListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
{
}
[Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
{
public bool ValidateLogin { get; set; }
@@ -596,7 +596,7 @@ namespace MediaBrowser.Api.LiveTv
}
[Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class DeleteListingProvider : IReturnVoid
{
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")]
@@ -604,7 +604,7 @@ namespace MediaBrowser.Api.LiveTv
}
[Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class GetLineups : IReturn<List<NameIdPair>>
{
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -621,13 +621,13 @@ namespace MediaBrowser.Api.LiveTv
}
[Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class GetSchedulesDirectCountries
{
}
[Route("/LiveTv/ChannelMappingOptions")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class GetChannelMappingOptions
{
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -635,7 +635,7 @@ namespace MediaBrowser.Api.LiveTv
}
[Route("/LiveTv/ChannelMappings")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class SetChannelMapping
{
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -660,20 +660,6 @@ namespace MediaBrowser.Api.LiveTv
public string Feature { get; set; }
}
- [Route("/LiveTv/TunerHosts/Satip/IniMappings", "GET", Summary = "Gets available mappings")]
- [Authenticated(AllowBeforeStartupWizard = true)]
- public class GetSatIniMappings : IReturn<List<NameValuePair>>
- {
-
- }
-
- [Route("/LiveTv/TunerHosts/Satip/ChannelScan", "GET", Summary = "Scans for available channels")]
- [Authenticated(AllowBeforeStartupWizard = true)]
- public class GetSatChannnelScanResult : TunerHostInfo
- {
-
- }
-
[Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")]
public class GetLiveStreamFile
{
@@ -687,6 +673,20 @@ namespace MediaBrowser.Api.LiveTv
public string Id { get; set; }
}
+ [Route("/LiveTv/TunerHosts/Types", "GET")]
+ [Authenticated]
+ public class GetTunerHostTypes : IReturn<List<NameIdPair>>
+ {
+
+ }
+
+ [Route("/LiveTv/Tuners/Discvover", "GET")]
+ [Authenticated]
+ public class DiscoverTuners : IReturn<List<TunerHostInfo>>
+ {
+ public bool NewDevicesOnly { get; set; }
+ }
+
public class LiveTvService : BaseApiService
{
private readonly ILiveTvManager _liveTvManager;
@@ -712,6 +712,12 @@ namespace MediaBrowser.Api.LiveTv
_sessionContext = sessionContext;
}
+ public object Get(GetTunerHostTypes request)
+ {
+ var list = _liveTvManager.GetTunerHostTypes();
+ return ToOptimizedResult(list);
+ }
+
public object Get(GetLiveRecordingFile request)
{
var path = _liveTvManager.GetEmbyTvActiveRecordingPath(request.Id);
@@ -731,6 +737,12 @@ namespace MediaBrowser.Api.LiveTv
};
}
+ public async Task<object> Get(DiscoverTuners request)
+ {
+ var result = await _liveTvManager.DiscoverTuners(request.NewDevicesOnly, CancellationToken.None).ConfigureAwait(false);
+ return ToOptimizedResult(result);
+ }
+
public async Task<object> Get(GetLiveStreamFile request)
{
var directStreamProvider = (await _liveTvManager.GetEmbyTvLiveStream(request.Id).ConfigureAwait(false)) as IDirectStreamProvider;
@@ -749,13 +761,6 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedResult(new ListingsProviderInfo());
}
- public async Task<object> Get(GetSatChannnelScanResult request)
- {
- var result = await _liveTvManager.GetSatChannelScanResult(request, CancellationToken.None).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
public async Task<object> Get(GetLiveTvRegistrationInfo request)
{
var result = await _liveTvManager.GetRegistrationInfo(request.Feature).ConfigureAwait(false);
@@ -803,11 +808,6 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedResult(result);
}
- public object Get(GetSatIniMappings request)
- {
- return ToOptimizedResult(_liveTvManager.GetSatIniMappings());
- }
-
public async Task<object> Get(GetSchedulesDirectCountries request)
{
// https://json.schedulesdirect.org/20141201/available/countries
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index e1559cabf..480508e6f 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -289,8 +289,10 @@ namespace MediaBrowser.Api.Playback
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
//process.BeginOutputReadLine();
+ state.TranscodingJob = transcodingJob;
+
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
- var task = Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
+ new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream);
// Wait for the file to exist before proceeeding
while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
@@ -340,134 +342,6 @@ namespace MediaBrowser.Api.Playback
// string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase);
}
- private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
- {
- try
- {
- using (var reader = new StreamReader(source))
- {
- while (!reader.EndOfStream)
- {
- var line = await reader.ReadLineAsync().ConfigureAwait(false);
-
- ParseLogLine(line, transcodingJob, state);
-
- var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
-
- await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
- await target.FlushAsync().ConfigureAwait(false);
- }
- }
- }
- catch (ObjectDisposedException)
- {
- // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error reading ffmpeg log", ex);
- }
- }
-
- private void ParseLogLine(string line, TranscodingJob transcodingJob, StreamState state)
- {
- float? framerate = null;
- double? percent = null;
- TimeSpan? transcodingPosition = null;
- long? bytesTranscoded = null;
- int? bitRate = null;
-
- var parts = line.Split(' ');
-
- var totalMs = state.RunTimeTicks.HasValue
- ? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds
- : 0;
-
- var startMs = state.Request.StartTimeTicks.HasValue
- ? TimeSpan.FromTicks(state.Request.StartTimeTicks.Value).TotalMilliseconds
- : 0;
-
- for (var i = 0; i < parts.Length; i++)
- {
- var part = parts[i];
-
- if (string.Equals(part, "fps=", StringComparison.OrdinalIgnoreCase) &&
- (i + 1 < parts.Length))
- {
- var rate = parts[i + 1];
- float val;
-
- if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
- {
- framerate = val;
- }
- }
- else if (state.RunTimeTicks.HasValue &&
- part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
- {
- var time = part.Split(new[] { '=' }, 2).Last();
- TimeSpan val;
-
- if (TimeSpan.TryParse(time, UsCulture, out val))
- {
- var currentMs = startMs + val.TotalMilliseconds;
-
- var percentVal = currentMs / totalMs;
- percent = 100 * percentVal;
-
- transcodingPosition = val;
- }
- }
- else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
- {
- var size = part.Split(new[] { '=' }, 2).Last();
-
- int? scale = null;
- if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
- {
- scale = 1024;
- size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase);
- }
-
- if (scale.HasValue)
- {
- long val;
-
- if (long.TryParse(size, NumberStyles.Any, UsCulture, out val))
- {
- bytesTranscoded = val * scale.Value;
- }
- }
- }
- else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
- {
- var rate = part.Split(new[] { '=' }, 2).Last();
-
- int? scale = null;
- if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
- {
- scale = 1024;
- rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase);
- }
-
- if (scale.HasValue)
- {
- float val;
-
- if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
- {
- bitRate = (int)Math.Ceiling(val * scale.Value);
- }
- }
- }
- }
-
- if (framerate.HasValue || percent.HasValue)
- {
- ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
- }
- }
-
/// <summary>
/// Processes the exited.
/// </summary>
@@ -697,6 +571,20 @@ namespace MediaBrowser.Api.Playback
{
request.SubtitleCodec = val;
}
+ else if (i == 31)
+ {
+ if (videoRequest != null)
+ {
+ videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+ else if (i == 32)
+ {
+ if (videoRequest != null)
+ {
+ videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+ }
}
}
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index aaca1793c..53813860a 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -41,9 +41,16 @@ namespace MediaBrowser.Api.Playback.Hls
/// <summary>
/// Gets the segment file extension.
/// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected abstract string GetSegmentFileExtension(StreamState state);
+ protected string GetSegmentFileExtension(StreamRequest request)
+ {
+ var segmentContainer = request.SegmentContainer;
+ if (!string.IsNullOrWhiteSpace(segmentContainer))
+ {
+ return "." + segmentContainer;
+ }
+
+ return ".ts";
+ }
/// <summary>
/// Gets the type of the transcoding job.
@@ -103,8 +110,11 @@ namespace MediaBrowser.Api.Playback.Hls
throw;
}
- var waitForSegments = state.SegmentLength >= 10 ? 2 : 3;
- await WaitForMinimumSegmentCount(playlist, waitForSegments, cancellationTokenSource.Token).ConfigureAwait(false);
+ var minSegments = state.MinSegments;
+ if (minSegments > 0)
+ {
+ await WaitForMinimumSegmentCount(playlist, minSegments, cancellationTokenSource.Token).ConfigureAwait(false);
+ }
}
}
finally
@@ -258,14 +268,22 @@ namespace MediaBrowser.Api.Playback.Hls
"hls/" + Path.GetFileNameWithoutExtension(outputPath));
}
- var useGenericSegmenter = false;
+ var useGenericSegmenter = true;
if (useGenericSegmenter)
{
- var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state);
+ var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
var timeDeltaParam = String.Empty;
- return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
+ var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.');
+ if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
+ {
+ segmentFormat = "mpegts";
+ }
+
+ baseUrlParam = string.Format("\"{0}/\"", "hls/" + Path.GetFileNameWithoutExtension(outputPath));
+
+ return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_entry_prefix {12} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
inputModifier,
EncodingHelper.GetInputArgument(state, encodingOptions),
threads,
@@ -276,7 +294,9 @@ namespace MediaBrowser.Api.Playback.Hls
startNumberParam,
outputPath,
outputTsArg,
- timeDeltaParam
+ timeDeltaParam,
+ segmentFormat,
+ baseUrlParam
).Trim();
}
@@ -286,7 +306,7 @@ namespace MediaBrowser.Api.Playback.Hls
var args = string.Format("{0} {1} {2} -map_metadata -1 -map_chapters -1 -threads {3} {4} {5} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero {6} -hls_time {7} -individual_header_trailer 0 -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
itsOffset,
inputModifier,
- EncodingHelper.GetInputArgument(state, encodingOptions),
+ EncodingHelper.GetInputArgument(state, encodingOptions),
threads,
EncodingHelper.GetMapArgs(state),
GetVideoArguments(state),
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 1074a8bc1..f77a66f8e 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -65,7 +65,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
}
- [Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.ts", "GET")]
+ [Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")]
public class GetHlsVideoSegment : VideoStreamRequest
{
public string PlaylistId { get; set; }
@@ -77,8 +77,7 @@ namespace MediaBrowser.Api.Playback.Hls
public string SegmentId { get; set; }
}
- [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.aac", "GET")]
- [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.ts", "GET")]
+ [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")]
public class GetHlsAudioSegment : StreamRequest
{
public string PlaylistId { get; set; }
@@ -158,7 +157,7 @@ namespace MediaBrowser.Api.Playback.Hls
var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex);
- var segmentExtension = GetSegmentFileExtension(state);
+ var segmentExtension = GetSegmentFileExtension(state.Request);
TranscodingJob job = null;
@@ -420,7 +419,7 @@ namespace MediaBrowser.Api.Playback.Hls
var filename = Path.GetFileNameWithoutExtension(playlist);
- return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state));
+ return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state.Request));
}
private async Task<object> GetSegmentResult(StreamState state, string playlistPath,
@@ -740,7 +739,7 @@ namespace MediaBrowser.Api.Playback.Hls
name,
index.ToString(UsCulture),
- GetSegmentFileExtension(isOutputVideo),
+ GetSegmentFileExtension(request),
queryString));
index++;
@@ -848,7 +847,7 @@ namespace MediaBrowser.Api.Playback.Hls
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
- args += " " + EncodingHelper.GetVideoQualityParam(state, EncodingHelper.GetH264Encoder(state, encodingOptions), encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
+ args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
//args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
@@ -897,7 +896,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (useGenericSegmenter)
{
- var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state);
+ var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
var timeDeltaParam = String.Empty;
@@ -907,7 +906,13 @@ namespace MediaBrowser.Api.Playback.Hls
timeDeltaParam = string.Format("-segment_time_delta -{0}", startTime);
}
- return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
+ var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.');
+ if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
+ {
+ segmentFormat = "mpegts";
+ }
+
+ return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
inputModifier,
EncodingHelper.GetInputArgument(state, encodingOptions),
threads,
@@ -918,7 +923,8 @@ namespace MediaBrowser.Api.Playback.Hls
startNumberParam,
outputPath,
outputTsArg,
- timeDeltaParam
+ timeDeltaParam,
+ segmentFormat
).Trim();
}
@@ -935,20 +941,5 @@ namespace MediaBrowser.Api.Playback.Hls
outputPath
).Trim();
}
-
- /// <summary>
- /// Gets the segment file extension.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected override string GetSegmentFileExtension(StreamState state)
- {
- return GetSegmentFileExtension(state.IsOutputVideo);
- }
-
- protected string GetSegmentFileExtension(bool isOutputVideo)
- {
- return isOutputVideo ? ".ts" : ".ts";
- }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
index f3683c6cb..03291670b 100644
--- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
+++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
@@ -63,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <summary>
/// Class GetHlsVideoSegment
/// </summary>
- [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")]
+ [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")]
public class GetHlsVideoSegmentLegacy : VideoStreamRequest
{
public string PlaylistId { get; set; }
@@ -109,11 +109,13 @@ namespace MediaBrowser.Api.Playback.Hls
public Task<object> Get(GetHlsVideoSegmentLegacy request)
{
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
- file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file);
+
+ var transcodeFolderPath = _config.ApplicationPaths.TranscodingTempPath;
+ file = Path.Combine(transcodeFolderPath, file);
var normalizedPlaylistId = request.PlaylistId;
- var playlistPath = _fileSystem.GetFilePaths(_config.ApplicationPaths.TranscodingTempPath)
+ var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath)
.FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1);
return GetFileResult(file, playlistPath);
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index c9c6acc1b..f9164c36f 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -99,7 +99,7 @@ namespace MediaBrowser.Api.Playback.Hls
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
- args += " " + EncodingHelper.GetVideoQualityParam(state, EncodingHelper.GetH264Encoder(state, encodingOptions), encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
+ args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
// Add resolution params, if specified
if (!hasGraphicalSubs)
@@ -118,16 +118,6 @@ namespace MediaBrowser.Api.Playback.Hls
return args;
}
- /// <summary>
- /// Gets the segment file extension.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected override string GetSegmentFileExtension(StreamState state)
- {
- return ".ts";
- }
-
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext)
{
}
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index ed8449b83..4e4e8858e 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -127,7 +127,7 @@ namespace MediaBrowser.Api.Playback
SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
- request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, true, true, true);
+ request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, true, true, true, true);
}
else
{
@@ -169,7 +169,7 @@ namespace MediaBrowser.Api.Playback
{
var mediaSourceId = request.MediaSourceId;
- SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, request.EnableDirectStream, request.EnableTranscoding);
+ SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, request.ForceDirectPlayRemoteMediaSource, request.EnableDirectStream, request.EnableTranscoding);
}
return info;
@@ -253,6 +253,7 @@ namespace MediaBrowser.Api.Playback
int? maxAudioChannels,
string userId,
bool enableDirectPlay,
+ bool forceDirectPlayRemoteMediaSource,
bool enableDirectStream,
bool enableTranscoding)
{
@@ -260,7 +261,7 @@ namespace MediaBrowser.Api.Playback
foreach (var mediaSource in result.MediaSources)
{
- SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, enableDirectStream, enableTranscoding);
+ SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, forceDirectPlayRemoteMediaSource, enableDirectStream, enableTranscoding);
}
SortMediaSources(result, maxBitrate);
@@ -279,6 +280,7 @@ namespace MediaBrowser.Api.Playback
string playSessionId,
string userId,
bool enableDirectPlay,
+ bool forceDirectPlayRemoteMediaSource,
bool enableDirectStream,
bool enableTranscoding)
{
@@ -318,43 +320,49 @@ namespace MediaBrowser.Api.Playback
if (mediaSource.SupportsDirectPlay)
{
- var supportsDirectStream = mediaSource.SupportsDirectStream;
+ if (mediaSource.IsRemote && forceDirectPlayRemoteMediaSource)
+ {
+ }
+ else
+ {
+ var supportsDirectStream = mediaSource.SupportsDirectStream;
- // Dummy this up to fool StreamBuilder
- mediaSource.SupportsDirectStream = true;
- options.MaxBitrate = maxBitrate;
+ // Dummy this up to fool StreamBuilder
+ mediaSource.SupportsDirectStream = true;
+ options.MaxBitrate = maxBitrate;
- if (item is Audio)
- {
- if (!user.Policy.EnableAudioPlaybackTranscoding)
+ if (item is Audio)
{
- options.ForceDirectPlay = true;
+ if (!user.Policy.EnableAudioPlaybackTranscoding)
+ {
+ options.ForceDirectPlay = true;
+ }
}
- }
- else if (item is Video)
- {
- if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
+ else if (item is Video)
{
- options.ForceDirectPlay = true;
+ if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
+ {
+ options.ForceDirectPlay = true;
+ }
}
- }
- // The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
- streamBuilder.BuildAudioItem(options) :
- streamBuilder.BuildVideoItem(options);
+ // The MediaSource supports direct stream, now test to see if the client supports it
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
+ streamBuilder.BuildAudioItem(options) :
+ streamBuilder.BuildVideoItem(options);
- if (streamInfo == null || !streamInfo.IsDirectStream)
- {
- mediaSource.SupportsDirectPlay = false;
- }
+ if (streamInfo == null || !streamInfo.IsDirectStream)
+ {
+ mediaSource.SupportsDirectPlay = false;
+ }
- // Set this back to what it was
- mediaSource.SupportsDirectStream = supportsDirectStream;
+ // Set this back to what it was
+ mediaSource.SupportsDirectStream = supportsDirectStream;
- if (streamInfo != null)
- {
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ if (streamInfo != null)
+ {
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
}
}
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index 23a84e480..646a91c27 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -15,9 +15,6 @@ using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.IO;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Playback.Progressive
@@ -298,7 +295,8 @@ namespace MediaBrowser.Api.Playback.Progressive
responseHeaders["Accept-Ranges"] = "none";
}
- if (response.ContentLength.HasValue)
+ // Seeing cases of -1 here
+ if (response.ContentLength.HasValue && response.ContentLength.Value >= 0)
{
responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture);
}
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index bc600d3ea..8394e016c 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -1,4 +1,3 @@
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -7,15 +6,8 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
-using System;
-using System.IO;
-using System.Linq;
using System.Threading.Tasks;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Playback.Progressive
@@ -100,181 +92,7 @@ namespace MediaBrowser.Api.Playback.Progressive
{
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
- // Get the output codec name
- var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
-
- var format = string.Empty;
- var keyFrame = string.Empty;
-
- if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase))
- {
- // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
- format = " -f mp4 -movflags frag_keyframe+empty_moov";
- }
-
- var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
-
- var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
-
- var subtitleArguments = state.SubtitleStream != null &&
- state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed
- ? GetSubtitleArguments(state)
- : string.Empty;
-
- return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
- inputModifier,
- EncodingHelper.GetInputArgument(state, encodingOptions),
- keyFrame,
- EncodingHelper.GetMapArgs(state),
- GetVideoArguments(state, videoCodec),
- threads,
- GetAudioArguments(state),
- subtitleArguments,
- format,
- outputPath
- ).Trim();
- }
-
- private string GetSubtitleArguments(StreamState state)
- {
- var format = state.SupportedSubtitleCodecs.FirstOrDefault();
- string codec;
-
- if (string.IsNullOrWhiteSpace(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.OrdinalIgnoreCase))
- {
- codec = "copy";
- }
- else
- {
- codec = format;
- }
-
- return " -codec:s:0 " + codec;
- }
-
- /// <summary>
- /// Gets video arguments to pass to ffmpeg
- /// </summary>
- /// <param name="state">The state.</param>
- /// <param name="videoCodec">The video codec.</param>
- /// <returns>System.String.</returns>
- private string GetVideoArguments(StreamState state, string videoCodec)
- {
- var args = "-codec:v:0 " + videoCodec;
-
- if (state.EnableMpegtsM2TsMode)
- {
- args += " -mpegts_m2ts_mode 1";
- }
-
- if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
- {
- args += " -bsf:v h264_mp4toannexb";
- }
-
- if (state.RunTimeTicks.HasValue && state.VideoRequest.CopyTimestamps)
- {
- args += " -copyts -avoid_negative_ts disabled -start_at_zero";
- }
-
- if (!state.RunTimeTicks.HasValue)
- {
- args += " -flags -global_header -fflags +genpts";
- }
-
- return args;
- }
-
- var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
- 5.ToString(UsCulture));
-
- args += keyFrameArg;
-
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
-
- var hasCopyTs = false;
- // Add resolution params, if specified
- if (!hasGraphicalSubs)
- {
- var outputSizeParam = EncodingHelper.GetOutputSizeParam(state, videoCodec);
- args += outputSizeParam;
- hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
- }
-
- if (state.RunTimeTicks.HasValue && state.VideoRequest.CopyTimestamps)
- {
- if (!hasCopyTs)
- {
- args += " -copyts";
- }
- args += " -avoid_negative_ts disabled -start_at_zero";
- }
-
- var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
- var qualityParam = EncodingHelper.GetVideoQualityParam(state, videoCodec, encodingOptions, GetDefaultH264Preset());
-
- if (!string.IsNullOrEmpty(qualityParam))
- {
- args += " " + qualityParam.Trim();
- }
-
- // This is for internal graphical subs
- if (hasGraphicalSubs)
- {
- args += EncodingHelper.GetGraphicalSubtitleParam(state, videoCodec);
- }
-
- if (!state.RunTimeTicks.HasValue)
- {
- args += " -flags -global_header";
- }
-
- return args;
- }
-
- /// <summary>
- /// Gets audio arguments to pass to ffmpeg
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- private string GetAudioArguments(StreamState state)
- {
- // If the video doesn't have an audio stream, return a default.
- if (state.AudioStream == null && state.VideoStream != null)
- {
- return string.Empty;
- }
-
- // Get the output codec name
- var codec = EncodingHelper.GetAudioEncoder(state);
-
- var args = "-codec:a:0 " + codec;
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- return args;
- }
-
- // Add the number of audio channels
- var channels = state.OutputAudioChannels;
-
- if (channels.HasValue)
- {
- args += " -ac " + channels.Value;
- }
-
- var bitrate = state.OutputAudioBitrate;
-
- if (bitrate.HasValue)
- {
- args += " -ab " + bitrate.Value.ToString(UsCulture);
- }
-
- args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), false);
-
- return args;
+ return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultH264Preset());
}
}
} \ No newline at end of file
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index f223c99ef..551dbf378 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -32,8 +32,6 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string AudioCodec { get; set; }
- public string SubtitleCodec { get; set; }
-
[ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string DeviceProfileId { get; set; }
@@ -41,6 +39,10 @@ namespace MediaBrowser.Api.Playback
public string PlaySessionId { get; set; }
public string LiveStreamId { get; set; }
public string Tag { get; set; }
+ public string SegmentContainer { get; set; }
+
+ public int? SegmentLength { get; set; }
+ public int? MinSegments { get; set; }
}
public class VideoStreamRequest : StreamRequest
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index 912d60889..2f5f6227a 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -60,6 +60,11 @@ namespace MediaBrowser.Api.Playback
{
get
{
+ if (Request.SegmentLength.HasValue)
+ {
+ return Request.SegmentLength.Value;
+ }
+
if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
var userAgent = UserAgent ?? string.Empty;
@@ -86,6 +91,19 @@ namespace MediaBrowser.Api.Playback
}
}
+ public int MinSegments
+ {
+ get
+ {
+ if (Request.MinSegments.HasValue)
+ {
+ return Request.MinSegments.Value;
+ }
+
+ return SegmentLength >= 10 ? 2 : 3;
+ }
+ }
+
public bool IsSegmentedLiveStream
{
get
@@ -102,7 +120,6 @@ namespace MediaBrowser.Api.Playback
}
}
- public List<string> SupportedSubtitleCodecs { get; set; }
public string UserAgent { get; set; }
public TranscodingJobType TranscodingType { get; set; }
@@ -111,14 +128,12 @@ namespace MediaBrowser.Api.Playback
{
_mediaSourceManager = mediaSourceManager;
_logger = logger;
- SupportedSubtitleCodecs = new List<string>();
TranscodingType = transcodingType;
}
public string MimeType { get; set; }
public bool EstimateContentLength { get; set; }
- public bool EnableMpegtsM2TsMode { get; set; }
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
public long? EncodingDurationTicks { get; set; }
@@ -139,6 +154,8 @@ namespace MediaBrowser.Api.Playback
DisposeLiveStream();
DisposeLogStream();
DisposeIsoMount();
+
+ TranscodingJob = null;
}
private void DisposeLogStream()
@@ -191,7 +208,6 @@ namespace MediaBrowser.Api.Playback
}
public string OutputFilePath { get; set; }
- public int? OutputAudioBitrate;
public string ActualOutputVideoCodec
{
@@ -462,5 +478,11 @@ namespace MediaBrowser.Api.Playback
return true;
}
}
+
+ public TranscodingJob TranscodingJob;
+ public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
+ {
+ ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
+ }
}
}
diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs
index e010f122c..b0f52dd85 100644
--- a/MediaBrowser.Api/StartupWizardService.cs
+++ b/MediaBrowser.Api/StartupWizardService.cs
@@ -1,12 +1,9 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Connect;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.LiveTv;
using System;
using System.Linq;
using System.Threading.Tasks;
@@ -52,16 +49,14 @@ namespace MediaBrowser.Api
private readonly IServerApplicationHost _appHost;
private readonly IUserManager _userManager;
private readonly IConnectManager _connectManager;
- private readonly ILiveTvManager _liveTvManager;
private readonly IMediaEncoder _mediaEncoder;
- public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager, IMediaEncoder mediaEncoder)
+ public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, IMediaEncoder mediaEncoder)
{
_config = config;
_appHost = appHost;
_userManager = userManager;
_connectManager = connectManager;
- _liveTvManager = liveTvManager;
_mediaEncoder = mediaEncoder;
}
@@ -92,20 +87,6 @@ namespace MediaBrowser.Api
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
};
- var tvConfig = GetLiveTVConfiguration();
-
- if (tvConfig.TunerHosts.Count > 0)
- {
- result.LiveTvTunerPath = tvConfig.TunerHosts[0].Url;
- result.LiveTvTunerType = tvConfig.TunerHosts[0].Type;
- }
-
- if (tvConfig.ListingProviders.Count > 0)
- {
- result.LiveTvGuideProviderId = tvConfig.ListingProviders[0].Id;
- result.LiveTvGuideProviderType = tvConfig.ListingProviders[0].Type;
- }
-
return result;
}
@@ -120,6 +101,7 @@ namespace MediaBrowser.Api
config.EnableSeriesPresentationUniqueKey = true;
config.EnableLocalizedGuids = true;
config.EnableSimpleArtistDetection = true;
+ config.EnableNormalizedItemByNameIds = true;
}
public void Post(UpdateStartupConfiguration request)
@@ -128,9 +110,6 @@ namespace MediaBrowser.Api
_config.Configuration.MetadataCountryCode = request.MetadataCountryCode;
_config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
_config.SaveConfiguration();
-
- var task = UpdateTuners(request);
- Task.WaitAll(task);
}
public object Get(GetStartupUser request)
@@ -165,51 +144,6 @@ namespace MediaBrowser.Api
return result;
}
-
- private async Task UpdateTuners(UpdateStartupConfiguration request)
- {
- var config = GetLiveTVConfiguration();
- var save = false;
-
- if (string.IsNullOrWhiteSpace(request.LiveTvTunerPath) ||
- string.IsNullOrWhiteSpace(request.LiveTvTunerType))
- {
- if (config.TunerHosts.Count > 0)
- {
- config.TunerHosts.Clear();
- save = true;
- }
- }
- else
- {
- if (!config.TunerHosts.Any(i => string.Equals(i.Type, request.LiveTvTunerType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.Url, request.LiveTvTunerPath, StringComparison.OrdinalIgnoreCase)))
- {
- // Add tuner
- await _liveTvManager.SaveTunerHost(new TunerHostInfo
- {
- IsEnabled = true,
- Type = request.LiveTvTunerType,
- Url = request.LiveTvTunerPath
-
- }).ConfigureAwait(false);
- }
- }
-
- if (save)
- {
- SaveLiveTVConfiguration(config);
- }
- }
-
- private void SaveLiveTVConfiguration(LiveTvOptions config)
- {
- _config.SaveConfiguration("livetv", config);
- }
-
- private LiveTvOptions GetLiveTVConfiguration()
- {
- return _config.GetConfiguration<LiveTvOptions>("livetv");
- }
}
public class StartupConfiguration
@@ -217,10 +151,6 @@ namespace MediaBrowser.Api
public string UICulture { get; set; }
public string MetadataCountryCode { get; set; }
public string PreferredMetadataLanguage { get; set; }
- public string LiveTvTunerType { get; set; }
- public string LiveTvTunerPath { get; set; }
- public string LiveTvGuideProviderId { get; set; }
- public string LiveTvGuideProviderType { get; set; }
}
public class StartupInfo
diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs
index 71c5e732a..43fe74d5f 100644
--- a/MediaBrowser.Api/TvShowsService.cs
+++ b/MediaBrowser.Api/TvShowsService.cs
@@ -72,6 +72,12 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
+ public bool EnableTotalRecordCount { get; set; }
+
+ public GetNextUpEpisodes()
+ {
+ EnableTotalRecordCount = true;
+ }
}
[Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")]
@@ -376,7 +382,8 @@ namespace MediaBrowser.Api
ParentId = request.ParentId,
SeriesId = request.SeriesId,
StartIndex = request.StartIndex,
- UserId = request.UserId
+ UserId = request.UserId,
+ EnableTotalRecordCount = request.EnableTotalRecordCount
});
var user = _userManager.GetUserById(request.UserId);
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 20b2529c0..8d83f8a35 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -289,7 +289,12 @@ namespace MediaBrowser.Controller.Entities.Audio
}
}
- public static string GetPath(string name, bool normalizeName = true)
+ public static string GetPath(string name)
+ {
+ return GetPath(name, true);
+ }
+
+ public static string GetPath(string name, bool normalizeName)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
index 74679b474..e26e0dfce 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
@@ -118,7 +118,12 @@ namespace MediaBrowser.Controller.Entities.Audio
return LibraryManager.GetItemList(query);
}
- public static string GetPath(string name, bool normalizeName = true)
+ public static string GetPath(string name)
+ {
+ return GetPath(name, true);
+ }
+
+ public static string GetPath(string name, bool normalizeName)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs
index 22a8675c5..4187167b9 100644
--- a/MediaBrowser.Controller/Entities/GameGenre.cs
+++ b/MediaBrowser.Controller/Entities/GameGenre.cs
@@ -96,7 +96,12 @@ namespace MediaBrowser.Controller.Entities
}
}
- public static string GetPath(string name, bool normalizeName = true)
+ public static string GetPath(string name)
+ {
+ return GetPath(name, true);
+ }
+
+ public static string GetPath(string name, bool normalizeName)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs
index 1b746ae51..9769efdd0 100644
--- a/MediaBrowser.Controller/Entities/Genre.cs
+++ b/MediaBrowser.Controller/Entities/Genre.cs
@@ -108,7 +108,12 @@ namespace MediaBrowser.Controller.Entities
}
}
- public static string GetPath(string name, bool normalizeName = true)
+ public static string GetPath(string name)
+ {
+ return GetPath(name, true);
+ }
+
+ public static string GetPath(string name, bool normalizeName)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs
index 4c033dc00..2104bee09 100644
--- a/MediaBrowser.Controller/Entities/IHasImages.cs
+++ b/MediaBrowser.Controller/Entities/IHasImages.cs
@@ -206,6 +206,8 @@ namespace MediaBrowser.Controller.Entities
void SetImage(ItemImageInfo image, int index);
double? GetDefaultPrimaryImageAspectRatio();
+
+ int? ProductionYear { get; set; }
}
public static class HasImagesExtensions
diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs
index ee1aea938..b68681d36 100644
--- a/MediaBrowser.Controller/Entities/Person.cs
+++ b/MediaBrowser.Controller/Entities/Person.cs
@@ -133,7 +133,12 @@ namespace MediaBrowser.Controller.Entities
}
}
- public static string GetPath(string name, bool normalizeName = true)
+ public static string GetPath(string name)
+ {
+ return GetPath(name, true);
+ }
+
+ public static string GetPath(string name, bool normalizeName)
{
// Trim the period at the end because windows will have a hard time with that
var validFilename = normalizeName ?
diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs
index b8ad691a9..2e5e6ce43 100644
--- a/MediaBrowser.Controller/Entities/Studio.cs
+++ b/MediaBrowser.Controller/Entities/Studio.cs
@@ -114,7 +114,12 @@ namespace MediaBrowser.Controller.Entities
}
}
- public static string GetPath(string name, bool normalizeName = true)
+ public static string GetPath(string name)
+ {
+ return GetPath(name, true);
+ }
+
+ public static string GetPath(string name, bool normalizeName)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 78f907d61..890626419 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -614,7 +614,8 @@ namespace MediaBrowser.Controller.Entities
Timestamp = i.Timestamp,
Type = type,
PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(),
- SupportsDirectStream = i.VideoType == VideoType.VideoFile
+ SupportsDirectStream = i.VideoType == VideoType.VideoFile,
+ IsRemote = i.IsShortcut
};
if (info.Protocol == MediaProtocol.File)
diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs
index 75fb69435..b352950a0 100644
--- a/MediaBrowser.Controller/Entities/Year.cs
+++ b/MediaBrowser.Controller/Entities/Year.cs
@@ -122,7 +122,12 @@ namespace MediaBrowser.Controller.Entities
}
}
- public static string GetPath(string name, bool normalizeName = true)
+ public static string GetPath(string name)
+ {
+ return GetPath(name, true);
+ }
+
+ public static string GetPath(string name, bool normalizeName)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index a908d2d3f..5242c5b1f 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -376,19 +376,14 @@ namespace MediaBrowser.Controller.LiveTv
/// <returns>Task.</returns>
Task OnRecordingFileDeleted(BaseItem recording);
- /// <summary>
- /// Gets the sat ini mappings.
- /// </summary>
- /// <returns>List&lt;NameValuePair&gt;.</returns>
- List<NameValuePair> GetSatIniMappings();
-
- Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken);
-
Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
List<IListingsProvider> ListingProviders { get; }
+ List<NameIdPair> GetTunerHostTypes();
+ Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken);
+
event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
index 5615649c2..fc344298b 100644
--- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs
+++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
@@ -44,6 +44,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns>
Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
+
+ Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken);
}
public interface IConfigurableTunerHost
{
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 4ebee531f..88153868f 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -190,6 +190,7 @@
<Compile Include="MediaEncoding\ImageEncodingOptions.cs" />
<Compile Include="MediaEncoding\IMediaEncoder.cs" />
<Compile Include="MediaEncoding\ISubtitleEncoder.cs" />
+ <Compile Include="MediaEncoding\JobLogger.cs" />
<Compile Include="MediaEncoding\MediaInfoRequest.cs" />
<Compile Include="MediaEncoding\MediaStreamSelector.cs" />
<Compile Include="Net\AuthenticatedAttribute.cs" />
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 80b9cc154..b7b31509c 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -154,6 +154,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return "mpegts";
}
+
// For these need to find out the ffmpeg names
if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
{
@@ -163,6 +164,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return null;
}
+ if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase))
{
return null;
@@ -179,12 +184,23 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return null;
}
+ if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
// Seeing reported failures here, not sure yet if this is related to specfying input format
if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
{
return null;
}
+ // obviously don't do this for strm files
+ if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
return container;
}
@@ -661,9 +677,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var level = NormalizeTranscodingLevel(state.OutputVideoCodec, request.Level);
// h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
- // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307
+ // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
{
switch (level)
@@ -700,10 +715,15 @@ namespace MediaBrowser.Controller.MediaEncoding
break;
}
}
+ // nvenc doesn't decode with param -level set ?!
+ if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)){
+ param += "";
+ }
else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
param += " -level " + level;
}
+
}
if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
@@ -727,12 +747,18 @@ namespace MediaBrowser.Controller.MediaEncoding
if (videoStream.IsInterlaced)
{
- return false;
+ if (request.DeInterlace)
+ {
+ return false;
+ }
}
if (videoStream.IsAnamorphic ?? false)
{
- return false;
+ if (request.RequireNonAnamorphic)
+ {
+ return false;
+ }
}
// Can't stream copy if we're burning in subtitles
@@ -1561,6 +1587,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;
@@ -1670,9 +1705,21 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_qsv"))
{
+ // qsv decoder does not support 10-bit input
+ if ((state.VideoStream.BitDepth ?? 8) > 8)
+ {
+ return null;
+ }
return "-c:v h264_qsv ";
}
break;
+ //case "hevc":
+ //case "h265":
+ // if (_mediaEncoder.SupportsDecoder("hevc_qsv"))
+ // {
+ // return "-c:v hevc_qsv ";
+ // }
+ // break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_qsv"))
{
@@ -1715,5 +1762,187 @@ namespace MediaBrowser.Controller.MediaEncoding
return threads;
}
+
+ public string GetSubtitleEmbedArguments(EncodingJobInfo state)
+ {
+ if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
+ {
+ return string.Empty;
+ }
+
+ var format = state.SupportedSubtitleCodecs.FirstOrDefault();
+ string codec;
+
+ if (string.IsNullOrWhiteSpace(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ codec = "copy";
+ }
+ else
+ {
+ codec = format;
+ }
+
+ // Muxing in dvbsub via either copy or -codec dvbsub does not seem to work
+ // It doesn't throw any errors but vlc on android will not render them
+ // They will need to be converted to an alternative format
+ // TODO: This is incorrectly assuming that dvdsub will be supported by the player
+ // The api will need to be expanded to accomodate this.
+ if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
+ {
+ codec = "dvdsub";
+ }
+
+ var args = " -codec:s:0 " + codec;
+
+ args += " -disposition:s:0 default";
+
+ return args;
+ }
+
+ public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultH264Preset)
+ {
+ // Get the output codec name
+ var videoCodec = GetVideoEncoder(state, encodingOptions);
+
+ var format = string.Empty;
+ var keyFrame = string.Empty;
+
+ if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase) &&
+ state.BaseRequest.Context == EncodingContext.Streaming)
+ {
+ // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
+ format = " -f mp4 -movflags frag_keyframe+empty_moov";
+ }
+
+ var threads = GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
+
+ var inputModifier = GetInputModifier(state, encodingOptions);
+
+ return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
+ inputModifier,
+ GetInputArgument(state, encodingOptions),
+ keyFrame,
+ GetMapArgs(state),
+ GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultH264Preset),
+ threads,
+ GetProgressiveVideoAudioArguments(state, encodingOptions),
+ GetSubtitleEmbedArguments(state),
+ format,
+ outputPath
+ ).Trim();
+ }
+
+ public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultH264Preset)
+ {
+ var args = "-codec:v:0 " + videoCodec;
+
+ if (state.EnableMpegtsM2TsMode)
+ {
+ args += " -mpegts_m2ts_mode 1";
+ }
+
+ if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
+ {
+ args += " -bsf:v h264_mp4toannexb";
+ }
+
+ if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
+ {
+ args += " -copyts -avoid_negative_ts disabled -start_at_zero";
+ }
+
+ if (!state.RunTimeTicks.HasValue)
+ {
+ args += " -flags -global_header -fflags +genpts";
+ }
+
+ return args;
+ }
+
+ var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
+ 5.ToString(_usCulture));
+
+ args += keyFrameArg;
+
+ var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.BaseRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
+
+ var hasCopyTs = false;
+ // Add resolution params, if specified
+ if (!hasGraphicalSubs)
+ {
+ var outputSizeParam = GetOutputSizeParam(state, videoCodec);
+ args += outputSizeParam;
+ hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
+ }
+
+ if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
+ {
+ if (!hasCopyTs)
+ {
+ args += " -copyts";
+ }
+ args += " -avoid_negative_ts disabled -start_at_zero";
+ }
+
+ var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset);
+
+ if (!string.IsNullOrEmpty(qualityParam))
+ {
+ args += " " + qualityParam.Trim();
+ }
+
+ // This is for internal graphical subs
+ if (hasGraphicalSubs)
+ {
+ args += GetGraphicalSubtitleParam(state, videoCodec);
+ }
+
+ if (!state.RunTimeTicks.HasValue)
+ {
+ args += " -flags -global_header";
+ }
+
+ return args;
+ }
+
+ public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
+ {
+ // If the video doesn't have an audio stream, return a default.
+ if (state.AudioStream == null && state.VideoStream != null)
+ {
+ return string.Empty;
+ }
+
+ // Get the output codec name
+ var codec = GetAudioEncoder(state);
+
+ var args = "-codec:a:0 " + codec;
+
+ if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ return args;
+ }
+
+ // Add the number of audio channels
+ var channels = state.OutputAudioChannels;
+
+ if (channels.HasValue)
+ {
+ args += " -ac " + channels.Value;
+ }
+
+ var bitrate = state.OutputAudioBitrate;
+
+ if (bitrate.HasValue)
+ {
+ args += " -ab " + bitrate.Value.ToString(_usCulture);
+ }
+
+ args += " " + GetAudioFilterParam(state, encodingOptions, false);
+
+ return args;
+ }
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index a18b86432..f5878864b 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -12,7 +12,7 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Controller.MediaEncoding
{
// For now, a common base class until the API and MediaEncoding classes are unified
- public class EncodingJobInfo
+ public abstract class EncodingJobInfo
{
private readonly ILogger _logger;
@@ -29,6 +29,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public int? OutputVideoBitrate { get; set; }
public MediaStream SubtitleStream { get; set; }
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
+ public List<string> SupportedSubtitleCodecs { get; set; }
public int InternalSubtitleStreamOffset { get; set; }
public MediaSourceInfo MediaSource { get; set; }
@@ -52,6 +53,8 @@ namespace MediaBrowser.Controller.MediaEncoding
public string InputContainer { get; set; }
public IsoType? IsoType { get; set; }
+ public bool EnableMpegtsM2TsMode { get; set; }
+
public BaseEncodingJobOptions BaseRequest { get; set; }
public long? StartTimeTicks
@@ -64,6 +67,7 @@ namespace MediaBrowser.Controller.MediaEncoding
get { return BaseRequest.CopyTimestamps; }
}
+ public int? OutputAudioBitrate;
public int? OutputAudioChannels;
public int? OutputAudioSampleRate;
public bool DeInterlace { get; set; }
@@ -74,8 +78,9 @@ namespace MediaBrowser.Controller.MediaEncoding
_logger = logger;
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
PlayableStreamFileNames = new List<string>();
+ SupportedAudioCodecs = new List<string>();
SupportedVideoCodecs = new List<string>();
- SupportedVideoCodecs = new List<string>();
+ SupportedSubtitleCodecs = new List<string>();
}
/// <summary>
@@ -110,5 +115,7 @@ namespace MediaBrowser.Controller.MediaEncoding
IsoMount = null;
}
}
+
+ public abstract void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
index 4bb180d4b..73be78dc9 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
@@ -14,7 +14,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public string AudioCodec { get; set; }
public DeviceProfile DeviceProfile { get; set; }
- public EncodingContext Context { get; set; }
public bool ReadInputAtNativeFramerate { get; set; }
@@ -46,7 +45,7 @@ namespace MediaBrowser.Controller.MediaEncoding
AudioBitRate = info.AudioBitrate;
AudioSampleRate = info.TargetAudioSampleRate;
DeviceProfile = deviceProfile;
- VideoCodec = info.VideoCodec;
+ VideoCodec = info.TargetVideoCodec;
VideoBitRate = info.VideoBitrate;
AudioStreamIndex = info.AudioStreamIndex;
MaxRefFrames = info.MaxRefFrames;
@@ -185,6 +184,8 @@ namespace MediaBrowser.Controller.MediaEncoding
[ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxVideoBitDepth { get; set; }
public bool RequireAvc { get; set; }
+ public bool DeInterlace { get; set; }
+ public bool RequireNonAnamorphic { get; set; }
public int? TranscodingMaxAudioChannels { get; set; }
public int? CpuCoreLimit { get; set; }
public string OutputContainer { get; set; }
@@ -196,6 +197,8 @@ namespace MediaBrowser.Controller.MediaEncoding
[ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string VideoCodec { get; set; }
+ public string SubtitleCodec { get; set; }
+
/// <summary>
/// Gets or sets the index of the audio stream.
/// </summary>
@@ -210,9 +213,12 @@ namespace MediaBrowser.Controller.MediaEncoding
[ApiMember(Name = "VideoStreamIndex", Description = "Optional. The index of the video stream to use. If omitted the first video stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? VideoStreamIndex { get; set; }
+ public EncodingContext Context { get; set; }
+
public BaseEncodingJobOptions()
{
EnableAutoStreamCopy = true;
+ Context = EncodingContext.Streaming;
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index cb6e58f17..03e4f7771 100644
--- a/MediaBrowser.MediaEncoding/Encoder/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -6,7 +6,7 @@ using System.IO;
using System.Linq;
using System.Text;
-namespace MediaBrowser.MediaEncoding.Encoder
+namespace MediaBrowser.Controller.MediaEncoding
{
public class JobLogger
{
@@ -18,7 +18,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger = logger;
}
- public async void StartStreamingLog(EncodingJob transcodingJob, Stream source, Stream target)
+ public async void StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
{
try
{
@@ -28,35 +28,41 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
var line = await reader.ReadLineAsync().ConfigureAwait(false);
- ParseLogLine(line, transcodingJob);
+ ParseLogLine(line, state);
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+ await target.FlushAsync().ConfigureAwait(false);
}
}
}
+ catch (ObjectDisposedException)
+ {
+ // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
+ }
catch (Exception ex)
{
_logger.ErrorException("Error reading ffmpeg log", ex);
}
}
- private void ParseLogLine(string line, EncodingJob transcodingJob)
+ private void ParseLogLine(string line, EncodingJobInfo state)
{
float? framerate = null;
double? percent = null;
TimeSpan? transcodingPosition = null;
long? bytesTranscoded = null;
+ int? bitRate = null;
var parts = line.Split(' ');
- var totalMs = transcodingJob.RunTimeTicks.HasValue
- ? TimeSpan.FromTicks(transcodingJob.RunTimeTicks.Value).TotalMilliseconds
+ var totalMs = state.RunTimeTicks.HasValue
+ ? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds
: 0;
- var startMs = transcodingJob.Options.StartTimeTicks.HasValue
- ? TimeSpan.FromTicks(transcodingJob.Options.StartTimeTicks.Value).TotalMilliseconds
+ var startMs = state.BaseRequest.StartTimeTicks.HasValue
+ ? TimeSpan.FromTicks(state.BaseRequest.StartTimeTicks.Value).TotalMilliseconds
: 0;
for (var i = 0; i < parts.Length; i++)
@@ -74,7 +80,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
framerate = val;
}
}
- else if (transcodingJob.RunTimeTicks.HasValue &&
+ else if (state.RunTimeTicks.HasValue &&
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
{
var time = part.Split(new[] { '=' }, 2).Last();
@@ -111,11 +117,32 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
}
+ else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
+ {
+ var rate = part.Split(new[] { '=' }, 2).Last();
+
+ int? scale = null;
+ if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ scale = 1024;
+ rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase);
+ }
+
+ if (scale.HasValue)
+ {
+ float val;
+
+ if (float.TryParse(rate, NumberStyles.Any, _usCulture, out val))
+ {
+ bitRate = (int)Math.Ceiling(val * scale.Value);
+ }
+ }
+ }
}
if (framerate.HasValue || percent.HasValue)
{
- transcodingJob.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded);
+ state.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
}
}
}
diff --git a/MediaBrowser.Controller/Net/StaticResultOptions.cs b/MediaBrowser.Controller/Net/StaticResultOptions.cs
index 272fa8b3b..62c13fbb5 100644
--- a/MediaBrowser.Controller/Net/StaticResultOptions.cs
+++ b/MediaBrowser.Controller/Net/StaticResultOptions.cs
@@ -23,21 +23,18 @@ namespace MediaBrowser.Controller.Net
public Action OnComplete { get; set; }
public Action OnError { get; set; }
+ public string Path { get; set; }
+
+ public FileShareMode FileShare { get; set; }
+
public StaticResultOptions()
{
ResponseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ FileShare = FileShareMode.Read;
}
}
public class StaticFileResultOptions : StaticResultOptions
{
- public string Path { get; set; }
-
- public FileShareMode FileShare { get; set; }
-
- public StaticFileResultOptions()
- {
- FileShare = FileShareMode.Read;
- }
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
index a4db8f3b8..05b3ca5fc 100644
--- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
@@ -17,7 +17,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
}
- protected override Task<string> GetCommandLineArguments(EncodingJob state)
+ protected override string GetCommandLineArguments(EncodingJob state)
{
var audioTranscodeParams = new List<string>();
@@ -78,7 +78,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
mapArgs,
metadata).Trim();
- return Task.FromResult(result);
+ return result;
}
protected override string GetOutputFileExtension(EncodingJob state)
diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
index b0b37f2d6..b6e695f1b 100644
--- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
@@ -66,7 +66,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
IProgress<double> progress,
CancellationToken cancellationToken)
{
- var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager)
+ var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager, MediaEncoder)
.CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
encodingJob.OutputFilePath = GetOutputFilePath(encodingJob);
@@ -76,7 +76,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false);
- var commandLineArgs = await GetCommandLineArguments(encodingJob).ConfigureAwait(false);
+ var commandLineArgs = GetCommandLineArguments(encodingJob);
var process = ProcessFactory.Create(new ProcessOptions
{
@@ -242,7 +242,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private void OnTranscodeBeginning(EncodingJob job)
{
- job.ReportTranscodingProgress(null, null, null, null);
+ job.ReportTranscodingProgress(null, null, null, null, null);
}
private void OnTranscodeFailedToStart(string path, EncodingJob job)
@@ -265,7 +265,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
}
- protected abstract Task<string> GetCommandLineArguments(EncodingJob job);
+ protected abstract string GetCommandLineArguments(EncodingJob job);
private string GetOutputFilePath(EncodingJob state)
{
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 9c1189f6c..15221c2ac 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -89,6 +89,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var found = new List<string>();
var required = new[]
{
+ "mpeg2video",
"h264_qsv",
"hevc_qsv",
"mpeg2_qsv",
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
index f6895696a..883709de8 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
@@ -36,7 +36,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
public string MimeType { get; set; }
public bool EstimateContentLength { get; set; }
- public bool EnableMpegtsM2TsMode { get; set; }
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
public long? EncodingDurationTicks { get; set; }
public string LiveStreamId { get; set; }
@@ -109,7 +108,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
public string OutputFilePath { get; set; }
- public int? OutputAudioBitrate;
public string ActualOutputVideoCodec
{
@@ -379,7 +377,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return count;
}
- public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded)
+ public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
@@ -387,8 +385,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!percentComplete.HasValue && ticks.HasValue && RunTimeTicks.HasValue)
{
- var pct = ticks.Value/RunTimeTicks.Value;
- percentComplete = pct*100;
+ var pct = ticks.Value / RunTimeTicks.Value;
+ percentComplete = pct * 100;
}
if (percentComplete.HasValue)
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
index 4b336e671..3e99d68ce 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
@@ -22,15 +22,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly ILibraryManager _libraryManager;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IConfigurationManager _config;
+ private readonly IMediaEncoder _mediaEncoder;
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
- public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IConfigurationManager config)
+ public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder)
{
_logger = logger;
_libraryManager = libraryManager;
_mediaSourceManager = mediaSourceManager;
_config = config;
+ _mediaEncoder = mediaEncoder;
}
public async Task<EncodingJob> CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken)
@@ -61,6 +63,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
}
+ if (!string.IsNullOrWhiteSpace(request.SubtitleCodec))
+ {
+ state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToSubtitleCodec(i))
+ ?? state.SupportedSubtitleCodecs.FirstOrDefault();
+ }
+
var item = _libraryManager.GetItemById(request.ItemId);
state.ItemType = item.GetType().Name;
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 580f5c615..97f2c57eb 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -647,9 +647,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
- if (videoStream != null)
+ if (videoStream != null && !videoStream.IsInterlaced)
{
- var isInterlaced = await DetectInterlaced(mediaInfo, videoStream, inputPath, probeSizeArgument).ConfigureAwait(false);
+ var isInterlaced = DetectInterlaced(mediaInfo, videoStream);
if (isInterlaced)
{
@@ -672,7 +672,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- private async Task<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>
diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
index 52ef4d834..96c126923 100644
--- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
@@ -18,143 +18,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
}
- protected override async Task<string> GetCommandLineArguments(EncodingJob state)
+ protected override string GetCommandLineArguments(EncodingJob state)
{
// Get the output codec name
var encodingOptions = GetEncodingOptions();
- var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
- var format = string.Empty;
- var keyFrame = string.Empty;
-
- if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase) &&
- state.Options.Context == EncodingContext.Streaming)
- {
- // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
- format = " -f mp4 -movflags frag_keyframe+empty_moov";
- }
-
- var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
-
- var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
-
- var videoArguments = await GetVideoArguments(state, videoCodec).ConfigureAwait(false);
-
- return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
- inputModifier,
- EncodingHelper.GetInputArgument(state, encodingOptions),
- keyFrame,
- EncodingHelper.GetMapArgs(state),
- videoArguments,
- threads,
- GetAudioArguments(state),
- format,
- state.OutputFilePath
- ).Trim();
- }
-
- /// <summary>
- /// Gets video arguments to pass to ffmpeg
- /// </summary>
- /// <param name="state">The state.</param>
- /// <param name="videoCodec">The video codec.</param>
- /// <returns>System.String.</returns>
- private async Task<string> GetVideoArguments(EncodingJob state, string videoCodec)
- {
- var args = "-codec:v:0 " + videoCodec;
-
- if (state.EnableMpegtsM2TsMode)
- {
- args += " -mpegts_m2ts_mode 1";
- }
-
- var isOutputMkv = string.Equals(state.Options.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase);
-
- if (state.RunTimeTicks.HasValue)
- {
- //args += " -copyts -avoid_negative_ts disabled -start_at_zero";
- }
-
- if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
- {
- args += " -bsf:v h264_mp4toannexb";
- }
-
- return args;
- }
-
- var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
- 5.ToString(UsCulture));
-
- args += keyFrameArg;
-
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode;
-
- // Add resolution params, if specified
- if (!hasGraphicalSubs)
- {
- args += EncodingHelper.GetOutputSizeParam(state, videoCodec);
- }
-
- var qualityParam = EncodingHelper.GetVideoQualityParam(state, videoCodec, GetEncodingOptions(), "superfast");
-
- if (!string.IsNullOrEmpty(qualityParam))
- {
- args += " " + qualityParam.Trim();
- }
-
- // This is for internal graphical subs
- if (hasGraphicalSubs)
- {
- args += EncodingHelper.GetGraphicalSubtitleParam(state, videoCodec);
- }
-
- return args;
- }
-
- /// <summary>
- /// Gets audio arguments to pass to ffmpeg
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- private string GetAudioArguments(EncodingJob state)
- {
- // If the video doesn't have an audio stream, return a default.
- if (state.AudioStream == null && state.VideoStream != null)
- {
- return string.Empty;
- }
-
- // Get the output codec name
- var codec = EncodingHelper.GetAudioEncoder(state);
-
- var args = "-codec:a:0 " + codec;
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- return args;
- }
-
- // Add the number of audio channels
- var channels = state.OutputAudioChannels;
-
- if (channels.HasValue)
- {
- args += " -ac " + channels.Value;
- }
-
- var bitrate = state.OutputAudioBitrate;
-
- if (bitrate.HasValue)
- {
- args += " -ab " + bitrate.Value.ToString(UsCulture);
- }
-
- args += " " + EncodingHelper.GetAudioFilterParam(state, GetEncodingOptions(), false);
-
- return args;
+ return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, state.OutputFilePath, "superfast");
}
protected override string GetOutputFileExtension(EncodingJob state)
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 63e789a59..ee19c4b66 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -55,7 +55,6 @@
<Compile Include="Encoder\EncodingUtils.cs" />
<Compile Include="Encoder\EncoderValidator.cs" />
<Compile Include="Encoder\FontConfigLoader.cs" />
- <Compile Include="Encoder\JobLogger.cs" />
<Compile Include="Encoder\MediaEncoder.cs" />
<Compile Include="Encoder\VideoEncoder.cs" />
<Compile Include="Probing\FFProbeHelpers.cs" />
diff --git a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
index f32dd178f..eef273250 100644
--- a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
+++ b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
@@ -264,6 +264,8 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <value>The loro_surmixlev.</value>
public string loro_surmixlev { get; set; }
+ public string field_order { get; set; }
+
/// <summary>
/// Gets or sets the disposition.
/// </summary>
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 7927ddb6a..8b20dca1b 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -508,6 +508,11 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.IsAVC = false;
}
+ if (!string.IsNullOrWhiteSpace(streamInfo.field_order) && !string.Equals(streamInfo.field_order, "progressive", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.IsInterlaced = true;
+ }
+
// Filter out junk
if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1)
{
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 4a1bf5305..77b976206 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -734,16 +734,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- var charsetFromLanguage = string.IsNullOrWhiteSpace(language)
- ? null
- : GetSubtitleFileCharacterSetFromLanguage(language);
-
- // This assumption should only be made for external subtitles
- if (!string.IsNullOrWhiteSpace(charsetFromLanguage) && !string.Equals(charsetFromLanguage, "windows-1252", StringComparison.OrdinalIgnoreCase))
- {
- return charsetFromLanguage;
- }
-
var charset = await DetectCharset(path, language, protocol, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(charset))
@@ -756,7 +746,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return charset;
}
- return charsetFromLanguage;
+ if (!string.IsNullOrWhiteSpace(language))
+ {
+ return GetSubtitleFileCharacterSetFromLanguage(language);
+ }
+
+ return null;
}
public string GetSubtitleFileCharacterSetFromLanguage(string language)
@@ -854,4 +849,4 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new ArgumentOutOfRangeException("protocol");
}
}
-}
+} \ No newline at end of file
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index 7af0acc59..9cd656fa7 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -16,6 +16,8 @@
public bool EnableAutomaticSeriesGrouping { get; set; }
public bool EnableEmbeddedTitles { get; set; }
+ public int AutomaticRefreshIntervalDays { get; set; }
+
/// <summary>
/// Gets or sets the preferred metadata language.
/// </summary>
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index c2b1e3c89..0562d0ac5 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -48,6 +48,7 @@ namespace MediaBrowser.Model.Configuration
public bool EnableHttps { get; set; }
public bool EnableSeriesPresentationUniqueKey { get; set; }
public bool EnableLocalizedGuids { get; set; }
+ public bool EnableNormalizedItemByNameIds { get; set; }
/// <summary>
/// Gets or sets the value pointing to the file system where the ssl certiifcate is located..
diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
index 7e2002f17..dbd574f86 100644
--- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
+++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
@@ -21,6 +21,7 @@
NumVideoStreams = 17,
IsSecondaryAudio = 18,
VideoCodecTag = 19,
- IsAvc = 20
+ IsAvc = 20,
+ IsInterlaced = 21
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 480eb23a5..36df67b34 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -231,6 +231,7 @@ namespace MediaBrowser.Model.Dlna
{
playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(',');
}
+
playlistItem.SubProtocol = transcodingProfile.Protocol;
List<CodecProfile> audioCodecProfiles = new List<CodecProfile>();
@@ -323,7 +324,7 @@ namespace MediaBrowser.Model.Dlna
if (directPlayProfile != null)
{
// While options takes the network and other factors into account. Only applies to direct stream
- if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true)) && options.EnableDirectStream)
+ if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true), PlayMethod.DirectStream) && options.EnableDirectStream)
{
playMethods.Add(PlayMethod.DirectStream);
}
@@ -331,7 +332,7 @@ namespace MediaBrowser.Model.Dlna
// The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay &&
- IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true)) && options.EnableDirectPlay)
+ IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true), PlayMethod.DirectPlay) && options.EnableDirectPlay)
{
playMethods.Add(PlayMethod.DirectPlay);
}
@@ -479,10 +480,19 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(',');
- playlistItem.VideoCodec = transcodingProfile.VideoCodec;
+ playlistItem.VideoCodecs = transcodingProfile.VideoCodec.Split(',');
playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
+ if (transcodingProfile.MinSegments > 0)
+ {
+ playlistItem.MinSegments = transcodingProfile.MinSegments;
+ }
+ if (transcodingProfile.SegmentLength > 0)
+ {
+ playlistItem.SegmentLength = transcodingProfile.SegmentLength;
+ }
+
if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels))
{
int transcodingMaxAudioChannels;
@@ -895,7 +905,7 @@ namespace MediaBrowser.Model.Dlna
}
}
- return IsAudioEligibleForDirectPlay(item, maxBitrate);
+ return IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
}
public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, string transcodingSubProtocol, string transcodingContainer)
@@ -1025,23 +1035,29 @@ namespace MediaBrowser.Model.Dlna
return null;
}
- private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long? maxBitrate)
+ private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long? maxBitrate, PlayMethod playMethod)
{
+ // Don't restrict by bitrate if coming from an external domain
+ if (item.IsRemote)
+ {
+ return true;
+ }
+
if (!maxBitrate.HasValue)
{
- _logger.Info("Cannot direct play due to unknown supported bitrate");
+ _logger.Info("Cannot "+ playMethod + " due to unknown supported bitrate");
return false;
}
if (!item.Bitrate.HasValue)
{
- _logger.Info("Cannot direct play due to unknown content bitrate");
+ _logger.Info("Cannot " + playMethod + " due to unknown content bitrate");
return false;
}
if (item.Bitrate.Value > maxBitrate.Value)
{
- _logger.Info("Bitrate exceeds DirectPlay limit: media bitrate: {0}, max bitrate: {1}", item.Bitrate.Value.ToString(CultureInfo.InvariantCulture), maxBitrate.Value.ToString(CultureInfo.InvariantCulture));
+ _logger.Info("Bitrate exceeds " + playMethod + " limit: media bitrate: {0}, max bitrate: {1}", item.Bitrate.Value.ToString(CultureInfo.InvariantCulture), maxBitrate.Value.ToString(CultureInfo.InvariantCulture));
return false;
}
@@ -1137,6 +1153,37 @@ namespace MediaBrowser.Model.Dlna
break;
}
case ProfileConditionValue.IsAnamorphic:
+ {
+ bool isAnamorphic;
+ if (bool.TryParse(value, out isAnamorphic))
+ {
+ if (isAnamorphic && condition.Condition == ProfileConditionType.Equals)
+ {
+ item.RequireNonAnamorphic = true;
+ }
+ else if (!isAnamorphic && condition.Condition == ProfileConditionType.NotEquals)
+ {
+ item.RequireNonAnamorphic = true;
+ }
+ }
+ break;
+ }
+ case ProfileConditionValue.IsInterlaced:
+ {
+ bool isInterlaced;
+ if (bool.TryParse(value, out isInterlaced))
+ {
+ if (isInterlaced && condition.Condition == ProfileConditionType.Equals)
+ {
+ item.DeInterlace = true;
+ }
+ else if (!isInterlaced && condition.Condition == ProfileConditionType.NotEquals)
+ {
+ item.DeInterlace = true;
+ }
+ }
+ break;
+ }
case ProfileConditionValue.AudioProfile:
case ProfileConditionValue.Has64BitOffsets:
case ProfileConditionValue.PacketLength:
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index a85e6085b..5705e6477 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -6,6 +6,7 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
namespace MediaBrowser.Model.Dlna
@@ -18,6 +19,7 @@ namespace MediaBrowser.Model.Dlna
public StreamInfo()
{
AudioCodecs = new string[] { };
+ VideoCodecs = new string[] { };
SubtitleCodecs = new string[] { };
}
@@ -34,13 +36,18 @@ namespace MediaBrowser.Model.Dlna
public long StartPositionTicks { get; set; }
- public string VideoCodec { get; set; }
public string VideoProfile { get; set; }
+ public int? SegmentLength { get; set; }
+ public int? MinSegments { get; set; }
+
public bool RequireAvc { get; set; }
+ public bool DeInterlace { get; set; }
+ public bool RequireNonAnamorphic { get; set; }
public bool CopyTimestamps { get; set; }
public bool EnableSubtitlesInManifest { get; set; }
public string[] AudioCodecs { get; set; }
+ public string[] VideoCodecs { get; set; }
public int? AudioStreamIndex { get; set; }
@@ -204,11 +211,15 @@ namespace MediaBrowser.Model.Dlna
string.Empty :
string.Join(",", item.AudioCodecs);
+ string videoCodecs = item.VideoCodecs.Length == 0 ?
+ string.Empty :
+ string.Join(",", item.VideoCodecs);
+
list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
list.Add(new NameValuePair("Static", item.IsDirectStream.ToString().ToLower()));
- list.Add(new NameValuePair("VideoCodec", item.VideoCodec ?? string.Empty));
+ list.Add(new NameValuePair("VideoCodec", videoCodecs));
list.Add(new NameValuePair("AudioCodec", audioCodecs));
list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty));
list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty));
@@ -232,7 +243,9 @@ namespace MediaBrowser.Model.Dlna
// }
//}
- if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls") && !forceStartPosition)
+ var isHls = StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls");
+
+ if (isHls && !forceStartPosition)
{
list.Add(new NameValuePair("StartTimeTicks", string.Empty));
}
@@ -276,6 +289,24 @@ namespace MediaBrowser.Model.Dlna
list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
+ list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString().ToLower()));
+ list.Add(new NameValuePair("DeInterlace", item.DeInterlace.ToString().ToLower()));
+
+ if (!isDlna && isHls)
+ {
+ list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
+
+ if (item.SegmentLength.HasValue)
+ {
+ list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
+ }
+
+ if (item.MinSegments.HasValue)
+ {
+ list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
+ }
+ }
+
return list;
}
@@ -609,9 +640,34 @@ namespace MediaBrowser.Model.Dlna
}
}
+ public string TargetVideoCodec
+ {
+ get
+ {
+ MediaStream stream = TargetVideoStream;
+
+ string inputCodec = stream == null ? null : stream.Codec;
+
+ if (IsDirectStream)
+ {
+ return inputCodec;
+ }
+
+ foreach (string codec in VideoCodecs)
+ {
+ if (StringHelper.EqualsIgnoreCase(codec, inputCodec))
+ {
+ return codec;
+ }
+ }
+
+ return VideoCodecs.Length == 0 ? null : VideoCodecs[0];
+ }
+ }
+
/// <summary>
- /// Predicts the audio channels that will be in the output stream
- /// </summary>
+ /// Predicts the audio channels that will be in the output stream
+ /// </summary>
public long? TargetSize
{
get
diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
index 76f2332f2..350556e90 100644
--- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs
+++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
@@ -42,6 +42,12 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("maxAudioChannels")]
public string MaxAudioChannels { get; set; }
+ [XmlAttribute("minSegments")]
+ public int MinSegments { get; set; }
+
+ [XmlAttribute("segmentLength")]
+ public int SegmentLength { get; set; }
+
public List<string> GetAudioCodecs()
{
List<string> list = new List<string>();
diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs
index f6d1bb351..f90119cf3 100644
--- a/MediaBrowser.Model/IO/IFileSystem.cs
+++ b/MediaBrowser.Model/IO/IFileSystem.cs
@@ -10,6 +10,8 @@ namespace MediaBrowser.Model.IO
/// </summary>
public interface IFileSystem
{
+ void AddShortcutHandler(IShortcutHandler handler);
+
/// <summary>
/// Determines whether the specified filename is shortcut.
/// </summary>
diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
index 79a484a46..91ea977f7 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
@@ -34,7 +34,7 @@ namespace MediaBrowser.Model.LiveTv
TunerHosts = new List<TunerHostInfo>();
ListingProviders = new List<ListingsProviderInfo>();
MediaLocationsCreated = new string[] { };
- RecordingEncodingFormat = "mp4";
+ RecordingEncodingFormat = "mkv";
RecordingPostProcessorArguments = "\"{path}\"";
EnableRecordingEncoding = true;
}
@@ -46,14 +46,13 @@ namespace MediaBrowser.Model.LiveTv
public string Url { get; set; }
public string Type { get; set; }
public string DeviceId { get; set; }
+ public string FriendlyName { get; set; }
public bool ImportFavoritesOnly { get; set; }
public bool AllowHWTranscoding { get; set; }
- public bool IsEnabled { get; set; }
public bool EnableTvgId { get; set; }
public TunerHostInfo()
{
- IsEnabled = true;
AllowHWTranscoding = true;
}
}
diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
index 57a2254b0..16c9464ac 100644
--- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
+++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
@@ -27,9 +27,11 @@ namespace MediaBrowser.Model.MediaInfo
public bool EnableDirectPlay { get; set; }
public bool EnableDirectStream { get; set; }
public bool EnableTranscoding { get; set; }
+ public bool ForceDirectPlayRemoteMediaSource { get; set; }
public PlaybackInfoRequest()
{
+ ForceDirectPlayRemoteMediaSource = true;
EnableDirectPlay = true;
EnableDirectStream = true;
EnableTranscoding = true;
diff --git a/MediaBrowser.Model/Net/IAcceptSocket.cs b/MediaBrowser.Model/Net/IAcceptSocket.cs
index cac23b337..4262e2390 100644
--- a/MediaBrowser.Model/Net/IAcceptSocket.cs
+++ b/MediaBrowser.Model/Net/IAcceptSocket.cs
@@ -1,4 +1,6 @@
using System;
+using System.Threading;
+using System.Threading.Tasks;
namespace MediaBrowser.Model.Net
{
@@ -13,6 +15,7 @@ namespace MediaBrowser.Model.Net
void Bind(IpEndPointInfo endpoint);
void Connect(IpEndPointInfo endPoint);
void StartAccept(Action<IAcceptSocket> onAccept, Func<bool> isClosed);
+ Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken);
}
public class SocketCreateException : Exception
diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs
index 90070b128..61fc0e28b 100644
--- a/MediaBrowser.Model/Net/ISocket.cs
+++ b/MediaBrowser.Model/Net/ISocket.cs
@@ -24,5 +24,6 @@ namespace MediaBrowser.Model.Net
/// Sends a UDP message to a particular end point (uni or multicast).
/// </summary>
Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken);
+ Task SendWithLockAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken);
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs
index 4b70f3362..e7dbf6cb1 100644
--- a/MediaBrowser.Model/Net/ISocketFactory.cs
+++ b/MediaBrowser.Model/Net/ISocketFactory.cs
@@ -14,6 +14,8 @@ namespace MediaBrowser.Model.Net
/// <returns>A <see cref="ISocket"/> implementation.</returns>
ISocket CreateUdpSocket(int localPort);
+ ISocket CreateUdpBroadcastSocket(int localPort);
+
ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort);
/// <summary>
diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs
index b5f50bde0..e3b0d578a 100644
--- a/MediaBrowser.Model/Querying/NextUpQuery.cs
+++ b/MediaBrowser.Model/Querying/NextUpQuery.cs
@@ -55,9 +55,12 @@ namespace MediaBrowser.Model.Querying
/// <value>The enable image types.</value>
public ImageType[] EnableImageTypes { get; set; }
+ public bool EnableTotalRecordCount { get; set; }
+
public NextUpQuery()
{
EnableImageTypes = new ImageType[] {};
+ EnableTotalRecordCount = true;
}
}
}
diff --git a/MediaBrowser.Model/Services/IAsyncStreamWriter.cs b/MediaBrowser.Model/Services/IAsyncStreamWriter.cs
index b10e12813..f61a94f6e 100644
--- a/MediaBrowser.Model/Services/IAsyncStreamWriter.cs
+++ b/MediaBrowser.Model/Services/IAsyncStreamWriter.cs
@@ -8,4 +8,9 @@ namespace MediaBrowser.Model.Services
{
Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken);
}
+
+ public interface IFileWriter
+ {
+ Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken);
+ }
}
diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs
index e9a9f1c5b..115ba25ce 100644
--- a/MediaBrowser.Model/Services/IRequest.cs
+++ b/MediaBrowser.Model/Services/IRequest.cs
@@ -2,6 +2,9 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
namespace MediaBrowser.Model.Services
{
@@ -151,6 +154,7 @@ namespace MediaBrowser.Model.Services
//Add Metadata to Response
Dictionary<string, object> Items { get; }
- }
+ Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken);
+ }
}
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index 75c4b46b2..7c797133f 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -172,18 +172,11 @@ namespace MediaBrowser.Providers.Manager
try
{
- var currentFile = _fileSystem.GetFileInfo(currentPath);
-
- // This will fail if the file is hidden
- if (currentFile.Exists)
- {
- if (currentFile.IsHidden)
- {
- _fileSystem.SetHidden(currentFile.FullName, false);
- }
-
- _fileSystem.DeleteFile(currentFile.FullName);
- }
+ _fileSystem.DeleteFile(currentPath);
+ }
+ catch (FileNotFoundException)
+ {
+
}
finally
{
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 9dff243c1..31cdc164c 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -373,20 +373,15 @@ namespace MediaBrowser.Providers.Manager
continue;
}
- // Delete the source file
- var currentFile = _fileSystem.GetFileInfo(image.Path);
-
- // Deletion will fail if the file is hidden so remove the attribute first
- if (currentFile.Exists)
+ try
{
- if (currentFile.IsHidden)
- {
- _fileSystem.SetHidden(currentFile.FullName, false);
- }
-
- _fileSystem.DeleteFile(currentFile.FullName);
+ _fileSystem.DeleteFile(image.Path);
deleted = true;
}
+ catch (FileNotFoundException)
+ {
+
+ }
}
foreach (var image in deletedImages)
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index bdfe13c1d..59a2278a1 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -45,12 +45,21 @@ namespace MediaBrowser.Providers.Manager
var updateType = ItemUpdateType.None;
var requiresRefresh = false;
+ var libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item);
+
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
{
// TODO: If this returns true, should we instead just change metadata refresh mode to Full?
requiresRefresh = item.RequiresRefresh();
}
+ if (!requiresRefresh &&
+ libraryOptions.AutomaticRefreshIntervalDays > 0 &&
+ (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays)
+ {
+ requiresRefresh = true;
+ }
+
var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem);
var localImagesFailed = false;
@@ -116,8 +125,6 @@ namespace MediaBrowser.Providers.Manager
}
}
- LibraryOptions libraryOptions = null;
-
// Next run remote image providers, but only if local image providers didn't throw an exception
if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
{
@@ -125,11 +132,6 @@ namespace MediaBrowser.Providers.Manager
if (providers.Count > 0)
{
- if (libraryOptions == null)
- {
- libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item);
- }
-
var result = await itemImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false);
updateType = updateType | result.UpdateType;
@@ -177,11 +179,6 @@ namespace MediaBrowser.Providers.Manager
item.DateLastRefreshed = default(DateTime);
}
- if (libraryOptions == null)
- {
- libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item);
- }
-
// Save to database
await SaveItem(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
index e6eb0951d..4aefb62c8 100644
--- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
@@ -45,14 +45,21 @@ namespace MediaBrowser.Providers.Music
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
{
var releaseId = searchInfo.GetReleaseId();
+ var releaseGroupId = searchInfo.GetReleaseGroupId();
string url = null;
var isNameSearch = false;
+ bool forceMusicBrainzProper = false;
if (!string.IsNullOrEmpty(releaseId))
{
url = string.Format("/ws/2/release/?query=reid:{0}", releaseId);
}
+ else if (!string.IsNullOrEmpty(releaseGroupId))
+ {
+ url = string.Format("/ws/2/release?release-group={0}", releaseGroupId);
+ forceMusicBrainzProper = true;
+ }
else
{
var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId();
@@ -75,7 +82,7 @@ namespace MediaBrowser.Providers.Music
if (!string.IsNullOrWhiteSpace(url))
{
- using (var stream = await GetMusicBrainzResponse(url, isNameSearch, cancellationToken).ConfigureAwait(false))
+ using (var stream = await GetMusicBrainzResponse(url, isNameSearch, forceMusicBrainzProper, cancellationToken).ConfigureAwait(false))
{
return GetResultsFromResponse(stream);
}
@@ -131,7 +138,14 @@ namespace MediaBrowser.Providers.Music
Item = new MusicAlbum()
};
- if (string.IsNullOrEmpty(releaseId))
+ // If we have a release group Id but not a release Id...
+ if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId))
+ {
+ releaseId = await GetReleaseIdFromReleaseGroupId(releaseGroupId, cancellationToken).ConfigureAwait(false);
+ result.HasMetadata = true;
+ }
+
+ if (string.IsNullOrWhiteSpace(releaseId))
{
var artistMusicBrainzId = id.GetMusicBrainzArtistId();
@@ -139,13 +153,13 @@ namespace MediaBrowser.Providers.Music
if (releaseResult != null)
{
- if (!string.IsNullOrEmpty(releaseResult.ReleaseId))
+ if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseId))
{
releaseId = releaseResult.ReleaseId;
result.HasMetadata = true;
}
- if (!string.IsNullOrEmpty(releaseResult.ReleaseGroupId))
+ if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseGroupId))
{
releaseGroupId = releaseResult.ReleaseGroupId;
result.HasMetadata = true;
@@ -157,13 +171,13 @@ namespace MediaBrowser.Providers.Music
}
// If we have a release Id but not a release group Id...
- if (!string.IsNullOrEmpty(releaseId) && string.IsNullOrEmpty(releaseGroupId))
+ if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId))
{
- releaseGroupId = await GetReleaseGroupId(releaseId, cancellationToken).ConfigureAwait(false);
+ releaseGroupId = await GetReleaseGroupFromReleaseId(releaseId, cancellationToken).ConfigureAwait(false);
result.HasMetadata = true;
}
- if (!string.IsNullOrEmpty(releaseId) || !string.IsNullOrEmpty(releaseGroupId))
+ if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId))
{
result.HasMetadata = true;
}
@@ -411,13 +425,42 @@ namespace MediaBrowser.Providers.Music
}
}
+ private async Task<string> GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken)
+ {
+ var url = string.Format("/ws/2/release?release-group={0}", releaseGroupId);
+
+ using (var stream = await GetMusicBrainzResponse(url, true, true, cancellationToken).ConfigureAwait(false))
+ {
+ using (var oReader = new StreamReader(stream, Encoding.UTF8))
+ {
+ var settings = _xmlSettings.Create(false);
+
+ settings.CheckCharacters = false;
+ settings.IgnoreProcessingInstructions = true;
+ settings.IgnoreComments = true;
+
+ using (var reader = XmlReader.Create(oReader, settings))
+ {
+ var result = ReleaseResult.Parse(reader).FirstOrDefault();
+
+ if (result != null)
+ {
+ return result.ReleaseId;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
/// <summary>
/// Gets the release group id internal.
/// </summary>
/// <param name="releaseEntryId">The release entry id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.String}.</returns>
- private async Task<string> GetReleaseGroupId(string releaseEntryId, CancellationToken cancellationToken)
+ private async Task<string> GetReleaseGroupFromReleaseId(string releaseEntryId, CancellationToken cancellationToken)
{
var url = string.Format("/ws/2/release-group/?query=reid:{0}", releaseEntryId);
@@ -514,11 +557,11 @@ namespace MediaBrowser.Providers.Music
private List<MbzUrl> _mbzUrls = null;
private MbzUrl _chosenUrl;
- private async Task<MbzUrl> GetMbzUrl()
+ private async Task<MbzUrl> GetMbzUrl(bool forceMusicBrainzProper = false)
{
if (_chosenUrl == null || _mbzUrls == null || (DateTime.UtcNow.Ticks - _lastMbzUrlQueryTicks) > TimeSpan.FromHours(12).Ticks)
{
- var urls = await RefreshMzbUrls().ConfigureAwait(false);
+ var urls = await RefreshMzbUrls(forceMusicBrainzProper).ConfigureAwait(false);
if (urls.Count > 1)
{
@@ -533,31 +576,44 @@ namespace MediaBrowser.Providers.Music
return _chosenUrl;
}
- private async Task<List<MbzUrl>> RefreshMzbUrls()
+ private async Task<List<MbzUrl>> RefreshMzbUrls(bool forceMusicBrainzProper = false)
{
List<MbzUrl> list;
- try
+ if (forceMusicBrainzProper)
{
- var options = new HttpRequestOptions
+ list = new List<MbzUrl>
{
- Url = "https://mb3admin.com/admin/service/standards/musicBrainzUrls",
- UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion
+ new MbzUrl
+ {
+ url = MusicBrainzBaseUrl,
+ throttleMs = 1000
+ }
};
-
- using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ }
+ else
+ {
+ try
{
- var results = _json.DeserializeFromStream<List<MbzUrl>>(stream);
+ var options = new HttpRequestOptions
+ {
+ Url = "https://mb3admin.com/admin/service/standards/musicBrainzUrls",
+ UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion
+ };
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ var results = _json.DeserializeFromStream<List<MbzUrl>>(stream);
- list = results;
+ list = results;
+ }
+ _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks;
}
- _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting music brainz info", ex);
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting music brainz info", ex);
- list = new List<MbzUrl>
+ list = new List<MbzUrl>
{
new MbzUrl
{
@@ -565,6 +621,7 @@ namespace MediaBrowser.Providers.Music
throttleMs = 1000
}
};
+ }
}
_mbzUrls = list.ToList();
@@ -572,16 +629,17 @@ namespace MediaBrowser.Providers.Music
return list;
}
+ internal Task<Stream> GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken)
+ {
+ return GetMusicBrainzResponse(url, isSearch, false, cancellationToken);
+ }
+
/// <summary>
/// Gets the music brainz response.
/// </summary>
- /// <param name="url">The URL.</param>
- /// <param name="isSearch">if set to <c>true</c> [is search].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{XmlDocument}.</returns>
- internal async Task<Stream> GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken)
+ internal async Task<Stream> GetMusicBrainzResponse(string url, bool isSearch, bool forceMusicBrainzProper, CancellationToken cancellationToken)
{
- var urlInfo = await GetMbzUrl().ConfigureAwait(false);
+ var urlInfo = await GetMbzUrl(forceMusicBrainzProper).ConfigureAwait(false);
var throttleMs = urlInfo.throttleMs;
if (throttleMs > 0)
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
index 46be61486..4a52b972f 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
@@ -119,7 +119,7 @@ namespace MediaBrowser.Providers.TV
if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) &&
(searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue))
{
- await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, null, null, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, searchInfo.SeriesProviderIds);
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
index f6af365fd..72bd62d9f 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
@@ -389,7 +389,7 @@ namespace MediaBrowser.Providers.TV
_fileSystem.CreateDirectory(seriesDataPath);
- return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken);
+ return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), null, null, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs
index 2c4ccddd7..e68b7ad1d 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs
@@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.TV
var seriesProviderIds = series.ProviderIds;
var seasonNumber = season.IndexNumber.Value;
- var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
+ var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.Name, series.ProductionYear, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(seriesDataPath))
{
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
index 97eedfa92..cdb9ac51e 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
@@ -70,7 +70,7 @@ namespace MediaBrowser.Providers.TV
{
var language = item.GetPreferredMetadataLanguage();
- var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(item.ProviderIds, language, cancellationToken).ConfigureAwait(false);
+ var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(item.ProviderIds, item.Name, item.ProductionYear, language, cancellationToken).ConfigureAwait(false);
var path = Path.Combine(seriesDataPath, "banners.xml");
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
index 44550d2be..c340d03f5 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
@@ -109,7 +109,7 @@ namespace MediaBrowser.Providers.TV
if (IsValidSeries(itemId.ProviderIds))
{
- await EnsureSeriesInfo(itemId.ProviderIds, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ await EnsureSeriesInfo(itemId.ProviderIds, itemId.Name, itemId.Year, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false);
result.Item = new Series();
result.HasMetadata = true;
@@ -160,19 +160,11 @@ namespace MediaBrowser.Providers.TV
/// <summary>
/// Downloads the series zip.
/// </summary>
- /// <param name="seriesId">The series id.</param>
- /// <param name="idType">Type of the identifier.</param>
- /// <param name="seriesDataPath">The series data path.</param>
- /// <param name="lastTvDbUpdateTime">The last tv database update time.</param>
- /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- /// <exception cref="System.ArgumentNullException">seriesId</exception>
- internal async Task DownloadSeriesZip(string seriesId, string idType, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
+ internal async Task DownloadSeriesZip(string seriesId, string idType, string seriesName, int? seriesYear, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
try
{
- await DownloadSeriesZip(seriesId, idType, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
+ await DownloadSeriesZip(seriesId, idType, seriesName, seriesYear, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
return;
}
catch (HttpException ex)
@@ -185,11 +177,11 @@ namespace MediaBrowser.Providers.TV
if (!string.Equals(preferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase))
{
- await DownloadSeriesZip(seriesId, idType, seriesDataPath, lastTvDbUpdateTime, "en", preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
+ await DownloadSeriesZip(seriesId, idType, seriesName, seriesYear, seriesDataPath, lastTvDbUpdateTime, "en", preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
}
}
- private async Task DownloadSeriesZip(string seriesId, string idType, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, string saveAsMetadataLanguage, CancellationToken cancellationToken)
+ private async Task DownloadSeriesZip(string seriesId, string idType, string seriesName, int? seriesYear, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, string saveAsMetadataLanguage, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(seriesId))
{
@@ -201,6 +193,23 @@ namespace MediaBrowser.Providers.TV
seriesId = await GetSeriesByRemoteId(seriesId, idType, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
}
+ // If searching by remote id came up empty, then do a regular search
+ if (string.IsNullOrWhiteSpace(seriesId) && !string.IsNullOrWhiteSpace(seriesName))
+ {
+ var searchInfo = new SeriesInfo
+ {
+ Name = seriesName,
+ Year = seriesYear,
+ MetadataLanguage = preferredMetadataLanguage
+ };
+ var results = await GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
+ var result = results.FirstOrDefault();
+ if (result != null)
+ {
+ seriesId = result.GetProviderId(MetadataProviders.Tvdb);
+ }
+ }
+
if (string.IsNullOrWhiteSpace(seriesId))
{
throw new ArgumentNullException("seriesId");
@@ -380,7 +389,7 @@ namespace MediaBrowser.Providers.TV
}
private SemaphoreSlim _ensureSemaphore = new SemaphoreSlim(1, 1);
- internal async Task<string> EnsureSeriesInfo(Dictionary<string, string> seriesProviderIds, string preferredMetadataLanguage, CancellationToken cancellationToken)
+ internal async Task<string> EnsureSeriesInfo(Dictionary<string, string> seriesProviderIds, string seriesName, int? seriesYear, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
await _ensureSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -395,7 +404,7 @@ namespace MediaBrowser.Providers.TV
// The post-scan task will take care of updates so we don't need to re-download here
if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage))
{
- await DownloadSeriesZip(seriesId, MetadataProviders.Tvdb.ToString(), seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
+ await DownloadSeriesZip(seriesId, MetadataProviders.Tvdb.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
}
return seriesDataPath;
@@ -409,7 +418,7 @@ namespace MediaBrowser.Providers.TV
// The post-scan task will take care of updates so we don't need to re-download here
if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage))
{
- await DownloadSeriesZip(seriesId, MetadataProviders.Imdb.ToString(), seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
+ await DownloadSeriesZip(seriesId, MetadataProviders.Imdb.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
}
return seriesDataPath;
diff --git a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
index 4d9f3e7c4..bb67a9e36 100644
--- a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
+++ b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
@@ -386,14 +386,8 @@
<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>
diff --git a/MediaBrowser.Server.Mac/MacAppHost.cs b/MediaBrowser.Server.Mac/MacAppHost.cs
index 59e72e71e..304472529 100644
--- a/MediaBrowser.Server.Mac/MacAppHost.cs
+++ b/MediaBrowser.Server.Mac/MacAppHost.cs
@@ -56,42 +56,6 @@ namespace MediaBrowser.Server.Mac
return new SyncManager();
}
- protected override FFMpegInstallInfo GetFfmpegInstallInfo()
- {
- var info = new FFMpegInstallInfo();
-
- info.ArchiveType = "7z";
-
- switch (EnvironmentInfo.SystemArchitecture)
- {
- case Architecture.X64:
- info.Version = "20160124";
- break;
- case Architecture.X86:
- info.Version = "20150110";
- break;
- }
-
- info.DownloadUrls = GetDownloadUrls();
-
- return info;
- }
-
- private string[] GetDownloadUrls()
- {
- switch (EnvironmentInfo.SystemArchitecture)
- {
- case Architecture.X64:
- return new[]
- {
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x64-2.8.5.7z"
- };
- }
-
- // No version available
- return new string[] { };
- }
-
protected override void RestartInternal()
{
MainClass.Restart();
@@ -123,10 +87,6 @@ namespace MediaBrowser.Server.Mac
throw new NotImplementedException();
}
- protected override void EnableLoopbackInternal(string appName)
- {
- }
-
public override bool SupportsRunningAsService
{
get
diff --git a/MediaBrowser.Server.Mac/Main.cs b/MediaBrowser.Server.Mac/Main.cs
index debd5f539..d703f7d0d 100644
--- a/MediaBrowser.Server.Mac/Main.cs
+++ b/MediaBrowser.Server.Mac/Main.cs
@@ -105,12 +105,11 @@ namespace MediaBrowser.Server.Mac
// Allow all https requests
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
- var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), false, false, appPaths.TempDirectory);
- fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
+ var environmentInfo = GetEnvironmentInfo();
- _fileSystem = fileSystem;
+ var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), environmentInfo, appPaths.TempDirectory);
- var environmentInfo = GetEnvironmentInfo();
+ _fileSystem = fileSystem;
var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger,
logManager,
diff --git a/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs b/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs
index daf2b90e6..7aeff5ac8 100644
--- a/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs
+++ b/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs
@@ -1,13 +1,14 @@
using Emby.Common.Implementations.IO;
using MediaBrowser.Model.Logging;
using Mono.Unix.Native;
+using MediaBrowser.Model.System;
namespace Emby.Server.Mac.Native
{
public class MonoFileSystem : ManagedFileSystem
{
- public MonoFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, string tempPath)
- : base(logger, supportsAsyncFileStreams, enableManagedInvalidFileNameChars, true, tempPath)
+ public MonoFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath)
+ : base(logger, environmentInfo, tempPath)
{
}
diff --git a/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs b/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs
index a5dc691a7..91c064efe 100644
--- a/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs
+++ b/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs
@@ -1,13 +1,14 @@
using Emby.Common.Implementations.IO;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.System;
using Mono.Unix.Native;
namespace MediaBrowser.Server.Mono.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 environment, string tempPath)
+ : base(logger, environment, tempPath)
{
}
diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs
index 649283410..66851f7e9 100644
--- a/MediaBrowser.Server.Mono/Program.cs
+++ b/MediaBrowser.Server.Mono/Program.cs
@@ -114,12 +114,11 @@ namespace MediaBrowser.Server.Mono
// Allow all https requests
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
- var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), false, false, appPaths.TempDirectory);
- fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
+ var environmentInfo = GetEnvironmentInfo();
- FileSystem = fileSystem;
+ var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), environmentInfo, appPaths.TempDirectory);
- var environmentInfo = GetEnvironmentInfo();
+ FileSystem = fileSystem;
var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths);
diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs
index b41e7607c..b02c5d6ac 100644
--- a/MediaBrowser.ServerApplication/MainStartup.cs
+++ b/MediaBrowser.ServerApplication/MainStartup.cs
@@ -331,9 +331,9 @@ namespace MediaBrowser.ServerApplication
/// <param name="options">The options.</param>
private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, bool runService, StartupOptions options)
{
- var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), true, true, false, appPaths.TempDirectory);
- fileSystem.AddShortcutHandler(new LnkShortcutHandler());
- fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
+ var environmentInfo = new EnvironmentInfo();
+
+ var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), environmentInfo, appPaths.TempDirectory);
var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths);
@@ -345,7 +345,7 @@ namespace MediaBrowser.ServerApplication
fileSystem,
new PowerManagement(),
"emby.windows.zip",
- new EnvironmentInfo(),
+ environmentInfo,
imageEncoder,
new Server.Startup.Common.SystemEvents(logManager.GetLogger("SystemEvents")),
new RecyclableMemoryStreamProvider(),
diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
index 7ebbc3809..b968c2fb6 100644
--- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
+++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
@@ -142,8 +142,7 @@
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="MainStartup.cs" />
- <Compile Include="Native\LnkShortcutHandler.cs" />
- <Compile Include="Native\LoopbackUtil.cs" />
+ <Compile Include="Native\LoopUtil.cs" />
<Compile Include="Native\PowerManagement.cs" />
<Compile Include="Native\Standby.cs" />
<Compile Include="Native\ServerAuthorization.cs" />
diff --git a/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs b/MediaBrowser.ServerApplication/Native/LoopUtil.cs
index 5b260685b..6160f853f 100644
--- a/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs
+++ b/MediaBrowser.ServerApplication/Native/LoopUtil.cs
@@ -5,6 +5,9 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
+/*
+ * Important - Even though this will compile in the shared projects, it will cause build failures within the mono runtime
+ */
namespace MediaBrowser.ServerApplication.Native
{
/// <summary>
@@ -71,12 +74,12 @@ namespace MediaBrowser.ServerApplication.Native
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool ConvertStringSidToSid(string strSid, out IntPtr pSid);
- [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
+ [DllImport("advapi32", /*CharSet = CharSet.Auto,*/ SetLastError = true)]
static extern bool ConvertSidToStringSid(
[MarshalAs(UnmanagedType.LPArray)] byte[] pSID,
out IntPtr ptrSid);
- [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
+ [DllImport("advapi32", /*CharSet = CharSet.Auto,*/ SetLastError = true)]
static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid);
// Use this API to convert a string reference (e.g. "@{blah.pri?ms-resource://whatever}") into a plain string
@@ -178,7 +181,7 @@ namespace MediaBrowser.ServerApplication.Native
foreach (var app in Apps)
{
- if ((app.appContainerName ?? string.Empty).IndexOf(appName, StringComparison.OrdinalIgnoreCase) != -1 ||
+ if ((app.appContainerName ?? string.Empty).IndexOf(appName, StringComparison.OrdinalIgnoreCase) != -1 ||
(app.displayName ?? string.Empty).IndexOf(appName, StringComparison.OrdinalIgnoreCase) != -1)
{
if (!app.LoopUtil)
diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs
index cc899462a..8f1a88a74 100644
--- a/MediaBrowser.ServerApplication/WindowsAppHost.cs
+++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs
@@ -4,6 +4,7 @@ using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices.ComTypes;
+using Emby.Common.Implementations.IO;
using Emby.Server.CinemaMode;
using Emby.Server.Connect;
using Emby.Server.Core;
@@ -47,6 +48,11 @@ namespace MediaBrowser.ServerApplication
MainStartup.Restart();
}
+ public override void EnableLoopback(string appName)
+ {
+ LoopUtil.Run(appName);
+ }
+
protected override List<Assembly> GetAssembliesWithPartsInternal()
{
var list = new List<Assembly>();
@@ -107,11 +113,6 @@ namespace MediaBrowser.ServerApplication
}
}
- protected override void EnableLoopbackInternal(string appName)
- {
- LoopUtil.Run(appName);
- }
-
public override bool SupportsRunningAsService
{
get
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index 8a150e0c1..650d7a138 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -464,10 +464,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val))
{
- item.ProductionLocations = val.Split('/')
+ item.ProductionLocations.AddRange(val.Split('/')
.Select(i => i.Trim())
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .ToList();
+ .Where(i => !string.IsNullOrWhiteSpace(i)));
}
break;
}
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index 1e03f1169..54133b718 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
- <version>3.0.696</version>
+ <version>3.0.697</version>
<title>Emby.Common</title>
<authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index d6777dad1..e59460c94 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
- <version>3.0.696</version>
+ <version>3.0.697</version>
<title>Emby.Server.Core</title>
<authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs
index 99e3969aa..cc464e689 100644
--- a/RSSDP/SsdpCommunicationsServer.cs
+++ b/RSSDP/SsdpCommunicationsServer.cs
@@ -177,12 +177,16 @@ namespace Rssdp.Infrastructure
{
try
{
- await socket.SendAsync(messageData, messageData.Length, destination, cancellationToken).ConfigureAwait(false);
+ await socket.SendWithLockAsync(messageData, messageData.Length, destination, cancellationToken).ConfigureAwait(false);
}
catch (ObjectDisposedException)
{
}
+ catch (OperationCanceledException)
+ {
+
+ }
catch (Exception ex)
{
_logger.ErrorException("Error sending socket message from {0} to {1}", ex, socket.LocalIPAddress.ToString(), destination.ToString());
@@ -341,11 +345,9 @@ namespace Rssdp.Infrastructure
foreach (var socket in sockets)
{
- await socket.SendAsync(messageData, messageData.Length, destination, cancellationToken).ConfigureAwait(false);
+ await SendFromSocket(socket, messageData, destination, cancellationToken).ConfigureAwait(false);
}
}
-
- ThrowIfDisposed();
}
private ISocket ListenForBroadcastsAsync()
diff --git a/SocketHttpListener.Portable/Net/EndPointListener.cs b/SocketHttpListener.Portable/Net/EndPointListener.cs
index c7642d5d1..4f1a17fc0 100644
--- a/SocketHttpListener.Portable/Net/EndPointListener.cs
+++ b/SocketHttpListener.Portable/Net/EndPointListener.cs
@@ -32,8 +32,9 @@ namespace SocketHttpListener.Net
private readonly ISocketFactory _socketFactory;
private readonly ITextEncoding _textEncoding;
private readonly IMemoryStreamFactory _memoryStreamFactory;
+ private readonly IFileSystem _fileSystem;
- public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
+ public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem)
{
this.listener = listener;
_logger = logger;
@@ -42,6 +43,7 @@ namespace SocketHttpListener.Net
_socketFactory = socketFactory;
_memoryStreamFactory = memoryStreamFactory;
_textEncoding = textEncoding;
+ _fileSystem = fileSystem;
this.secure = secure;
this.cert = cert;
@@ -107,7 +109,7 @@ namespace SocketHttpListener.Net
return;
}
- HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding).ConfigureAwait(false);
+ HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding, _fileSystem).ConfigureAwait(false);
//_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId);
lock (listener.unregistered)
diff --git a/SocketHttpListener.Portable/Net/EndPointManager.cs b/SocketHttpListener.Portable/Net/EndPointManager.cs
index 797684b3e..11f774915 100644
--- a/SocketHttpListener.Portable/Net/EndPointManager.cs
+++ b/SocketHttpListener.Portable/Net/EndPointManager.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using SocketHttpListener.Primitives;
@@ -105,7 +106,7 @@ namespace SocketHttpListener.Net
}
else
{
- epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding);
+ epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem);
p[port] = epl;
}
diff --git a/SocketHttpListener.Portable/Net/HttpConnection.cs b/SocketHttpListener.Portable/Net/HttpConnection.cs
index 4a2cf3f5b..5fe47fc63 100644
--- a/SocketHttpListener.Portable/Net/HttpConnection.cs
+++ b/SocketHttpListener.Portable/Net/HttpConnection.cs
@@ -35,13 +35,14 @@ namespace SocketHttpListener.Net
ICertificate cert;
Stream ssl_stream;
- private ILogger _logger;
+ private readonly ILogger _logger;
private readonly ICryptoProvider _cryptoProvider;
private readonly IMemoryStreamFactory _memoryStreamFactory;
private readonly ITextEncoding _textEncoding;
private readonly IStreamFactory _streamFactory;
+ private readonly IFileSystem _fileSystem;
- private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
+ private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem)
{
_logger = logger;
this.sock = sock;
@@ -51,6 +52,7 @@ namespace SocketHttpListener.Net
_cryptoProvider = cryptoProvider;
_memoryStreamFactory = memoryStreamFactory;
_textEncoding = textEncoding;
+ _fileSystem = fileSystem;
_streamFactory = streamFactory;
}
@@ -82,9 +84,9 @@ namespace SocketHttpListener.Net
Init();
}
- public static async Task<HttpConnection> Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
+ public static async Task<HttpConnection> Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem)
{
- var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding);
+ var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding, fileSystem);
await connection.InitStream().ConfigureAwait(false);
@@ -121,7 +123,7 @@ namespace SocketHttpListener.Net
position = 0;
input_state = InputState.RequestLine;
line_state = LineState.None;
- context = new HttpListenerContext(this, _logger, _cryptoProvider, _memoryStreamFactory, _textEncoding);
+ context = new HttpListenerContext(this, _logger, _cryptoProvider, _memoryStreamFactory, _textEncoding, _fileSystem);
}
public bool IsClosed
@@ -213,7 +215,9 @@ namespace SocketHttpListener.Net
if (context.Response.SendChunked || isExpect100Continue || context.Request.IsWebSocketRequest || true)
{
- o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding);
+ var supportsDirectSocketAccess = !context.Response.SendChunked && !isExpect100Continue && !secure;
+
+ o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess);
}
else
{
diff --git a/SocketHttpListener.Portable/Net/HttpListener.cs b/SocketHttpListener.Portable/Net/HttpListener.cs
index 2b0f75d01..c2e7acd8e 100644
--- a/SocketHttpListener.Portable/Net/HttpListener.cs
+++ b/SocketHttpListener.Portable/Net/HttpListener.cs
@@ -18,6 +18,7 @@ namespace SocketHttpListener.Net
internal ICryptoProvider CryptoProvider { get; private set; }
internal IStreamFactory StreamFactory { get; private set; }
internal ISocketFactory SocketFactory { get; private set; }
+ internal IFileSystem FileSystem { get; private set; }
internal ITextEncoding TextEncoding { get; private set; }
internal IMemoryStreamFactory MemoryStreamFactory { get; private set; }
internal INetworkManager NetworkManager { get; private set; }
@@ -39,7 +40,7 @@ namespace SocketHttpListener.Net
public Action<HttpListenerContext> OnContext { get; set; }
- public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory)
+ public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem)
{
_logger = logger;
CryptoProvider = cryptoProvider;
@@ -48,19 +49,20 @@ namespace SocketHttpListener.Net
NetworkManager = networkManager;
TextEncoding = textEncoding;
MemoryStreamFactory = memoryStreamFactory;
+ FileSystem = fileSystem;
prefixes = new HttpListenerPrefixCollection(logger, this);
registry = new Dictionary<HttpListenerContext, HttpListenerContext>();
connections = new Dictionary<HttpConnection, HttpConnection>();
auth_schemes = AuthenticationSchemes.Anonymous;
}
- public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory)
- :this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory)
+ public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem)
+ :this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem)
{
}
- public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory)
- : this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory)
+ public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem)
+ : this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem)
{
_certificate = certificate;
}
diff --git a/SocketHttpListener.Portable/Net/HttpListenerContext.cs b/SocketHttpListener.Portable/Net/HttpListenerContext.cs
index 182fd2d2a..58d769f22 100644
--- a/SocketHttpListener.Portable/Net/HttpListenerContext.cs
+++ b/SocketHttpListener.Portable/Net/HttpListenerContext.cs
@@ -18,20 +18,18 @@ namespace SocketHttpListener.Net
HttpConnection cnc;
string error;
int err_status = 400;
- private readonly ILogger _logger;
private readonly ICryptoProvider _cryptoProvider;
private readonly IMemoryStreamFactory _memoryStreamFactory;
private readonly ITextEncoding _textEncoding;
- internal HttpListenerContext(HttpConnection cnc, ILogger logger, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
+ internal HttpListenerContext(HttpConnection cnc, ILogger logger, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem)
{
this.cnc = cnc;
- _logger = logger;
_cryptoProvider = cryptoProvider;
_memoryStreamFactory = memoryStreamFactory;
_textEncoding = textEncoding;
request = new HttpListenerRequest(this, _textEncoding);
- response = new HttpListenerResponse(this, _logger, _textEncoding);
+ response = new HttpListenerResponse(this, logger, _textEncoding, fileSystem);
}
internal int ErrorStatus
diff --git a/SocketHttpListener.Portable/Net/HttpListenerRequest.cs b/SocketHttpListener.Portable/Net/HttpListenerRequest.cs
index 811cc6ddb..cfbd49203 100644
--- a/SocketHttpListener.Portable/Net/HttpListenerRequest.cs
+++ b/SocketHttpListener.Portable/Net/HttpListenerRequest.cs
@@ -456,7 +456,7 @@ namespace SocketHttpListener.Net
public long ContentLength64
{
- get { return content_length; }
+ get { return is_chunked ? -1 : content_length; }
}
public string ContentType
diff --git a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs
index 9a5862cb9..d9f91c0cc 100644
--- a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs
+++ b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs
@@ -3,6 +3,9 @@ using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Text;
using SocketHttpListener.Primitives;
@@ -32,12 +35,14 @@ namespace SocketHttpListener.Net
private readonly ILogger _logger;
private readonly ITextEncoding _textEncoding;
+ private readonly IFileSystem _fileSystem;
- internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding)
+ internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding, IFileSystem fileSystem)
{
this.context = context;
_logger = logger;
_textEncoding = textEncoding;
+ _fileSystem = fileSystem;
}
internal bool CloseConnection
@@ -366,7 +371,7 @@ namespace SocketHttpListener.Net
{
if (chunked)
{
- return ;
+ return;
}
Version v = context.Request.ProtocolVersion;
@@ -509,5 +514,10 @@ namespace SocketHttpListener.Net
cookies.Add(cookie);
}
+
+ public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
+ {
+ return ((ResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken);
+ }
}
} \ No newline at end of file
diff --git a/SocketHttpListener.Portable/Net/ResponseStream.cs b/SocketHttpListener.Portable/Net/ResponseStream.cs
index a79a18791..3c79f47c2 100644
--- a/SocketHttpListener.Portable/Net/ResponseStream.cs
+++ b/SocketHttpListener.Portable/Net/ResponseStream.cs
@@ -5,6 +5,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Net;
using MediaBrowser.Model.Text;
using SocketHttpListener.Primitives;
@@ -22,12 +23,18 @@ namespace SocketHttpListener.Net
Stream stream;
private readonly IMemoryStreamFactory _memoryStreamFactory;
private readonly ITextEncoding _textEncoding;
+ private readonly IFileSystem _fileSystem;
+ private readonly IAcceptSocket _socket;
+ private readonly bool _supportsDirectSocketAccess;
- internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
+ internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IAcceptSocket socket, bool supportsDirectSocketAccess)
{
this.response = response;
_memoryStreamFactory = memoryStreamFactory;
_textEncoding = textEncoding;
+ _fileSystem = fileSystem;
+ _socket = socket;
+ _supportsDirectSocketAccess = supportsDirectSocketAccess;
this.stream = stream;
}
@@ -299,5 +306,80 @@ namespace SocketHttpListener.Net
{
throw new NotSupportedException();
}
+
+ public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
+ {
+ //if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !response.SendChunked)
+ //{
+ // return TransmitFileOverSocket(path, offset, count, cancellationToken);
+ //}
+ return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
+ }
+
+ private readonly byte[] _emptyBuffer = new byte[] { };
+ private async Task TransmitFileOverSocket(string path, long offset, long count, CancellationToken cancellationToken)
+ {
+ MemoryStream ms = GetHeaders(response, _memoryStreamFactory, false);
+
+ var buffer = new byte[] {};
+ if (ms != null)
+ {
+ ms.Position = 0;
+
+ byte[] msBuffer;
+ _memoryStreamFactory.TryGetBuffer(ms, out msBuffer);
+ buffer = msBuffer;
+ }
+
+ await _socket.SendFile(path, buffer, _emptyBuffer, cancellationToken).ConfigureAwait(false);
+ }
+
+ private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
+ {
+ var chunked = response.SendChunked;
+
+ if (!chunked)
+ {
+ await WriteAsync(_emptyBuffer, 0, 0, cancellationToken).ConfigureAwait(false);
+ }
+
+ using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, true))
+ {
+ if (offset > 0)
+ {
+ fs.Position = offset;
+ }
+
+ var targetStream = chunked ? this : stream;
+
+ if (count > 0)
+ {
+ await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await fs.CopyToAsync(targetStream, 81920, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+
+ private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
+ {
+ var array = new byte[81920];
+ int count;
+ while ((count = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ {
+ var bytesToCopy = Math.Min(count, copyLength);
+
+ await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false);
+
+ copyLength -= bytesToCopy;
+
+ if (copyLength <= 0)
+ {
+ break;
+ }
+ }
+ }
}
}