Merge pull request #2224 from MediaBrowser/beta

Beta
This commit is contained in:
Luke
2016-10-12 15:43:28 -04:00
committed by GitHub
337 changed files with 10368 additions and 6073 deletions

View File

@@ -30,12 +30,12 @@ namespace MediaBrowser.Server.Implementations.Channels
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
}
public Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
public Task CloseMediaSource(string liveStreamId)
{
throw new NotImplementedException();
}

View File

@@ -1577,97 +1577,5 @@ namespace MediaBrowser.Server.Implementations.Channels
return await _libraryManager.GetNamedView(name, "channels", "zz_" + name, cancellationToken).ConfigureAwait(false);
}
public async Task DownloadChannelItem(BaseItem item, string destination,
IProgress<double> progress, CancellationToken cancellationToken)
{
var sources = await GetDynamicMediaSources(item, cancellationToken)
.ConfigureAwait(false);
var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList();
foreach (var source in list)
{
await TryDownloadChannelItem(source, item, destination, progress, cancellationToken).ConfigureAwait(false);
return;
}
}
private async Task TryDownloadChannelItem(MediaSourceInfo source,
BaseItem item,
string destination,
IProgress<double> progress,
CancellationToken cancellationToken)
{
var options = new HttpRequestOptions
{
CancellationToken = cancellationToken,
Url = source.Path,
Progress = new Progress<double>()
};
var channel = GetChannel(item.ChannelId);
var channelProvider = GetChannelProvider(channel);
var features = channelProvider.GetChannelFeatures();
if (!features.SupportsContentDownloading)
{
throw new ArgumentException("The channel does not support downloading.");
}
var limit = features.DailyDownloadLimit;
foreach (var header in source.RequiredHttpHeaders)
{
options.RequestHeaders[header.Key] = header.Value;
}
_fileSystem.CreateDirectory(Path.GetDirectoryName(destination));
// Determine output extension
var response = await _httpClient.GetTempFileResponse(options).ConfigureAwait(false);
if (response.ContentType.StartsWith("text/html"))
{
throw new HttpException("File not found")
{
StatusCode = HttpStatusCode.NotFound
};
}
if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase) && response.ContentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
{
var extension = response.ContentType.Split('/')
.Last()
.Replace("quicktime", "mov", StringComparison.OrdinalIgnoreCase);
destination += "." + extension;
}
else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) && response.ContentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase))
{
var extension = response.ContentType.Replace("audio/mpeg", "audio/mp3", StringComparison.OrdinalIgnoreCase)
.Split('/')
.Last();
destination += "." + extension;
}
else
{
_fileSystem.DeleteFile(response.TempFilePath);
throw new ApplicationException("Unexpected response type encountered: " + response.ContentType);
}
_fileSystem.CopyFile(response.TempFilePath, destination, true);
try
{
_fileSystem.DeleteFile(response.TempFilePath);
}
catch
{
}
}
}
}

View File

@@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Channels
public string Category
{
get { return "Channels"; }
get { return "Internet Channels"; }
}
public async Task Execute(System.Threading.CancellationToken cancellationToken, IProgress<double> progress)

View File

@@ -219,7 +219,9 @@ namespace MediaBrowser.Server.Implementations.Collections
foreach (var itemId in itemIds)
{
var child = collection.LinkedChildren.FirstOrDefault(i => i.ItemId.HasValue && i.ItemId.Value == itemId);
var childItem = _libraryManager.GetItemById(itemId);
var child = collection.LinkedChildren.FirstOrDefault(i => (i.ItemId.HasValue && i.ItemId.Value == itemId) || (childItem != null && string.Equals(childItem.Path, i.Path, StringComparison.OrdinalIgnoreCase)));
if (child == null)
{
@@ -228,47 +230,15 @@ namespace MediaBrowser.Server.Implementations.Collections
list.Add(child);
var childItem = _libraryManager.GetItemById(itemId);
if (childItem != null)
{
itemList.Add(childItem);
}
}
var shortcutFiles = _fileSystem
.GetFilePaths(collection.Path)
.Where(i => _fileSystem.IsShortcut(i))
.ToList();
var shortcutFilesToDelete = list.Where(child => !string.IsNullOrWhiteSpace(child.Path) && child.Type == LinkedChildType.Shortcut)
.Select(child => shortcutFiles.FirstOrDefault(i => string.Equals(child.Path, _fileSystem.ResolveShortcut(i), StringComparison.OrdinalIgnoreCase)))
.Where(i => !string.IsNullOrWhiteSpace(i))
.ToList();
foreach (var file in shortcutFilesToDelete)
foreach (var child in list)
{
_iLibraryMonitor.ReportFileSystemChangeBeginning(file);
}
try
{
foreach (var file in shortcutFilesToDelete)
{
_fileSystem.DeleteFile(file);
}
foreach (var child in list)
{
collection.LinkedChildren.Remove(child);
}
}
finally
{
foreach (var file in shortcutFilesToDelete)
{
_iLibraryMonitor.ReportFileSystemChangeComplete(file, false);
}
collection.LinkedChildren.Remove(child);
}
collection.UpdateRatingToContent();

View File

@@ -127,7 +127,8 @@ namespace MediaBrowser.Server.Implementations.Connect
// Seeing block length errors with our server
EnableHttpCompression = false,
PreferIpv4 = preferIpv4
PreferIpv4 = preferIpv4,
BufferContent = false
}).ConfigureAwait(false))
{

View File

@@ -266,7 +266,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = CancellationToken.None
CancellationToken = CancellationToken.None,
BufferContent = false
};
options.SetPostData(postData);
@@ -314,7 +315,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = CancellationToken.None
CancellationToken = CancellationToken.None,
BufferContent = false
};
options.SetPostData(postData);
@@ -403,6 +405,14 @@ namespace MediaBrowser.Server.Implementations.Connect
public async Task<UserLinkResult> LinkUser(string userId, string connectUsername)
{
if (string.IsNullOrWhiteSpace(userId))
{
throw new ArgumentNullException("userId");
}
if (string.IsNullOrWhiteSpace(connectUsername))
{
throw new ArgumentNullException("connectUsername");
}
if (string.IsNullOrWhiteSpace(ConnectServerId))
{
await UpdateConnectInfo().ConfigureAwait(false);
@@ -422,14 +432,6 @@ namespace MediaBrowser.Server.Implementations.Connect
private async Task<UserLinkResult> LinkUserInternal(string userId, string connectUsername)
{
if (string.IsNullOrWhiteSpace(userId))
{
throw new ArgumentNullException("userId");
}
if (string.IsNullOrWhiteSpace(connectUsername))
{
throw new ArgumentNullException("connectUsername");
}
if (string.IsNullOrWhiteSpace(ConnectServerId))
{
throw new ArgumentNullException("ConnectServerId");
@@ -446,11 +448,17 @@ namespace MediaBrowser.Server.Implementations.Connect
throw new ArgumentException("The Emby account has been disabled.");
}
var existingUser = _userManager.Users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectUser.Id) && !string.IsNullOrWhiteSpace(i.ConnectAccessKey));
if (existingUser != null)
{
throw new InvalidOperationException("This connect user is already linked to local user " + existingUser.Name);
}
var user = GetUser(userId);
if (!string.IsNullOrWhiteSpace(user.ConnectUserId))
{
await RemoveConnect(user, connectUser.Id).ConfigureAwait(false);
await RemoveConnect(user, user.ConnectUserId).ConfigureAwait(false);
}
var url = GetConnectUrl("ServerAuthorizations");
@@ -458,7 +466,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = CancellationToken.None
CancellationToken = CancellationToken.None,
BufferContent = false
};
var accessToken = Guid.NewGuid().ToString("N");
@@ -593,7 +602,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = CancellationToken.None
CancellationToken = CancellationToken.None,
BufferContent = false
};
var accessToken = Guid.NewGuid().ToString("N");
@@ -646,7 +656,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = CancellationToken.None
CancellationToken = CancellationToken.None,
BufferContent = false
};
var postData = new Dictionary<string, string>
@@ -720,7 +731,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
CancellationToken = cancellationToken,
Url = url
Url = url,
BufferContent = false
};
SetServerAccessToken(options);
@@ -784,7 +796,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = cancellationToken
CancellationToken = cancellationToken,
BufferContent = false
};
SetServerAccessToken(options);
@@ -1072,7 +1085,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = CancellationToken.None
CancellationToken = CancellationToken.None,
BufferContent = false
};
var postData = new Dictionary<string, string>
@@ -1120,7 +1134,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = GetConnectUrl("user/authenticate")
Url = GetConnectUrl("user/authenticate"),
BufferContent = false
};
options.SetPostData(new Dictionary<string, string>

View File

@@ -437,6 +437,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.TrailerCount = taggedItems.Count(i => i is Trailer);
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
dto.SeriesCount = taggedItems.Count(i => i is Series);
dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram);
dto.SongCount = taggedItems.Count(i => i is Audio);
}
@@ -597,7 +598,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.Altitude = item.Altitude;
dto.IsoSpeedRating = item.IsoSpeedRating;
var album = item.Album;
var album = item.AlbumEntity;
if (album != null)
{
@@ -906,11 +907,6 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.Keywords = item.Keywords;
}
if (fields.Contains(ItemFields.ProductionLocations))
{
SetProductionLocations(item, dto);
}
var hasAspectRatio = item as IHasAspectRatio;
if (hasAspectRatio != null)
{
@@ -997,8 +993,11 @@ namespace MediaBrowser.Server.Implementations.Dto
}
dto.Audio = item.Audio;
dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
if (fields.Contains(ItemFields.Settings))
{
dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
}
dto.CriticRating = item.CriticRating;
@@ -1088,10 +1087,9 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.Taglines))
{
var hasTagline = item as IHasTaglines;
if (hasTagline != null)
if (!string.IsNullOrWhiteSpace(item.Tagline))
{
dto.Taglines = hasTagline.Taglines;
dto.Taglines = new List<string> { item.Tagline };
}
if (dto.Taglines == null)
@@ -1175,6 +1173,12 @@ namespace MediaBrowser.Server.Implementations.Dto
.Except(foundArtists, new DistinctNameComparer())
.Select(i =>
{
// This should not be necessary but we're seeing some cases of it
if (string.IsNullOrWhiteSpace(i))
{
return null;
}
var artist = _libraryManager.GetArtist(i);
if (artist != null)
{
@@ -1419,6 +1423,11 @@ namespace MediaBrowser.Server.Implementations.Dto
SetBookProperties(dto, book);
}
if (item.ProductionLocations.Count > 0 || item is Movie)
{
dto.ProductionLocations = item.ProductionLocations.ToArray();
}
var photo = item as Photo;
if (photo != null)
{
@@ -1508,7 +1517,7 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
private string GetMappedPath(IHasMetadata item)
private string GetMappedPath(BaseItem item)
{
var path = item.Path;
@@ -1516,40 +1525,12 @@ namespace MediaBrowser.Server.Implementations.Dto
if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
{
foreach (var map in _config.Configuration.PathSubstitutions)
{
path = _libraryManager.SubstitutePath(path, map.From, map.To);
}
path = _libraryManager.GetPathAfterNetworkSubstitution(path, item);
}
return path;
}
private void SetProductionLocations(BaseItem item, BaseItemDto dto)
{
var hasProductionLocations = item as IHasProductionLocations;
if (hasProductionLocations != null)
{
dto.ProductionLocations = hasProductionLocations.ProductionLocations;
}
var person = item as Person;
if (person != null)
{
dto.ProductionLocations = new List<string>();
if (!string.IsNullOrEmpty(person.PlaceOfBirth))
{
dto.ProductionLocations.Add(person.PlaceOfBirth);
}
}
if (dto.ProductionLocations == null)
{
dto.ProductionLocations = new List<string>();
}
}
/// <summary>
/// Attaches the primary image aspect ratio.
/// </summary>

View File

@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Net;
using MediaBrowser.Common.Threading;
using MediaBrowser.Model.Events;
namespace MediaBrowser.Server.Implementations.EntryPoints
{
@@ -17,17 +18,17 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private readonly ISsdpHandler _ssdp;
private readonly IDeviceDiscovery _deviceDiscovery;
private PeriodicTimer _timer;
private bool _isStarted;
public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery)
{
_logger = logmanager.GetLogger("PortMapper");
_appHost = appHost;
_config = config;
_ssdp = ssdp;
_deviceDiscovery = deviceDiscovery;
}
private string _lastConfigIdentifier;
@@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
public void Run()
{
//NatUtility.Logger = new LogWriter(_logger);
NatUtility.Logger = _logger;
if (_config.Configuration.EnableUPnP)
{
@@ -93,33 +94,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
_ssdp.MessageReceived += _ssdp_MessageReceived;
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
_lastConfigIdentifier = GetConfigIdentifier();
_isStarted = true;
}
private void ClearCreatedRules(object state)
private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
_createdRules = new List<string>();
_usnsHandled = new List<string>();
}
void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e)
{
var endpoint = e.EndPoint as IPEndPoint;
if (endpoint == null || e.LocalEndPoint == null)
{
return;
}
var info = e.Argument;
string usn;
if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
string nt;
if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
// Filter device type
if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
@@ -132,15 +122,45 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
if (!_usnsHandled.Contains(identifier))
if (info.Location != null && !_usnsHandled.Contains(identifier))
{
_usnsHandled.Add(identifier);
_logger.Debug("Calling Nat.Handle on " + identifier);
NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp);
IPAddress address;
if (IPAddress.TryParse(info.Location.Host, out address))
{
// The Handle method doesn't need the port
var endpoint = new IPEndPoint(address, info.Location.Port);
IPAddress localAddress = null;
try
{
var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
if (!IPAddress.TryParse(localAddressString, out localAddress))
{
return;
}
}
catch
{
return;
}
NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp);
}
}
}
private void ClearCreatedRules(object state)
{
_createdRules = new List<string>();
_usnsHandled = new List<string>();
}
void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var ex = e.ExceptionObject as Exception;
@@ -228,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_timer = null;
}
_ssdp.MessageReceived -= _ssdp_MessageReceived;
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
try
{

View File

@@ -377,10 +377,10 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
DisposeLibraryUpdateTimer();
}
if (items.Count == 1)
{
var item = items.First();
items = items.Take(10).ToList();
foreach (var item in items)
{
var notification = new NotificationRequest
{
NotificationType = NotificationType.NewLibraryContent.ToString()
@@ -388,17 +388,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
notification.Variables["Name"] = GetItemName(item);
await SendNotification(notification).ConfigureAwait(false);
}
else
{
var notification = new NotificationRequest
{
NotificationType = NotificationType.NewLibraryContentMultiple.ToString()
};
notification.Variables["ItemCount"] = items.Count.ToString(CultureInfo.InvariantCulture);
await SendNotification(notification).ConfigureAwait(false);
}
}

View File

@@ -64,7 +64,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
EnableHttpCompression = false,
LogRequest = false,
LogErrors = logErrors
LogErrors = logErrors,
BufferContent = false
};
options.SetPostData(data);
@@ -114,7 +115,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
EnableHttpCompression = false,
LogRequest = false,
LogErrors = logErrors
LogErrors = logErrors,
BufferContent = false
};
options.SetPostData(data);

View File

@@ -19,6 +19,7 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Security;
using MediaBrowser.Model.Extensions;
@@ -45,16 +46,18 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
private readonly IMemoryStreamProvider _memoryStreamProvider;
public HttpListenerHost(IApplicationHost applicationHost,
ILogManager logManager,
IServerConfigurationManager config,
string serviceName,
string defaultRedirectPath, INetworkManager networkManager, params Assembly[] assembliesWithServices)
string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamProvider memoryStreamProvider, params Assembly[] assembliesWithServices)
: base(serviceName, assembliesWithServices)
{
DefaultRedirectPath = defaultRedirectPath;
_networkManager = networkManager;
_memoryStreamProvider = memoryStreamProvider;
_config = config;
_logger = logManager.GetLogger("HttpServer");
@@ -88,14 +91,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
HostConfig.Instance.DebugMode = false;
HostConfig.Instance.LogFactory = LogManager.LogFactory;
HostConfig.Instance.AllowJsonpRequests = false;
// The Markdown feature causes slow startup times (5 mins+) on cold boots for some users
// Custom format allows images
HostConfig.Instance.EnableFeatures = Feature.Csv | Feature.Html | Feature.Json | Feature.Jsv | Feature.Metadata | Feature.Xml | Feature.CustomFormat;
HostConfig.Instance.EnableFeatures = Feature.Html | Feature.Json | Feature.Xml | Feature.CustomFormat;
container.Adapter = _containerAdapter;
Plugins.Add(new SwaggerFeature());
Plugins.RemoveAll(x => x is NativeTypesFeature);
//Plugins.Add(new SwaggerFeature());
Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"));
//Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {
@@ -179,7 +184,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private IHttpListener GetListener()
{
return new WebSocketSharpListener(_logger, CertificatePath);
return new WebSocketSharpListener(_logger, CertificatePath, _memoryStreamProvider);
}
private void OnWebSocketConnecting(WebSocketConnectingEventArgs args)
@@ -542,8 +547,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
}
throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
else
{
httpRes.Close();
}
}
/// <summary>

