add subtitle management page

This commit is contained in:
Luke Pulverenti 2014-05-17 00:24:10 -04:00
parent 26aa47eefd
commit c8e4889ac7
21 changed files with 426 additions and 129 deletions

View File

@ -7,7 +7,6 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using ServiceStack;
@ -32,16 +31,6 @@ namespace MediaBrowser.Api
public string Id { get; set; }
}
[Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
[ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Language { get; set; }
}
[Route("/Items/RemoteSearch/Movie", "POST")]
[Api(Description = "Gets external id infos for an item")]
public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
@ -121,24 +110,13 @@ namespace MediaBrowser.Api
private readonly IServerApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
private readonly ISubtitleManager _subtitleManager;
public ItemLookupService(IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem, ILibraryManager libraryManager, ISubtitleManager subtitleManager)
public ItemLookupService(IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem, ILibraryManager libraryManager)
{
_providerManager = providerManager;
_appPaths = appPaths;
_fileSystem = fileSystem;
_libraryManager = libraryManager;
_subtitleManager = subtitleManager;
}
public object Get(SearchRemoteSubtitles request)
{
var video = (Video)_libraryManager.GetItemById(request.Id);
var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result;
return ToOptimizedResult(response);
}
public object Get(GetExternalIdInfos request)

View File

@ -1,5 +1,4 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@ -35,21 +34,6 @@ namespace MediaBrowser.Api.Library
public string Id { get; set; }
}
[Route("/Videos/{Id}/Subtitles/{Index}", "GET")]
[Api(Description = "Gets an external subtitle file")]
public class GetSubtitle
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
[ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
public int Index { get; set; }
}
/// <summary>
/// Class GetCriticReviews
/// </summary>
@ -305,25 +289,6 @@ namespace MediaBrowser.Api.Library
return ToStaticFileResult(item.Path);
}
public object Get(GetSubtitle request)
{
var subtitleStream = _itemRepo.GetMediaStreams(new MediaStreamQuery
{
Index = request.Index,
ItemId = new Guid(request.Id),
Type = MediaStreamType.Subtitle
}).FirstOrDefault();
if (subtitleStream == null)
{
throw new ResourceNotFoundException();
}
return ToStaticFileResult(subtitleStream.Path);
}
/// <summary>
/// Gets the specified request.
/// </summary>

View File

@ -0,0 +1,162 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Library
{
[Route("/Videos/{Id}/Subtitles/{Index}", "GET", Summary = "Gets an external subtitle file")]
public class GetSubtitle
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
[ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
public int Index { get; set; }
}
[Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")]
public class DeleteSubtitle
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
public string Id { get; set; }
[ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")]
public int Index { get; set; }
}
[Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
[ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Language { get; set; }
}
[Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")]
public class GetSubtitleProviders : IReturn<List<SubtitleProviderInfo>>
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
}
[Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")]
public class DownloadRemoteSubtitles : IReturnVoid
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Id { get; set; }
[ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string SubtitleId { get; set; }
}
[Route("/Providers/Subtitles/Subtitles/{Id}", "GET")]
public class GetRemoteSubtitles : IReturnVoid
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
}
public class SubtitleService : BaseApiService
{
private readonly ILibraryManager _libraryManager;
private readonly ISubtitleManager _subtitleManager;
private readonly IItemRepository _itemRepo;
public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_subtitleManager = subtitleManager;
_itemRepo = itemRepo;
}
public object Get(SearchRemoteSubtitles request)
{
var video = (Video)_libraryManager.GetItemById(request.Id);
var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result;
return ToOptimizedResult(response);
}
public object Get(GetSubtitle request)
{
var subtitleStream = _itemRepo.GetMediaStreams(new MediaStreamQuery
{
Index = request.Index,
ItemId = new Guid(request.Id),
Type = MediaStreamType.Subtitle
}).FirstOrDefault();
if (subtitleStream == null)
{
throw new ResourceNotFoundException();
}
return ToStaticFileResult(subtitleStream.Path);
}
public void Delete(DeleteSubtitle request)
{
var task = _subtitleManager.DeleteSubtitles(request.Id, request.Index);
Task.WaitAll(task);
}
public object Get(GetSubtitleProviders request)
{
var result = _subtitleManager.GetProviders(request.Id);
return ToOptimizedResult(result);
}
public object Get(GetRemoteSubtitles request)
{
var result = _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).Result;
return ResultFactory.GetResult(result.Stream, MimeTypes.GetMimeType("file." + result.Format));
}
public void Post(DownloadRemoteSubtitles request)
{
var video = (Video)_libraryManager.GetItemById(request.Id);
Task.Run(async () =>
{
try
{
await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None)
.ConfigureAwait(false);
await video.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.ErrorException("Error downloading subtitles", ex);
}
});
}
}
}

