aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations
diff options
context:
space:
mode:
authorEric Reed <ebr@mediabrowser3.com>2013-09-25 14:32:36 -0400
committerEric Reed <ebr@mediabrowser3.com>2013-09-25 14:32:36 -0400
commitc02c0db35af078e1a78897aecdade2efe57d3f06 (patch)
treeaf9ef64305efd2e353a202c27b188d2c44cd9b5b /MediaBrowser.Server.Implementations
parentc6e57c6448c04998bcae5a906e7a064300542e75 (diff)
parent2d9b48d00fd31aaa96676c82a054b2794493fbf9 (diff)
Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
Diffstat (limited to 'MediaBrowser.Server.Implementations')
-rw-r--r--MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs94
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs4
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs97
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs15
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs54
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs6
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs8
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj86
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs15
-rw-r--r--MediaBrowser.Server.Implementations/Providers/ImageSaver.cs12
-rw-r--r--MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs151
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs52
-rw-r--r--MediaBrowser.Server.Implementations/Session/WebSocketController.cs92
-rw-r--r--MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs4
19 files changed, 542 insertions, 178 deletions
diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
index 1f7361d2f..305bede56 100644
--- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
@@ -121,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
}
catch (IOException)
{
- // Cache file doesn't exist or is currently being written ro
+ // Cache file doesn't exist or is currently being written to
}
var semaphore = GetLock(cacheFilePath);
@@ -129,21 +129,24 @@ namespace MediaBrowser.Server.Implementations.Drawing
await semaphore.WaitAsync().ConfigureAwait(false);
// Check again in case of lock contention
- if (File.Exists(cacheFilePath))
+ try
{
- try
- {
- using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
- {
- await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
- return;
- }
- }
- finally
+ using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
{
+ await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
semaphore.Release();
+ return;
}
}
+ catch (IOException)
+ {
+ // Cache file doesn't exist or is currently being written to
+ }
+ catch
+ {
+ semaphore.Release();
+ throw;
+ }
try
{
@@ -188,12 +191,10 @@ namespace MediaBrowser.Server.Implementations.Drawing
var bytes = outputMemoryStream.ToArray();
- var outputTask = toStream.WriteAsync(bytes, 0, bytes.Length);
+ await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
// kick off a task to cache the result
- var cacheTask = CacheResizedImage(cacheFilePath, bytes);
-
- await Task.WhenAll(outputTask, cacheTask).ConfigureAwait(false);
+ CacheResizedImage(cacheFilePath, bytes, semaphore);
}
}
}
@@ -202,13 +203,52 @@ namespace MediaBrowser.Server.Implementations.Drawing
}
}
}
- finally
+ catch
{
semaphore.Release();
+
+ throw;
}
}
/// <summary>
+ /// Caches the resized image.
+ /// </summary>
+ /// <param name="cacheFilePath">The cache file path.</param>
+ /// <param name="bytes">The bytes.</param>
+ /// <param name="semaphore">The semaphore.</param>
+ private void CacheResizedImage(string cacheFilePath, byte[] bytes, SemaphoreSlim semaphore)
+ {
+ Task.Run(async () =>
+ {
+ try
+ {
+ var parentPath = Path.GetDirectoryName(cacheFilePath);
+
+ if (!Directory.Exists(parentPath))
+ {
+ Directory.CreateDirectory(parentPath);
+ }
+
+ // Save to the cache location
+ using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+ {
+ // Save to the filestream
+ await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error writing to image cache file {0}", ex, cacheFilePath);
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ });
+ }
+
+ /// <summary>
/// Sets the color of the background.
/// </summary>
/// <param name="graphics">The graphics.</param>
@@ -364,28 +404,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
}
/// <summary>
- /// Caches the resized image.
- /// </summary>
- /// <param name="cacheFilePath">The cache file path.</param>
- /// <param name="bytes">The bytes.</param>
- private async Task CacheResizedImage(string cacheFilePath, byte[] bytes)
- {
- var parentPath = Path.GetDirectoryName(cacheFilePath);
-
- if (!Directory.Exists(parentPath))
- {
- Directory.CreateDirectory(parentPath);
- }
-
- // Save to the cache location
- using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
- {
- // Save to the filestream
- await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
- }
- }
-
- /// <summary>
/// Gets the cache file path based on a set of parameters
/// </summary>
private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageOutputFormat format, ImageOverlay? overlay, int percentPlayed, string backgroundColor)
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index e5260004a..a5f54b938 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -237,7 +237,9 @@ namespace MediaBrowser.Server.Implementations.Dto
NowViewingItemId = session.NowViewingItemId,
NowViewingItemName = session.NowViewingItemName,
NowViewingItemType = session.NowViewingItemType,
- ApplicationVersion = session.ApplicationVersion
+ ApplicationVersion = session.ApplicationVersion,
+ CanSeek = session.CanSeek,
+ QueueableMediaTypes = session.QueueableMediaTypes
};
if (session.NowPlayingItem != null)
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
new file mode 100644
index 000000000..9c1a953b1
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -0,0 +1,97 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Implementations.Udp;
+using System.Net.Sockets;
+
+namespace MediaBrowser.Server.Implementations.EntryPoints
+{
+ /// <summary>
+ /// Class UdpServerEntryPoint
+ /// </summary>
+ public class UdpServerEntryPoint : IServerEntryPoint
+ {
+ /// <summary>
+ /// Gets or sets the UDP server.
+ /// </summary>
+ /// <value>The UDP server.</value>
+ private UdpServer UdpServer { get; set; }
+
+ /// <summary>
+ /// The _logger
+ /// </summary>
+ private readonly ILogger _logger;
+ /// <summary>
+ /// The _network manager
+ /// </summary>
+ private readonly INetworkManager _networkManager;
+ /// <summary>
+ /// The _server configuration manager
+ /// </summary>
+ private readonly IServerConfigurationManager _serverConfigurationManager;
+ /// <summary>
+ /// The _HTTP server
+ /// </summary>
+ private readonly IHttpServer _httpServer;
+
+ public const int PortNumber = 7359;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UdpServerEntryPoint"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="networkManager">The network manager.</param>
+ /// <param name="serverConfigurationManager">The server configuration manager.</param>
+ /// <param name="httpServer">The HTTP server.</param>
+ public UdpServerEntryPoint(ILogger logger, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager, IHttpServer httpServer)
+ {
+ _logger = logger;
+ _networkManager = networkManager;
+ _serverConfigurationManager = serverConfigurationManager;
+ _httpServer = httpServer;
+ }
+
+ /// <summary>
+ /// Runs this instance.
+ /// </summary>
+ public void Run()
+ {
+ var udpServer = new UdpServer(_logger, _networkManager, _serverConfigurationManager, _httpServer);
+
+ try
+ {
+ udpServer.Start(PortNumber);
+
+ UdpServer = udpServer;
+ }
+ catch (SocketException ex)
+ {
+ _logger.ErrorException("Failed to start UDP Server", ex);
+ }
+ }
+
+ /// <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)
+ {
+ if (UdpServer != null)
+ {
+ UdpServer.Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs
index f6547dec1..4f795fdd5 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs
@@ -314,6 +314,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="context">The CTX.</param>
private async void ProcessHttpRequestAsync(HttpListenerContext context)
{
+ var date = DateTime.Now;
+
LogHttpRequest(context);
if (context.Request.IsWebSocketRequest)
@@ -360,7 +362,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer
var url = context.Request.Url.ToString();
var endPoint = context.Request.RemoteEndPoint;
- LogResponse(context, url, endPoint);
+ var duration = DateTime.Now - date;
+
+ LogResponse(context, url, endPoint, duration);
}
catch (Exception ex)
@@ -461,14 +465,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="ctx">The CTX.</param>
/// <param name="url">The URL.</param>
/// <param name="endPoint">The end point.</param>
- private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint)
+ /// <param name="duration">The duration.</param>
+ private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint, TimeSpan duration)
{
if (!EnableHttpRequestLogging)
{
return;
}
- var statusode = ctx.Response.StatusCode;
+ var statusCode = ctx.Response.StatusCode;
var log = new StringBuilder();
@@ -476,7 +481,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer
log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k])));
- var msg = "Http Response Sent (" + statusode + ") to " + endPoint;
+ var responseTime = string.Format(". Response time: {0} ms", duration.TotalMilliseconds);
+
+ var msg = "Response code " + statusCode + " sent to " + endPoint + responseTime;
_logger.LogMultiline(msg, LogSeverity.Debug, log);
}
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index a5b792726..1bc3f1094 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -829,10 +829,6 @@ namespace MediaBrowser.Server.Implementations.Library
/// <returns>Task.</returns>
public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
{
- const int maxTasks = 3;
-
- var tasks = new List<Task>();
-
var people = RootFolder.RecursiveChildren
.SelectMany(c => c.People)
.DistinctBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
@@ -842,47 +838,27 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var person in people)
{
- if (tasks.Count > maxTasks)
+ cancellationToken.ThrowIfCancellationRequested();
+
+ try
{
- await Task.WhenAll(tasks).ConfigureAwait(false);
- tasks.Clear();
+ var item = GetPerson(person.Name);
- // Safe cancellation point, when there are no pending tasks
- cancellationToken.ThrowIfCancellationRequested();
+ await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
}
-
- // Avoid accessing the foreach variable within the closure
- var currentPerson = person;
-
- tasks.Add(Task.Run(async () =>
+ catch (IOException ex)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- try
- {
- var item = GetPerson(currentPerson.Name);
-
- await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
- }
- catch (IOException ex)
- {
- _logger.ErrorException("Error validating IBN entry {0}", ex, currentPerson.Name);
- }
+ _logger.ErrorException("Error validating IBN entry {0}", ex, person.Name);
+ }
- // Update progress
- lock (progress)
- {
- numComplete++;
- double percent = numComplete;
- percent /= people.Count;
+ // Update progress
+ numComplete++;
+ double percent = numComplete;
+ percent /= people.Count;
- progress.Report(100 * percent);
- }
- }));
+ progress.Report(100 * percent);
}
- await Task.WhenAll(tasks).ConfigureAwait(false);
-
progress.Report(100);
_logger.Info("People validation complete");
@@ -956,7 +932,9 @@ namespace MediaBrowser.Server.Implementations.Library
public Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken)
{
// Just run the scheduled task so that the user can see it
- return Task.Run(() => _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>());
+ _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>();
+
+ return Task.FromResult(true);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs
index eb89210ff..b9e033d23 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs
@@ -41,8 +41,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- var allItems = _libraryManager.RootFolder.RecursiveChildren.OfType<Game>().ToList();
-
var userLibraries = _userManager.Users
.Select(i => new Tuple<Guid, List<Game>>(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType<Game>().ToList()))
.ToList();
@@ -79,6 +77,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false);
}
+ catch (OperationCanceledException)
+ {
+ // Don't clutter the log
+ }
catch (Exception ex)
{
_logger.ErrorException("Error updating counts for {0}", ex, name);
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs
index 9a34dd1b0..e4d989c33 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs
@@ -42,16 +42,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- var allItems = _libraryManager.RootFolder.RecursiveChildren
- .Where(i => !(i is IHasMusicGenres) && !(i is Game))
- .ToList();
-
var userLibraries = _userManager.Users
.Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).Where(m => !(m is IHasMusicGenres) && !(m is Game)).ToList()))
.ToList();
- var allLibraryItems = allItems;
-
var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase);
// Populate counts of items
@@ -84,6 +78,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false);
}
+ catch (OperationCanceledException)
+ {
+ // Don't clutter the log
+ }
catch (Exception ex)
{
_logger.ErrorException("Error updating counts for {0}", ex, name);
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs
index 1b211d5f4..1edc24762 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs
@@ -42,16 +42,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- var allItems = _libraryManager.RootFolder.RecursiveChildren
- .Where(i => i is IHasMusicGenres)
- .ToList();
-
var userLibraries = _userManager.Users
.Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).Where(m => m is IHasMusicGenres).ToList()))
.ToList();
- var allLibraryItems = allItems;
-
var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase);
// Populate counts of items
@@ -84,6 +78,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false);
}
+ catch (OperationCanceledException)
+ {
+ // Don't clutter the log
+ }
catch (Exception ex)
{
_logger.ErrorException("Error updating counts for {0}", ex, name);
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs
index efefaeba3..dc96632f6 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs
@@ -41,7 +41,9 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
/// <returns>Task.</returns>
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- return Task.Run(() => RunInternal(progress, cancellationToken));
+ RunInternal(progress, cancellationToken);
+
+ return Task.FromResult(true);
}
private void RunInternal(IProgress<double> progress, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
index a4d880329..05689f8e5 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -41,14 +41,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- var allItems = _libraryManager.RootFolder.RecursiveChildren.ToList();
-
var userLibraries = _userManager.Users
.Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).ToList()))
.ToList();
- var allLibraryItems = allItems;
-
var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase);
// Populate counts of items
@@ -81,6 +77,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false);
}
+ catch (OperationCanceledException)
+ {
+ // Don't clutter the log
+ }
catch (Exception ex)
{
_logger.ErrorException("Error updating counts for {0}", ex, name);
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 9d2fc8c6b..f409b7205 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -13,6 +13,8 @@
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
+ <ProductVersion>10.0.0</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -35,77 +37,65 @@
<Reference Include="Alchemy">
<HintPath>..\packages\Alchemy.2.2.1\lib\net40\Alchemy.dll</HintPath>
</Reference>
- <Reference Include="BdInfo, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.2\lib\net45\BdInfo.dll</HintPath>
- </Reference>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="Lucene.Net">
<HintPath>..\packages\Lucene.Net.3.0.3\lib\NET40\Lucene.Net.dll</HintPath>
</Reference>
- <Reference Include="MoreLinq, Version=1.0.16006.0, Culture=neutral, PublicKeyToken=384d532d7e88985d, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Reactive.Core">
+ <HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Reactive.Interfaces">
+ <HintPath>..\packages\Rx-Interfaces.2.1.30214.0\lib\Net45\System.Reactive.Interfaces.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Reactive.Linq">
+ <HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Net45\System.Reactive.Linq.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Xml" />
+ <Reference Include="BDInfo">
+ <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.2\lib\net45\BdInfo.dll</HintPath>
+ </Reference>
+ <Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="ServiceStack">
<HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.Api.Swagger, Version=3.9.59.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="ServiceStack.Api.Swagger">
<HintPath>..\packages\ServiceStack.Api.Swagger.3.9.59\lib\net35\ServiceStack.Api.Swagger.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.Common, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="ServiceStack.Common">
<HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.Interfaces, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="ServiceStack.Interfaces">
<HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.OrmLite.SqlServer, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="ServiceStack.OrmLite.SqlServer">
<HintPath>..\packages\ServiceStack.OrmLite.SqlServer.3.9.43\lib\ServiceStack.OrmLite.SqlServer.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.Redis, Version=3.9.43.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="ServiceStack.Redis">
<HintPath>..\packages\ServiceStack.Redis.3.9.43\lib\net35\ServiceStack.Redis.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.ServiceInterface, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="ServiceStack.ServiceInterface">
<HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.ServiceInterface.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.Text, Version=3.9.59.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="ServiceStack.Text">
<HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
</Reference>
- <Reference Include="System" />
- <Reference Include="System.Core" />
- <Reference Include="System.Data.SQLite, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="System.Data.SQLite">
<HintPath>..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.dll</HintPath>
</Reference>
- <Reference Include="System.Data.SQLite.Linq, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
+ <Reference Include="System.Data.SQLite.Linq">
<HintPath>..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath>
</Reference>
- <Reference Include="System.Drawing" />
- <Reference Include="System.Reactive.Core">
- <HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll</HintPath>
- </Reference>
- <Reference Include="System.Reactive.Interfaces">
- <HintPath>..\packages\Rx-Interfaces.2.1.30214.0\lib\Net45\System.Reactive.Interfaces.dll</HintPath>
- </Reference>
- <Reference Include="System.Reactive.Linq">
- <HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Net45\System.Reactive.Linq.dll</HintPath>
- </Reference>
- <Reference Include="System.Runtime.Serialization" />
- <Reference Include="Microsoft.CSharp" />
- <Reference Include="System.Data" />
- <Reference Include="System.Web" />
- <Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -123,6 +113,7 @@
<Compile Include="EntryPoints\Notifications\RemoteNotifications.cs" />
<Compile Include="EntryPoints\Notifications\WebSocketNotifier.cs" />
<Compile Include="EntryPoints\RefreshUsersMetadata.cs" />
+ <Compile Include="EntryPoints\UdpServerEntryPoint.cs" />
<Compile Include="EntryPoints\WebSocketEvents.cs" />
<Compile Include="HttpServer\HttpResultFactory.cs" />
<Compile Include="HttpServer\HttpServer.cs" />
@@ -183,6 +174,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="Session\SessionWebSocketListener.cs" />
+ <Compile Include="Session\WebSocketController.cs" />
<Compile Include="Sorting\AlbumArtistComparer.cs" />
<Compile Include="Sorting\AlbumComparer.cs" />
<Compile Include="Sorting\AlbumCountComparer.cs" />
@@ -222,19 +214,19 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common.Implementations\MediaBrowser.Common.Implementations.csproj">
- <Project>{c4d2573a-3fd3-441f-81af-174ac4cd4e1d}</Project>
+ <Project>{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}</Project>
<Name>MediaBrowser.Common.Implementations</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
- <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Project>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</Project>
<Name>MediaBrowser.Common</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
- <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project>
<Name>MediaBrowser.Controller</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
- <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+ <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
</ItemGroup>
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
index b24c9a5ca..2f353b8c0 100644
--- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
@@ -57,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <summary>
/// The FF probe resource pool
/// </summary>
- private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(2, 2);
+ private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(1, 1);
public string FFMpegPath { get; private set; }
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
index f4f5f08e4..9c5cf6f1c 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
@@ -333,17 +333,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <returns>Task.</returns>
public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews)
{
- return Task.Run(() =>
+ if (!Directory.Exists(_criticReviewsPath))
{
- if (!Directory.Exists(_criticReviewsPath))
- {
- Directory.CreateDirectory(_criticReviewsPath);
- }
+ Directory.CreateDirectory(_criticReviewsPath);
+ }
- var path = Path.Combine(_criticReviewsPath, itemId + ".json");
+ var path = Path.Combine(_criticReviewsPath, itemId + ".json");
+
+ _jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
- _jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
- });
+ return Task.FromResult(true);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
index 608738f7f..d8872f318 100644
--- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
+++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
@@ -102,6 +102,18 @@ namespace MediaBrowser.Server.Implementations.Providers
using (source)
{
+ // If the file is currently hidden we'll have to remove that or the save will fail
+ var file = new FileInfo(path);
+
+ // This will fail if the file is hidden
+ if (file.Exists)
+ {
+ if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
+ {
+ file.Attributes &= ~FileAttributes.Hidden;
+ }
+ }
+
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
{
await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
index d9b88368b..4829dc405 100644
--- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
+++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
@@ -159,6 +159,10 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
{
previouslyFailedImages = new List<string>();
}
+ catch (DirectoryNotFoundException)
+ {
+ previouslyFailedImages = new List<string>();
+ }
foreach (var video in videos)
{
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index ce757d142..79dfbc8a5 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -6,6 +7,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Session;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -75,6 +77,12 @@ namespace MediaBrowser.Server.Implementations.Session
_userRepository = userRepository;
}
+ private List<ISessionRemoteController> _remoteControllers;
+ public void AddParts(IEnumerable<ISessionRemoteController> remoteControllers)
+ {
+ _remoteControllers = remoteControllers.ToList();
+ }
+
/// <summary>
/// Gets all connections.
/// </summary>
@@ -122,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.Session
var activityDate = DateTime.UtcNow;
var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, user);
-
+
session.LastActivityDate = activityDate;
if (user == null)
@@ -207,25 +215,33 @@ namespace MediaBrowser.Server.Implementations.Session
/// <summary>
/// Used to report that playback has started for an item
/// </summary>
- /// <param name="item">The item.</param>
- /// <param name="sessionId">The session id.</param>
+ /// <param name="info">The info.</param>
/// <returns>Task.</returns>
- /// <exception cref="System.ArgumentNullException"></exception>
- public async Task OnPlaybackStart(BaseItem item, Guid sessionId)
+ /// <exception cref="System.ArgumentNullException">info</exception>
+ public async Task OnPlaybackStart(PlaybackInfo info)
{
- if (item == null)
+ if (info == null)
{
- throw new ArgumentNullException();
+ throw new ArgumentNullException("info");
+ }
+ if (info.SessionId == Guid.Empty)
+ {
+ throw new ArgumentNullException("info");
}
- var session = Sessions.First(i => i.Id.Equals(sessionId));
+ var session = Sessions.First(i => i.Id.Equals(info.SessionId));
+
+ var item = info.Item;
UpdateNowPlayingItem(session, item, false, false);
+ session.CanSeek = info.CanSeek;
+ session.QueueableMediaTypes = info.QueueableMediaTypes;
+
var key = item.GetUserDataKey();
var user = session.User;
-
+
var data = _userDataRepository.GetUserData(user.Id, key);
data.PlayCount++;
@@ -313,7 +329,7 @@ namespace MediaBrowser.Server.Implementations.Session
{
throw new ArgumentOutOfRangeException("positionTicks");
}
-
+
var session = Sessions.First(i => i.Id.Equals(sessionId));
RemoveNowPlayingItem(session, item);
@@ -321,7 +337,7 @@ namespace MediaBrowser.Server.Implementations.Session
var key = item.GetUserDataKey();
var user = session.User;
-
+
var data = _userDataRepository.GetUserData(user.Id, key);
if (positionTicks.HasValue)
@@ -400,5 +416,118 @@ namespace MediaBrowser.Server.Implementations.Session
data.PlaybackPositionTicks = positionTicks;
}
+
+ /// <summary>
+ /// Gets the session for remote control.
+ /// </summary>
+ /// <param name="sessionId">The session id.</param>
+ /// <returns>SessionInfo.</returns>
+ /// <exception cref="ResourceNotFoundException"></exception>
+ private SessionInfo GetSessionForRemoteControl(Guid sessionId)
+ {
+ var session = Sessions.First(i => i.Id.Equals(sessionId));
+
+ if (session == null)
+ {
+ throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
+ }
+
+ if (!session.SupportsRemoteControl)
+ {
+ throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
+ }
+
+ return session;
+ }
+
+ /// <summary>
+ /// Gets the controllers.
+ /// </summary>
+ /// <param name="session">The session.</param>
+ /// <returns>IEnumerable{ISessionRemoteController}.</returns>
+ private IEnumerable<ISessionRemoteController> GetControllers(SessionInfo session)
+ {
+ return _remoteControllers.Where(i => i.Supports(session));
+ }
+
+ /// <summary>
+ /// Sends the system command.
+ /// </summary>
+ /// <param name="sessionId">The session id.</param>
+ /// <param name="command">The command.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ public Task SendSystemCommand(Guid sessionId, SystemCommand command, CancellationToken cancellationToken)
+ {
+ var session = GetSessionForRemoteControl(sessionId);
+
+ var tasks = GetControllers(session).Select(i => i.SendSystemCommand(session, command, cancellationToken));
+
+ return Task.WhenAll(tasks);
+ }
+
+ /// <summary>
+ /// Sends the message command.
+ /// </summary>
+ /// <param name="sessionId">The session id.</param>
+ /// <param name="command">The command.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ public Task SendMessageCommand(Guid sessionId, MessageCommand command, CancellationToken cancellationToken)
+ {
+ var session = GetSessionForRemoteControl(sessionId);
+
+ var tasks = GetControllers(session).Select(i => i.SendMessageCommand(session, command, cancellationToken));
+
+ return Task.WhenAll(tasks);
+ }
+
+ /// <summary>
+ /// Sends the play command.
+ /// </summary>
+ /// <param name="sessionId">The session id.</param>
+ /// <param name="command">The command.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ public Task SendPlayCommand(Guid sessionId, PlayRequest command, CancellationToken cancellationToken)
+ {
+ var session = GetSessionForRemoteControl(sessionId);
+
+ var tasks = GetControllers(session).Select(i => i.SendPlayCommand(session, command, cancellationToken));
+
+ return Task.WhenAll(tasks);
+ }
+
+ /// <summary>
+ /// Sends the browse command.
+ /// </summary>
+ /// <param name="sessionId">The session id.</param>
+ /// <param name="command">The command.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ public Task SendBrowseCommand(Guid sessionId, BrowseRequest command, CancellationToken cancellationToken)
+ {
+ var session = GetSessionForRemoteControl(sessionId);
+
+ var tasks = GetControllers(session).Select(i => i.SendBrowseCommand(session, command, cancellationToken));
+
+ return Task.WhenAll(tasks);
+ }
+
+ /// <summary>
+ /// Sends the playstate command.
+ /// </summary>
+ /// <param name="sessionId">The session id.</param>
+ /// <param name="command">The command.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ public Task SendPlaystateCommand(Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken)
+ {
+ var session = GetSessionForRemoteControl(sessionId);
+
+ var tasks = GetControllers(session).Select(i => i.SendPlaystateCommand(session, command, cancellationToken));
+
+ return Task.WhenAll(tasks);
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
index 2a4361e61..95eb5948f 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -101,16 +101,7 @@ namespace MediaBrowser.Server.Implementations.Session
}
else if (string.Equals(message.MessageType, "PlaybackStart", StringComparison.OrdinalIgnoreCase))
{
- _logger.Debug("Received PlaybackStart message");
-
- var session = _sessionManager.Sessions.FirstOrDefault(i => i.WebSockets.Contains(message.Connection));
-
- if (session != null && session.User != null)
- {
- var item = _dtoService.GetItemByDtoId(message.Data);
-
- _sessionManager.OnPlaybackStart(item, session.Id);
- }
+ ReportPlaybackStart(message);
}
else if (string.Equals(message.MessageType, "PlaybackProgress", StringComparison.OrdinalIgnoreCase))
{
@@ -170,5 +161,46 @@ namespace MediaBrowser.Server.Implementations.Session
return _trueTaskResult;
}
+
+ /// <summary>
+ /// Reports the playback start.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ private void ReportPlaybackStart(WebSocketMessageInfo message)
+ {
+ _logger.Debug("Received PlaybackStart message");
+
+ var session = _sessionManager.Sessions
+ .FirstOrDefault(i => i.WebSockets.Contains(message.Connection));
+
+ if (session != null && session.User != null)
+ {
+ var vals = message.Data.Split('|');
+
+ var item = _dtoService.GetItemByDtoId(vals[0]);
+
+ var queueableMediaTypes = string.Empty;
+ var canSeek = true;
+
+ if (vals.Length > 1)
+ {
+ canSeek = string.Equals(vals[1], "true", StringComparison.OrdinalIgnoreCase);
+ }
+ if (vals.Length > 2)
+ {
+ queueableMediaTypes = vals[2];
+ }
+
+ var info = new PlaybackInfo
+ {
+ CanSeek = canSeek,
+ Item = item,
+ SessionId = session.Id,
+ QueueableMediaTypes = queueableMediaTypes.Split(',').ToList()
+ };
+
+ _sessionManager.OnPlaybackStart(info);
+ }
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs
new file mode 100644
index 000000000..6915cfc64
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs
@@ -0,0 +1,92 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Session;
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Session
+{
+ public class WebSocketController : ISessionRemoteController
+ {
+ public bool Supports(SessionInfo session)
+ {
+ return session.WebSockets.Any(i => i.State == WebSocketState.Open);
+ }
+
+ private IWebSocketConnection GetSocket(SessionInfo session)
+ {
+ var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open);
+
+
+ if (socket == null)
+ {
+ throw new InvalidOperationException("The requested session does not have an open web socket.");
+ }
+
+ return socket;
+ }
+
+ public Task SendSystemCommand(SessionInfo session, SystemCommand command, CancellationToken cancellationToken)
+ {
+ var socket = GetSocket(session);
+
+ return socket.SendAsync(new WebSocketMessage<string>
+ {
+ MessageType = "SystemCommand",
+ Data = command.ToString()
+
+ }, cancellationToken);
+ }
+
+ public Task SendMessageCommand(SessionInfo session, MessageCommand command, CancellationToken cancellationToken)
+ {
+ var socket = GetSocket(session);
+
+ return socket.SendAsync(new WebSocketMessage<MessageCommand>
+ {
+ MessageType = "MessageCommand",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendPlayCommand(SessionInfo session, PlayRequest command, CancellationToken cancellationToken)
+ {
+ var socket = GetSocket(session);
+
+ return socket.SendAsync(new WebSocketMessage<PlayRequest>
+ {
+ MessageType = "Play",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendBrowseCommand(SessionInfo session, BrowseRequest command, CancellationToken cancellationToken)
+ {
+ var socket = GetSocket(session);
+
+ return socket.SendAsync(new WebSocketMessage<BrowseRequest>
+ {
+ MessageType = "Browse",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendPlaystateCommand(SessionInfo session, PlaystateRequest command, CancellationToken cancellationToken)
+ {
+ var socket = GetSocket(session);
+
+ return socket.SendAsync(new WebSocketMessage<PlaystateRequest>
+ {
+ MessageType = "Playstate",
+ Data = command
+
+ }, cancellationToken);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs b/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs
index 958201625..de998254c 100644
--- a/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs
+++ b/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs
@@ -92,7 +92,9 @@ namespace MediaBrowser.Server.Implementations.WebSocket
/// <returns>Task.</returns>
public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken)
{
- return Task.Run(() => UserContext.Send(bytes));
+ UserContext.Send(bytes);
+
+ return Task.FromResult(true);
}
/// <summary>