View File

@@ -93,12 +93,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
}
if (responseHeaders != null)
if (responseHeaders == null)
{
AddResponseHeaders(result, responseHeaders);
responseHeaders = new Dictionary<string, string>();
}
responseHeaders["Expires"] = "-1";
AddResponseHeaders(result, responseHeaders);
return result;
}
@@ -681,29 +683,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
/// <summary>
/// Gets the error result.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <param name="errorMessage">The error message.</param>
/// <param name="responseHeaders">The response headers.</param>
/// <returns>System.Object.</returns>
public void ThrowError(int statusCode, string errorMessage, IDictionary<string, string> responseHeaders = null)
{
var error = new HttpError
{
Status = statusCode,
ErrorCode = errorMessage
};
if (responseHeaders != null)
{
AddResponseHeaders(error, responseHeaders);
}
throw error;
}
public object GetAsyncStreamWriter(IAsyncStreamSource streamSource)
{
return new AsyncStreamWriter(streamSource);

View File

@@ -1,240 +0,0 @@
using MediaBrowser.Common.Events;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WebSocketMessageType = MediaBrowser.Model.Net.WebSocketMessageType;
using WebSocketState = MediaBrowser.Model.Net.WebSocketState;
namespace MediaBrowser.Server.Implementations.HttpServer
{
/// <summary>
/// Class NativeWebSocket
/// </summary>
public class NativeWebSocket : IWebSocket
{
/// <summary>
/// The logger
/// </summary>
private readonly ILogger _logger;
public event EventHandler<EventArgs> Closed;
/// <summary>
/// Gets or sets the web socket.
/// </summary>
/// <value>The web socket.</value>
private System.Net.WebSockets.WebSocket WebSocket { get; set; }
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
/// <summary>
/// Initializes a new instance of the <see cref="NativeWebSocket" /> class.
/// </summary>
/// <param name="socket">The socket.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">socket</exception>
public NativeWebSocket(WebSocket socket, ILogger logger)
{
if (socket == null)
{
throw new ArgumentNullException("socket");
}
if (logger == null)
{
throw new ArgumentNullException("logger");
}
_logger = logger;
WebSocket = socket;
Receive();
}
/// <summary>
/// Gets or sets the state.
/// </summary>
/// <value>The state.</value>
public WebSocketState State
{
get
{
WebSocketState commonState;
if (!Enum.TryParse(WebSocket.State.ToString(), true, out commonState))
{
_logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.State.ToString());
}
return commonState;
}
}
/// <summary>
/// Receives this instance.
/// </summary>
private async void Receive()
{
while (true)
{
byte[] bytes;
try
{
bytes = await ReceiveBytesAsync(_cancellationTokenSource.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
break;
}
catch (WebSocketException ex)
{
_logger.ErrorException("Error receiving web socket message", ex);
break;
}
if (bytes == null)
{
// Connection closed
EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
break;
}
if (OnReceiveBytes != null)
{
OnReceiveBytes(bytes);
}
}
}
/// <summary>
/// Receives the async.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{WebSocketMessageInfo}.</returns>
/// <exception cref="System.Net.WebSockets.WebSocketException">Connection closed</exception>
private async Task<byte[]> ReceiveBytesAsync(CancellationToken cancellationToken)
{
var bytes = new byte[4096];
var buffer = new ArraySegment<byte>(bytes);
var result = await WebSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
if (result.CloseStatus.HasValue)
{
_logger.Info("Web socket connection closed by client. Reason: {0}", result.CloseStatus.Value);
return null;
}
return buffer.Array;
}
/// <summary>
/// Sends the async.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="type">The type.</param>
/// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken)
{
System.Net.WebSockets.WebSocketMessageType nativeType;
if (!Enum.TryParse(type.ToString(), true, out nativeType))
{
_logger.Warn("Unrecognized WebSocketMessageType: {0}", type.ToString());
}
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
return WebSocket.SendAsync(new ArraySegment<byte>(bytes), nativeType, true, linkedTokenSource.Token);
}
public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken)
{
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
return WebSocket.SendAsync(new ArraySegment<byte>(bytes), System.Net.WebSockets.WebSocketMessageType.Binary, true, linkedTokenSource.Token);
}
public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken)
{
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
var bytes = Encoding.UTF8.GetBytes(text);
return WebSocket.SendAsync(new ArraySegment<byte>(bytes), System.Net.WebSockets.WebSocketMessageType.Text, true, linkedTokenSource.Token);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <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>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
_cancellationTokenSource.Cancel();
WebSocket.Dispose();
}
}
/// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
public Action<byte[]> OnReceiveBytes { get; set; }
/// <summary>
/// Gets or sets the on receive.
/// </summary>
/// <value>The on receive.</value>
public Action<string> OnReceive { get; set; }
/// <summary>
/// The _supports native web socket
/// </summary>
private static bool? _supportsNativeWebSocket;
/// <summary>
/// Gets a value indicating whether [supports web sockets].
/// </summary>
/// <value><c>true</c> if [supports web sockets]; otherwise, <c>false</c>.</value>
public static bool IsSupported
{
get
{
if (!_supportsNativeWebSocket.HasValue)
{
try
{
new ClientWebSocket();
_supportsNativeWebSocket = true;
}
catch (PlatformNotSupportedException)
{
_supportsNativeWebSocket = false;
}
}
return _supportsNativeWebSocket.Value;
}
}
}
}

View File

@@ -191,15 +191,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
}
catch (IOException)
{
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error in range request writer", ex);
throw;
}
finally
{
if (OnComplete != null)
@@ -251,15 +242,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
}
catch (IOException ex)
{
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error in range request writer", ex);
throw;
}
finally
{
if (OnComplete != null)

View File

@@ -1,4 +1,5 @@
using MediaBrowser.Common;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
@@ -15,23 +16,18 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <summary>
/// Creates the server.
/// </summary>
/// <param name="applicationHost">The application host.</param>
/// <param name="logManager">The log manager.</param>
/// <param name="config">The configuration.</param>
/// <param name="_networkmanager">The _networkmanager.</param>
/// <param name="serverName">Name of the server.</param>
/// <param name="defaultRedirectpath">The default redirectpath.</param>
/// <returns>IHttpServer.</returns>
public static IHttpServer CreateServer(IApplicationHost applicationHost,
ILogManager logManager,
IServerConfigurationManager config,
INetworkManager _networkmanager,
IMemoryStreamProvider streamProvider,
string serverName,
string defaultRedirectpath)
{
LogManager.LogFactory = new ServerLogFactory(logManager);
return new HttpListenerHost(applicationHost, logManager, config, serverName, defaultRedirectpath, _networkmanager);
return new HttpListenerHost(applicationHost, logManager, config, serverName, defaultRedirectpath, _networkmanager, streamProvider);
}
}
}

View File

@@ -39,11 +39,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
if (boundary == null)
return;
using (var requestStream = GetSubStream(InputStream))
using (var requestStream = GetSubStream(InputStream, _memoryStreamProvider))
{
//DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
//Not ending with \r\n?
var ms = new MemoryStream(32 * 1024);
var ms = _memoryStreamProvider.CreateNew(32 * 1024);
await requestStream.CopyToAsync(ms).ConfigureAwait(false);
var input = ms;
@@ -229,9 +229,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
async Task LoadWwwForm()
{
using (Stream input = GetSubStream(InputStream))
using (Stream input = GetSubStream(InputStream, _memoryStreamProvider))
{
using (var ms = new MemoryStream())
using (var ms = _memoryStreamProvider.CreateNew())
{
await input.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;

View File

@@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
@@ -18,11 +19,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
private readonly ILogger _logger;
private readonly string _certificatePath;
private readonly IMemoryStreamProvider _memoryStreamProvider;
public WebSocketSharpListener(ILogger logger, string certificatePath)
public WebSocketSharpListener(ILogger logger, string certificatePath, IMemoryStreamProvider memoryStreamProvider)
{
_logger = logger;
_certificatePath = certificatePath;
_memoryStreamProvider = memoryStreamProvider;
}
public Action<Exception, IRequest> ErrorHandler { get; set; }
@@ -148,7 +151,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
var operationName = httpContext.Request.GetOperationName();
var req = new WebSocketSharpRequest(httpContext, operationName, RequestAttributes.None, _logger);
var req = new WebSocketSharpRequest(httpContext, operationName, RequestAttributes.None, _logger, _memoryStreamProvider);
req.RequestAttributes = req.GetAttributes();
return req;

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using Funq;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.Logging;
using ServiceStack;
using ServiceStack.Host;
@@ -16,11 +17,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
public Container Container { get; set; }
private readonly HttpListenerRequest request;
private readonly IHttpResponse response;
private readonly IMemoryStreamProvider _memoryStreamProvider;
public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, RequestAttributes requestAttributes, ILogger logger)
public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, RequestAttributes requestAttributes, ILogger logger, IMemoryStreamProvider memoryStreamProvider)
{
this.OperationName = operationName;
this.RequestAttributes = requestAttributes;
_memoryStreamProvider = memoryStreamProvider;
this.request = httpContext.Request;
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
@@ -403,7 +406,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
set
{
bufferedStream = value
? bufferedStream ?? new MemoryStream(request.InputStream.ReadFully())
? bufferedStream ?? _memoryStreamProvider.CreateNew(request.InputStream.ReadFully())
: null;
}
}
@@ -447,7 +450,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
}
}
static Stream GetSubStream(Stream stream)
static Stream GetSubStream(Stream stream, IMemoryStreamProvider streamProvider)
{
if (stream is MemoryStream)
{

View File

@@ -81,20 +81,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
public void Write(string text)
{
try
{
var bOutput = System.Text.Encoding.UTF8.GetBytes(text);
response.ContentLength64 = bOutput.Length;
var bOutput = System.Text.Encoding.UTF8.GetBytes(text);
response.ContentLength64 = bOutput.Length;
var outputStream = response.OutputStream;
outputStream.Write(bOutput, 0, bOutput.Length);
Close();
}
catch (Exception ex)
{
_logger.ErrorException("Could not WriteTextToResponse: " + ex.Message, ex);
throw;
}
var outputStream = response.OutputStream;
outputStream.Write(bOutput, 0, bOutput.Length);
Close();
}
public void Close()

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using ServiceStack;
namespace MediaBrowser.Server.Implementations.HttpServer
@@ -17,7 +18,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private ILogger Logger { get; set; }
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary>
/// Gets or sets the source stream.
/// </summary>
@@ -39,6 +40,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
public Action OnComplete { get; set; }
public Action OnError { get; set; }
private readonly byte[] _bytes;
/// <summary>
/// Initializes a new instance of the <see cref="StreamWriter" /> class.
@@ -73,6 +75,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer
public StreamWriter(byte[] source, string contentType, ILogger logger)
: this(new MemoryStream(source), contentType, logger)
{
if (string.IsNullOrEmpty(contentType))
{
throw new ArgumentNullException("contentType");
}
_bytes = source;
Logger = logger;
Options["Content-Type"] = contentType;
Options["Content-Length"] = source.Length.ToString(UsCulture);
}
private const int BufferSize = 81920;
@@ -85,9 +98,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
try
{
using (var src = SourceStream)
if (_bytes != null)
{
src.CopyTo(responseStream, BufferSize);
responseStream.Write(_bytes, 0, _bytes.Length);
}
else
{
using (var src = SourceStream)
{
src.CopyTo(responseStream, BufferSize);
}
}
}
catch (Exception ex)
@@ -114,9 +134,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
try
{
using (var src = SourceStream)
if (_bytes != null)
{
await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
await responseStream.WriteAsync(_bytes, 0, _bytes.Length);
}
else
{
using (var src = SourceStream)
{
await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
}
}
}
catch (Exception ex)

View File

@@ -172,32 +172,16 @@ namespace MediaBrowser.Server.Implementations.IO
Start();
}
private bool EnableLibraryMonitor
{
get
{
switch (ConfigurationManager.Configuration.EnableLibraryMonitor)
{
case AutoOnOff.Auto:
return Environment.OSVersion.Platform == PlatformID.Win32NT;
case AutoOnOff.Enabled:
return true;
default:
return false;
}
}
}
private bool IsLibraryMonitorEnabaled(BaseItem item)
{
var options = LibraryManager.GetLibraryOptions(item);
if (options != null && options.SchemaVersion >= 1)
if (options != null)
{
return options.EnableRealtimeMonitor;
}
return EnableLibraryMonitor;
return false;
}
public void Start()

View File

@@ -99,8 +99,9 @@ namespace MediaBrowser.Server.Implementations.Intros
IncludeItemTypes = new[] { typeof(Trailer).Name },
TrailerTypes = trailerTypes.ToArray(),
SimilarTo = item,
IsPlayed = config.EnableIntrosForWatchedContent ? (bool?) null : false,
IsPlayed = config.EnableIntrosForWatchedContent ? (bool?)null : false,
MaxParentalRating = config.EnableIntrosParentalControl ? ratingLevel : null,
BlockUnratedItems = config.EnableIntrosParentalControl ? new[] { UnratedItem.Trailer } : new UnratedItem[] { },
Limit = config.TrailerLimit
});
@@ -110,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.Intros
Type = i.SourceType == SourceType.Channel ? ItemWithTrailerType.ChannelTrailer : ItemWithTrailerType.ItemWithTrailer,
LibraryManager = _libraryManager
}));
}
}
return GetResult(item, candidates, config);
}
@@ -197,7 +198,7 @@ namespace MediaBrowser.Server.Implementations.Intros
}
returnResult.AddRange(GetMediaInfoIntrosByTags(allIntros, item.Tags).Take(1));
return returnResult.DistinctBy(i => i.Path, StringComparer.OrdinalIgnoreCase);
}
catch (IOException)

View File

@@ -33,7 +33,8 @@ namespace MediaBrowser.Server.Implementations.Library
// Synology
"@eaDir",
"eaDir"
"eaDir",
"#recycle"
};

View File