View File

@ -68,6 +68,7 @@
<Compile Include="ChannelService.cs" />
<Compile Include="Dlna\DlnaServerService.cs" />
<Compile Include="Dlna\DlnaService.cs" />
<Compile Include="Library\SubtitleService.cs" />
<Compile Include="Movies\CollectionService.cs" />
<Compile Include="Music\AlbumsService.cs" />
<Compile Include="AppThemeService.cs" />

View File

@ -34,17 +34,22 @@ namespace MediaBrowser.Controller.Providers
/// <summary>
/// Providers will be executed based on default rules
/// </summary>
EnsureMetadata,
EnsureMetadata = 0,
/// <summary>
/// No providers will be executed
/// </summary>
None,
None = 1,
/// <summary>
/// All providers will be executed to search for new metadata
/// </summary>
FullRefresh
FullRefresh = 2,
/// <summary>
/// The validation only
/// </summary>
ValidationOnly = 3
}
public enum ImageRefreshMode
@ -52,16 +57,16 @@ namespace MediaBrowser.Controller.Providers
/// <summary>
/// The default
/// </summary>
Default,
Default = 0,
/// <summary>
/// Existing images will be validated
/// </summary>
ValidationOnly,
ValidationOnly = 1,
/// <summary>
/// All providers will be executed to search for new metadata
/// </summary>
FullRefresh
FullRefresh = 2
}
}

View File

@ -39,12 +39,33 @@ namespace MediaBrowser.Controller.Subtitles
/// </summary>
/// <param name="video">The video.</param>
/// <param name="subtitleId">The subtitle identifier.</param>
/// <param name="providerName">Name of the provider.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task DownloadSubtitles(Video video,
string subtitleId,
string providerName,
CancellationToken cancellationToken);
/// <summary>
/// Gets the remote subtitles.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{SubtitleResponse}.</returns>
Task<SubtitleResponse> GetRemoteSubtitles(string id, CancellationToken cancellationToken);
/// <summary>
/// Deletes the subtitles.
/// </summary>
/// <param name="itemId">The item identifier.</param>
/// <param name="index">The index.</param>
/// <returns>Task.</returns>
Task DeleteSubtitles(string itemId, int index);
/// <summary>
/// Gets the providers.
/// </summary>
/// <param name="itemId">The item identifier.</param>
/// <returns>IEnumerable{SubtitleProviderInfo}.</returns>
IEnumerable<SubtitleProviderInfo> GetProviders(string itemId);
}
}

View File

@ -6,6 +6,7 @@ namespace MediaBrowser.Controller.Subtitles
{
public string Language { get; set; }
public string Format { get; set; }
public bool IsForced { get; set; }
public Stream Stream { get; set; }
}
}

View File

@ -21,8 +21,11 @@ namespace MediaBrowser.Controller.Subtitles
public long? RuntimeTicks { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
public bool SearchAllProviders { get; set; }
public SubtitleSearchRequest()
{
SearchAllProviders = true;
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
namespace MediaBrowser.Model.Entities
{

View File

@ -16,4 +16,10 @@ namespace MediaBrowser.Model.Providers
public int? DownloadCount { get; set; }
public bool? IsHashMatch { get; set; }
}
public class SubtitleProviderInfo
{
public string Name { get; set; }
public string Id { get; set; }
}
}

View File

@ -142,7 +142,7 @@ namespace MediaBrowser.Providers.MediaInfo
var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager);
return prober.ProbeVideo(item, directoryService, cancellationToken);
return prober.ProbeVideo(item, directoryService, true, cancellationToken);
}
public Task<ItemUpdateType> FetchAudioInfo<T>(T item, CancellationToken cancellationToken)