@@ -43,6 +43,7 @@ using MediaBrowser.Server.Implementations.Library.Resolvers;
using MoreLinq;
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
using VideoResolver = MediaBrowser.Naming.Video.VideoResolver;
using MediaBrowser.Common.Configuration;
namespace MediaBrowser.Server.Implementations.Library
{
@@ -334,15 +335,6 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
/// <summary>
/// Updates the item in library cache.
/// </summary>
/// <param name="item">The item.</param>
private void UpdateItemInLibraryCache(BaseItem item)
{
RegisterItem(item);
}
public void RegisterItem(BaseItem item)
{
if (item == null)
@@ -695,7 +687,7 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
IDirectoryService directoryService,
Folder parent,
Folder parent,
LibraryOptions libraryOptions,
string collectionType,
IItemResolver[] resolvers)
@@ -1216,12 +1208,7 @@ namespace MediaBrowser.Server.Implementations.Library
if (libraryFolder != null)
{
info.ItemId = libraryFolder.Id.ToString("N");
}
var collectionFolder = libraryFolder as CollectionFolder;
if (collectionFolder != null)
{
info.LibraryOptions = collectionFolder.GetLibraryOptions();
info.LibraryOptions = GetLibraryOptions(libraryFolder);
}
return info;
@@ -1490,10 +1477,10 @@ namespace MediaBrowser.Server.Implementations.Library
private void AddUserToQuery(InternalItemsQuery query, User user)
{
if (query.AncestorIds.Length == 0 &&
!query.ParentId.HasValue &&
query.ChannelIds.Length == 0 &&
query.TopParentIds.Length == 0 &&
if (query.AncestorIds.Length == 0 &&
!query.ParentId.HasValue &&
query.ChannelIds.Length == 0 &&
query.TopParentIds.Length == 0 &&
string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)
&& query.ItemIds.Length == 0)
{
@@ -1781,7 +1768,7 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var item in list)
{
UpdateItemInLibraryCache(item);
RegisterItem(item);
}
if (ItemAdded != null)
@@ -1822,7 +1809,7 @@ namespace MediaBrowser.Server.Implementations.Library
await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false);
UpdateItemInLibraryCache(item);
RegisterItem(item);
if (ItemUpdated != null)
{
@@ -1889,11 +1876,41 @@ namespace MediaBrowser.Server.Implementations.Library
public LibraryOptions GetLibraryOptions(BaseItem item)
{
var collectionFolder = GetCollectionFolders(item)
.OfType<CollectionFolder>()
.FirstOrDefault();
var collectionFolder = item as CollectionFolder;
if (collectionFolder == null)
{
collectionFolder = GetCollectionFolders(item)
.OfType<CollectionFolder>()
.FirstOrDefault();
}
return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
var options = collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
if (options.SchemaVersion < 3)
{
options.SaveLocalMetadata = ConfigurationManager.Configuration.SaveLocalMeta;
options.EnableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders;
}
if (options.SchemaVersion < 2)
{
var chapterOptions = ConfigurationManager.GetConfiguration<ChapterOptions>("chapters");
options.ExtractChapterImagesDuringLibraryScan = chapterOptions.ExtractDuringLibraryScan;
if (collectionFolder != null)
{
if (string.Equals(collectionFolder.CollectionType, "movies", StringComparison.OrdinalIgnoreCase))
{
options.EnableChapterImageExtraction = chapterOptions.EnableMovieChapterImageExtraction;
}
else if (string.Equals(collectionFolder.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
options.EnableChapterImageExtraction = chapterOptions.EnableEpisodeChapterImageExtraction;
}
}
}
return options;
}
public string GetContentType(BaseItem item)
@@ -2437,7 +2454,7 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
var files = owner.DetectIsInMixedFolder() ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => string.Equals(i.Name, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
.ToList();
@@ -2527,7 +2544,59 @@ namespace MediaBrowser.Server.Implementations.Library
}).OrderBy(i => i.Path).ToList();
}
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
{
if (ownerItem != null)
{
var libraryOptions = GetLibraryOptions(ownerItem);
if (libraryOptions != null)
{
foreach (var pathInfo in libraryOptions.PathInfos)
{
if (string.IsNullOrWhiteSpace(pathInfo.NetworkPath))
{
continue;
}
var substitutionResult = SubstitutePathInternal(path, pathInfo.Path, pathInfo.NetworkPath);
if (substitutionResult.Item2)
{
return substitutionResult.Item1;
}
}
}
}
var metadataPath = ConfigurationManager.Configuration.MetadataPath;
var metadataNetworkPath = ConfigurationManager.Configuration.MetadataNetworkPath;
if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
{
var metadataSubstitutionResult = SubstitutePathInternal(path, metadataPath, metadataNetworkPath);
if (metadataSubstitutionResult.Item2)
{
return metadataSubstitutionResult.Item1;
}
}
foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
{
var substitutionResult = SubstitutePathInternal(path, map.From, map.To);
if (substitutionResult.Item2)
{
return substitutionResult.Item1;
}
}
return path;
}
public string SubstitutePath(string path, string from, string to)
{
return SubstitutePathInternal(path, from, to).Item1;
}
private Tuple<string, bool> SubstitutePathInternal(string path, string from, string to)
{
if (string.IsNullOrWhiteSpace(path))
{
@@ -2542,7 +2611,11 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("to");
}
from = from.Trim();
to = to.Trim();
var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
var changed = false;
if (!string.Equals(newPath, path))
{
@@ -2554,9 +2627,11 @@ namespace MediaBrowser.Server.Implementations.Library
{
newPath = newPath.Replace('/', '\\');
}
changed = true;
}
return newPath;
return new Tuple<string, bool>(newPath, changed);
}
private void SetExtraTypeFromFilename(Video item)
@@ -2685,7 +2760,7 @@ namespace MediaBrowser.Server.Implementations.Library
throw new InvalidOperationException();
}
public void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, LibraryOptions options, bool refreshLibrary)
public void AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -2703,12 +2778,13 @@ namespace MediaBrowser.Server.Implementations.Library
virtualFolderPath = Path.Combine(rootFolderPath, name);
}
if (mediaPaths != null)
var mediaPathInfos = options.PathInfos;
if (mediaPathInfos != null)
{
var invalidpath = mediaPaths.FirstOrDefault(i => !_fileSystem.DirectoryExists(i));
var invalidpath = mediaPathInfos.FirstOrDefault(i => !_fileSystem.DirectoryExists(i.Path));
if (invalidpath != null)
{
throw new ArgumentException("The specified path does not exist: " + invalidpath + ".");
throw new ArgumentException("The specified path does not exist: " + invalidpath.Path + ".");
}
}
@@ -2730,11 +2806,11 @@ namespace MediaBrowser.Server.Implementations.Library
CollectionFolder.SaveLibraryOptions(virtualFolderPath, options);
if (mediaPaths != null)
if (mediaPathInfos != null)
{
foreach (var path in mediaPaths)
foreach (var path in mediaPathInfos)
{
AddMediaPath(name, path);
AddMediaPathInternal(name, path, false);
}
}
}
@@ -2760,6 +2836,137 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
private bool ValidateNetworkPath(string path)
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT || !path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase))
{
return Directory.Exists(path);
}
// Without native support for unc, we cannot validate this when running under mono
return true;
}
private const string ShortcutFileExtension = ".mblink";
private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
{
AddMediaPathInternal(virtualFolderName, pathInfo, true);
}
private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions)
{
if (pathInfo == null)
{
throw new ArgumentNullException("path");
}
var path = pathInfo.Path;
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException("path");
}
if (!_fileSystem.DirectoryExists(path))
{
throw new DirectoryNotFoundException("The path does not exist.");
}
if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
{
throw new DirectoryNotFoundException("The network path does not exist.");
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
var shortcutFilename = _fileSystem.GetFileNameWithoutExtension(path);
var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
while (_fileSystem.FileExists(lnk))
{
shortcutFilename += "1";
lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
}
_fileSystem.CreateShortcut(lnk, path);
RemoveContentTypeOverrides(path);
if (saveLibraryOptions)
{
var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
var list = libraryOptions.PathInfos.ToList();
list.Add(pathInfo);
libraryOptions.PathInfos = list.ToArray();
SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions);
CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
}
}
public void UpdateMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
{
if (pathInfo == null)
{
throw new ArgumentNullException("path");
}
if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
{
throw new DirectoryNotFoundException("The network path does not exist.");
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions);
var list = libraryOptions.PathInfos.ToList();
foreach (var originalPathInfo in list)
{
if (string.Equals(pathInfo.Path, originalPathInfo.Path, StringComparison.Ordinal))
{
originalPathInfo.NetworkPath = pathInfo.NetworkPath;
break;
}
}
libraryOptions.PathInfos = list.ToArray();
CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
}
private void SyncLibraryOptionsToLocations(string virtualFolderPath, LibraryOptions options)
{
var topLibraryFolders = GetUserRootFolder().Children.ToList();
var info = GetVirtualFolderInfo(virtualFolderPath, topLibraryFolders);
if (info.Locations.Count > 0 && info.Locations.Count != options.PathInfos.Length)
{
var list = options.PathInfos.ToList();
foreach (var location in info.Locations)
{
if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal)))
{
list.Add(new MediaPathInfo
{
Path = location
});
}
}
options.PathInfos = list.ToArray();
}
}
public void RemoveVirtualFolder(string name, bool refreshLibrary)
{
if (string.IsNullOrWhiteSpace(name))
@@ -2804,38 +3011,6 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
private const string ShortcutFileExtension = ".mblink";
private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
public void AddMediaPath(string virtualFolderName, string path)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException("path");
}
if (!_fileSystem.DirectoryExists(path))
{
throw new DirectoryNotFoundException("The path does not exist.");
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
var shortcutFilename = _fileSystem.GetFileNameWithoutExtension(path);
var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
while (_fileSystem.FileExists(lnk))
{
shortcutFilename += "1";
lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
}
_fileSystem.CreateShortcut(lnk, path);
RemoveContentTypeOverrides(path);
}
private void RemoveContentTypeOverrides(string path)
{
if (string.IsNullOrWhiteSpace(path))
@@ -2872,19 +3047,28 @@ namespace MediaBrowser.Server.Implementations.Library
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var path = Path.Combine(rootFolderPath, virtualFolderName);
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
if (!_fileSystem.DirectoryExists(path))
if (!_fileSystem.DirectoryExists(virtualFolderPath))
{
throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
}
var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
var shortcut = Directory.EnumerateFiles(virtualFolderPath, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(shortcut))
{
_fileSystem.DeleteFile(shortcut);
}
var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
libraryOptions.PathInfos = libraryOptions
.PathInfos
.Where(i => !string.Equals(i.Path, mediaPath, StringComparison.Ordinal))
.ToArray();
CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
}
}
}

View File

@@ -221,8 +221,28 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
public async Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution)
public async Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken)
{
if (!string.IsNullOrWhiteSpace(liveStreamId))
{
return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
}
//await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
//try
//{
// var stream = _openStreams.Values.FirstOrDefault(i => string.Equals(i.MediaSource.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
// if (stream != null)
// {
// return stream.MediaSource;
// }
//}
//finally
//{
// _liveStreamSemaphore.Release();
//}
var sources = await GetPlayackMediaSources(item.Id.ToString("N"), null, enablePathSubstitution, new[] { MediaType.Audio, MediaType.Video },
CancellationToken.None).ConfigureAwait(false);
@@ -335,7 +355,7 @@ namespace MediaBrowser.Server.Implementations.Library
.ToList();
}
private readonly ConcurrentDictionary<string, LiveStreamInfo> _openStreams = new ConcurrentDictionary<string, LiveStreamInfo>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, LiveStreamInfo> _openStreams = new Dictionary<string, LiveStreamInfo>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken)
@@ -347,7 +367,9 @@ namespace MediaBrowser.Server.Implementations.Library
var tuple = GetProvider(request.OpenToken);
var provider = tuple.Item1;
var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
var mediaSourceTuple = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
var mediaSource = mediaSourceTuple.Item1;
if (string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
{
@@ -361,9 +383,11 @@ namespace MediaBrowser.Server.Implementations.Library
Date = DateTime.UtcNow,
EnableCloseTimer = enableAutoClose,
Id = mediaSource.LiveStreamId,
MediaSource = mediaSource
MediaSource = mediaSource,
DirectStreamProvider = mediaSourceTuple.Item2
};
_openStreams.AddOrUpdate(mediaSource.LiveStreamId, info, (key, i) => info);
_openStreams[mediaSource.LiveStreamId] = info;
if (enableAutoClose)
{
@@ -394,14 +418,14 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException("id");
}
_logger.Debug("Getting live stream {0}", id);
_logger.Debug("Getting already opened live stream {0}", id);
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -410,7 +434,7 @@ namespace MediaBrowser.Server.Implementations.Library
LiveStreamInfo info;
if (_openStreams.TryGetValue(id, out info))
{
return info.MediaSource;
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info.DirectStreamProvider);
}
else
{
@@ -423,6 +447,12 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
{
var result = await GetLiveStreamWithDirectStreamProvider(id, cancellationToken).ConfigureAwait(false);
return result.Item1;
}
public async Task PingLiveStream(string id, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -436,7 +466,7 @@ namespace MediaBrowser.Server.Implementations.Library
}
else
{
_logger.Error("Failed to update MediaSource timestamp for {0}", id);
_logger.Error("Failed to ping live stream {0}", id);
}
}
finally
@@ -445,17 +475,16 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId, CancellationToken cancellationToken)
private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId)
{
_logger.Info("Closing live stream {0} with provider {1}", streamId, provider.GetType().Name);
try
{
await provider.CloseMediaSource(streamId, cancellationToken).ConfigureAwait(false);
await provider.CloseMediaSource(streamId).ConfigureAwait(false);
}
catch (NotImplementedException)
{
}
catch (Exception ex)
{
@@ -463,37 +492,35 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
public async Task CloseLiveStream(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException("id");
}
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false);
try
{
LiveStreamInfo current;
if (_openStreams.TryGetValue(id, out current))
{
_openStreams.Remove(id);
current.Closed = true;
if (current.MediaSource.RequiresClosing)
{
var tuple = GetProvider(id);
await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false);
await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2).ConfigureAwait(false);
}
}
LiveStreamInfo removed;
if (_openStreams.TryRemove(id, out removed))
{
removed.Closed = true;
}
if (_openStreams.Count == 0)
{
StopCloseTimer();
if (_openStreams.Count == 0)
{
StopCloseTimer();
}
}
}
finally
@@ -523,7 +550,7 @@ namespace MediaBrowser.Server.Implementations.Library
}
private Timer _closeTimer;
private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(60);
private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(180);
private void StartCloseTimer()
{
@@ -545,10 +572,20 @@ namespace MediaBrowser.Server.Implementations.Library
private async void CloseTimerCallback(object state)
{
var infos = _openStreams
.Values
.Where(i => i.EnableCloseTimer && DateTime.UtcNow - i.Date > _openStreamMaxAge)
.ToList();
List<LiveStreamInfo> infos;
await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false);
try
{
infos = _openStreams
.Values
.Where(i => i.EnableCloseTimer && DateTime.UtcNow - i.Date > _openStreamMaxAge)
.ToList();
}
finally
{
_liveStreamSemaphore.Release();
}
foreach (var info in infos)
{
@@ -556,7 +593,7 @@ namespace MediaBrowser.Server.Implementations.Library
{
try
{
await CloseLiveStream(info.Id, CancellationToken.None).ConfigureAwait(false);
await CloseLiveStream(info.Id).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -588,12 +625,10 @@ namespace MediaBrowser.Server.Implementations.Library
{
foreach (var key in _openStreams.Keys.ToList())
{
var task = CloseLiveStream(key, CancellationToken.None);
var task = CloseLiveStream(key);
Task.WaitAll(task);
}
_openStreams.Clear();
}
}
}
@@ -605,6 +640,7 @@ namespace MediaBrowser.Server.Implementations.Library
public string Id;
public bool Closed;
public MediaSourceInfo MediaSource;
public IDirectStreamProvider DirectStreamProvider;
}
}
}

View File

@@ -54,8 +54,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
if (!args.IsDirectory) return null;
// Avoid mis-identifying top folders
if (args.Parent.IsRoot) return null;
if (args.HasParent<MusicAlbum>()) return null;
if (args.Parent.IsRoot) return null;
var collectionType = args.GetCollectionType();

View File

@@ -7,6 +7,7 @@ using System;
using System.IO;
using System.Linq;
using CommonIO;
using MediaBrowser.Controller.Configuration;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{
@@ -18,12 +19,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager)
public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager, IServerConfigurationManager config)
{
_logger = logger;
_fileSystem = fileSystem;
_libraryManager = libraryManager;
_config = config;
}
/// <summary>
@@ -48,9 +51,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{
if (!args.IsDirectory) return null;
// Avoid mis-identifying top folders
if (args.Parent.IsRoot) return null;
// Don't allow nested artists
if (args.HasParent<MusicArtist>() || args.HasParent<MusicAlbum>())
{
@@ -67,6 +67,19 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
return null;
}
if (args.ContainsFileSystemEntryByName("artist.nfo"))
{
return new MusicArtist();
}
if (_config.Configuration.EnableSimpleArtistDetection)
{
return null;
}
// Avoid mis-identifying top folders
if (args.Parent.IsRoot) return null;
var directoryService = args.DirectoryService;
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);

View File

@@ -51,7 +51,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
base.SetInitialItemValues(item, args);
item.IsRoot = args.Parent == null;
item.IsPhysicalRoot = args.IsPhysicalRoot;
}
}
}

View File

@@ -48,12 +48,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
string collectionType,
IDirectoryService directoryService)
{
if (parent != null && parent.Path != null && parent.Path.IndexOf("disney", StringComparison.OrdinalIgnoreCase) != -1)
{
var b = true;
var a = b;
}
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
if (result != null)
@@ -213,26 +207,22 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
// Find movies with their own folders
if (args.IsDirectory)
{
var files = args.FileSystemChildren
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList();
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<MusicVideo>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
if (string.IsNullOrEmpty(collectionType))
{
// Owned items should just use the plain video type
// Owned items will be caught by the plain video resolver
if (args.Parent == null)
{
return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
if (args.HasParent<Series>())
@@ -240,11 +230,21 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
return null;
}
return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
{
var files = args.FileSystemChildren
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList();
return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
}
}
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
var files = args.FileSystemChildren
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList();
return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
}

View File

@@ -54,11 +54,20 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
if (args.IsDirectory)
{
if (args.HasParent<Series>())
if (args.HasParent<Series>() || args.HasParent<Season>())
{
return null;
}
if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
{
return new Series
{
Path = args.Path,
Name = Path.GetFileName(args.Path)
};
}
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
@@ -72,22 +81,20 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
};
}
}
else
else if (string.IsNullOrWhiteSpace(collectionType))
{
if (string.IsNullOrWhiteSpace(collectionType))
if (args.Parent.IsRoot)
{
if (args.Parent.IsRoot)
return null;
}
if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false))
{
return new Series
{
return null;
}
if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false))
{
return new Series
{
Path = args.Path,
Name = Path.GetFileName(args.Path)
};
}
Path = args.Path,
Name = Path.GetFileName(args.Path)
};
}
}
}

View File

@@ -105,6 +105,7 @@ namespace MediaBrowser.Server.Implementations.Library
var includeItemTypes = (query.IncludeItemTypes ?? new string[] { }).ToList();
excludeItemTypes.Add(typeof(Year).Name);
excludeItemTypes.Add(typeof(Folder).Name);
if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Genre", StringComparer.OrdinalIgnoreCase)))
{

View File

@@ -61,16 +61,7 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var key in keys)
{
try
{
await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error saving user data", ex);
throw;
}
await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
}
var cacheKey = GetCacheKey(userId, item.Id);
@@ -107,18 +98,7 @@ namespace MediaBrowser.Server.Implementations.Library
cancellationToken.ThrowIfCancellationRequested();
try
{
await Repository.SaveAllUserData(userId, userData, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error saving user data", ex);
throw;
}
await Repository.SaveAllUserData(userId, userData, cancellationToken).ConfigureAwait(false);
}
/// <summary>
@@ -289,6 +269,11 @@ namespace MediaBrowser.Server.Implementations.Library
positionTicks = 0;
}
if (!item.SupportsPlayedStatus)
{
positionTicks = 0;
data.Played = false;
}
if (item is Audio)
{
positionTicks = 0;

View File

@@ -47,25 +47,60 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Info("Copying recording stream to file {0}", targetFile);
if (mediaSource.RunTimeTicks.HasValue)
{
// The media source already has a fixed duration
// But add another stop 1 minute later just in case the recording gets stuck for any reason
var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1)));
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
}
else
{
// The media source if infinite so we need to handle stopping ourselves
var durationToken = new CancellationTokenSource(duration);
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
}
// The media source if infinite so we need to handle stopping ourselves
var durationToken = new CancellationTokenSource(duration);
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
await CopyUntilCancelled(response.Content, output, cancellationToken).ConfigureAwait(false);
}
}
_logger.Info("Recording completed to file {0}", targetFile);
}
private const int BufferSize = 81920;
public static Task CopyUntilCancelled(Stream source, Stream target, CancellationToken cancellationToken)
{
return CopyUntilCancelled(source, target, null, cancellationToken);
}
public static async Task CopyUntilCancelled(Stream source, Stream target, Action onStarted, CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, onStarted, cancellationToken).ConfigureAwait(false);
onStarted = null;
//var position = fs.Position;
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
if (bytesRead == 0)
{
await Task.Delay(100).ConfigureAwait(false);
}
}
}
private static async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, Action onStarted, CancellationToken cancellationToken)
{
byte[] buffer = new byte[bufferSize];
int bytesRead;
int totalBytesRead = 0;
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
{
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
if (onStarted != null)
{
onStarted();
}
onStarted = null;
}
return totalBytesRead;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -67,36 +67,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
if (mediaSource.Path.IndexOf("m3u8", StringComparison.OrdinalIgnoreCase) != -1)
{
await RecordWithoutTempFile(mediaSource, targetFile, duration, onStarted, cancellationToken)
.ConfigureAwait(false);
return;
}
var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts");
try
{
await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken)
.ConfigureAwait(false);
}
finally
{
try
{
File.Delete(tempfile);
}
catch (Exception ex)
{
_logger.ErrorException("Error deleting recording temp file", ex);
}
}
}
private async Task RecordWithoutTempFile(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
var durationToken = new CancellationTokenSource(duration);
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
@@ -106,59 +76,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Info("Recording completed to file {0}", targetFile);
}
private async Task RecordWithTempFile(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
var httpRequestOptions = new HttpRequestOptions()
{
Url = mediaSource.Path
};
httpRequestOptions.BufferContent = false;
using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
{
_logger.Info("Opened recording stream from tuner provider");
Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
using (var output = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read))
{
//onStarted();
_logger.Info("Copying recording stream to file {0}", tempFile);
var bufferMs = 5000;
if (mediaSource.RunTimeTicks.HasValue)
{
// The media source already has a fixed duration
// But add another stop 1 minute later just in case the recording gets stuck for any reason
var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1)));
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
}
else
{
// The media source if infinite so we need to handle stopping ourselves
var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMilliseconds(bufferMs)));
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
}
var tempFileTask = response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken);
// Give the temp file a little time to build up
await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false);
var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, duration, onStarted, cancellationToken), cancellationToken);
await tempFileTask.ConfigureAwait(false);
await recordTask.ConfigureAwait(false);
}
}
_logger.Info("Recording completed to file {0}", targetFile);
}
private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
_targetPath = targetFile;
@@ -200,7 +117,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
process.Exited += (sender, args) => OnFfMpegProcessExited(process);
process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
process.Start();
@@ -214,6 +131,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
_logger.Info("ffmpeg recording process started for {0}", _targetPath);
return _taskCompletionSource.Task;
}
@@ -234,16 +153,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -re -i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
var inputModifiers = "-fflags +genpts -async 1 -vsync -1";
var commandLineArgs = "-i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
long startTimeTicks = 0;
//if (mediaSource.DateLiveStreamOpened.HasValue)
//{
// var elapsed = DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value;
// elapsed -= TimeSpan.FromSeconds(10);
// if (elapsed.TotalSeconds >= 0)
// {
// startTimeTicks = elapsed.Ticks + startTimeTicks;
// }
//}
if (mediaSource.ReadAtNativeFramerate)
{
commandLineArgs = "-re " + commandLineArgs;
inputModifiers += " -re";
}
if (startTimeTicks > 0)
{
inputModifiers = "-ss " + _mediaEncoder.GetTimeParameter(startTimeTicks) + " " + inputModifiers;
}
commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), durationParam);
return commandLineArgs;
return inputModifiers + " " + commandLineArgs;
}
private string GetAudioArgs(MediaSourceInfo mediaSource)
@@ -309,8 +245,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
/// <summary>
/// Processes the exited.
/// </summary>
/// <param name="process">The process.</param>
private void OnFfMpegProcessExited(Process process)
private void OnFfMpegProcessExited(Process process, string inputFile)
{
_hasExited = true;

View File

@@ -2,6 +2,7 @@
using MediaBrowser.Controller.LiveTv;
using System;
using System.Globalization;
using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
@@ -12,37 +13,55 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return timer.StartDate.AddSeconds(-timer.PrePaddingSeconds);
}
public static TimerInfo CreateTimer(ProgramInfo parent, SeriesTimerInfo series)
public static TimerInfo CreateTimer(ProgramInfo parent, SeriesTimerInfo seriesTimer)
{
var timer = new TimerInfo();
timer.ChannelId = parent.ChannelId;
timer.Id = (series.Id + parent.Id).GetMD5().ToString("N");
timer.Id = (seriesTimer.Id + parent.Id).GetMD5().ToString("N");
timer.StartDate = parent.StartDate;
timer.EndDate = parent.EndDate;
timer.ProgramId = parent.Id;
timer.PrePaddingSeconds = series.PrePaddingSeconds;
timer.PostPaddingSeconds = series.PostPaddingSeconds;
timer.IsPostPaddingRequired = series.IsPostPaddingRequired;
timer.IsPrePaddingRequired = series.IsPrePaddingRequired;
timer.Priority = series.Priority;
timer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
timer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
timer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
timer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
timer.KeepUntil = seriesTimer.KeepUntil;
timer.Priority = seriesTimer.Priority;
timer.Name = parent.Name;
timer.Overview = parent.Overview;
timer.SeriesTimerId = series.Id;
timer.SeriesTimerId = seriesTimer.Id;
CopyProgramInfoToTimerInfo(parent, timer);
return timer;
}
public static string GetRecordingName(TimerInfo timer, ProgramInfo info)
public static void CopyProgramInfoToTimerInfo(ProgramInfo programInfo, TimerInfo timerInfo)
{
if (info == null)
{
return timer.ProgramId;
}
timerInfo.SeasonNumber = programInfo.SeasonNumber;
timerInfo.EpisodeNumber = programInfo.EpisodeNumber;
timerInfo.IsMovie = programInfo.IsMovie;
timerInfo.IsKids = programInfo.IsKids;
timerInfo.IsNews = programInfo.IsNews;
timerInfo.IsSports = programInfo.IsSports;
timerInfo.ProductionYear = programInfo.ProductionYear;
timerInfo.EpisodeTitle = programInfo.EpisodeTitle;
timerInfo.OriginalAirDate = programInfo.OriginalAirDate;
timerInfo.IsProgramSeries = programInfo.IsSeries;
timerInfo.HomePageUrl = programInfo.HomePageUrl;
timerInfo.CommunityRating = programInfo.CommunityRating;
timerInfo.ShortOverview = programInfo.ShortOverview;
timerInfo.OfficialRating = programInfo.OfficialRating;
timerInfo.IsRepeat = programInfo.IsRepeat;
}
public static string GetRecordingName(TimerInfo info)
{
var name = info.Name;
if (info.IsSeries)
if (info.IsProgramSeries)
{
var addHyphen = true;

View File

@@ -9,7 +9,6 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using CommonIO;
using MediaBrowser.Controller.Power;
using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
@@ -17,15 +16,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public class TimerManager : ItemDataProvider<TimerInfo>
{
private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase);
private readonly IPowerManagement _powerManagement;
private readonly ILogger _logger;
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, IPowerManagement powerManagement, ILogger logger1)
public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
_powerManagement = powerManagement;
_logger = logger1;
}
@@ -35,7 +32,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
foreach (var item in GetAll().ToList())
{
AddTimer(item);
AddOrUpdateSystemTimer(item);
}
}
@@ -58,18 +55,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public override void Update(TimerInfo item)
{
base.Update(item);
Timer timer;
if (_timers.TryGetValue(item.Id, out timer))
{
var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow;
timer.Change(timespan, TimeSpan.Zero);
ScheduleWake(item);
}
else
{
AddTimer(item);
}
AddOrUpdateSystemTimer(item);
}
public void AddOrUpdate(TimerInfo item, bool resetTimer)
@@ -100,13 +86,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
base.Add(item);
AddTimer(item);
ScheduleWake(item);
AddOrUpdateSystemTimer(item);
}
private void AddTimer(TimerInfo item)
private bool ShouldStartTimer(TimerInfo item)
{
if (item.Status == RecordingStatus.Completed)
if (item.Status == RecordingStatus.Completed ||
item.Status == RecordingStatus.Cancelled)
{
return false;
}
return true;
}
private void AddOrUpdateSystemTimer(TimerInfo item)
{
StopTimer(item);
if (!ShouldStartTimer(item))
{
return;
}
@@ -120,33 +118,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return;
}
var timerLength = startDate - now;
StartTimer(item, timerLength);
var dueTime = startDate - now;
StartTimer(item, dueTime);
}
private void ScheduleWake(TimerInfo info)
private void StartTimer(TimerInfo item, TimeSpan dueTime)
{
var startDate = RecordingHelper.GetStartTime(info).AddMinutes(-5);
try
{
_powerManagement.ScheduleWake(startDate);
_logger.Info("Scheduled system wake timer at {0} (UTC)", startDate);
}
catch (NotImplementedException)
{
}
catch (Exception ex)
{
_logger.ErrorException("Error scheduling wake timer", ex);
}
}
public void StartTimer(TimerInfo item, TimeSpan dueTime)
{
StopTimer(item);
var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero);
if (_timers.TryAdd(item.Id, timer))
@@ -179,5 +156,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
EventHelper.FireEventIfNotNull(TimerFired, this, new GenericEventArgs<TimerInfo> { Argument = timer }, Logger);
}
}
public TimerInfo GetTimer(string id)
{
return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@@ -11,6 +11,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Emby.XmlTv.Classes;
using Emby.XmlTv.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -115,7 +116,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
var reader = new XmlTvReader(path, GetLanguage(), null);
var results = reader.GetProgrammes(channelNumber, startDateUtc, endDateUtc, cancellationToken);
return results.Select(p => new ProgramInfo()
return results.Select(p => GetProgramInfo(p, info));
}
private ProgramInfo GetProgramInfo(XmlTvProgram p, ListingsProviderInfo info)
{
var programInfo = new ProgramInfo
{
ChannelId = p.ChannelId,
EndDate = GetDate(p.EndDate),
@@ -141,7 +147,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
OfficialRating = p.Rating != null && !String.IsNullOrEmpty(p.Rating.Value) ? p.Rating.Value : null,
CommunityRating = p.StarRating.HasValue ? p.StarRating.Value : (float?)null,
SeriesId = p.Episode != null ? p.Title.GetMD5().ToString("N") : null
});
};
if (programInfo.IsMovie)
{
programInfo.IsSeries = false;
programInfo.EpisodeNumber = null;
programInfo.EpisodeTitle = null;
}
return programInfo;
}
private DateTime GetDate(DateTime date)

View File

@@ -0,0 +1,110 @@
using System;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Implementations.LiveTv
{
public class LiveStreamHelper
{
private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger _logger;
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger)
{
_mediaEncoder = mediaEncoder;
_logger = logger;
}
public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
{
var originalRuntime = mediaSource.RunTimeTicks;
var now = DateTime.UtcNow;
var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
{
InputPath = mediaSource.Path,
Protocol = mediaSource.Protocol,
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
ExtractChapters = false,
AnalyzeDurationSections = 2
}, cancellationToken).ConfigureAwait(false);
_logger.Info("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture));
mediaSource.Bitrate = info.Bitrate;
mediaSource.Container = info.Container;
mediaSource.Formats = info.Formats;
mediaSource.MediaStreams = info.MediaStreams;
mediaSource.RunTimeTicks = info.RunTimeTicks;
mediaSource.Size = info.Size;
mediaSource.Timestamp = info.Timestamp;
mediaSource.Video3DFormat = info.Video3DFormat;
mediaSource.VideoType = info.VideoType;
mediaSource.DefaultSubtitleStreamIndex = null;
// Null this out so that it will be treated like a live stream
if (!originalRuntime.HasValue)
{
mediaSource.RunTimeTicks = null;
}
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Audio);
if (audioStream == null || audioStream.Index == -1)
{
mediaSource.DefaultAudioStreamIndex = null;
}
else
{
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
}
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Video);
if (videoStream != null)
{
if (!videoStream.BitRate.HasValue)
{
var width = videoStream.Width ?? 1920;
if (width >= 1900)
{
videoStream.BitRate = 8000000;
}
else if (width >= 1260)
{
videoStream.BitRate = 3000000;
}
else if (width >= 700)
{
videoStream.BitRate = 1000000;
}
}
// This is coming up false and preventing stream copy
videoStream.IsAVC = null;
}
// Try to estimate this
if (!mediaSource.Bitrate.HasValue)
{
var total = mediaSource.MediaStreams.Select(i => i.BitRate ?? 0).Sum();
if (total > 0)
{
mediaSource.Bitrate = total;
}
}
}
}
}

View File

@@ -10,6 +10,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -52,6 +53,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
PostPaddingSeconds = info.PostPaddingSeconds,
IsPostPaddingRequired = info.IsPostPaddingRequired,
IsPrePaddingRequired = info.IsPrePaddingRequired,
KeepUntil = info.KeepUntil,
ExternalChannelId = info.ChannelId,
ExternalSeriesTimerId = info.SeriesTimerId,
ServiceName = service.Name,
@@ -71,6 +73,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.ProgramInfo = _dtoService.GetBaseItemDto(program, new DtoOptions());
dto.ProgramInfo.TimerId = dto.Id;
dto.ProgramInfo.SeriesTimerId = dto.SeriesTimerId;
}
@@ -100,6 +103,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
Priority = info.Priority,
RecordAnyChannel = info.RecordAnyChannel,
RecordAnyTime = info.RecordAnyTime,
SkipEpisodesInLibrary = info.SkipEpisodesInLibrary,
KeepUpTo = info.KeepUpTo,
KeepUntil = info.KeepUntil,
RecordNewOnly = info.RecordNewOnly,
ExternalChannelId = info.ChannelId,
ExternalProgramId = info.ProgramId,
@@ -120,6 +126,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days);
if (!string.IsNullOrWhiteSpace(info.SeriesId))
{
var program = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
ExternalSeriesId = info.SeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary }
}).FirstOrDefault();
if (program != null)
{
var image = program.GetImageInfo(ImageType.Primary, 0);
if (image != null)
{
try
{
dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
dto.ParentPrimaryImageItemId = program.Id.ToString("N");
}
catch (Exception ex)
{
}
}
}
}
return dto;
}
@@ -244,6 +278,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
PostPaddingSeconds = dto.PostPaddingSeconds,
IsPostPaddingRequired = dto.IsPostPaddingRequired,
IsPrePaddingRequired = dto.IsPrePaddingRequired,
KeepUntil = dto.KeepUntil,
Priority = dto.Priority,
SeriesTimerId = dto.ExternalSeriesTimerId,
ProgramId = dto.ExternalProgramId,
@@ -308,6 +343,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
Priority = dto.Priority,
RecordAnyChannel = dto.RecordAnyChannel,
RecordAnyTime = dto.RecordAnyTime,
SkipEpisodesInLibrary = dto.SkipEpisodesInLibrary,
KeepUpTo = dto.KeepUpTo,
KeepUntil = dto.KeepUntil,
RecordNewOnly = dto.RecordNewOnly,
ProgramId = dto.ExternalProgramId,
ChannelId = dto.ExternalChannelId,

View File