View File

@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.MediaInfo
_subtitleManager = subtitleManager;
}
public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, CancellationToken cancellationToken)
public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, bool enableSubtitleDownloading, CancellationToken cancellationToken)
where T : Video
{
var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);
@ -105,7 +105,7 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested();
await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, directoryService).ConfigureAwait(false);
await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, directoryService, enableSubtitleDownloading).ConfigureAwait(false);
}
finally
@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.MediaInfo
return result;
}
protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, BlurayDiscInfo blurayInfo, IDirectoryService directoryService)
protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, BlurayDiscInfo blurayInfo, IDirectoryService directoryService, bool enableSubtitleDownloading)
{
var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data);
var mediaStreams = mediaInfo.MediaStreams;
@ -208,7 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo
FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
}
await AddExternalSubtitles(video, mediaStreams, directoryService, cancellationToken).ConfigureAwait(false);
await AddExternalSubtitles(video, mediaStreams, directoryService, enableSubtitleDownloading, cancellationToken).ConfigureAwait(false);
FetchWtvInfo(video, data);
@ -416,13 +416,17 @@ namespace MediaBrowser.Providers.MediaInfo
/// </summary>
/// <param name="video">The video.</param>
/// <param name="currentStreams">The current streams.</param>
private async Task AddExternalSubtitles(Video video, List<MediaStream> currentStreams, IDirectoryService directoryService, CancellationToken cancellationToken)
/// <param name="directoryService">The directory service.</param>
/// <param name="enableSubtitleDownloading">if set to <c>true</c> [enable subtitle downloading].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
private async Task AddExternalSubtitles(Video video, List<MediaStream> currentStreams, IDirectoryService directoryService, bool enableSubtitleDownloading, CancellationToken cancellationToken)
{
var subtitleResolver = new SubtitleResolver(_localization);
var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, directoryService, false).ToList();
if ((_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles &&
if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles &&
video is Episode) ||
(_config.Configuration.SubtitleOptions.DownloadMovieSubtitles &&
video is Movie))

View File