@@ -62,9 +62,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
private readonly ConcurrentDictionary<string, LiveStreamData> _openStreams =
new ConcurrentDictionary<string, LiveStreamData>();
private readonly SemaphoreSlim _refreshRecordingsLock = new SemaphoreSlim(1, 1);
private readonly List<ITunerHost> _tunerHosts = new List<ITunerHost>();
@@ -124,9 +121,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
foreach (var service in _services)
{
service.DataSourceChanged += service_DataSourceChanged;
service.RecordingStatusChanged += Service_RecordingStatusChanged;
}
}
private void Service_RecordingStatusChanged(object sender, RecordingStatusChangedEventArgs e)
{
_lastRecordingRefreshTime = DateTime.MinValue;
}
public List<ITunerHost> TunerHosts
{
get { return _tunerHosts; }
@@ -151,112 +154,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
var channels = _libraryManager.GetItemList(new InternalItemsQuery
var internalQuery = new InternalItemsQuery(user)
{
IsMovie = query.IsMovie,
IsNews = query.IsNews,
IsKids = query.IsKids,
IsSports = query.IsSports,
IsSeries = query.IsSeries,
IncludeItemTypes = new[] { typeof(LiveTvChannel).Name },
SortBy = new[] { ItemSortBy.SortName },
TopParentIds = new[] { topFolder.Id.ToString("N") }
SortOrder = query.SortOrder ?? SortOrder.Ascending,
TopParentIds = new[] { topFolder.Id.ToString("N") },
IsFavorite = query.IsFavorite,
IsLiked = query.IsLiked,
StartIndex = query.StartIndex,
Limit = query.Limit
};
}).Cast<LiveTvChannel>();
internalQuery.OrderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending)));
if (user != null)
if (query.EnableFavoriteSorting)
{
// Avoid implicitly captured closure
var currentUser = user;
channels = channels
.Where(i => i.IsVisible(currentUser))
.OrderBy(i =>
{
double number = 0;
if (!string.IsNullOrEmpty(i.Number))
{
double.TryParse(i.Number, out number);
}
return number;
});
if (query.IsFavorite.HasValue)
{
var val = query.IsFavorite.Value;
channels = channels
.Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val);
}
if (query.IsLiked.HasValue)
{
var val = query.IsLiked.Value;
channels = channels
.Where(i =>
{
var likes = _userDataManager.GetUserData(user, i).Likes;
return likes.HasValue && likes.Value == val;
});
}
if (query.IsDisliked.HasValue)
{
var val = query.IsDisliked.Value;
channels = channels
.Where(i =>
{
var likes = _userDataManager.GetUserData(user, i).Likes;
return likes.HasValue && likes.Value != val;
});
}
internalQuery.OrderBy.Insert(0, new Tuple<string, SortOrder>(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
}
var enableFavoriteSorting = query.EnableFavoriteSorting;
channels = channels.OrderBy(i =>
if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
{
if (enableFavoriteSorting)
{
var userData = _userDataManager.GetUserData(user, i);
if (userData.IsFavorite)
{
return 0;
}
if (userData.Likes.HasValue)
{
if (!userData.Likes.Value)
{
return 3;
}
return 1;
}
}
return 2;
});
var allChannels = channels.ToList();
IEnumerable<LiveTvChannel> allEnumerable = allChannels;
if (query.StartIndex.HasValue)
{
allEnumerable = allEnumerable.Skip(query.StartIndex.Value);
internalQuery.OrderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
}
if (query.Limit.HasValue)
{
allEnumerable = allEnumerable.Take(query.Limit.Value);
}
var channelResult = _libraryManager.GetItemsResult(internalQuery);
var result = new QueryResult<LiveTvChannel>
{
Items = allEnumerable.ToArray(),
TotalRecordCount = allChannels.Count
Items = channelResult.Items.Cast<LiveTvChannel>().ToArray(),
TotalRecordCount = channelResult.TotalRecordCount
};
return result;
@@ -298,32 +229,32 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return result.Items.FirstOrDefault();
}
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
public async Task<MediaSourceInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
{
return await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false);
var info = await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false);
return info.Item1;
}
public async Task<MediaSourceInfo> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken)
public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken)
{
return await GetLiveStream(id, mediaSourceId, true, cancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken)
public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
{
var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
var service = GetService(item);
var baseItem = (BaseItem)item;
var service = GetService(baseItem);
return await service.GetRecordingStreamMediaSources(id, cancellationToken).ConfigureAwait(false);
return await service.GetRecordingStreamMediaSources(baseItem.ExternalId, cancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken)
public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
{
var item = GetInternalChannel(id);
var service = GetService(item);
var baseItem = (LiveTvChannel)item;
var service = GetService(baseItem);
var sources = await service.GetChannelStreamMediaSources(item.ExternalId, cancellationToken).ConfigureAwait(false);
var sources = await service.GetChannelStreamMediaSources(baseItem.ExternalId, cancellationToken).ConfigureAwait(false);
if (sources.Count == 0)
{
@@ -334,7 +265,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
foreach (var source in list)
{
Normalize(source, service, item.ChannelType == ChannelType.TV);
Normalize(source, service, baseItem.ChannelType == ChannelType.TV);
}
return list;
@@ -355,79 +286,67 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return _services.FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
}
private async Task<MediaSourceInfo> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
private async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
{
mediaSourceId = null;
}
try
MediaSourceInfo info;
bool isVideo;
ILiveTvService service;
IDirectStreamProvider directStreamProvider = null;
if (isChannel)
{
MediaSourceInfo info;
bool isVideo;
ILiveTvService service;
var channel = GetInternalChannel(id);
isVideo = channel.ChannelType == ChannelType.TV;
service = GetService(channel);
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
if (isChannel)
var supportsManagedStream = service as ISupportsDirectStreamProvider;
if (supportsManagedStream != null)
{
var channel = GetInternalChannel(id);
isVideo = channel.ChannelType == ChannelType.TV;
service = GetService(channel);
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
info.RequiresClosing = true;
if (info.RequiresClosing)
{
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
info.LiveStreamId = idPrefix + info.Id;
}
var streamInfo = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
info = streamInfo.Item1;
directStreamProvider = streamInfo.Item2;
}
else
{
var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
service = GetService(recording);
_logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId);
info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false);
info.RequiresClosing = true;
if (info.RequiresClosing)
{
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
info.LiveStreamId = idPrefix + info.Id;
}
info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
}
info.RequiresClosing = true;
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
Normalize(info, service, isVideo);
var data = new LiveStreamData
if (info.RequiresClosing)
{
Info = info,
IsChannel = isChannel,
ItemId = id
};
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
_openStreams.AddOrUpdate(info.Id, data, (key, i) => data);
return info;
info.LiveStreamId = idPrefix + info.Id;
}
}
catch (Exception ex)
else
{
_logger.ErrorException("Error getting channel stream", ex);
var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
service = GetService(recording);
throw;
}
finally
{
_liveStreamSemaphore.Release();
_logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId);
info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false);
info.RequiresClosing = true;
if (info.RequiresClosing)
{
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
info.LiveStreamId = idPrefix + info.Id;
}
}
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
Normalize(info, service, isVideo);
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider);
}
private void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
@@ -628,11 +547,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return item;
}
private async Task<LiveTvProgram> GetProgram(ProgramInfo info, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
{
var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
var item = _libraryManager.GetItemById(id) as LiveTvProgram;
LiveTvProgram item = null;
allExistingPrograms.TryGetValue(id, out item);
var isNew = false;
var forceUpdate = false;
@@ -649,6 +570,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
};
}
var seriesId = info.SeriesId;
if (!item.ParentId.Equals(channel.Id))
{
forceUpdate = true;
@@ -668,6 +591,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.EpisodeTitle = info.EpisodeTitle;
item.ExternalId = info.Id;
item.ExternalSeriesIdLegacy = seriesId;
if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.Ordinal))
{
forceUpdate = true;
}
item.ExternalSeriesId = seriesId;
item.Genres = info.Genres;
item.IsHD = info.IsHD;
item.IsKids = info.IsKids;
@@ -698,7 +629,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.HomePageUrl = info.HomePageUrl;
item.ProductionYear = info.ProductionYear;
item.PremiereDate = info.OriginalAirDate;
if (!info.IsSeries || info.IsRepeat)
{
item.PremiereDate = info.OriginalAirDate;
}
item.IndexNumber = info.EpisodeNumber;
item.ParentIndexNumber = info.SeasonNumber;
@@ -725,13 +660,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
var isUpdated = false;
if (isNew)
{
await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
}
else if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag))
{
await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
isUpdated = true;
}
else
{
@@ -741,13 +676,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (!string.Equals(etag, item.ExternalEtag, StringComparison.OrdinalIgnoreCase))
{
item.ExternalEtag = etag;
await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
isUpdated = true;
}
}
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem));
return item;
return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated);
}
private async Task<Guid> CreateRecordingRecord(RecordingInfo info, string serviceName, Guid parentFolderId, CancellationToken cancellationToken)
@@ -811,6 +744,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
recording.IsRepeat = info.IsRepeat;
recording.IsSports = info.IsSports;
recording.SeriesTimerId = info.SeriesTimerId;
recording.TimerId = info.TimerId;
recording.StartDate = info.StartDate;
if (!dataChanged)
@@ -903,8 +837,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
var list = new List<Tuple<BaseItemDto, string, string>>();
list.Add(new Tuple<BaseItemDto, string, string>(dto, program.ServiceName, program.ExternalId));
var list = new List<Tuple<BaseItemDto, string, string, string>>();
list.Add(new Tuple<BaseItemDto, string, string, string>(dto, program.ServiceName, program.ExternalId, program.ExternalSeriesIdLegacy));
await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
@@ -932,17 +866,41 @@ namespace MediaBrowser.Server.Implementations.LiveTv
MaxStartDate = query.MaxStartDate,
ChannelIds = query.ChannelIds,
IsMovie = query.IsMovie,
IsSeries = query.IsSeries,
IsSports = query.IsSports,
IsKids = query.IsKids,
IsNews = query.IsNews,
Genres = query.Genres,
StartIndex = query.StartIndex,
Limit = query.Limit,
SortBy = query.SortBy,
SortOrder = query.SortOrder ?? SortOrder.Ascending,
EnableTotalRecordCount = query.EnableTotalRecordCount,
TopParentIds = new[] { topFolder.Id.ToString("N") }
TopParentIds = new[] { topFolder.Id.ToString("N") },
DtoOptions = options
};
if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
{
var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false);
var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.ServiceName, i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
if (seriesTimer != null)
{
internalQuery.ExternalSeriesId = seriesTimer.SeriesId;
if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
{
// Better to return nothing than every program in the database
return new QueryResult<BaseItemDto>();
}
}
else
{
// Better to return nothing than every program in the database
return new QueryResult<BaseItemDto>();
}
}
if (query.HasAired.HasValue)
{
if (query.HasAired.Value)
@@ -970,7 +928,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return result;
}
public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken)
public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
{
var user = _userManager.GetUserById(query.UserId);
@@ -980,12 +938,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
IsAiring = query.IsAiring,
IsNews = query.IsNews,
IsMovie = query.IsMovie,
IsSeries = query.IsSeries,
IsSports = query.IsSports,
IsKids = query.IsKids,
EnableTotalRecordCount = query.EnableTotalRecordCount,
SortBy = new[] { ItemSortBy.StartDate },
TopParentIds = new[] { topFolder.Id.ToString("N") }
TopParentIds = new[] { topFolder.Id.ToString("N") },
DtoOptions = options
};
if (query.Limit.HasValue)
@@ -1009,9 +970,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var programList = programs.ToList();
var factorChannelWatchCount = (query.IsAiring ?? false) || (query.IsKids ?? false) || (query.IsSports ?? false) || (query.IsMovie ?? false);
var factorChannelWatchCount = (query.IsAiring ?? false) || (query.IsKids ?? false) || (query.IsSports ?? false) || (query.IsMovie ?? false) || (query.IsNews ?? false) || (query.IsSeries ?? false);
programs = programList.OrderBy(i => i.HasImage(ImageType.Primary) ? 0 : 1)
programs = programList.OrderBy(i => i.StartDate.Date)
.ThenByDescending(i => GetRecommendationScore(i, user.Id, factorChannelWatchCount))
.ThenBy(i => i.StartDate);
@@ -1035,7 +996,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task<QueryResult<BaseItemDto>> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
{
var internalResult = await GetRecommendedProgramsInternal(query, cancellationToken).ConfigureAwait(false);
var internalResult = await GetRecommendedProgramsInternal(query, options, cancellationToken).ConfigureAwait(false);
var user = _userManager.GetUserById(query.UserId);
@@ -1092,15 +1053,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return score;
}
private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string>> programs, CancellationToken cancellationToken)
private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string, string>> programs, CancellationToken cancellationToken)
{
var timers = new Dictionary<string, List<TimerInfo>>();
var seriesTimers = new Dictionary<string, List<SeriesTimerInfo>>();
foreach (var programTuple in programs)
{
var program = programTuple.Item1;
var serviceName = programTuple.Item2;
var externalProgramId = programTuple.Item3;
string externalSeriesId = programTuple.Item4;
if (string.IsNullOrWhiteSpace(serviceName))
{
@@ -1123,18 +1086,54 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase));
var foundSeriesTimer = false;
if (timer != null)
{
program.TimerId = _tvDtoService.GetInternalTimerId(serviceName, timer.Id)
.ToString("N");
if (timer.Status != RecordingStatus.Cancelled && timer.Status != RecordingStatus.Error)
{
program.TimerId = _tvDtoService.GetInternalTimerId(serviceName, timer.Id)
.ToString("N");
program.Status = timer.Status.ToString();
}
if (!string.IsNullOrEmpty(timer.SeriesTimerId))
{
program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, timer.SeriesTimerId)
.ToString("N");
foundSeriesTimer = true;
}
}
if (foundSeriesTimer || string.IsNullOrWhiteSpace(externalSeriesId))
{
continue;
}
List<SeriesTimerInfo> seriesTimerList;
if (!seriesTimers.TryGetValue(serviceName, out seriesTimerList))
{
try
{
var tempTimers = await GetService(serviceName).GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
seriesTimers[serviceName] = seriesTimerList = tempTimers.ToList();
}
catch (Exception ex)
{
_logger.ErrorException("Error getting series timer infos", ex);
seriesTimers[serviceName] = seriesTimerList = new List<SeriesTimerInfo>();
}
}
var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase));
if (seriesTimer != null)
{
program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, seriesTimer.Id)
.ToString("N");
}
}
}
@@ -1267,14 +1266,96 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var start = DateTime.UtcNow.AddHours(-1);
var end = start.AddDays(guideDays);
var isMovie = false;
var isSports = false;
var isNews = false;
var isKids = false;
var iSSeries = false;
var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false);
var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
ChannelIds = new string[] { currentChannel.Id.ToString("N") }
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
var newPrograms = new List<LiveTvProgram>();
var updatedPrograms = new List<LiveTvProgram>();
foreach (var program in channelPrograms)
{
var programItem = await GetProgram(program, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false);
var programTuple = GetProgram(program, existingPrograms, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken);
var programItem = programTuple.Item1;
if (programTuple.Item2)
{
newPrograms.Add(programItem);
}
else if (programTuple.Item3)
{
updatedPrograms.Add(programItem);
}
programs.Add(programItem.Id);
if (program.IsMovie)
{
isMovie = true;
}
if (program.IsSeries)
{
iSSeries = true;
}
if (program.IsSports)
{
isSports = true;
}
if (program.IsNews)
{
isNews = true;
}
if (program.IsKids)
{
isKids = true;
}
}
_logger.Debug("Channel {0} has {1} new programs and {2} updated programs", currentChannel.Name, newPrograms.Count, updatedPrograms.Count);
if (newPrograms.Count > 0)
{
await _libraryManager.CreateItems(newPrograms, cancellationToken).ConfigureAwait(false);
}
// TODO: Do this in bulk
foreach (var program in updatedPrograms)
{
await _libraryManager.UpdateItem(program, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
}
foreach (var program in newPrograms)
{
_providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem));
}
foreach (var program in updatedPrograms)
{
_providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem));
}
currentChannel.IsMovie = isMovie;
currentChannel.IsNews = isNews;
currentChannel.IsSports = isSports;
currentChannel.IsKids = isKids;
currentChannel.IsSeries = iSSeries;
await currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -1361,7 +1442,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private DateTime _lastRecordingRefreshTime;
private async Task RefreshRecordings(CancellationToken cancellationToken)
{
const int cacheMinutes = 5;
const int cacheMinutes = 3;
if ((DateTime.UtcNow - _lastRecordingRefreshTime).TotalMinutes < cacheMinutes)
{
@@ -1409,9 +1490,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, User user)
private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, DtoOptions dtoOptions, User user)
{
if (user == null || (query.IsInProgress ?? false))
if (user == null)
{
return new QueryResult<BaseItem>();
}
if ((query.IsInProgress ?? false))
{
return new QueryResult<BaseItem>();
}
@@ -1485,7 +1571,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
EnableTotalRecordCount = query.EnableTotalRecordCount,
IncludeItemTypes = includeItemTypes.ToArray(),
ExcludeItemTypes = excludeItemTypes.ToArray(),
Genres = genres.ToArray()
Genres = genres.ToArray(),
DtoOptions = dtoOptions
});
}
@@ -1556,9 +1643,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return new QueryResult<BaseItem>();
}
if (_services.Count == 1)
if (_services.Count == 1 && !(query.IsInProgress ?? false))
{
return GetEmbyRecordings(query, user);
return GetEmbyRecordings(query, new DtoOptions(), user);
}
await RefreshRecordings(cancellationToken).ConfigureAwait(false);
@@ -1609,6 +1696,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
recordings = recordings.Where(i => i.IsMovie == val);
}
if (query.IsNews.HasValue)
{
var val = query.IsNews.Value;
recordings = recordings.Where(i => i.IsNews == val);
}
if (query.IsSeries.HasValue)
{
var val = query.IsSeries.Value;
@@ -1659,7 +1752,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, List<ItemFields> fields, User user = null)
{
var recordingTuples = new List<Tuple<BaseItemDto, string, string>>();
var recordingTuples = new List<Tuple<BaseItemDto, string, string, string>>();
foreach (var tuple in tuples)
{
@@ -1727,7 +1820,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.ServiceName = serviceName;
}
recordingTuples.Add(new Tuple<BaseItemDto, string, string>(dto, serviceName, program.ExternalId));
recordingTuples.Add(new Tuple<BaseItemDto, string, string, string>(dto, serviceName, program.ExternalId, program.ExternalSeriesIdLegacy));
}
await AddRecordingInfo(recordingTuples, CancellationToken.None).ConfigureAwait(false);
@@ -1746,6 +1839,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
? null
: _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
dto.TimerId = string.IsNullOrEmpty(info.TimerId)
? null
: _tvDtoService.GetInternalTimerId(service.Name, info.TimerId).ToString("N");
dto.StartDate = info.StartDate;
dto.RecordingStatus = info.Status;
dto.IsRepeat = info.IsRepeat;
@@ -1842,6 +1939,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
if (query.IsScheduled.HasValue)
{
if (query.IsScheduled.Value)
{
timers = timers.Where(i => i.Item1.Status == RecordingStatus.New);
}
else
{
timers = timers.Where(i => !(i.Item1.Status == RecordingStatus.New));
}
}
if (!string.IsNullOrEmpty(query.ChannelId))
{
var guid = new Guid(query.ChannelId);
@@ -2002,6 +2111,56 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
}
private async Task<QueryResult<SeriesTimerInfo>> GetSeriesTimersInternal(SeriesTimerQuery query, CancellationToken cancellationToken)
{
var tasks = _services.Select(async i =>
{
try
{
var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
return recs.Select(r =>
{
r.ServiceName = i.Name;
return new Tuple<SeriesTimerInfo, ILiveTvService>(r, i);
});
}
catch (Exception ex)
{
_logger.ErrorException("Error getting recordings", ex);
return new List<Tuple<SeriesTimerInfo, ILiveTvService>>();
}
});
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
var timers = results.SelectMany(i => i.ToList());
if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
{
timers = query.SortOrder == SortOrder.Descending ?
timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) :
timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name);
}
else
{
timers = query.SortOrder == SortOrder.Descending ?
timers.OrderByStringDescending(i => i.Item1.Name) :
timers.OrderByString(i => i.Item1.Name);
}
var returnArray = timers
.Select(i =>
{
return i.Item1;
})
.ToArray();
return new QueryResult<SeriesTimerInfo>
{
Items = returnArray,
TotalRecordCount = returnArray.Length
};
}
public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
{
var tasks = _services.Select(async i =>
@@ -2146,6 +2305,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
info.RecordAnyChannel = true;
info.RecordAnyTime = true;
info.Days = new List<DayOfWeek>
{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
info.Id = null;
return new Tuple<SeriesTimerInfo, ILiveTvService>(info, service);
@@ -2399,47 +2571,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv
};
}
class LiveStreamData
public async Task CloseLiveStream(string id)
{
internal MediaSourceInfo Info;
internal string ItemId;
internal bool IsChannel;
}
var parts = id.Split(new[] { '_' }, 2);
public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
try
if (service == null)
{
var parts = id.Split(new[] { '_' }, 2);
var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
if (service == null)
{
throw new ArgumentException("Service not found.");
}
id = parts[1];
LiveStreamData data;
_openStreams.TryRemove(id, out data);
_logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false);
throw new ArgumentException("Service not found.");
}
catch (Exception ex)
{
_logger.ErrorException("Error closing live stream", ex);
throw;
}
finally
{
_liveStreamSemaphore.Release();
}
id = parts[1];
_logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
await service.CloseLiveStream(id, CancellationToken.None).ConfigureAwait(false);
}
public GuideInfo GetGuideInfo()
@@ -2462,7 +2609,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
Dispose(true);
}
private readonly object _disposeLock = new object();
private bool _isDisposed = false;
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
@@ -2473,18 +2619,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (dispose)
{
_isDisposed = true;
lock (_disposeLock)
{
foreach (var stream in _openStreams.Values.ToList())
{
var task = CloseLiveStream(stream.Info.Id, CancellationToken.None);
Task.WaitAll(task);
}
_openStreams.Clear();
}
}
}
@@ -2620,7 +2754,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
{
info = (TunerHostInfo)_jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info), typeof(TunerHostInfo));
info = _jsonSerializer.DeserializeFromString<TunerHostInfo>(_jsonSerializer.SerializeToString(info));
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
@@ -2661,7 +2795,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings)
{
info = (ListingsProviderInfo)_jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info), typeof(ListingsProviderInfo));
info = _jsonSerializer.DeserializeFromString< ListingsProviderInfo>(_jsonSerializer.SerializeToString(info));
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
@@ -2803,7 +2937,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
feature = "embytvseriesrecordings";
}
if (string.Equals(feature, "dvr", StringComparison.OrdinalIgnoreCase))
if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase))
{
var config = GetConfiguration();
if (config.TunerHosts.Count(i => i.IsEnabled) > 0 &&

View File

@@ -9,9 +9,11 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Server.Implementations.LiveTv
{
@@ -63,12 +65,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
if (item is ILiveTvRecording)
{
sources = await _liveTvManager.GetRecordingMediaSources(item.Id.ToString("N"), cancellationToken)
sources = await _liveTvManager.GetRecordingMediaSources(item, cancellationToken)
.ConfigureAwait(false);
}
else
{
sources = await _liveTvManager.GetChannelMediaSources(item.Id.ToString("N"), cancellationToken)
sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken)
.ConfigureAwait(false);
}
}
@@ -116,17 +118,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return list;
}
public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
MediaSourceInfo stream;
MediaSourceInfo stream = null;
const bool isAudio = false;
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
IDirectStreamProvider directStreamProvider = null;
if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
{
stream = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false);
var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false);
stream = info.Item1;
directStreamProvider = info.Item2;
}
else
{
@@ -135,14 +140,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv
try
{
await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false);
if (stream.MediaStreams.Any(i => i.Index != -1))
{
await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false);
}
else
{
await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception ex)
{
_logger.ErrorException("Error probing live tv stream", ex);
}
return stream;
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(stream, directStreamProvider);
}
private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
@@ -204,9 +216,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
public Task CloseMediaSource(string liveStreamId)
{
return _liveTvManager.CloseLiveStream(liveStreamId, cancellationToken);
return _liveTvManager.CloseLiveStream(liveStreamId);
}
}
}

View File

@@ -6,9 +6,11 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Serialization;
@@ -17,7 +19,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
public abstract class BaseTunerHost
{
protected readonly IConfigurationManager Config;
protected readonly IServerConfigurationManager Config;
protected readonly ILogger Logger;
protected IJsonSerializer JsonSerializer;
protected readonly IMediaEncoder MediaEncoder;
@@ -25,7 +27,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
protected BaseTunerHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
{
Config = config;
Logger = logger;
@@ -71,7 +73,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
.ToList();
}
public async Task<IEnumerable<ChannelInfo>> GetChannels(CancellationToken cancellationToken)
public async Task<IEnumerable<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken)
{
var list = new List<ChannelInfo>();
@@ -81,7 +83,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
try
{
var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
var channels = await GetChannels(host, enableCache, cancellationToken).ConfigureAwait(false);
var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.OrdinalIgnoreCase))).ToList();
list.AddRange(newChannels);
@@ -124,12 +126,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
foreach (var host in hostsWithChannel)
{
var resourcePool = GetLock(host.Url);
Logger.Debug("GetChannelStreamMediaSources - Waiting on tuner resource pool");
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
Logger.Debug("GetChannelStreamMediaSources - Unlocked resource pool");
try
{
// Check to make sure the tuner is available
@@ -155,89 +151,63 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
Logger.Error("Error opening tuner", ex);
}
finally
{
resourcePool.Release();
}
}
}
return new List<MediaSourceInfo>();
}
protected abstract Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken);
protected abstract Task<LiveStream> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken);
public async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
public async Task<LiveStream> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
{
if (IsValidChannelId(channelId))
if (!IsValidChannelId(channelId))
{
var hosts = GetTunerHosts();
throw new FileNotFoundException();
}
var hostsWithChannel = new List<TunerHostInfo>();
var hosts = GetTunerHosts();
foreach (var host in hosts)
var hostsWithChannel = new List<TunerHostInfo>();
foreach (var host in hosts)
{
if (string.IsNullOrWhiteSpace(streamId))
{
if (string.IsNullOrWhiteSpace(streamId))
{
try
{
var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
if (channels.Any(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)))
{
hostsWithChannel.Add(host);
}
}
catch (Exception ex)
{
Logger.Error("Error getting channels", ex);
}
}
else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase))
{
hostsWithChannel = new List<TunerHostInfo> { host };
streamId = streamId.Substring(host.Id.Length);
break;
}
}
foreach (var host in hostsWithChannel)
{
var resourcePool = GetLock(host.Url);
Logger.Debug("GetChannelStream - Waiting on tuner resource pool");
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
Logger.Debug("GetChannelStream - Unlocked resource pool");
try
{
// 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 a streamId is specified then availibility has already been checked in GetChannelStreamMediaSources
if (string.IsNullOrWhiteSpace(streamId) && hostsWithChannel.Count > 1)
var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
if (channels.Any(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)))
{
if (!await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false))
{
Logger.Error("Tuner is not currently available");
resourcePool.Release();
continue;
}
hostsWithChannel.Add(host);
}
var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
if (EnableMediaProbing)
{
await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false);
}
return new Tuple<MediaSourceInfo, SemaphoreSlim>(stream, resourcePool);
}
catch (Exception ex)
{
Logger.Error("Error opening tuner", ex);
resourcePool.Release();
Logger.Error("Error getting channels", ex);
}
}
else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase))
{
hostsWithChannel = new List<TunerHostInfo> { host };
streamId = streamId.Substring(host.Id.Length);
break;
}
}
foreach (var host in hostsWithChannel)
{
try
{
var liveStream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
await liveStream.Open(cancellationToken).ConfigureAwait(false);
return liveStream;
}
catch (Exception ex)
{
Logger.Error("Error opening tuner", ex);
}
}
throw new LiveTvConflictException();
@@ -263,117 +233,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken);
/// <summary>
/// The _semaphoreLocks
/// </summary>
private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = new ConcurrentDictionary<string, SemaphoreSlim>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Gets the lock.
/// </summary>
/// <param name="url">The filename.</param>
/// <returns>System.Object.</returns>
private SemaphoreSlim GetLock(string url)
{
return _semaphoreLocks.GetOrAdd(url, key => new SemaphoreSlim(1, 1));
}
private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
{
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
await AddMediaInfoInternal(mediaSource, isAudio, cancellationToken).ConfigureAwait(false);
// Leave the resource locked. it will be released upstream
}
catch (Exception)
{
// Release the resource if there's some kind of failure.
resourcePool.Release();
throw;
}
}
private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
{
var originalRuntime = mediaSource.RunTimeTicks;
var info = await MediaEncoder.GetMediaInfo(new MediaInfoRequest
{
InputPath = mediaSource.Path,
Protocol = mediaSource.Protocol,
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
ExtractChapters = false
}, cancellationToken).ConfigureAwait(false);
mediaSource.Bitrate = info.Bitrate;
mediaSource.Container = info.Container;
mediaSource.Formats = info.Formats;
mediaSource.MediaStreams = info.MediaStreams;
mediaSource.RunTimeTicks = info.RunTimeTicks;
mediaSource.Size = info.Size;
mediaSource.Timestamp = info.Timestamp;
mediaSource.Video3DFormat = info.Video3DFormat;
mediaSource.VideoType = info.VideoType;
mediaSource.DefaultSubtitleStreamIndex = null;
// Null this out so that it will be treated like a live stream
if (!originalRuntime.HasValue)
{
mediaSource.RunTimeTicks = null;
}
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Audio);
if (audioStream == null || audioStream.Index == -1)
{
mediaSource.DefaultAudioStreamIndex = null;
}
else
{
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
}
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Video);
if (videoStream != null)
{
if (!videoStream.BitRate.HasValue)
{
var width = videoStream.Width ?? 1920;
if (width >= 1900)
{
videoStream.BitRate = 8000000;
}
else if (width >= 1260)
{
videoStream.BitRate = 3000000;
}
else if (width >= 700)
{
videoStream.BitRate = 1000000;
}
}
}
// Try to estimate this
if (!mediaSource.Bitrate.HasValue)
{
var total = mediaSource.MediaStreams.Select(i => i.BitRate ?? 0).Sum();
if (total > 0)
{
mediaSource.Bitrate = total;
}
}
}
protected abstract bool IsValidChannelId(string channelId);
protected LiveTvOptions GetConfiguration()

View File