@ -124,7 +124,10 @@ namespace MediaBrowser.Providers.MediaInfo
Name = video.Name,
ParentIndexNumber = video.ParentIndexNumber,
ProductionYear = video.ProductionYear,
ProviderIds = video.ProviderIds
ProviderIds = video.ProviderIds,
// Stop as soon as we find something
SearchAllProviders = false
};
var episode = video as Episode;
@ -143,7 +146,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (result != null)
{
await _subtitleManager.DownloadSubtitles(video, result.Id, result.ProviderName, cancellationToken)
await _subtitleManager.DownloadSubtitles(video, result.Id, cancellationToken)
.ConfigureAwait(false);
return true;

View File

@ -1,8 +1,10 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Entities;
@ -23,12 +25,16 @@ namespace MediaBrowser.Providers.Subtitles
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _monitor;
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepo;
public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor)
public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor, ILibraryManager libraryManager, IItemRepository itemRepo)
{
_logger = logger;
_fileSystem = fileSystem;
_monitor = monitor;
_libraryManager = libraryManager;
_itemRepo = itemRepo;
}
public void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders)
@ -38,15 +44,45 @@ namespace MediaBrowser.Providers.Subtitles
public async Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken)
{
var contentType = request.ContentType;
var providers = _subtitleProviders
.Where(i => i.SupportedMediaTypes.Contains(request.ContentType))
.Where(i => i.SupportedMediaTypes.Contains(contentType))
.ToList();
// If not searching all, search one at a time until something is found
if (!request.SearchAllProviders)
{
foreach (var provider in providers)
{
try
{
var searchResults = await provider.Search(request, cancellationToken).ConfigureAwait(false);
var list = searchResults.ToList();
if (list.Count > 0)
{
Normalize(list);
return list;
}
}
catch (Exception ex)
{
_logger.ErrorException("Error downloading subtitles from {0}", ex, provider.Name);
}
}
return new List<RemoteSubtitleInfo>();
}
var tasks = providers.Select(async i =>
{
try
{
return await i.Search(request, cancellationToken).ConfigureAwait(false);
var searchResults = await i.Search(request, cancellationToken).ConfigureAwait(false);
var list = searchResults.ToList();
Normalize(list);
return list;
}
catch (Exception ex)
{
@ -62,17 +98,21 @@ namespace MediaBrowser.Providers.Subtitles
public async Task DownloadSubtitles(Video video,
string subtitleId,
string providerName,
CancellationToken cancellationToken)
{
var provider = _subtitleProviders.First(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
var response = await provider.GetSubtitles(subtitleId, cancellationToken).ConfigureAwait(false);
var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false);
using (var stream = response.Stream)
{
var savePath = Path.Combine(Path.GetDirectoryName(video.Path),
Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower() + "." + response.Format.ToLower());
var savePath = Path.Combine(Path.GetDirectoryName(video.Path),
Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower());
if (response.IsForced)
{
savePath += ".forced";
}
savePath += "." + response.Format.ToLower();
_logger.Info("Saving subtitles to {0}", savePath);
@ -139,5 +179,93 @@ namespace MediaBrowser.Providers.Subtitles
return SearchSubtitles(request, cancellationToken);
}
private void Normalize(IEnumerable<RemoteSubtitleInfo> subtitles)
{
foreach (var sub in subtitles)
{
sub.Id = GetProviderId(sub.ProviderName) + "_" + sub.Id;
}
}
private string GetProviderId(string name)
{
return name.ToLower().GetMD5().ToString("N");
}
private ISubtitleProvider GetProvider(string id)
{
return _subtitleProviders.First(i => string.Equals(id, GetProviderId(i.Name)));
}
public Task DeleteSubtitles(string itemId, int index)
{
var stream = _itemRepo.GetMediaStreams(new MediaStreamQuery
{
Index = index,
ItemId = new Guid(itemId),
Type = MediaStreamType.Subtitle
}).First();
var path = stream.Path;
_monitor.ReportFileSystemChangeBeginning(path);
try
{
File.Delete(path);
}
finally
{
_monitor.ReportFileSystemChangeComplete(path, false);
}
return _libraryManager.GetItemById(itemId).RefreshMetadata(new MetadataRefreshOptions
{
ImageRefreshMode = ImageRefreshMode.ValidationOnly,
MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
}, CancellationToken.None);
}
public Task<SubtitleResponse> GetRemoteSubtitles(string id, CancellationToken cancellationToken)
{
var parts = id.Split(new[] { '_' }, 2);
var provider = GetProvider(parts.First());
id = parts.Last();
return provider.GetSubtitles(id, cancellationToken);
}
public IEnumerable<SubtitleProviderInfo> GetProviders(string itemId)
{
var video = _libraryManager.GetItemById(itemId) as Video;
VideoContentType mediaType;
if (video is Episode)
{
mediaType = VideoContentType.Episode;
}
else if (video is Movie)
{
mediaType = VideoContentType.Movie;
}
else
{
// These are the only supported types
return new List<SubtitleProviderInfo>();
}
var providers = _subtitleProviders
.Where(i => i.SupportedMediaTypes.Contains(mediaType))
.ToList();
return providers.Select(i => new SubtitleProviderInfo
{
Name = i.Name,
Id = GetProviderId(i.Name)
});
}
}
}

View File

@ -44,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
/// <summary>
/// The library update duration
/// </summary>
private const int LibraryUpdateDuration = 20000;
private const int LibraryUpdateDuration = 5000;
public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger)
{

View File

@ -759,5 +759,7 @@
"LabelEpisodeNumber": "Episode number",
"LabelEndingEpisodeNumber": "Ending episode number",
"HeaderTypeText": "Enter Text",
"LabelTypeText": "Text"
"LabelTypeText": "Text",
"HeaderSearchForSubtitles": "Search for Subtitles",
"MessageNoSubtitleSearchResultsFound": "No search results founds."
}

View File

@ -138,7 +138,7 @@ namespace MediaBrowser.Server.Implementations.Session
if (controller == null)
{
controller = new WebSocketController(session, _appHost);
controller = new WebSocketController(session, _appHost, _logger);
}
controller.Sockets.Add(message.Connection);

View File

@ -2,6 +2,7 @@
using MediaBrowser.Controller;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.System;
@ -19,11 +20,13 @@ namespace MediaBrowser.Server.Implementations.Session
public List<IWebSocketConnection> Sockets { get; private set; }
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
public WebSocketController(SessionInfo session, IServerApplicationHost appHost)
public WebSocketController(SessionInfo session, IServerApplicationHost appHost, ILogger logger)
{
Session = session;
_appHost = appHost;
_logger = logger;
Sockets = new List<IWebSocketConnection>();
}
@ -35,11 +38,17 @@ namespace MediaBrowser.Server.Implementations.Session
}
}
private IEnumerable<IWebSocketConnection> GetActiveSockets()
{
return Sockets
.OrderByDescending(i => i.LastActivityDate)
.Where(i => i.State == WebSocketState.Open);
}
private IWebSocketConnection GetActiveSocket()
{
var socket = Sockets
.OrderByDescending(i => i.LastActivityDate)
.FirstOrDefault(i => i.State == WebSocketState.Open);
var socket = GetActiveSockets()
.FirstOrDefault();
if (socket == null)
{
@ -51,9 +60,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<PlayRequest>
return SendMessage(new WebSocketMessage<PlayRequest>
{
MessageType = "Play",
Data = command
@ -63,9 +70,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<PlaystateRequest>
return SendMessage(new WebSocketMessage<PlaystateRequest>
{
MessageType = "Playstate",
Data = command
@ -75,9 +80,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<LibraryUpdateInfo>
return SendMessages(new WebSocketMessage<LibraryUpdateInfo>
{
MessageType = "LibraryChanged",
Data = info
@ -92,9 +95,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns>
public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<SystemInfo>
return SendMessages(new WebSocketMessage<SystemInfo>
{
MessageType = "RestartRequired",
Data = _appHost.GetSystemInfo()
@ -111,9 +112,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns>
public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<UserDataChangeInfo>
return SendMessages(new WebSocketMessage<UserDataChangeInfo>
{
MessageType = "UserDataChanged",
Data = info
@ -128,9 +127,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns>
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<string>
return SendMessages(new WebSocketMessage<string>
{
MessageType = "ServerShuttingDown",
Data = string.Empty
@ -145,9 +142,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns>
public Task SendServerRestartNotification(CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<string>
return SendMessages(new WebSocketMessage<string>
{
MessageType = "ServerRestarting",
Data = string.Empty
@ -157,9 +152,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<GeneralCommand>
return SendMessage(new WebSocketMessage<GeneralCommand>
{
MessageType = "GeneralCommand",
Data = command
@ -169,9 +162,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
return SendMessages(new WebSocketMessage<SessionInfoDto>
{
MessageType = "SessionEnded",
Data = sessionInfo
@ -181,9 +172,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
return SendMessages(new WebSocketMessage<SessionInfoDto>
{
MessageType = "PlaybackStart",
Data = sessionInfo
@ -193,14 +182,37 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
return SendMessages(new WebSocketMessage<SessionInfoDto>
{
MessageType = "PlaybackStopped",
Data = sessionInfo
}, cancellationToken);
}
private Task SendMessage<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
{
var socket = GetActiveSocket();
return socket.SendAsync(message, cancellationToken);
}
private Task SendMessages<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
{
var tasks = GetActiveSockets().Select(i => Task.Run(async () =>
{
try
{
await i.SendAsync(message, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error sending web socket message", ex);
}
}, cancellationToken));
return Task.WhenAll(tasks);
}
}
}

View File

@ -537,7 +537,7 @@ namespace MediaBrowser.ServerApplication
RegisterSingleInstance<IEncryptionManager>(new EncryptionManager());
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor);
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, ItemRepository);
RegisterSingleInstance(SubtitleManager);
var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));

View File

@ -550,6 +550,7 @@ namespace MediaBrowser.WebDashboard.Api
"editcollectionitems.js",
"edititemmetadata.js",
"edititemimages.js",
"edititemsubtitles.js",
"encodingsettings.js",
"gamesrecommendedpage.js",
"gamesystemspage.js",

View File

@ -307,6 +307,9 @@
<Content Include="dashboard-ui\editcollectionitems.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\edititemsubtitles.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\encodingsettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -616,6 +619,9 @@
<Content Include="dashboard-ui\scripts\editcollectionitems.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\edititemsubtitles.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\encodingsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>