@@ -10,6 +10,7 @@ using System;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@@ -39,13 +40,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
string server = null;
if (e.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
var info = e.Argument;
if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
{
string location;
if (e.Headers.TryGetValue("Location", out location))
if (info.Headers.TryGetValue("Location", out location))
{
//_logger.Debug("HdHomerun found at {0}", location);
@@ -85,7 +88,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
using (var stream = await _httpClient.Get(new HttpRequestOptions
{
Url = string.Format("{0}/discover.json", url),
CancellationToken = CancellationToken.None
CancellationToken = CancellationToken.None,
BufferContent = false
}))
{
var response = _json.DeserializeFromStream<HdHomerunHost.DiscoverResponse>(stream);

View File

@@ -14,7 +14,10 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Net;
@@ -24,11 +27,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationHost _appHost;
public HdHomerunHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient)
public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost)
: base(config, logger, jsonSerializer, mediaEncoder)
{
_httpClient = httpClient;
_fileSystem = fileSystem;
_appHost = appHost;
}
public string Name
@@ -60,20 +67,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return id;
}
public string ApplyDuration(string streamPath, TimeSpan duration)
{
streamPath += streamPath.IndexOf('?') == -1 ? "?" : "&";
streamPath += "duration=" + Convert.ToInt32(duration.TotalSeconds).ToString(CultureInfo.InvariantCulture);
return streamPath;
}
private async Task<IEnumerable<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
{
var options = new HttpRequestOptions
{
Url = string.Format("{0}/lineup.json", GetApiUrl(info, false)),
CancellationToken = cancellationToken
CancellationToken = cancellationToken,
BufferContent = false
};
using (var stream = await _httpClient.Get(options))
{
@@ -105,8 +105,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
});
}
private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
{
lock (_modelCache)
{
DiscoverResponse response;
if (_modelCache.TryGetValue(info.Url, out response))
{
return response.ModelNumber;
}
}
try
{
using (var stream = await _httpClient.Get(new HttpRequestOptions()
@@ -115,11 +125,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
CancellationToken = cancellationToken,
CacheLength = TimeSpan.FromDays(1),
CacheMode = CacheMode.Unconditional,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
BufferContent = false
}))
{
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
lock (_modelCache)
{
_modelCache[info.Id] = response;
}
return response.ModelNumber;
}
}
@@ -127,8 +143,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
{
var defaultValue = "HDHR";
// HDHR4 doesn't have this api
return "HDHR";
lock (_modelCache)
{
_modelCache[info.Id] = new DiscoverResponse
{
ModelNumber = defaultValue
};
}
return defaultValue;
}
throw;
@@ -143,7 +167,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
Url = string.Format("{0}/tuners.html", GetApiUrl(info, false)),
CancellationToken = cancellationToken,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
BufferContent = false
}))
{
var tuners = new List<LiveTvTunerInfo>();
@@ -319,18 +344,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
videoBitrate = 1000000;
}
if (string.IsNullOrWhiteSpace(videoCodec))
var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase));
if (channel != null)
{
var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase));
if (channel != null)
if (string.IsNullOrWhiteSpace(videoCodec))
{
videoCodec = channel.VideoCodec;
audioCodec = channel.AudioCodec;
videoBitrate = (channel.IsHD ?? true) ? 15000000 : 2000000;
audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000;
}
audioCodec = channel.AudioCodec;
if (!videoBitrate.HasValue)
{
videoBitrate = (channel.IsHD ?? true) ? 15000000 : 2000000;
}
audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000;
}
// normalize
@@ -352,6 +380,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
url += "?transcode=" + profile;
}
var id = profile;
if (string.IsNullOrWhiteSpace(id))
{
id = "native";
}
id += "_" + url.GetMD5().ToString("N");
var mediaSource = new MediaSourceInfo
{
Path = url,
@@ -380,14 +415,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
BitRate = audioBitrate
}
},
RequiresOpening = false,
RequiresOpening = true,
RequiresClosing = false,
BufferMs = 0,
Container = "ts",
Id = profile,
SupportsDirectPlay = true,
SupportsDirectStream = false,
SupportsTranscoding = true
Id = id,
SupportsDirectPlay = false,
SupportsDirectStream = true,
SupportsTranscoding = true,
IsInfiniteStream = true
};
return mediaSource;
@@ -417,18 +453,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
try
{
string model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false);
model = model ?? string.Empty;
if (info.AllowHWTranscoding && (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
if (info.AllowHWTranscoding)
{
list.Add(await GetMediaSource(info, hdhrId, "heavy").ConfigureAwait(false));
string model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false);
model = model ?? string.Empty;
list.Add(await GetMediaSource(info, hdhrId, "internet540").ConfigureAwait(false));
list.Add(await GetMediaSource(info, hdhrId, "internet480").ConfigureAwait(false));
list.Add(await GetMediaSource(info, hdhrId, "internet360").ConfigureAwait(false));
list.Add(await GetMediaSource(info, hdhrId, "internet240").ConfigureAwait(false));
list.Add(await GetMediaSource(info, hdhrId, "mobile").ConfigureAwait(false));
if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
{
list.Add(await GetMediaSource(info, hdhrId, "heavy").ConfigureAwait(false));
list.Add(await GetMediaSource(info, hdhrId, "internet540").ConfigureAwait(false));
list.Add(await GetMediaSource(info, hdhrId, "internet480").ConfigureAwait(false));
list.Add(await GetMediaSource(info, hdhrId, "internet360").ConfigureAwait(false));
list.Add(await GetMediaSource(info, hdhrId, "internet240").ConfigureAwait(false));
list.Add(await GetMediaSource(info, hdhrId, "mobile").ConfigureAwait(false));
}
}
}
catch
@@ -449,9 +488,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
{
Logger.Info("GetChannelStream: channel id: {0}. stream id: {1}", channelId, streamId ?? string.Empty);
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))
{
@@ -459,7 +500,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
var hdhrId = GetHdHrIdFromChannelId(channelId);
return await GetMediaSource(info, hdhrId, streamId).ConfigureAwait(false);
var mediaSource = await GetMediaSource(info, hdhrId, profile).ConfigureAwait(false);
var liveStream = new HdHomerunLiveStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
liveStream.EnableStreamSharing = true;
return liveStream;
}
public async Task Validate(TunerHostInfo info)
@@ -469,13 +514,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return;
}
lock (_modelCache)
{
_modelCache.Clear();
}
try
{
// Test it by pulling down the lineup
using (var stream = await _httpClient.Get(new HttpRequestOptions
{
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
CancellationToken = CancellationToken.None
CancellationToken = CancellationToken.None,
BufferContent = false
}))
{
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);

View File

@@ -0,0 +1,145 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunLiveStream : LiveStream, IDirectStreamProvider
{
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
private readonly IServerApplicationHost _appHost;
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
private readonly MulticastStream _multicastStream;
public HdHomerunLiveStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
: base(mediaSource)
{
_fileSystem = fileSystem;
_httpClient = httpClient;
_logger = logger;
_appPaths = appPaths;
_appHost = appHost;
OriginalStreamId = originalStreamId;
_multicastStream = new MulticastStream(_logger);
}
protected override async Task OpenInternal(CancellationToken openCancellationToken)
{
_liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
var mediaSource = OriginalMediaSource;
var url = mediaSource.Path;
_logger.Info("Opening HDHR Live stream from {0}", url);
var taskCompletionSource = new TaskCompletionSource<bool>();
StartStreaming(url, taskCompletionSource, _liveStreamCancellationTokenSource.Token);
//OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile;
//OpenedMediaSource.ReadAtNativeFramerate = true;
OpenedMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
OpenedMediaSource.Protocol = MediaProtocol.Http;
OpenedMediaSource.SupportsDirectPlay = false;
OpenedMediaSource.SupportsDirectStream = true;
OpenedMediaSource.SupportsTranscoding = true;
await taskCompletionSource.Task.ConfigureAwait(false);
//await Task.Delay(5000).ConfigureAwait(false);
}
public override Task Close()
{
_logger.Info("Closing HDHR live stream");
_liveStreamCancellationTokenSource.Cancel();
return _liveStreamTaskCompletionSource.Task;
}
private async Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
await Task.Run(async () =>
{
var isFirstAttempt = true;
while (!cancellationToken.IsCancellationRequested)
{
try
{
using (var response = await _httpClient.SendAsync(new HttpRequestOptions
{
Url = url,
CancellationToken = cancellationToken,
BufferContent = false
}, "GET").ConfigureAwait(false))
{
_logger.Info("Opened HDHR stream from {0}", url);
if (!cancellationToken.IsCancellationRequested)
{
_logger.Info("Beginning multicastStream.CopyUntilCancelled");
Action onStarted = null;
if (isFirstAttempt)
{
onStarted = () => openTaskCompletionSource.TrySetResult(true);
}
await _multicastStream.CopyUntilCancelled(response.Content, onStarted, cancellationToken).ConfigureAwait(false);
}
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
if (isFirstAttempt)
{
_logger.ErrorException("Error opening live stream:", ex);
openTaskCompletionSource.TrySetException(ex);
break;
}
_logger.ErrorException("Error copying live stream, will reopen", ex);
}
isFirstAttempt = false;
}
_liveStreamTaskCompletionSource.TrySetResult(true);
}).ConfigureAwait(false);
}
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{
return _multicastStream.CopyToAsync(stream);
}
}
}

View File

@@ -13,8 +13,10 @@ using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
@@ -23,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
public M3UTunerHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient)
public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient)
: base(config, logger, jsonSerializer, mediaEncoder)
{
_fileSystem = fileSystem;
@@ -63,11 +65,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return Task.FromResult(list);
}
protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
{
var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false);
return sources.First();
var liveStream = new LiveStream(sources.First());
return liveStream;
}
public async Task Validate(TunerHostInfo info)
@@ -136,7 +139,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
RequiresOpening = false,
RequiresClosing = false,
ReadAtNativeFramerate = false
ReadAtNativeFramerate = false,
Id = channel.Path.GetMD5().ToString("N"),
IsInfiniteStream = true
};
return new List<MediaSourceInfo> { mediaSource };
@@ -148,10 +154,5 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
return Task.FromResult(true);
}
public string ApplyDuration(string streamPath, TimeSpan duration)
{
return streamPath;
}
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
public class MulticastStream
{
private readonly List<QueueStream> _outputStreams = new List<QueueStream>();
private const int BufferSize = 81920;
private CancellationToken _cancellationToken;
private readonly ILogger _logger;
public MulticastStream(ILogger logger)
{
_logger = logger;
}
public async Task CopyUntilCancelled(Stream source, Action onStarted, CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
while (!cancellationToken.IsCancellationRequested)
{
byte[] buffer = new byte[BufferSize];
var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
if (bytesRead > 0)
{
byte[] copy = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
List<QueueStream> streams = null;
lock (_outputStreams)
{
streams = _outputStreams.ToList();
}
foreach (var stream in streams)
{
stream.Queue(copy);
}
if (onStarted != null)
{
var onStartedCopy = onStarted;
onStarted = null;
Task.Run(onStartedCopy);
}
}
else
{
await Task.Delay(100).ConfigureAwait(false);
}
}
}
public Task CopyToAsync(Stream stream)
{
var result = new QueueStream(stream, _logger)
{
OnFinished = OnFinished
};
lock (_outputStreams)
{
_outputStreams.Add(result);
}
result.Start(_cancellationToken);
return result.TaskCompletion.Task;
}
public void RemoveOutputStream(QueueStream stream)
{
lock (_outputStreams)
{
_outputStreams.Remove(stream);
}
}
private void OnFinished(QueueStream queueStream)
{
RemoveOutputStream(queueStream);
}
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
public class QueueStream
{
private readonly Stream _outputStream;
private readonly ConcurrentQueue<byte[]> _queue = new ConcurrentQueue<byte[]>();
private CancellationToken _cancellationToken;
public TaskCompletionSource<bool> TaskCompletion { get; private set; }
public Action<QueueStream> OnFinished { get; set; }
private readonly ILogger _logger;
public QueueStream(Stream outputStream, ILogger logger)
{
_outputStream = outputStream;
_logger = logger;
TaskCompletion = new TaskCompletionSource<bool>();
}
public void Queue(byte[] bytes)
{
_queue.Enqueue(bytes);
}
public void Start(CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
Task.Run(() => StartInternal());
}
private byte[] Dequeue()
{
byte[] bytes;
if (_queue.TryDequeue(out bytes))
{
return bytes;
}
return null;
}
private async Task StartInternal()
{
var cancellationToken = _cancellationToken;
try
{
while (!cancellationToken.IsCancellationRequested)
{
var bytes = Dequeue();
if (bytes != null)
{
await _outputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
}
else
{
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
}
}
TaskCompletion.TrySetResult(true);
_logger.Debug("QueueStream complete");
}
catch (OperationCanceledException)
{
_logger.Debug("QueueStream cancelled");
TaskCompletion.TrySetCanceled();
}
catch (Exception ex)
{
_logger.ErrorException("Error in QueueStream", ex);
TaskCompletion.TrySetException(ex);
}
finally
{
if (OnFinished != null)
{
OnFinished(this);
}
}
}
}
}

View File

@@ -14,6 +14,7 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Extensions;
using System.Xml.Linq;
using MediaBrowser.Model.Events;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
@@ -50,18 +51,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
var info = e.Argument;
string st = null;
string nt = null;
e.Headers.TryGetValue("ST", out st);
e.Headers.TryGetValue("NT", out nt);
info.Headers.TryGetValue("ST", out st);
info.Headers.TryGetValue("NT", out nt);
if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
{
string location;
if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
if (info.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
{
_logger.Debug("SAT IP found at {0}", location);

View File

@@ -8,6 +8,7 @@ using CommonIO;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
@@ -16,6 +17,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
@@ -24,7 +26,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
public SatIpHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient)
public SatIpHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient)
: base(config, logger, jsonSerializer, mediaEncoder)
{
_fileSystem = fileSystem;
@@ -113,11 +115,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
return new List<MediaSourceInfo>();
}
protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken)
protected override async Task<LiveStream> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken)
{
var sources = await GetChannelStreamMediaSources(tuner, channelId, cancellationToken).ConfigureAwait(false);
return sources.First();
var liveStream = new LiveStream(sources.First());
return liveStream;
}
protected override async Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)

View File

@@ -47,7 +47,6 @@
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
"NotificationOptionUserLockedOut": "User locked out",
"NotificationOptionServerRestartRequired": "Server restart required",

View File

@@ -1,5 +1,10 @@
DE-0,1
FSK-0,1
DE-6,5
FSK-6,5
DE-12,7
FSK-12,7
DE-16,8
FSK-16,8
DE-18,9
FSK-18,9

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -11,9 +11,10 @@
<AssemblyName>MediaBrowser.Server.Implementations</AssemblyName>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<ReleaseVersion>
</ReleaseVersion>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -69,12 +70,12 @@
<Reference Include="ServiceStack.Api.Swagger">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
</Reference>
<Reference Include="SimpleInjector, Version=3.2.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
<HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
<Reference Include="SimpleInjector, Version=3.2.2.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
<HintPath>..\packages\SimpleInjector.3.2.2\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SocketHttpListener, Version=1.0.6063.4624, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SocketHttpListener.1.0.0.39\lib\net45\SocketHttpListener.dll</HintPath>
<Reference Include="SocketHttpListener, Version=1.0.6109.26162, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SocketHttpListener.1.0.0.40\lib\net45\SocketHttpListener.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@@ -105,9 +106,6 @@
<Reference Include="UniversalDetector">
<HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
</Reference>
<Reference Include="Mono.Nat">
<HintPath>..\packages\Mono.Nat.1.2.24.0\lib\net40\Mono.Nat.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -164,7 +162,6 @@
<Compile Include="HttpServer\HttpListenerHost.cs" />
<Compile Include="HttpServer\HttpResultFactory.cs" />
<Compile Include="HttpServer\LoggerUtils.cs" />
<Compile Include="HttpServer\NativeWebSocket.cs" />
<Compile Include="HttpServer\RangeRequestWriter.cs" />
<Compile Include="HttpServer\ResponseFilter.cs" />
<Compile Include="HttpServer\Security\AuthService.cs" />
@@ -237,6 +234,7 @@
<Compile Include="LiveTv\EmbyTV\TimerManager.cs" />
<Compile Include="LiveTv\Listings\SchedulesDirect.cs" />
<Compile Include="LiveTv\Listings\XmlTvListingsProvider.cs" />
<Compile Include="LiveTv\LiveStreamHelper.cs" />
<Compile Include="LiveTv\LiveTvConfigurationFactory.cs" />
<Compile Include="LiveTv\LiveTvDtoService.cs" />
<Compile Include="LiveTv\LiveTvManager.cs" />
@@ -244,11 +242,14 @@
<Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunLiveStream.cs" />
<Compile Include="LiveTv\TunerHosts\M3uParser.cs" />
<Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" />
<Compile Include="LiveTv\ProgramImageProvider.cs" />
<Compile Include="LiveTv\RecordingImageProvider.cs" />
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
<Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
<Compile Include="LiveTv\TunerHosts\QueueStream.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\ChannelScan.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\ReportBlock.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpAppPacket.cs" />
@@ -390,6 +391,10 @@
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj">
<Project>{d7453b88-2266-4805-b39b-2b5a2a33e1ba}</Project>
<Name>Mono.Nat</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Localization\Ratings\us.txt" />

View File

@@ -61,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
}
var libraryOptions = _libraryManager.GetLibraryOptions(video);
if (libraryOptions != null && libraryOptions.SchemaVersion >= 2)
if (libraryOptions != null)
{
if (!libraryOptions.EnableChapterImageExtraction)
{
@@ -70,29 +70,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
}
else
{
var options = _chapterManager.GetConfiguration();
if (video is Movie)
{
if (!options.EnableMovieChapterImageExtraction)
{
return false;
}
}
else if (video is Episode)
{
if (!options.EnableEpisodeChapterImageExtraction)
{
return false;
}
}
else
{
if (!options.EnableOtherVideoChapterImageExtraction)
{
return false;
}
}
return false;
}
// Can't extract images if there are no video streams
@@ -160,7 +138,9 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
var tempFile = await _encoder.ExtractVideoImage(inputPath, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false);
var container = video.Container;
var tempFile = await _encoder.ExtractVideoImage(inputPath, container, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false);
File.Copy(tempFile, path, true);
try

View File

@@ -81,7 +81,8 @@ namespace MediaBrowser.Server.Implementations.News
{
Url = "http://emby.media/community/index.php?/blog/rss/1-media-browser-developers-blog",
Progress = new Progress<double>(),
UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.42 Safari/537.36"
UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.42 Safari/537.36",
BufferContent = false
};
using (var stream = await _httpClient.Get(requestOptions).ConfigureAwait(false))

View File

@@ -89,13 +89,6 @@ namespace MediaBrowser.Server.Implementations.Notifications
Variables = new List<string>{"Name"}
},
new NotificationTypeInfo
{
Type = NotificationType.NewLibraryContentMultiple.ToString(),
DefaultTitle = "{ItemCount} new items have been added to your media library.",
Variables = new List<string>{"ItemCount"}
},
new NotificationTypeInfo
{
Type = NotificationType.AudioPlayback.ToString(),

View File

@@ -4,6 +4,7 @@ using MediaBrowser.Model.Serialization;
using System;
using System.Data;
using System.IO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Persistence
{
@@ -51,18 +52,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <summary>
/// Gets a stream from a DataReader at a given ordinal
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="ordinal">The ordinal.</param>
/// <returns>Stream.</returns>
/// <exception cref="System.ArgumentNullException">reader</exception>
public static Stream GetMemoryStream(this IDataReader reader, int ordinal)
public static Stream GetMemoryStream(this IDataReader reader, int ordinal, IMemoryStreamProvider streamProvider)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
var memoryStream = new MemoryStream();
var memoryStream = streamProvider.CreateNew();
var num = 0L;
var array = new byte[4096];
long bytes;
@@ -132,18 +131,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <summary>
/// Serializes to bytes.
/// </summary>
/// <param name="json">The json.</param>
/// <param name="obj">The obj.</param>
/// <returns>System.Byte[][].</returns>
/// <exception cref="System.ArgumentNullException">obj</exception>
public static byte[] SerializeToBytes(this IJsonSerializer json, object obj)
public static byte[] SerializeToBytes(this IJsonSerializer json, object obj, IMemoryStreamProvider streamProvider)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
using (var stream = new MemoryStream())
using (var stream = streamProvider.CreateNew())
{
json.SerializeToStream(obj, stream);
return stream.ToArray();

View File

@@ -10,6 +10,7 @@ using System.Data;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Persistence
{
@@ -18,10 +19,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// </summary>
public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository
{
public SqliteDisplayPreferencesRepository(ILogManager logManager, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IDbConnector dbConnector)
private readonly IMemoryStreamProvider _memoryStreamProvider;
public SqliteDisplayPreferencesRepository(ILogManager logManager, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IDbConnector dbConnector, IMemoryStreamProvider memoryStreamProvider)
: base(logManager, dbConnector)
{
_jsonSerializer = jsonSerializer;
_memoryStreamProvider = memoryStreamProvider;
DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db");
}
@@ -82,7 +86,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
cancellationToken.ThrowIfCancellationRequested();
var serialized = _jsonSerializer.SerializeToBytes(displayPreferences);
var serialized = _jsonSerializer.SerializeToBytes(displayPreferences, _memoryStreamProvider);
using (var connection = await CreateConnection().ConfigureAwait(false))
{
@@ -166,7 +170,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
foreach (var displayPreference in displayPreferences)
{
var serialized = _jsonSerializer.SerializeToBytes(displayPreference);
var serialized = _jsonSerializer.SerializeToBytes(displayPreference, _memoryStreamProvider);
using (var cmd = connection.CreateCommand())
{
@@ -246,7 +250,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
if (reader.Read())
{
using (var stream = reader.GetMemoryStream(0))
using (var stream = reader.GetMemoryStream(0, _memoryStreamProvider))
{
return _jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream);
}
@@ -283,7 +287,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
while (reader.Read())
{
using (var stream = reader.GetMemoryStream(0))
using (var stream = reader.GetMemoryStream(0, _memoryStreamProvider))
{
list.Add(_jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream));
}

View File

@@ -9,6 +9,7 @@ using System.Data;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Persistence
{
@@ -18,10 +19,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IMemoryStreamProvider _memoryStreamProvider;
public SqliteUserRepository(ILogManager logManager, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer, IDbConnector dbConnector) : base(logManager, dbConnector)
public SqliteUserRepository(ILogManager logManager, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer, IDbConnector dbConnector, IMemoryStreamProvider memoryStreamProvider) : base(logManager, dbConnector)
{
_jsonSerializer = jsonSerializer;
_memoryStreamProvider = memoryStreamProvider;
DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
}
@@ -75,7 +78,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
cancellationToken.ThrowIfCancellationRequested();
var serialized = _jsonSerializer.SerializeToBytes(user);
var serialized = _jsonSerializer.SerializeToBytes(user, _memoryStreamProvider);
cancellationToken.ThrowIfCancellationRequested();
@@ -150,7 +153,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
var id = reader.GetGuid(0);
using (var stream = reader.GetMemoryStream(1))
using (var stream = reader.GetMemoryStream(1, _memoryStreamProvider))
{
var user = _jsonSerializer.DeserializeFromStream<User>(stream);
user.Id = id;

View File

@@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using CommonIO;
using MediaBrowser.Model.Querying;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Playlists
{
@@ -17,7 +19,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
public override bool IsVisible(User user)
{
return base.IsVisible(user) && GetChildren(user, false).Any();
return base.IsVisible(user) && GetChildren(user, true).Any();
}
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
@@ -37,6 +39,12 @@ namespace MediaBrowser.Server.Implementations.Playlists
{
get { return Model.Entities.CollectionType.Playlists; }
}
protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{
query.Recursive = false;
return base.GetItemsInternal(query);
}
}
public class PlaylistsDynamicFolder : IVirtualFolderCreator

View File

@@ -13,6 +13,7 @@ using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.ServerManager
{
@@ -72,6 +73,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
private readonly List<IWebSocketListener> _webSocketListeners = new List<IWebSocketListener>();
private bool _disposed;
private readonly IMemoryStreamProvider _memoryStreamProvider;
/// <summary>
/// Initializes a new instance of the <see cref="ServerManager" /> class.
@@ -81,7 +83,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="logger">The logger.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <exception cref="System.ArgumentNullException">applicationHost</exception>
public ServerManager(IServerApplicationHost applicationHost, IJsonSerializer jsonSerializer, ILogger logger, IServerConfigurationManager configurationManager)
public ServerManager(IServerApplicationHost applicationHost, IJsonSerializer jsonSerializer, ILogger logger, IServerConfigurationManager configurationManager, IMemoryStreamProvider memoryStreamProvider)
{
if (applicationHost == null)
{
@@ -100,6 +102,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
_jsonSerializer = jsonSerializer;
_applicationHost = applicationHost;
ConfigurationManager = configurationManager;
_memoryStreamProvider = memoryStreamProvider;
}
/// <summary>
@@ -150,7 +153,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
return;
}
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger, _memoryStreamProvider)
{
OnReceive = ProcessWebSocketMessageReceived,
Url = e.Url,

View File

@@ -9,6 +9,7 @@ using System.Collections.Specialized;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using UniversalDetector;
namespace MediaBrowser.Server.Implementations.ServerManager
@@ -19,7 +20,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
public class WebSocketConnection : IWebSocketConnection
{
public event EventHandler<EventArgs> Closed;
/// <summary>
/// The _socket
/// </summary>
@@ -35,11 +36,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// </summary>
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
/// <summary>
/// The _send semaphore
/// </summary>
private readonly SemaphoreSlim _sendSemaphore = new SemaphoreSlim(1, 1);
/// <summary>
/// The logger
/// </summary>
@@ -78,7 +74,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// </summary>
/// <value>The query string.</value>
public NameValueCollection QueryString { get; set; }
private readonly IMemoryStreamProvider _memoryStreamProvider;
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
/// </summary>
@@ -87,7 +84,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">socket</exception>
public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger)
public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger, IMemoryStreamProvider memoryStreamProvider)
{
if (socket == null)
{
@@ -113,6 +110,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
_socket.OnReceive = OnReceiveInternal;
RemoteEndPoint = remoteEndPoint;
_logger = logger;
_memoryStreamProvider = memoryStreamProvider;
socket.Closed += socket_Closed;
}
@@ -149,7 +147,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
{
try
{
using (var ms = new MemoryStream(bytes))
using (var ms = _memoryStreamProvider.CreateNew(bytes))
{
var detector = new CharsetDetector();
detector.Feed(ms);
@@ -207,7 +205,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
_logger.ErrorException("Error processing web socket message", ex);
}
}
/// <summary>
/// Sends a message asynchronously.
/// </summary>
@@ -234,7 +232,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="buffer">The buffer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
public Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
{
if (buffer == null)
{
@@ -243,33 +241,10 @@ namespace MediaBrowser.Server.Implementations.ServerManager
cancellationToken.ThrowIfCancellationRequested();
// Per msdn docs, attempting to send simultaneous messages will result in one failing.
// This should help us workaround that and ensure all messages get sent
await _sendSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
await _socket.SendAsync(buffer, true, cancellationToken);
}
catch (OperationCanceledException)
{
_logger.Info("WebSocket message to {0} was cancelled", RemoteEndPoint);
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error sending WebSocket message {0}", ex, RemoteEndPoint);
throw;
}
finally
{
_sendSemaphore.Release();
}
return _socket.SendAsync(buffer, true, cancellationToken);
}
public async Task SendAsync(string text, CancellationToken cancellationToken)
public Task SendAsync(string text, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(text))
{
@@ -278,30 +253,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
cancellationToken.ThrowIfCancellationRequested();
// Per msdn docs, attempting to send simultaneous messages will result in one failing.
// This should help us workaround that and ensure all messages get sent
await _sendSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
await _socket.SendAsync(text, true, cancellationToken);
}
catch (OperationCanceledException)
{
_logger.Info("WebSocket message to {0} was cancelled", RemoteEndPoint);
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error sending WebSocket message {0}", ex, RemoteEndPoint);
throw;
}
finally
{
_sendSemaphore.Release();
}
return _socket.SendAsync(text, true, cancellationToken);
}
/// <summary>

View File

@@ -75,7 +75,8 @@ namespace MediaBrowser.Server.Implementations.Session
await _httpClient.Post(new HttpRequestOptions
{
Url = url,
CancellationToken = cancellationToken
CancellationToken = cancellationToken,
BufferContent = false
}).ConfigureAwait(false);
}

View File

@@ -41,12 +41,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <summary>
/// The _user data repository
/// </summary>
private readonly IUserDataManager _userDataRepository;
/// <summary>
/// The _user repository
/// </summary>
private readonly IUserRepository _userRepository;
private readonly IUserDataManager _userDataManager;
/// <summary>
/// The _logger
@@ -99,11 +94,10 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
public SessionManager(IUserDataManager userDataRepository, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager)
public SessionManager(IUserDataManager userDataManager, ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager)
{
_userDataRepository = userDataRepository;
_userDataManager = userDataManager;
_logger = logger;
_userRepository = userRepository;
_libraryManager = libraryManager;
_userManager = userManager;
_musicManager = musicManager;
@@ -248,13 +242,11 @@ namespace MediaBrowser.Server.Implementations.Session
var userLastActivityDate = user.LastActivityDate ?? DateTime.MinValue;
user.LastActivityDate = activityDate;
// Don't log in the db anymore frequently than 10 seconds
if ((activityDate - userLastActivityDate).TotalSeconds > 10)
if ((activityDate - userLastActivityDate).TotalSeconds > 60)
{
try
{
// Save this directly. No need to fire off all the events for this.
await _userRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
await _userManager.UpdateUser(user).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -294,11 +286,9 @@ namespace MediaBrowser.Server.Implementations.Session
var key = GetSessionKey(session.Client, session.DeviceId);
SessionInfo removed;
_activeConnections.TryRemove(key, out removed);
if (_activeConnections.TryRemove(key, out removed))
{
OnSessionEnded(removed);
}
OnSessionEnded(session);
}
}
finally
@@ -307,9 +297,9 @@ namespace MediaBrowser.Server.Implementations.Session
}
}
private Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId)
private Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId)
{
return _mediaSourceManager.GetMediaSource(item, mediaSourceId, false);
return _mediaSourceManager.GetMediaSource(item, mediaSourceId, liveStreamId, false, CancellationToken.None);
}
/// <summary>
@@ -337,7 +327,7 @@ namespace MediaBrowser.Server.Implementations.Session
var hasMediaSources = libraryItem as IHasMediaSources;
if (hasMediaSources != null)
{
mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId).ConfigureAwait(false);
mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false);
if (mediaSource != null)
{
@@ -638,17 +628,21 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns>
private async Task OnPlaybackStart(Guid userId, IHasUserData item)
{
var data = _userDataRepository.GetUserData(userId, item);
var data = _userDataManager.GetUserData(userId, item);
data.PlayCount++;
data.LastPlayedDate = DateTime.UtcNow;
if (!(item is Video))
if (!(item is Video) && item.SupportsPlayedStatus)
{
data.Played = true;
}
else
{
data.Played = false;
}
await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None).ConfigureAwait(false);
await _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None).ConfigureAwait(false);
}
/// <summary>
@@ -715,17 +709,17 @@ namespace MediaBrowser.Server.Implementations.Session
private async Task OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info)
{
var data = _userDataRepository.GetUserData(user.Id, item);
var data = _userDataManager.GetUserData(user.Id, item);
var positionTicks = info.PositionTicks;
if (positionTicks.HasValue)
{
_userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
_userDataManager.UpdatePlayState(item, data, positionTicks.Value);
UpdatePlaybackSettings(user, info, data);
await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
await _userDataManager.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
}
}
@@ -792,7 +786,7 @@ namespace MediaBrowser.Server.Implementations.Session
var hasMediaSources = libraryItem as IHasMediaSources;
if (hasMediaSources != null)
{
mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId).ConfigureAwait(false);
mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false);
}
info.Item = GetItemInfo(libraryItem, libraryItem, mediaSource);
@@ -820,7 +814,7 @@ namespace MediaBrowser.Server.Implementations.Session
{
try
{
await _mediaSourceManager.CloseLiveStream(info.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
await _mediaSourceManager.CloseLiveStream(info.LiveStreamId).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -851,22 +845,22 @@ namespace MediaBrowser.Server.Implementations.Session
if (!playbackFailed)
{
var data = _userDataRepository.GetUserData(userId, item);
var data = _userDataManager.GetUserData(userId, item);
if (positionTicks.HasValue)
{
playedToCompletion = _userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
playedToCompletion = _userDataManager.UpdatePlayState(item, data, positionTicks.Value);
}
else
else
{
// If the client isn't able to report this, then we'll just have to make an assumption
data.PlayCount++;
data.Played = true;
data.Played = item.SupportsPlayedStatus;
data.PlaybackPositionTicks = 0;
playedToCompletion = true;
}
await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None).ConfigureAwait(false);
await _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None).ConfigureAwait(false);
}
return playedToCompletion;

View File

@@ -30,6 +30,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Sync
{
@@ -51,6 +52,7 @@ namespace MediaBrowser.Server.Implementations.Sync
private readonly Func<IMediaSourceManager> _mediaSourceManager;
private readonly IJsonSerializer _json;
private readonly ITaskManager _taskManager;
private readonly IMemoryStreamProvider _memoryStreamProvider;
private ISyncProvider[] _providers = { };
@@ -60,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.Sync
public event EventHandler<GenericEventArgs<SyncJobItem>> SyncJobItemUpdated;
public event EventHandler<GenericEventArgs<SyncJobItem>> SyncJobItemCreated;
public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func<IDtoService> dtoService, IServerApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func<IMediaEncoder> mediaEncoder, IFileSystem fileSystem, Func<ISubtitleEncoder> subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func<IMediaSourceManager> mediaSourceManager, IJsonSerializer json, ITaskManager taskManager)
public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func<IDtoService> dtoService, IServerApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func<IMediaEncoder> mediaEncoder, IFileSystem fileSystem, Func<ISubtitleEncoder> subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func<IMediaSourceManager> mediaSourceManager, IJsonSerializer json, ITaskManager taskManager, IMemoryStreamProvider memoryStreamProvider)
{
_libraryManager = libraryManager;
_repo = repo;
@@ -78,6 +80,7 @@ namespace MediaBrowser.Server.Implementations.Sync
_mediaSourceManager = mediaSourceManager;
_json = json;
_taskManager = taskManager;
_memoryStreamProvider = memoryStreamProvider;
}
public void AddParts(IEnumerable<ISyncProvider> providers)
@@ -95,7 +98,7 @@ namespace MediaBrowser.Server.Implementations.Sync
public ISyncDataProvider GetDataProvider(IServerSyncProvider provider, SyncTarget target)
{
return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost, _logger, _json, _fileSystem, _config.CommonApplicationPaths));
return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost, _logger, _json, _fileSystem, _config.CommonApplicationPaths, _memoryStreamProvider));
}
public async Task<SyncJobCreationResult> CreateJob(SyncJobRequest request)

View File

@@ -99,7 +99,7 @@ namespace MediaBrowser.Server.Implementations.Sync
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const string StreamIdDelimeterString = "_";
public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
var openKeys = openToken.Split(new[] { StreamIdDelimeterString[0] }, 3);
@@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Sync
mediaSource.Protocol = dynamicInfo.Protocol;
mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders;
return mediaSource;
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(mediaSource, null);
}
private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource)
@@ -150,7 +150,7 @@ namespace MediaBrowser.Server.Implementations.Sync
}
}
public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
public Task CloseMediaSource(string liveStreamId)
{
throw new NotImplementedException();
}

View File

@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using Interfaces.IO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Sync
{
@@ -28,8 +29,9 @@ namespace MediaBrowser.Server.Implementations.Sync
private readonly IFileSystem _fileSystem;
private readonly IApplicationPaths _appPaths;
private readonly IServerApplicationHost _appHost;
private readonly IMemoryStreamProvider _memoryStreamProvider;
public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths)
public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths, IMemoryStreamProvider memoryStreamProvider)
{
_logger = logger;
_json = json;
@@ -37,6 +39,7 @@ namespace MediaBrowser.Server.Implementations.Sync
_target = target;
_fileSystem = fileSystem;
_appPaths = appPaths;
_memoryStreamProvider = memoryStreamProvider;
_appHost = appHost;
}
@@ -90,7 +93,7 @@ namespace MediaBrowser.Server.Implementations.Sync
private async Task SaveData(List<LocalItem> items, CancellationToken cancellationToken)
{
using (var stream = new MemoryStream())
using (var stream = _memoryStreamProvider.CreateNew())
{
_json.SerializeToStream(items, stream);

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0" />
<assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>

View File

@@ -5,9 +5,8 @@
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.55" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
<package id="SocketHttpListener" version="1.0.0.39" targetFramework="net45" />
<package id="SimpleInjector" version="3.2.2" targetFramework="net45" />
<package id="SocketHttpListener" version="1.0.0.40" targetFramework="net45" />
</packages>