Merge pull request #1043 from MediaBrowser/dev

3.0.5557.0
This commit is contained in:
Luke 2015-03-19 13:21:35 -04:00
commit 9926be0d9d
426 changed files with 12029 additions and 5306 deletions

View File

@ -132,6 +132,7 @@ namespace MediaBrowser.Api
/// Called when [transcode beginning]. /// Called when [transcode beginning].
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <param name="streamId">The stream identifier.</param>
/// <param name="transcodingJobId">The transcoding job identifier.</param> /// <param name="transcodingJobId">The transcoding job identifier.</param>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="process">The process.</param> /// <param name="process">The process.</param>
@ -140,6 +141,7 @@ namespace MediaBrowser.Api
/// <param name="cancellationTokenSource">The cancellation token source.</param> /// <param name="cancellationTokenSource">The cancellation token source.</param>
/// <returns>TranscodingJob.</returns> /// <returns>TranscodingJob.</returns>
public TranscodingJob OnTranscodeBeginning(string path, public TranscodingJob OnTranscodeBeginning(string path,
string streamId,
string transcodingJobId, string transcodingJobId,
TranscodingJobType type, TranscodingJobType type,
Process process, Process process,
@ -157,7 +159,8 @@ namespace MediaBrowser.Api
ActiveRequestCount = 1, ActiveRequestCount = 1,
DeviceId = deviceId, DeviceId = deviceId,
CancellationTokenSource = cancellationTokenSource, CancellationTokenSource = cancellationTokenSource,
Id = transcodingJobId Id = transcodingJobId,
StreamId = streamId
}; };
_activeTranscodingJobs.Add(job); _activeTranscodingJobs.Add(job);
@ -316,17 +319,26 @@ namespace MediaBrowser.Api
/// Kills the single transcoding job. /// Kills the single transcoding job.
/// </summary> /// </summary>
/// <param name="deviceId">The device id.</param> /// <param name="deviceId">The device id.</param>
/// <param name="streamId">The stream identifier.</param>
/// <param name="deleteFiles">The delete files.</param> /// <param name="deleteFiles">The delete files.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">deviceId</exception> internal void KillTranscodingJobs(string deviceId, string streamId, Func<string, bool> deleteFiles)
internal void KillTranscodingJobs(string deviceId, Func<string, bool> deleteFiles)
{ {
if (string.IsNullOrEmpty(deviceId)) if (string.IsNullOrEmpty(deviceId))
{ {
throw new ArgumentNullException("deviceId"); throw new ArgumentNullException("deviceId");
} }
KillTranscodingJobs(j => string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase), deleteFiles); KillTranscodingJobs(j =>
{
if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase))
{
return string.IsNullOrWhiteSpace(streamId) || string.Equals(streamId, j.StreamId, StringComparison.OrdinalIgnoreCase);
}
return false;
}, deleteFiles);
} }
/// <summary> /// <summary>
@ -335,7 +347,7 @@ namespace MediaBrowser.Api
/// <param name="killJob">The kill job.</param> /// <param name="killJob">The kill job.</param>
/// <param name="deleteFiles">The delete files.</param> /// <param name="deleteFiles">The delete files.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
internal void KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles) private void KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles)
{ {
var jobs = new List<TranscodingJob>(); var jobs = new List<TranscodingJob>();
@ -516,6 +528,11 @@ namespace MediaBrowser.Api
/// </summary> /// </summary>
public class TranscodingJob public class TranscodingJob
{ {
/// <summary>
/// Gets or sets the stream identifier.
/// </summary>
/// <value>The stream identifier.</value>
public string StreamId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the path. /// Gets or sets the path.
/// </summary> /// </summary>

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Dto; using System.Threading.Tasks;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -72,6 +73,29 @@ namespace MediaBrowser.Api
return ResultFactory.GetOptimizedResultUsingCache(Request, cacheKey, lastDateModified, cacheDuration, factoryFn); return ResultFactory.GetOptimizedResultUsingCache(Request, cacheKey, lastDateModified, cacheDuration, factoryFn);
} }
protected void AssertCanUpdateUser(IUserManager userManager, string userId)
{
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
var authenticatedUser = userManager.GetUserById(auth.UserId);
// If they're going to update the record of another user, they must be an administrator
if (!string.Equals(userId, auth.UserId, StringComparison.OrdinalIgnoreCase))
{
if (!authenticatedUser.Policy.IsAdministrator)
{
throw new SecurityException("Unauthorized access.");
}
}
else
{
if (!authenticatedUser.Policy.EnableUserPreferenceAccess)
{
throw new SecurityException("Unauthorized access.");
}
}
}
/// <summary> /// <summary>
/// To the optimized serialized result using cache. /// To the optimized serialized result using cache.
/// </summary> /// </summary>
@ -88,9 +112,9 @@ namespace MediaBrowser.Api
/// Gets the session. /// Gets the session.
/// </summary> /// </summary>
/// <returns>SessionInfo.</returns> /// <returns>SessionInfo.</returns>
protected SessionInfo GetSession() protected async Task<SessionInfo> GetSession()
{ {
var session = SessionContext.GetSession(Request); var session = await SessionContext.GetSession(Request).ConfigureAwait(false);
if (session == null) if (session == null)
{ {

View File

@ -1,8 +1,10 @@
using MediaBrowser.Common.Extensions; using System;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Connect; using MediaBrowser.Controller.Connect;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Connect; using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Dto;
using ServiceStack; using ServiceStack;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -73,6 +75,28 @@ namespace MediaBrowser.Api
public string ConnectUserId { get; set; } public string ConnectUserId { get; set; }
} }
[Route("/Connect/Supporters", "GET")]
[Authenticated(Roles = "Admin")]
public class GetConnectSupporterSummary : IReturn<ConnectSupporterSummary>
{
}
[Route("/Connect/Supporters", "DELETE")]
[Authenticated(Roles = "Admin")]
public class RemoveConnectSupporter : IReturnVoid
{
[ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
public string Id { get; set; }
}
[Route("/Connect/Supporters", "POST")]
[Authenticated(Roles = "Admin")]
public class AddConnectSupporter : IReturnVoid
{
[ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string Id { get; set; }
}
public class ConnectService : BaseApiService public class ConnectService : BaseApiService
{ {
private readonly IConnectManager _connectManager; private readonly IConnectManager _connectManager;
@ -84,6 +108,35 @@ namespace MediaBrowser.Api
_userManager = userManager; _userManager = userManager;
} }
public async Task<object> Get(GetConnectSupporterSummary request)
{
var result = await _connectManager.GetConnectSupporterSummary().ConfigureAwait(false);
var existingConnectUserIds = result.Users.Select(i => i.Id).ToList();
result.EligibleUsers = _userManager.Users
.Where(i => !string.IsNullOrWhiteSpace(i.ConnectUserId))
.Where(i => !existingConnectUserIds.Contains(i.ConnectUserId, StringComparer.OrdinalIgnoreCase))
.OrderBy(i => i.Name)
.Select(i => _userManager.GetUserDto(i))
.ToList();
return ToOptimizedResult(result);
}
public void Delete(RemoveConnectSupporter request)
{
var task = _connectManager.RemoveConnectSupporter(request.Id);
Task.WaitAll(task);
}
public void Post(AddConnectSupporter request)
{
var task = _connectManager.AddConnectSupporter(request.Id);
Task.WaitAll(task);
}
public object Post(CreateConnectLink request) public object Post(CreateConnectLink request)
{ {
return _connectManager.LinkUser(request.Id, request.ConnectUsername); return _connectManager.LinkUser(request.Id, request.ConnectUsername);

View File

@ -56,7 +56,7 @@ namespace MediaBrowser.Api.Images
/// Class UpdateItemImageIndex /// Class UpdateItemImageIndex
/// </summary> /// </summary>
[Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST", Summary = "Updates the index for an item image")] [Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST", Summary = "Updates the index for an item image")]
[Authenticated] [Authenticated(Roles = "admin")]
public class UpdateItemImageIndex : IReturnVoid public class UpdateItemImageIndex : IReturnVoid
{ {
/// <summary> /// <summary>
@ -64,7 +64,7 @@ namespace MediaBrowser.Api.Images
/// </summary> /// </summary>
/// <value>The id.</value> /// <value>The id.</value>
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public Guid Id { get; set; } public string Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the type of the image. /// Gets or sets the type of the image.
@ -143,7 +143,7 @@ namespace MediaBrowser.Api.Images
/// </summary> /// </summary>
/// <value>The id.</value> /// <value>The id.</value>
[ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public Guid Id { get; set; } public string Id { get; set; }
} }
/// <summary> /// <summary>
@ -151,7 +151,7 @@ namespace MediaBrowser.Api.Images
/// </summary> /// </summary>
[Route("/Items/{Id}/Images/{Type}", "DELETE")] [Route("/Items/{Id}/Images/{Type}", "DELETE")]
[Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")] [Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")]
[Authenticated] [Authenticated(Roles = "admin")]
public class DeleteItemImage : DeleteImageRequest, IReturnVoid public class DeleteItemImage : DeleteImageRequest, IReturnVoid
{ {
/// <summary> /// <summary>
@ -159,7 +159,7 @@ namespace MediaBrowser.Api.Images
/// </summary> /// </summary>
/// <value>The id.</value> /// <value>The id.</value>
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
public Guid Id { get; set; } public string Id { get; set; }
} }
/// <summary> /// <summary>
@ -175,7 +175,7 @@ namespace MediaBrowser.Api.Images
/// </summary> /// </summary>
/// <value>The id.</value> /// <value>The id.</value>
[ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
public Guid Id { get; set; } public string Id { get; set; }
} }
/// <summary> /// <summary>
@ -191,7 +191,7 @@ namespace MediaBrowser.Api.Images
/// </summary> /// </summary>
/// <value>The id.</value> /// <value>The id.</value>
[ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public Guid Id { get; set; } public string Id { get; set; }
/// <summary> /// <summary>
/// The raw Http Request Input Stream /// The raw Http Request Input Stream
@ -206,7 +206,7 @@ namespace MediaBrowser.Api.Images
[Route("/Items/{Id}/Images/{Type}", "POST")] [Route("/Items/{Id}/Images/{Type}", "POST")]
[Route("/Items/{Id}/Images/{Type}/{Index}", "POST")] [Route("/Items/{Id}/Images/{Type}/{Index}", "POST")]
[Api(Description = "Posts an item image")] [Api(Description = "Posts an item image")]
[Authenticated] [Authenticated(Roles = "admin")]
public class PostItemImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid public class PostItemImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
{ {
/// <summary> /// <summary>
@ -318,7 +318,7 @@ namespace MediaBrowser.Api.Images
try try
{ {
var size = _imageProcessor.GetImageSize(info.Path, info.DateModified); var size = _imageProcessor.GetImageSize(info);
width = Convert.ToInt32(size.Width); width = Convert.ToInt32(size.Width);
height = Convert.ToInt32(size.Height); height = Convert.ToInt32(size.Height);
@ -417,11 +417,12 @@ namespace MediaBrowser.Api.Images
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
public void Post(PostUserImage request) public void Post(PostUserImage request)
{ {
var id = new Guid(GetPathValue(1)); var userId = GetPathValue(1);
AssertCanUpdateUser(_userManager, userId);
request.Type = (ImageType)Enum.Parse(typeof(ImageType), GetPathValue(3), true); request.Type = (ImageType)Enum.Parse(typeof(ImageType), GetPathValue(3), true);
var item = _userManager.GetUserById(id); var item = _userManager.GetUserById(userId);
var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType); var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType);
@ -434,7 +435,7 @@ namespace MediaBrowser.Api.Images
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
public void Post(PostItemImage request) public void Post(PostItemImage request)
{ {
var id = new Guid(GetPathValue(1)); var id = GetPathValue(1);
request.Type = (ImageType)Enum.Parse(typeof(ImageType), GetPathValue(3), true); request.Type = (ImageType)Enum.Parse(typeof(ImageType), GetPathValue(3), true);
@ -451,7 +452,10 @@ namespace MediaBrowser.Api.Images
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
public void Delete(DeleteUserImage request) public void Delete(DeleteUserImage request)
{ {
var item = _userManager.GetUserById(request.Id); var userId = request.Id;
AssertCanUpdateUser(_userManager, userId);
var item = _userManager.GetUserById(userId);
var task = item.DeleteImage(request.Type, request.Index ?? 0); var task = item.DeleteImage(request.Type, request.Index ?? 0);
@ -492,7 +496,6 @@ namespace MediaBrowser.Api.Images
/// <param name="currentIndex">Index of the current.</param> /// <param name="currentIndex">Index of the current.</param>
/// <param name="newIndex">The new index.</param> /// <param name="newIndex">The new index.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
/// <exception cref="System.ArgumentException">The change index operation is only applicable to backdrops and screenshots</exception>
private Task UpdateItemIndex(IHasImages item, ImageType type, int currentIndex, int newIndex) private Task UpdateItemIndex(IHasImages item, ImageType type, int currentIndex, int newIndex)
{ {
return item.SwapImages(type, currentIndex, newIndex); return item.SwapImages(type, currentIndex, newIndex);

View File

@ -200,24 +200,15 @@ namespace MediaBrowser.Api
//} //}
item.ProviderIds = request.ProviderIds; item.ProviderIds = request.ProviderIds;
var service = new ItemRefreshService(_libraryManager) var task = _providerManager.RefreshFullItem(item, new MetadataRefreshOptions
{ {
Logger = Logger,
Request = Request,
ResultFactory = ResultFactory,
SessionContext = SessionContext,
AuthorizationContext = AuthorizationContext
};
service.Post(new RefreshItem
{
Id = request.Id,
MetadataRefreshMode = MetadataRefreshMode.FullRefresh, MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
ImageRefreshMode = ImageRefreshMode.FullRefresh, ImageRefreshMode = ImageRefreshMode.FullRefresh,
ReplaceAllMetadata = true, ReplaceAllMetadata = true,
ReplaceAllImages = request.ReplaceAllImages, ReplaceAllImages = request.ReplaceAllImages
Recursive = true
}); }, CancellationToken.None);
Task.WaitAll(task);
} }
/// <summary> /// <summary>

View File

@ -1,13 +1,7 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using ServiceStack; using ServiceStack;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Api namespace MediaBrowser.Api
{ {
@ -40,41 +34,12 @@ namespace MediaBrowser.Api
public class ItemRefreshService : BaseApiService public class ItemRefreshService : BaseApiService
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IProviderManager _providerManager;
public ItemRefreshService(ILibraryManager libraryManager) public ItemRefreshService(ILibraryManager libraryManager, IProviderManager providerManager)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
} _providerManager = providerManager;
private async Task RefreshArtist(RefreshItem request, MusicArtist item)
{
var cancellationToken = CancellationToken.None;
var albums = _libraryManager.RootFolder
.GetRecursiveChildren()
.OfType<MusicAlbum>()
.Where(i => i.HasArtist(item.Name))
.ToList();
var musicArtists = albums
.Select(i => i.Parent)
.OfType<MusicArtist>()
.ToList();
var options = GetRefreshOptions(request);
var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), cancellationToken, options, true));
await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);
try
{
await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.ErrorException("Error refreshing library", ex);
}
} }
/// <summary> /// <summary>
@ -85,68 +50,9 @@ namespace MediaBrowser.Api
{ {
var item = _libraryManager.GetItemById(request.Id); var item = _libraryManager.GetItemById(request.Id);
var task = item is MusicArtist ? RefreshArtist(request, (MusicArtist)item) : RefreshItem(request, item);
Task.WaitAll(task);
}
/// <summary>
/// Refreshes the item.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>Task.</returns>
private async Task RefreshItem(RefreshItem request, BaseItem item)
{
var options = GetRefreshOptions(request); var options = GetRefreshOptions(request);
try _providerManager.QueueRefresh(item.Id, options);
{
await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
if (item.IsFolder)
{
// Collection folders don't validate their children so we'll have to simulate that here
var collectionFolder = item as CollectionFolder;
if (collectionFolder != null)
{
await RefreshCollectionFolderChildren(request, collectionFolder).ConfigureAwait(false);
}
else
{
var folder = (Folder)item;
await folder.ValidateChildren(new Progress<double>(), CancellationToken.None, options, request.Recursive).ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
Logger.ErrorException("Error refreshing library", ex);
}
}
/// <summary>
/// Refreshes the collection folder children.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="collectionFolder">The collection folder.</param>
/// <returns>Task.</returns>
private async Task RefreshCollectionFolderChildren(RefreshItem request, CollectionFolder collectionFolder)
{
var options = GetRefreshOptions(request);
foreach (var child in collectionFolder.Children.ToList())
{
await child.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
if (child.IsFolder)
{
var folder = (Folder)child;
await folder.ValidateChildren(new Progress<double>(), CancellationToken.None, options, request.Recursive).ConfigureAwait(false);
}
}
} }
private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request) private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request)

View File

@ -42,7 +42,7 @@ namespace MediaBrowser.Api
public string ContentType { get; set; } public string ContentType { get; set; }
} }
[Authenticated] [Authenticated(Roles = "admin")]
public class ItemUpdateService : BaseApiService public class ItemUpdateService : BaseApiService
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@ -389,20 +389,33 @@ namespace MediaBrowser.Api
game.PlayersSupported = request.Players; game.PlayersSupported = request.Players;
} }
var song = item as Audio; var hasAlbumArtists = item as IHasAlbumArtist;
if (hasAlbumArtists != null)
{
hasAlbumArtists.AlbumArtists = request
.AlbumArtists
.Select(i => i.Name)
.ToList();
}
var hasArtists = item as IHasArtist;
if (hasArtists != null)
{
hasArtists.Artists = request
.ArtistItems
.Select(i => i.Name)
.ToList();
}
var song = item as Audio;
if (song != null) if (song != null)
{ {
song.Album = request.Album; song.Album = request.Album;
song.AlbumArtists = string.IsNullOrWhiteSpace(request.AlbumArtist) ? new List<string>() : new List<string> { request.AlbumArtist };
song.Artists = request.Artists.ToList();
} }
var musicVideo = item as MusicVideo; var musicVideo = item as MusicVideo;
if (musicVideo != null) if (musicVideo != null)
{ {
musicVideo.Artists = request.Artists.ToList();
musicVideo.Album = request.Album; musicVideo.Album = request.Album;
} }

View File

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Activity; using MediaBrowser.Controller.Activity;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -9,11 +8,9 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Activity; using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using ServiceStack; using ServiceStack;
using System; using System;

View File

@ -212,24 +212,26 @@ namespace MediaBrowser.Api.Library
File.Create(path); File.Create(path);
} }
}
finally
{
Task.Run(() =>
{
// No need to start if scanning the library because it will handle it
if (request.RefreshLibrary)
{
_libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
}
else
{
// Need to add a delay here or directory watchers may still pick up the changes // Need to add a delay here or directory watchers may still pick up the changes
var task = Task.Delay(1000); var task = Task.Delay(1000);
// Have to block here to allow exceptions to bubble // Have to block here to allow exceptions to bubble
Task.WaitAll(task); Task.WaitAll(task);
}
finally
{
// No need to start if scanning the library because it will handle it
if (!request.RefreshLibrary)
{
_libraryMonitor.Start(); _libraryMonitor.Start();
} }
} });
if (request.RefreshLibrary)
{
_libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
} }
} }
@ -279,24 +281,26 @@ namespace MediaBrowser.Api.Library
} }
Directory.Move(currentPath, newPath); Directory.Move(currentPath, newPath);
}
finally
{
Task.Run(() =>
{
// No need to start if scanning the library because it will handle it
if (request.RefreshLibrary)
{
_libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
}
else
{
// Need to add a delay here or directory watchers may still pick up the changes // Need to add a delay here or directory watchers may still pick up the changes
var task = Task.Delay(1000); var task = Task.Delay(1000);
// Have to block here to allow exceptions to bubble // Have to block here to allow exceptions to bubble
Task.WaitAll(task); Task.WaitAll(task);
}
finally
{
// No need to start if scanning the library because it will handle it
if (!request.RefreshLibrary)
{
_libraryMonitor.Start(); _libraryMonitor.Start();
} }
} });
if (request.RefreshLibrary)
{
_libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
} }
} }
@ -325,25 +329,27 @@ namespace MediaBrowser.Api.Library
try try
{ {
_fileSystem.DeleteDirectory(path, true); _fileSystem.DeleteDirectory(path, true);
// Need to add a delay here or directory watchers may still pick up the changes
var delayTask = Task.Delay(1000);
// Have to block here to allow exceptions to bubble
Task.WaitAll(delayTask);
} }
finally finally
{ {
// No need to start if scanning the library because it will handle it Task.Run(() =>
if (!request.RefreshLibrary)
{ {
_libraryMonitor.Start(); // No need to start if scanning the library because it will handle it
}
}
if (request.RefreshLibrary) if (request.RefreshLibrary)
{ {
_libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None); _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
} }
else
{
// Need to add a delay here or directory watchers may still pick up the changes
var task = Task.Delay(1000);
// Have to block here to allow exceptions to bubble
Task.WaitAll(task);
_libraryMonitor.Start();
}
});
}
} }
/// <summary> /// <summary>
@ -362,24 +368,26 @@ namespace MediaBrowser.Api.Library
try try
{ {
LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, _appPaths); LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, _appPaths);
}
finally
{
Task.Run(() =>
{
// No need to start if scanning the library because it will handle it
if (request.RefreshLibrary)
{
_libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
}
else
{
// Need to add a delay here or directory watchers may still pick up the changes // Need to add a delay here or directory watchers may still pick up the changes
var task = Task.Delay(1000); var task = Task.Delay(1000);
// Have to block here to allow exceptions to bubble // Have to block here to allow exceptions to bubble
Task.WaitAll(task); Task.WaitAll(task);
}
finally
{
// No need to start if scanning the library because it will handle it
if (!request.RefreshLibrary)
{
_libraryMonitor.Start(); _libraryMonitor.Start();
} }
} });
if (request.RefreshLibrary)
{
_libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
} }
} }
@ -399,25 +407,27 @@ namespace MediaBrowser.Api.Library
try try
{ {
LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, _appPaths); LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, _appPaths);
// Need to add a delay here or directory watchers may still pick up the changes
var task = Task.Delay(1000);
// Have to block here to allow exceptions to bubble
Task.WaitAll(task);
} }
finally finally
{ {
// No need to start if scanning the library because it will handle it Task.Run(() =>
if (!request.RefreshLibrary)
{ {
_libraryMonitor.Start(); // No need to start if scanning the library because it will handle it
}
}
if (request.RefreshLibrary) if (request.RefreshLibrary)
{ {
_libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None); _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
} }
else
{
// Need to add a delay here or directory watchers may still pick up the changes
var task = Task.Delay(1000);
// Have to block here to allow exceptions to bubble
Task.WaitAll(task);
_libraryMonitor.Start();
}
});
}
} }
} }
} }

View File

@ -171,6 +171,9 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "MinStartDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] [ApiMember(Name = "MinStartDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string MinStartDate { get; set; } public string MinStartDate { get; set; }
[ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? HasAired { get; set; }
[ApiMember(Name = "MaxStartDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] [ApiMember(Name = "MaxStartDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string MaxStartDate { get; set; } public string MaxStartDate { get; set; }
@ -179,6 +182,24 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "MaxEndDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] [ApiMember(Name = "MaxEndDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string MaxEndDate { get; set; } public string MaxEndDate { get; set; }
[ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
public bool? IsMovie { get; set; }
[ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? StartIndex { get; set; }
[ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Limit { get; set; }
[ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Name, StartDate", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string SortBy { get; set; }
[ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public SortOrder? SortOrder { get; set; }
[ApiMember(Name = "Genres", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string Genres { get; set; }
} }
[Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")] [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")]
@ -196,6 +217,9 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? HasAired { get; set; } public bool? HasAired { get; set; }
[ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsMovie { get; set; }
} }
[Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")] [Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")]
@ -312,7 +336,7 @@ namespace MediaBrowser.Api.LiveTv
private void AssertUserCanManageLiveTv() private void AssertUserCanManageLiveTv()
{ {
var user = SessionContext.GetUser(Request); var user = SessionContext.GetUser(Request).Result;
if (user == null) if (user == null)
{ {
@ -368,8 +392,9 @@ namespace MediaBrowser.Api.LiveTv
{ {
var query = new ProgramQuery var query = new ProgramQuery
{ {
ChannelIdList = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(), ChannelIds = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(),
UserId = request.UserId UserId = request.UserId,
HasAired = request.HasAired
}; };
if (!string.IsNullOrEmpty(request.MinStartDate)) if (!string.IsNullOrEmpty(request.MinStartDate))
@ -392,6 +417,13 @@ namespace MediaBrowser.Api.LiveTv
query.MaxEndDate = DateTime.Parse(request.MaxEndDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); query.MaxEndDate = DateTime.Parse(request.MaxEndDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
} }
query.StartIndex = request.StartIndex;
query.Limit = request.Limit;
query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
query.SortOrder = request.SortOrder;
query.IsMovie = request.IsMovie;
query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false); var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false);
return ToOptimizedSerializedResultUsingCache(result); return ToOptimizedSerializedResultUsingCache(result);
@ -404,7 +436,8 @@ namespace MediaBrowser.Api.LiveTv
UserId = request.UserId, UserId = request.UserId,
IsAiring = request.IsAiring, IsAiring = request.IsAiring,
Limit = request.Limit, Limit = request.Limit,
HasAired = request.HasAired HasAired = request.HasAired,
IsMovie = request.IsMovie
}; };
var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false); var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false);

View File

@ -79,8 +79,10 @@
<Compile Include="FilterService.cs" /> <Compile Include="FilterService.cs" />
<Compile Include="IHasDtoOptions.cs" /> <Compile Include="IHasDtoOptions.cs" />
<Compile Include="Library\ChapterService.cs" /> <Compile Include="Library\ChapterService.cs" />
<Compile Include="Playback\Hls\MpegDashService.cs" /> <Compile Include="Playback\Dash\ManifestBuilder.cs" />
<Compile Include="Playback\Dash\MpegDashService.cs" />
<Compile Include="Playback\MediaInfoService.cs" /> <Compile Include="Playback\MediaInfoService.cs" />
<Compile Include="Playback\TranscodingThrottler.cs" />
<Compile Include="PlaylistService.cs" /> <Compile Include="PlaylistService.cs" />
<Compile Include="Reports\ReportFieldType.cs" /> <Compile Include="Reports\ReportFieldType.cs" />
<Compile Include="Reports\ReportResult.cs" /> <Compile Include="Reports\ReportResult.cs" />
@ -134,6 +136,7 @@
<Compile Include="SearchService.cs" /> <Compile Include="SearchService.cs" />
<Compile Include="Session\SessionsService.cs" /> <Compile Include="Session\SessionsService.cs" />
<Compile Include="SimilarItemsHelper.cs" /> <Compile Include="SimilarItemsHelper.cs" />
<Compile Include="Sync\SyncHelper.cs" />
<Compile Include="Sync\SyncJobWebSocketListener.cs" /> <Compile Include="Sync\SyncJobWebSocketListener.cs" />
<Compile Include="Sync\SyncJobsWebSocketListener.cs" /> <Compile Include="Sync\SyncJobsWebSocketListener.cs" />
<Compile Include="Sync\SyncService.cs" /> <Compile Include="Sync\SyncService.cs" />

View File

@ -77,15 +77,13 @@ namespace MediaBrowser.Api.Music
var album1 = (MusicAlbum)item1; var album1 = (MusicAlbum)item1;
var album2 = (MusicAlbum)item2; var album2 = (MusicAlbum)item2;
var artists1 = album1.GetRecursiveChildren(i => i is IHasArtist) var artists1 = album1
.Cast<IHasArtist>() .AllArtists
.SelectMany(i => i.AllArtists)
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.ToList(); .ToList();
var artists2 = album2.GetRecursiveChildren(i => i is IHasArtist) var artists2 = album2
.Cast<IHasArtist>() .AllArtists
.SelectMany(i => i.AllArtists)
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);

View File

@ -1,9 +1,9 @@
using MediaBrowser.Controller.Devices; using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Diagnostics;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -14,6 +14,7 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using System; using System;
@ -68,17 +69,21 @@ namespace MediaBrowser.Api.Playback
protected ILiveTvManager LiveTvManager { get; private set; } protected ILiveTvManager LiveTvManager { get; private set; }
protected IDlnaManager DlnaManager { get; private set; } protected IDlnaManager DlnaManager { get; private set; }
protected IDeviceManager DeviceManager { get; private set; } protected IDeviceManager DeviceManager { get; private set; }
protected IChannelManager ChannelManager { get; private set; }
protected ISubtitleEncoder SubtitleEncoder { get; private set; } protected ISubtitleEncoder SubtitleEncoder { get; private set; }
protected IProcessManager ProcessManager { get; private set; }
protected IMediaSourceManager MediaSourceManager { get; private set; }
protected IZipClient ZipClient { get; private set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class. /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary> /// </summary>
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager) protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient)
{ {
ZipClient = zipClient;
MediaSourceManager = mediaSourceManager;
ProcessManager = processManager;
DeviceManager = deviceManager; DeviceManager = deviceManager;
SubtitleEncoder = subtitleEncoder; SubtitleEncoder = subtitleEncoder;
ChannelManager = channelManager;
DlnaManager = dlnaManager; DlnaManager = dlnaManager;
LiveTvManager = liveTvManager; LiveTvManager = liveTvManager;
FileSystem = fileSystem; FileSystem = fileSystem;
@ -129,9 +134,21 @@ namespace MediaBrowser.Api.Playback
var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false); var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false);
data += "-" + (state.Request.DeviceId ?? string.Empty); data += "-" + (state.Request.DeviceId ?? string.Empty);
data += "-" + (state.Request.ClientTime ?? string.Empty); data += "-" + (state.Request.StreamId ?? state.Request.ClientTime ?? string.Empty);
return Path.Combine(folder, data.GetMD5().ToString("N") + (outputFileExtension ?? string.Empty).ToLower()); var dataHash = data.GetMD5().ToString("N");
if (EnableOutputInSubFolder)
{
return Path.Combine(folder, dataHash, dataHash + (outputFileExtension ?? string.Empty).ToLower());
}
return Path.Combine(folder, dataHash + (outputFileExtension ?? string.Empty).ToLower());
}
protected virtual bool EnableOutputInSubFolder
{
get { return false; }
} }
protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@ -877,14 +894,6 @@ namespace MediaBrowser.Api.Playback
return "copy"; return "copy";
} }
private bool SupportsThrottleWithStream
{
get
{
return false;
}
}
/// <summary> /// <summary>
/// Gets the input argument. /// Gets the input argument.
/// </summary> /// </summary>
@ -908,23 +917,15 @@ namespace MediaBrowser.Api.Playback
private string GetInputPathArgument(string transcodingJobId, StreamState state) private string GetInputPathArgument(string transcodingJobId, StreamState state)
{ {
if (state.InputProtocol == MediaProtocol.File && //if (state.InputProtocol == MediaProtocol.File &&
state.RunTimeTicks.HasValue && // state.RunTimeTicks.HasValue &&
state.VideoType == VideoType.VideoFile && // state.VideoType == VideoType.VideoFile &&
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) // !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ //{
if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo) // if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
{ // {
if (SupportsThrottleWithStream) // }
{ //}
var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/videos/" + state.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + state.Request.MediaSourceId;
url += "&transcodingJobId=" + transcodingJobId;
return string.Format("\"{0}\"", url);
}
}
}
var protocol = state.InputProtocol; var protocol = state.InputProtocol;
@ -1053,6 +1054,7 @@ namespace MediaBrowser.Api.Playback
} }
var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
state.Request.StreamId ?? state.Request.ClientTime,
transcodingId, transcodingId,
TranscodingJobType, TranscodingJobType,
process, process,
@ -1094,7 +1096,7 @@ namespace MediaBrowser.Api.Playback
StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream); StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream);
// Wait for the file to exist before proceeeding // Wait for the file to exist before proceeeding
while (!File.Exists(outputPath) && !transcodingJob.HasExited) while (!File.Exists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
{ {
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
} }
@ -1109,9 +1111,26 @@ namespace MediaBrowser.Api.Playback
} }
} }
StartThrottler(state, transcodingJob);
return transcodingJob; return transcodingJob;
} }
private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
{
if (state.InputProtocol == MediaProtocol.File &&
state.RunTimeTicks.HasValue &&
state.VideoType == VideoType.VideoFile &&
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
{
state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ProcessManager);
state.TranscodingThrottler.Start();
}
}
}
private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target) private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
{ {
try try
@ -1505,7 +1524,7 @@ namespace MediaBrowser.Api.Playback
} }
else if (i == 16) else if (i == 16)
{ {
request.ClientTime = val; request.StreamId = val;
} }
else if (i == 17) else if (i == 17)
{ {
@ -1640,6 +1659,9 @@ namespace MediaBrowser.Api.Playback
List<MediaStream> mediaStreams = null; List<MediaStream> mediaStreams = null;
state.ItemType = item.GetType().Name; state.ItemType = item.GetType().Name;
state.ItemId = item.Id.ToString("N");
var archivable = item as IArchivable;
state.IsInputArchive = archivable != null && archivable.IsArchive;
if (item is ILiveTvRecording) if (item is ILiveTvRecording)
{ {
@ -1653,7 +1675,7 @@ namespace MediaBrowser.Api.Playback
var source = string.IsNullOrEmpty(request.MediaSourceId) var source = string.IsNullOrEmpty(request.MediaSourceId)
? recording.GetMediaSources(false).First() ? recording.GetMediaSources(false).First()
: recording.GetMediaSources(false).First(i => string.Equals(i.Id, request.MediaSourceId)); : MediaSourceManager.GetStaticMediaSource(recording, request.MediaSourceId, false);
mediaStreams = source.MediaStreams; mediaStreams = source.MediaStreams;
@ -1692,25 +1714,13 @@ namespace MediaBrowser.Api.Playback
// Just to prevent this from being null and causing other methods to fail // Just to prevent this from being null and causing other methods to fail
state.MediaPath = string.Empty; state.MediaPath = string.Empty;
} }
else if (item is IChannelMediaItem)
{
var mediaSource = await GetChannelMediaInfo(request.Id, request.MediaSourceId, cancellationToken).ConfigureAwait(false);
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
state.InputProtocol = mediaSource.Protocol;
state.MediaPath = mediaSource.Path;
state.RunTimeTicks = item.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
state.InputBitrate = mediaSource.Bitrate;
state.InputFileSize = mediaSource.Size;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
mediaStreams = mediaSource.MediaStreams;
}
else else
{ {
var hasMediaSources = (IHasMediaSources)item; var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false);
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
? hasMediaSources.GetMediaSources(false).First() ? mediaSources.First()
: hasMediaSources.GetMediaSources(false).First(i => string.Equals(i.Id, request.MediaSourceId)); : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
mediaStreams = mediaSource.MediaStreams; mediaStreams = mediaSource.MediaStreams;
@ -1720,6 +1730,8 @@ namespace MediaBrowser.Api.Playback
state.InputFileSize = mediaSource.Size; state.InputFileSize = mediaSource.Size;
state.InputBitrate = mediaSource.Bitrate; state.InputBitrate = mediaSource.Bitrate;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
var video = item as Video; var video = item as Video;
@ -1742,7 +1754,6 @@ namespace MediaBrowser.Api.Playback
} }
} }
state.RunTimeTicks = mediaSource.RunTimeTicks;
} }
var videoRequest = request as VideoStreamRequest; var videoRequest = request as VideoStreamRequest;
@ -1865,29 +1876,6 @@ namespace MediaBrowser.Api.Playback
state.AllMediaStreams = mediaStreams; state.AllMediaStreams = mediaStreams;
} }
private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
string mediaSourceId,
CancellationToken cancellationToken)
{
var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(id, true, cancellationToken)
.ConfigureAwait(false);
var list = channelMediaSources.ToList();
if (!string.IsNullOrWhiteSpace(mediaSourceId))
{
var source = list
.FirstOrDefault(i => string.Equals(mediaSourceId, i.Id));
if (source != null)
{
return source;
}
}
return list.First();
}
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
{ {
if (videoStream.IsInterlaced) if (videoStream.IsInterlaced)

View File

@ -0,0 +1,224 @@
using System;
using System.Globalization;
using System.Security;
using System.Text;
namespace MediaBrowser.Api.Playback.Dash
{
public class ManifestBuilder
{
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
public string GetManifestText(StreamState state, string playlistUrl)
{
var builder = new StringBuilder();
var time = TimeSpan.FromTicks(state.RunTimeTicks.Value);
var duration = "PT" + time.Hours.ToString("00", UsCulture) + "H" + time.Minutes.ToString("00", UsCulture) + "M" + time.Seconds.ToString("00", UsCulture) + ".00S";
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
builder.AppendFormat(
"<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" type=\"static\" mediaPresentationDuration=\"{0}\" minBufferTime=\"PT5.0S\">",
duration);
builder.Append("<ProgramInformation>");
builder.Append("</ProgramInformation>");
builder.Append("<Period start=\"PT0S\">");
builder.Append(GetVideoAdaptationSet(state, playlistUrl));
builder.Append(GetAudioAdaptationSet(state, playlistUrl));
builder.Append("</Period>");
builder.Append("</MPD>");
return builder.ToString();
}
private string GetVideoAdaptationSet(StreamState state, string playlistUrl)
{
var builder = new StringBuilder();
builder.Append("<AdaptationSet id=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">");
builder.Append(GetVideoRepresentationOpenElement(state));
AppendSegmentList(state, builder, "0", playlistUrl);
builder.Append("</Representation>");
builder.Append("</AdaptationSet>");
return builder.ToString();
}
private string GetAudioAdaptationSet(StreamState state, string playlistUrl)
{
var builder = new StringBuilder();
builder.Append("<AdaptationSet id=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">");
builder.Append(GetAudioRepresentationOpenElement(state));
builder.Append("<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"6\" />");
AppendSegmentList(state, builder, "1", playlistUrl);
builder.Append("</Representation>");
builder.Append("</AdaptationSet>");
return builder.ToString();
}
private string GetVideoRepresentationOpenElement(StreamState state)
{
var codecs = GetVideoCodecDescriptor(state);
var mime = "video/mp4";
var xml = "<Representation id=\"0\" mimeType=\"" + mime + "\" codecs=\"" + codecs + "\"";
if (state.OutputWidth.HasValue)
{
xml += " width=\"" + state.OutputWidth.Value.ToString(UsCulture) + "\"";
}
if (state.OutputHeight.HasValue)
{
xml += " height=\"" + state.OutputHeight.Value.ToString(UsCulture) + "\"";
}
if (state.OutputVideoBitrate.HasValue)
{
xml += " bandwidth=\"" + state.OutputVideoBitrate.Value.ToString(UsCulture) + "\"";
}
xml += ">";
return xml;
}
private string GetAudioRepresentationOpenElement(StreamState state)
{
var codecs = GetAudioCodecDescriptor(state);
var mime = "audio/mp4";
var xml = "<Representation id=\"1\" mimeType=\"" + mime + "\" codecs=\"" + codecs + "\"";
if (state.OutputAudioSampleRate.HasValue)
{
xml += " audioSamplingRate=\"" + state.OutputAudioSampleRate.Value.ToString(UsCulture) + "\"";
}
if (state.OutputAudioBitrate.HasValue)
{
xml += " bandwidth=\"" + state.OutputAudioBitrate.Value.ToString(UsCulture) + "\"";
}
xml += ">";
return xml;
}
private string GetVideoCodecDescriptor(StreamState state)
{
// https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
// http://www.chipwreck.de/blog/2010/02/25/html-5-video-tag-and-attributes/
var level = state.TargetVideoLevel ?? 0;
var profile = state.TargetVideoProfile ?? string.Empty;
if (profile.IndexOf("high", StringComparison.OrdinalIgnoreCase) != -1)
{
if (level >= 4.1)
{
return "avc1.640028";
}
if (level >= 4)
{
return "avc1.640028";
}
return "avc1.64001f";
}
if (profile.IndexOf("main", StringComparison.OrdinalIgnoreCase) != -1)
{
if (level >= 4)
{
return "avc1.4d0028";
}
if (level >= 3.1)
{
return "avc1.4d001f";
}
return "avc1.4d001e";
}
if (level >= 3.1)
{
return "avc1.42001f";
}
return "avc1.42E01E";
}
private string GetAudioCodecDescriptor(StreamState state)
{
// https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
if (string.Equals(state.OutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
{
return "mp4a.40.34";
}
// AAC 5ch
if (state.OutputAudioChannels.HasValue && state.OutputAudioChannels.Value >= 5)
{
return "mp4a.40.5";
}
// AAC 2ch
return "mp4a.40.2";
}
private void AppendSegmentList(StreamState state, StringBuilder builder, string type, string playlistUrl)
{
var extension = ".m4s";
var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
var queryStringIndex = playlistUrl.IndexOf('?');
var queryString = queryStringIndex == -1 ? string.Empty : playlistUrl.Substring(queryStringIndex);
var index = 0;
var duration = 1000000 * state.SegmentLength;
builder.AppendFormat("<SegmentList timescale=\"1000000\" duration=\"{0}\" startNumber=\"1\">", duration.ToString(CultureInfo.InvariantCulture));
while (seconds > 0)
{
var filename = index == 0
? "init"
: (index - 1).ToString(UsCulture);
var segmentUrl = string.Format("dash/{3}/{0}{1}{2}",
filename,
extension,
SecurityElement.Escape(queryString),
type);
if (index == 0)
{
builder.AppendFormat("<Initialization sourceURL=\"{0}\"/>", segmentUrl);
}
else
{
builder.AppendFormat("<SegmentURL media=\"{0}\"/>", segmentUrl);
}
seconds -= state.SegmentLength;
index++;
}
builder.Append("</SegmentList>");
}
}
}

View File

@ -0,0 +1,561 @@
using MediaBrowser.Api.Playback.Hls;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Diagnostics;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.IO;
using ServiceStack;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace MediaBrowser.Api.Playback.Dash
{
/// <summary>
/// Options is needed for chromecast. Threw Head in there since it's related
/// </summary>
[Route("/Videos/{Id}/master.mpd", "GET", Summary = "Gets a video stream using Mpeg dash.")]
[Route("/Videos/{Id}/master.mpd", "HEAD", Summary = "Gets a video stream using Mpeg dash.")]
public class GetMasterManifest : VideoStreamRequest
{
public bool EnableAdaptiveBitrateStreaming { get; set; }
public GetMasterManifest()
{
EnableAdaptiveBitrateStreaming = true;
}
}
[Route("/Videos/{Id}/dash/{RepresentationId}/{SegmentId}.m4s", "GET")]
public class GetDashSegment : VideoStreamRequest
{
/// <summary>
/// Gets or sets the segment id.
/// </summary>
/// <value>The segment id.</value>
public string SegmentId { get; set; }
/// <summary>
/// Gets or sets the representation identifier.
/// </summary>
/// <value>The representation identifier.</value>
public string RepresentationId { get; set; }
}
public class MpegDashService : BaseHlsService
{
public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager)
: base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, processManager, mediaSourceManager, zipClient)
{
NetworkManager = networkManager;
}
protected INetworkManager NetworkManager { get; private set; }
public object Get(GetMasterManifest request)
{
var result = GetAsync(request, "GET").Result;
return result;
}
public object Head(GetMasterManifest request)
{
var result = GetAsync(request, "HEAD").Result;
return result;
}
protected override bool EnableOutputInSubFolder
{
get
{
return true;
}
}
private async Task<object> GetAsync(GetMasterManifest request, string method)
{
if (string.IsNullOrEmpty(request.MediaSourceId))
{
throw new ArgumentException("MediaSourceId is required");
}
var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
var playlistText = string.Empty;
if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase))
{
playlistText = new ManifestBuilder().GetManifestText(state, Request.RawUrl);
}
return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.mpd"), new Dictionary<string, string>());
}
public object Get(GetDashSegment request)
{
return GetDynamicSegment(request, request.SegmentId, request.RepresentationId).Result;
}
private async Task<object> GetDynamicSegment(VideoStreamRequest request, string segmentId, string representationId)
{
if ((request.StartTimeTicks ?? 0) > 0)
{
throw new ArgumentException("StartTimeTicks is not allowed.");
}
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var requestedIndex = string.Equals(segmentId, "init", StringComparison.OrdinalIgnoreCase) ?
-1 :
int.Parse(segmentId, NumberStyles.Integer, UsCulture);
var state = await GetState(request, cancellationToken).ConfigureAwait(false);
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".mpd");
var segmentExtension = GetSegmentFileExtension(state);
var segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex);
var segmentLength = state.SegmentLength;
TranscodingJob job = null;
if (!string.IsNullOrWhiteSpace(segmentPath))
{
job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
}
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try
{
segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex);
if (!string.IsNullOrWhiteSpace(segmentPath))
{
job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
}
else
{
if (string.Equals(representationId, "0", StringComparison.OrdinalIgnoreCase))
{
job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength;
Logger.Debug("Current transcoding index is {0}. requestedIndex={1}. segmentGapRequiringTranscodingChange={2}", currentTranscodingIndex ?? -2, requestedIndex, segmentGapRequiringTranscodingChange);
if (currentTranscodingIndex == null || requestedIndex < currentTranscodingIndex.Value || (requestedIndex - currentTranscodingIndex.Value) > segmentGapRequiringTranscodingChange)
{
// If the playlist doesn't already exist, startup ffmpeg
try
{
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false);
if (currentTranscodingIndex.HasValue)
{
DeleteLastTranscodedFiles(playlistPath, 0);
}
var positionTicks = GetPositionTicks(state, requestedIndex);
request.StartTimeTicks = positionTicks;
var startNumber = GetStartNumber(state);
var workingDirectory = Path.Combine(Path.GetDirectoryName(playlistPath), (startNumber == -1 ? 0 : startNumber).ToString(CultureInfo.InvariantCulture));
state.WaitForPath = Path.Combine(workingDirectory, Path.GetFileName(playlistPath));
Directory.CreateDirectory(workingDirectory);
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource, workingDirectory).ConfigureAwait(false);
await WaitForMinimumDashSegmentCount(Path.Combine(workingDirectory, Path.GetFileName(playlistPath)), 1, cancellationTokenSource.Token).ConfigureAwait(false);
}
catch
{
state.Dispose();
throw;
}
}
}
}
}
finally
{
ApiEntryPoint.Instance.TranscodingStartLock.Release();
}
while (string.IsNullOrWhiteSpace(segmentPath))
{
segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex);
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
}
Logger.Info("returning {0}", segmentPath);
return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job ?? ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType), cancellationToken).ConfigureAwait(false);
}
private long GetPositionTicks(StreamState state, int requestedIndex)
{
if (requestedIndex <= 0)
{
return 0;
}
var startSeconds = requestedIndex * state.SegmentLength;
return TimeSpan.FromSeconds(startSeconds).Ticks;
}
protected Task WaitForMinimumDashSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
{
return WaitForSegment(playlist, "stream0-" + segmentCount.ToString("00000", CultureInfo.InvariantCulture) + ".m4s", cancellationToken);
}
private async Task<object> GetSegmentResult(string playlistPath,
string segmentPath,
int segmentIndex,
int segmentLength,
TranscodingJob transcodingJob,
CancellationToken cancellationToken)
{
// If all transcoding has completed, just return immediately
if (transcodingJob != null && transcodingJob.HasExited)
{
return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
}
// Wait for the file to stop being written to, then stream it
var length = new FileInfo(segmentPath).Length;
var eofCount = 0;
while (eofCount < 10)
{
var info = new FileInfo(segmentPath);
if (!info.Exists)
{
break;
}
var newLength = info.Length;
if (newLength == length)
{
eofCount++;
}
else
{
eofCount = 0;
}
length = newLength;
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
}
private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob)
{
var segmentEndingSeconds = (1 + index) * segmentLength;
var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks;
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
Path = segmentPath,
FileShare = FileShare.ReadWrite,
OnComplete = () =>
{
if (transcodingJob != null)
{
transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
}
}
});
}
public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
{
var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType);
if (job == null || job.HasExited)
{
return null;
}
var file = GetLastTranscodingFiles(playlist, segmentExtension, FileSystem, 1).FirstOrDefault();
if (file == null)
{
return null;
}
return GetIndex(file.FullName);
}
public int GetIndex(string segmentPath)
{
var indexString = Path.GetFileNameWithoutExtension(segmentPath).Split('-').LastOrDefault();
if (string.Equals(indexString, "init", StringComparison.OrdinalIgnoreCase))
{
return -1;
}
var startNumber = int.Parse(Path.GetFileNameWithoutExtension(Path.GetDirectoryName(segmentPath)), NumberStyles.Integer, UsCulture);
return startNumber + int.Parse(indexString, NumberStyles.Integer, UsCulture) - 1;
}
private void DeleteLastTranscodedFiles(string playlistPath, int retryCount)
{
if (retryCount >= 5)
{
return;
}
}
private static List<FileInfo> GetLastTranscodingFiles(string playlist, string segmentExtension, IFileSystem fileSystem, int count)
{
var folder = Path.GetDirectoryName(playlist);
try
{
return new DirectoryInfo(folder)
.EnumerateFiles("*", SearchOption.AllDirectories)
.Where(i => string.Equals(i.Extension, segmentExtension, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(fileSystem.GetLastWriteTimeUtc)
.Take(count)
.ToList();
}
catch (DirectoryNotFoundException)
{
return new List<FileInfo>();
}
}
private string FindSegment(string playlist, string representationId, string segmentExtension, int requestedIndex)
{
var folder = Path.GetDirectoryName(playlist);
if (requestedIndex == -1)
{
var path = Path.Combine(folder, "0", "stream" + representationId + "-" + "init" + segmentExtension);
return File.Exists(path) ? path : null;
}
try
{
foreach (var subfolder in new DirectoryInfo(folder).EnumerateDirectories().ToList())
{
var subfolderName = Path.GetFileNameWithoutExtension(subfolder.FullName);
int startNumber;
if (int.TryParse(subfolderName, NumberStyles.Any, UsCulture, out startNumber))
{
var segmentIndex = requestedIndex - startNumber + 1;
var path = Path.Combine(folder, subfolderName, "stream" + representationId + "-" + segmentIndex.ToString("00000", CultureInfo.InvariantCulture) + segmentExtension);
if (File.Exists(path))
{
return path;
}
}
}
}
catch (DirectoryNotFoundException)
{
}
return null;
}
protected override string GetAudioArguments(StreamState state)
{
var codec = state.OutputAudioCodec;
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
return "-codec:a:0 copy";
}
var args = "-codec:a:0 " + codec;
var channels = state.OutputAudioChannels;
if (channels.HasValue)
{
args += " -ac " + channels.Value;
}
var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue)
{
args += " -ab " + bitrate.Value.ToString(UsCulture);
}
args += " " + GetAudioFilterParam(state, true);
return args;
}
protected override string GetVideoArguments(StreamState state)
{
var codec = state.OutputVideoCodec;
var args = "-codec:v:0 " + codec;
if (state.EnableMpegtsM2TsMode)
{
args += " -mpegts_m2ts_mode 1";
}
// See if we can save come cpu cycles by avoiding encoding
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
return state.VideoStream != null && IsH264(state.VideoStream) ?
args + " -bsf:v h264_mp4toannexb" :
args;
}
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
state.SegmentLength.ToString(UsCulture));
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
args += " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg;
// Add resolution params, if specified
if (!hasGraphicalSubs)
{
args += GetOutputSizeParam(state, codec, false);
}
// This is for internal graphical subs
if (hasGraphicalSubs)
{
args += GetGraphicalSubtitleParam(state, codec);
}
return args;
}
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
{
// test url http://192.168.1.2:8096/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3
// Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/
var threads = GetNumberOfThreads(state, false);
var inputModifier = GetInputModifier(state);
var initSegmentName = "stream$RepresentationID$-init.m4s";
var segmentName = "stream$RepresentationID$-$Number%05d$.m4s";
var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f dash -init_seg_name \"{6}\" -media_seg_name \"{7}\" -use_template 0 -use_timeline 1 -min_seg_duration {8} -y \"{9}\"",
inputModifier,
GetInputArgument(transcodingJobId, state),
threads,
GetMapArgs(state),
GetVideoArguments(state),
GetAudioArguments(state),
initSegmentName,
segmentName,
(state.SegmentLength * 1000000).ToString(CultureInfo.InvariantCulture),
state.WaitForPath
).Trim();
return args;
}
protected override int GetStartNumber(StreamState state)
{
return GetStartNumber(state.VideoRequest);
}
private int GetStartNumber(VideoStreamRequest request)
{
var segmentId = "0";
var segmentRequest = request as GetDashSegment;
if (segmentRequest != null)
{
segmentId = segmentRequest.SegmentId;
}
if (string.Equals(segmentId, "init", StringComparison.OrdinalIgnoreCase))
{
return -1;
}
return int.Parse(segmentId, NumberStyles.Integer, UsCulture);
}
/// <summary>
/// Gets the segment file extension.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected override string GetSegmentFileExtension(StreamState state)
{
return ".m4s";
}
protected override TranscodingJobType TranscodingJobType
{
get
{
return TranscodingJobType.Dash;
}
}
private async Task WaitForSegment(string playlist, string segment, CancellationToken cancellationToken)
{
var tmpPath = playlist + ".tmp";
var segmentFilename = Path.GetFileName(segment);
Logger.Debug("Waiting for {0} in {1}", segmentFilename, playlist);
while (true)
{
FileStream fileStream;
try
{
fileStream = FileSystem.GetFileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
}
catch (IOException)
{
fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
}
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
using (fileStream)
{
using (var reader = new StreamReader(fileStream))
{
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync().ConfigureAwait(false);
if (line.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
{
Logger.Debug("Finished waiting for {0} in {1}", segmentFilename, playlist);
return;
}
}
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
}
}
}
}
}

View File

@ -1,7 +1,7 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Diagnostics;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
@ -23,7 +23,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary> /// </summary>
public abstract class BaseHlsService : BaseStreamingService public abstract class BaseHlsService : BaseStreamingService
{ {
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager) protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, processManager, mediaSourceManager, zipClient)
{ {
} }
@ -181,7 +181,7 @@ namespace MediaBrowser.Api.Playback.Hls
return builder.ToString(); return builder.ToString();
} }
protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken) protected virtual async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
{ {
Logger.Debug("Waiting for {0} segments in {1}", segmentCount, playlist); Logger.Debug("Waiting for {0} segments in {1}", segmentCount, playlist);

View File

@ -1,9 +1,8 @@
using MediaBrowser.Controller.Devices; using MediaBrowser.Common.IO;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Diagnostics;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
@ -11,6 +10,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using ServiceStack; using ServiceStack;
using System; using System;
@ -63,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService public class DynamicHlsService : BaseHlsService
{ {
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager) public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, processManager, mediaSourceManager, zipClient)
{ {
NetworkManager = networkManager; NetworkManager = networkManager;
} }
@ -100,13 +100,13 @@ namespace MediaBrowser.Api.Playback.Hls
var cancellationTokenSource = new CancellationTokenSource(); var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token; var cancellationToken = cancellationTokenSource.Token;
var index = int.Parse(segmentId, NumberStyles.Integer, UsCulture); var requestedIndex = int.Parse(segmentId, NumberStyles.Integer, UsCulture);
var state = await GetState(request, cancellationToken).ConfigureAwait(false); var state = await GetState(request, cancellationToken).ConfigureAwait(false);
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
var segmentPath = GetSegmentPath(playlistPath, index); var segmentPath = GetSegmentPath(playlistPath, requestedIndex);
var segmentLength = state.SegmentLength; var segmentLength = state.SegmentLength;
var segmentExtension = GetSegmentFileExtension(state); var segmentExtension = GetSegmentFileExtension(state);
@ -115,7 +115,8 @@ namespace MediaBrowser.Api.Playback.Hls
if (File.Exists(segmentPath)) if (File.Exists(segmentPath))
{ {
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false); job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
} }
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
@ -123,26 +124,26 @@ namespace MediaBrowser.Api.Playback.Hls
{ {
if (File.Exists(segmentPath)) if (File.Exists(segmentPath))
{ {
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false); job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
var segmentGapRequiringTranscodingChange = 24/state.SegmentLength;
if (currentTranscodingIndex == null || index < currentTranscodingIndex.Value || (index - currentTranscodingIndex.Value) > 4) if (currentTranscodingIndex == null || requestedIndex < currentTranscodingIndex.Value || (requestedIndex - currentTranscodingIndex.Value) > segmentGapRequiringTranscodingChange)
{ {
// If the playlist doesn't already exist, startup ffmpeg // If the playlist doesn't already exist, startup ffmpeg
try try
{ {
ApiEntryPoint.Instance.KillTranscodingJobs(j => j.Type == TranscodingJobType && string.Equals(j.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase), p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase)); ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId ?? request.ClientTime, p => false);
if (currentTranscodingIndex.HasValue) if (currentTranscodingIndex.HasValue)
{ {
DeleteLastFile(playlistPath, segmentExtension, 0); DeleteLastFile(playlistPath, segmentExtension, 0);
} }
var startSeconds = index * state.SegmentLength; request.StartTimeTicks = GetSeekPositionTicks(state, requestedIndex);
request.StartTimeTicks = TimeSpan.FromSeconds(startSeconds).Ticks;
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
} }
@ -152,7 +153,7 @@ namespace MediaBrowser.Api.Playback.Hls
throw; throw;
} }
await WaitForMinimumSegmentCount(playlistPath, 2, cancellationTokenSource.Token).ConfigureAwait(false); await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
} }
} }
} }
@ -169,11 +170,26 @@ namespace MediaBrowser.Api.Playback.Hls
Logger.Info("returning {0}", segmentPath); Logger.Info("returning {0}", segmentPath);
job = job ?? ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); job = job ?? ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false); return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
}
private long GetSeekPositionTicks(StreamState state, int requestedIndex)
{
var startSeconds = requestedIndex * state.SegmentLength;
var position = TimeSpan.FromSeconds(startSeconds).Ticks;
return position;
} }
public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
{ {
var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType);
if (job == null || job.HasExited)
{
return null;
}
var file = GetLastTranscodingFile(playlist, segmentExtension, FileSystem); var file = GetLastTranscodingFile(playlist, segmentExtension, FileSystem);
if (file == null) if (file == null)
@ -277,7 +293,7 @@ namespace MediaBrowser.Api.Playback.Hls
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// If all transcoding has completed, just return immediately // If all transcoding has completed, just return immediately
if (!IsTranscoding(playlistPath)) if (transcodingJob != null && transcodingJob.HasExited)
{ {
return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
} }
@ -288,7 +304,9 @@ namespace MediaBrowser.Api.Playback.Hls
{ {
using (var reader = new StreamReader(fileStream)) using (var reader = new StreamReader(fileStream))
{ {
var text = await reader.ReadToEndAsync().ConfigureAwait(false); while (!reader.EndOfStream)
{
var text = await reader.ReadLineAsync().ConfigureAwait(false);
// If it appears in the playlist, it's done // If it appears in the playlist, it's done
if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
@ -297,6 +315,7 @@ namespace MediaBrowser.Api.Playback.Hls
} }
} }
} }
}
// if a different file is encoding, it's done // if a different file is encoding, it's done
//var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath); //var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath);
@ -356,13 +375,6 @@ namespace MediaBrowser.Api.Playback.Hls
}); });
} }
private bool IsTranscoding(string playlistPath)
{
var job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
return job != null && !job.HasExited;
}
private async Task<object> GetAsync(GetMasterHlsVideoStream request, string method) private async Task<object> GetAsync(GetMasterHlsVideoStream request, string method)
{ {
var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
@ -681,7 +693,25 @@ namespace MediaBrowser.Api.Playback.Hls
// If isEncoding is true we're actually starting ffmpeg // If isEncoding is true we're actually starting ffmpeg
var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0"; var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"", if (state.EnableGenericHlsSegmenter)
{
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d.ts";
return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
inputModifier,
GetInputArgument(transcodingJobId, state),
threads,
GetMapArgs(state),
GetVideoArguments(state),
GetAudioArguments(state),
state.SegmentLength.ToString(UsCulture),
startNumberParam,
outputPath,
outputTsArg
).Trim();
}
return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
inputModifier, inputModifier,
GetInputArgument(transcodingJobId, state), GetInputArgument(transcodingJobId, state),
threads, threads,
@ -693,8 +723,6 @@ namespace MediaBrowser.Api.Playback.Hls
state.HlsListSize.ToString(UsCulture), state.HlsListSize.ToString(UsCulture),
outputPath outputPath
).Trim(); ).Trim();
return args;
} }
/// <summary> /// <summary>

View File

@ -47,6 +47,9 @@ namespace MediaBrowser.Api.Playback.Hls
{ {
[ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
public string DeviceId { get; set; } public string DeviceId { get; set; }
[ApiMember(Name = "StreamId", Description = "The stream id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
public string StreamId { get; set; }
} }
public class HlsSegmentService : BaseApiService public class HlsSegmentService : BaseApiService
@ -69,7 +72,7 @@ namespace MediaBrowser.Api.Playback.Hls
public void Delete(StopEncodingProcess request) public void Delete(StopEncodingProcess request)
{ {
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, path => true); ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, path => true);
} }
/// <summary> /// <summary>

View File

@ -1,675 +0,0 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.IO;
using ServiceStack;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace MediaBrowser.Api.Playback.Hls
{
/// <summary>
/// Options is needed for chromecast. Threw Head in there since it's related
/// </summary>
[Route("/Videos/{Id}/master.mpd", "GET", Summary = "Gets a video stream using Mpeg dash.")]
[Route("/Videos/{Id}/master.mpd", "HEAD", Summary = "Gets a video stream using Mpeg dash.")]
public class GetMasterManifest : VideoStreamRequest
{
public bool EnableAdaptiveBitrateStreaming { get; set; }
public GetMasterManifest()
{
EnableAdaptiveBitrateStreaming = true;
}
}
[Route("/Videos/{Id}/dash/{SegmentId}.ts", "GET")]
[Route("/Videos/{Id}/dash/{SegmentId}.mp4", "GET")]
public class GetDashSegment : VideoStreamRequest
{
/// <summary>
/// Gets or sets the segment id.
/// </summary>
/// <value>The segment id.</value>
public string SegmentId { get; set; }
}
public class MpegDashService : BaseHlsService
{
public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
{
NetworkManager = networkManager;
}
protected INetworkManager NetworkManager { get; private set; }
public object Get(GetMasterManifest request)
{
var result = GetAsync(request, "GET").Result;
return result;
}
public object Head(GetMasterManifest request)
{
var result = GetAsync(request, "HEAD").Result;
return result;
}
private async Task<object> GetAsync(GetMasterManifest request, string method)
{
if (string.Equals(request.AudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Audio codec copy is not allowed here.");
}
if (string.Equals(request.VideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Video codec copy is not allowed here.");
}
if (string.IsNullOrEmpty(request.MediaSourceId))
{
throw new ArgumentException("MediaSourceId is required");
}
var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
var playlistText = string.Empty;
if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase))
{
playlistText = GetManifestText(state);
}
return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.mpd"), new Dictionary<string, string>());
}
private string GetManifestText(StreamState state)
{
var builder = new StringBuilder();
var time = TimeSpan.FromTicks(state.RunTimeTicks.Value);
var duration = "PT" + time.Hours.ToString("00", UsCulture) + "H" + time.Minutes.ToString("00", UsCulture) + "M" + time.Seconds.ToString("00", UsCulture) + ".00S";
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
var profile = string.Equals(GetSegmentFileExtension(state), ".ts", StringComparison.OrdinalIgnoreCase)
? "urn:mpeg:dash:profile:mp2t-simple:2011"
: "urn:mpeg:dash:profile:mp2t-simple:2011";
builder.AppendFormat(
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" minBufferTime=\"PT2.00S\" mediaPresentationDuration=\"{0}\" maxSegmentDuration=\"PT{1}S\" type=\"static\" profiles=\""+profile+"\">",
duration,
state.SegmentLength.ToString(CultureInfo.InvariantCulture));
builder.Append("<ProgramInformation moreInformationURL=\"http://gpac.sourceforge.net\">");
builder.Append("</ProgramInformation>");
builder.AppendFormat("<Period start=\"PT0S\" duration=\"{0}\">", duration);
builder.Append("<AdaptationSet segmentAlignment=\"true\">");
builder.Append("<ContentComponent id=\"1\" contentType=\"video\"/>");
var lang = state.AudioStream != null ? state.AudioStream.Language : null;
if (string.IsNullOrWhiteSpace(lang)) lang = "und";
builder.AppendFormat("<ContentComponent id=\"2\" contentType=\"audio\" lang=\"{0}\"/>", lang);
builder.Append(GetRepresentationOpenElement(state, lang));
AppendSegmentList(state, builder);
builder.Append("</Representation>");
builder.Append("</AdaptationSet>");
builder.Append("</Period>");
builder.Append("</MPD>");
return builder.ToString();
}
private string GetRepresentationOpenElement(StreamState state, string language)
{
var codecs = GetVideoCodecDescriptor(state) + "," + GetAudioCodecDescriptor(state);
var mime = string.Equals(GetSegmentFileExtension(state), ".ts", StringComparison.OrdinalIgnoreCase)
? "video/mp2t"
: "video/mp4";
var xml = "<Representation id=\"1\" mimeType=\"" + mime + "\" startWithSAP=\"1\" codecs=\"" + codecs + "\"";
if (state.OutputWidth.HasValue)
{
xml += " width=\"" + state.OutputWidth.Value.ToString(UsCulture) + "\"";
}
if (state.OutputHeight.HasValue)
{
xml += " height=\"" + state.OutputHeight.Value.ToString(UsCulture) + "\"";
}
if (state.OutputAudioSampleRate.HasValue)
{
xml += " sampleRate=\"" + state.OutputAudioSampleRate.Value.ToString(UsCulture) + "\"";
}
if (state.TotalOutputBitrate.HasValue)
{
xml += " bandwidth=\"" + state.TotalOutputBitrate.Value.ToString(UsCulture) + "\"";
}
xml += ">";
return xml;
}
private string GetVideoCodecDescriptor(StreamState state)
{
// https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
// http://www.chipwreck.de/blog/2010/02/25/html-5-video-tag-and-attributes/
var level = state.TargetVideoLevel ?? 0;
var profile = state.TargetVideoProfile ?? string.Empty;
if (profile.IndexOf("high", StringComparison.OrdinalIgnoreCase) != -1)
{
if (level >= 4.1)
{
return "avc1.640028";
}
if (level >= 4)
{
return "avc1.640028";
}
return "avc1.64001f";
}
if (profile.IndexOf("main", StringComparison.OrdinalIgnoreCase) != -1)
{
if (level >= 4)
{
return "avc1.4d0028";
}
if (level >= 3.1)
{
return "avc1.4d001f";
}
return "avc1.4d001e";
}
if (level >= 3.1)
{
return "avc1.42001f";
}
return "avc1.42E01E";
}
private string GetAudioCodecDescriptor(StreamState state)
{
// https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
if (string.Equals(state.OutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
{
return "mp4a.40.34";
}
// AAC 5ch
if (state.OutputAudioChannels.HasValue && state.OutputAudioChannels.Value >= 5)
{
return "mp4a.40.5";
}
// AAC 2ch
return "mp4a.40.2";
}
public object Get(GetDashSegment request)
{
return GetDynamicSegment(request, request.SegmentId).Result;
}
private void AppendSegmentList(StreamState state, StringBuilder builder)
{
var extension = GetSegmentFileExtension(state);
var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
var queryStringIndex = Request.RawUrl.IndexOf('?');
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
var index = 0;
builder.Append("<SegmentList timescale=\"1000\" duration=\"10000\">");
while (seconds > 0)
{
var segmentUrl = string.Format("dash/{0}{1}{2}",
index.ToString(UsCulture),
extension,
SecurityElement.Escape(queryString));
if (index == 0)
{
builder.AppendFormat("<Initialization sourceURL=\"{0}\"/>", segmentUrl);
}
else
{
builder.AppendFormat("<SegmentURL media=\"{0}\"/>", segmentUrl);
}
seconds -= state.SegmentLength;
index++;
}
builder.Append("</SegmentList>");
}
private async Task<object> GetDynamicSegment(VideoStreamRequest request, string segmentId)
{
if ((request.StartTimeTicks ?? 0) > 0)
{
throw new ArgumentException("StartTimeTicks is not allowed.");
}
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var index = int.Parse(segmentId, NumberStyles.Integer, UsCulture);
var state = await GetState(request, cancellationToken).ConfigureAwait(false);
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
var segmentExtension = GetSegmentFileExtension(state);
var segmentPath = GetSegmentPath(playlistPath, segmentExtension, index);
var segmentLength = state.SegmentLength;
TranscodingJob job = null;
if (File.Exists(segmentPath))
{
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
}
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try
{
if (File.Exists(segmentPath))
{
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
}
else
{
var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
if (currentTranscodingIndex == null || index < currentTranscodingIndex.Value || (index - currentTranscodingIndex.Value) > 4)
{
// If the playlist doesn't already exist, startup ffmpeg
try
{
ApiEntryPoint.Instance.KillTranscodingJobs(j => j.Type == TranscodingJobType && string.Equals(j.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase), p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase));
if (currentTranscodingIndex.HasValue)
{
DeleteLastFile(playlistPath, segmentExtension, 0);
}
var startSeconds = index * state.SegmentLength;
request.StartTimeTicks = TimeSpan.FromSeconds(startSeconds).Ticks;
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource, Path.GetDirectoryName(playlistPath)).ConfigureAwait(false);
}
catch
{
state.Dispose();
throw;
}
await WaitForMinimumSegmentCount(playlistPath, 2, cancellationTokenSource.Token).ConfigureAwait(false);
}
}
}
finally
{
ApiEntryPoint.Instance.TranscodingStartLock.Release();
}
Logger.Info("waiting for {0}", segmentPath);
while (!File.Exists(segmentPath))
{
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
}
Logger.Info("returning {0}", segmentPath);
job = job ?? ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
}
private async Task<object> GetSegmentResult(string playlistPath,
string segmentPath,
int segmentIndex,
int segmentLength,
TranscodingJob transcodingJob,
CancellationToken cancellationToken)
{
// If all transcoding has completed, just return immediately
if (!IsTranscoding(playlistPath))
{
return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
}
var segmentFilename = Path.GetFileName(segmentPath);
using (var fileStream = FileSystem.GetFileStream(playlistPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
{
using (var reader = new StreamReader(fileStream))
{
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
// If it appears in the playlist, it's done
if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
{
return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
}
}
}
// if a different file is encoding, it's done
//var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath);
//if (currentTranscodingIndex > segmentIndex)
//{
//return GetSegmentResult(segmentPath, segmentIndex);
//}
// Wait for the file to stop being written to, then stream it
var length = new FileInfo(segmentPath).Length;
var eofCount = 0;
while (eofCount < 10)
{
var info = new FileInfo(segmentPath);
if (!info.Exists)
{
break;
}
var newLength = info.Length;
if (newLength == length)
{
eofCount++;
}
else
{
eofCount = 0;
}
length = newLength;
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
}
private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob)
{
var segmentEndingSeconds = (1 + index) * segmentLength;
var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks;
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
Path = segmentPath,
FileShare = FileShare.ReadWrite,
OnComplete = () =>
{
if (transcodingJob != null)
{
transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
}
}
});
}
private bool IsTranscoding(string playlistPath)
{
var job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
return job != null && !job.HasExited;
}
public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
{
var file = GetLastTranscodingFile(playlist, segmentExtension, FileSystem);
if (file == null)
{
return null;
}
var playlistFilename = Path.GetFileNameWithoutExtension(playlist);
var indexString = Path.GetFileNameWithoutExtension(file.Name).Substring(playlistFilename.Length);
return int.Parse(indexString, NumberStyles.Integer, UsCulture);
}
private void DeleteLastFile(string path, string segmentExtension, int retryCount)
{
if (retryCount >= 5)
{
return;
}
var file = GetLastTranscodingFile(path, segmentExtension, FileSystem);
if (file != null)
{
try
{
FileSystem.DeleteFile(file.FullName);
}
catch (IOException ex)
{
Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, file.FullName);
Thread.Sleep(100);
DeleteLastFile(path, segmentExtension, retryCount + 1);
}
catch (Exception ex)
{
Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, file.FullName);
}
}
}
private static FileInfo GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem)
{
var folder = Path.GetDirectoryName(playlist);
try
{
return new DirectoryInfo(folder)
.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
.Where(i => string.Equals(i.Extension, segmentExtension, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(fileSystem.GetLastWriteTimeUtc)
.FirstOrDefault();
}
catch (DirectoryNotFoundException)
{
return null;
}
}
protected override int GetStartNumber(StreamState state)
{
return GetStartNumber(state.VideoRequest);
}
private int GetStartNumber(VideoStreamRequest request)
{
var segmentId = "0";
var segmentRequest = request as GetDynamicHlsVideoSegment;
if (segmentRequest != null)
{
segmentId = segmentRequest.SegmentId;
}
return int.Parse(segmentId, NumberStyles.Integer, UsCulture);
}
private string GetSegmentPath(string playlist, string segmentExtension, int index)
{
var folder = Path.GetDirectoryName(playlist);
var filename = Path.GetFileNameWithoutExtension(playlist);
return Path.Combine(folder, filename + index.ToString("000", UsCulture) + segmentExtension);
}
protected override string GetAudioArguments(StreamState state)
{
var codec = state.OutputAudioCodec;
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
return "-codec:a:0 copy";
}
var args = "-codec:a:0 " + codec;
var channels = state.OutputAudioChannels;
if (channels.HasValue)
{
args += " -ac " + channels.Value;
}
var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue)
{
args += " -ab " + bitrate.Value.ToString(UsCulture);
}
args += " " + GetAudioFilterParam(state, true);
return args;
}
protected override string GetVideoArguments(StreamState state)
{
var codec = state.OutputVideoCodec;
var args = "-codec:v:0 " + codec;
if (state.EnableMpegtsM2TsMode)
{
args += " -mpegts_m2ts_mode 1";
}
// See if we can save come cpu cycles by avoiding encoding
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
return state.VideoStream != null && IsH264(state.VideoStream) ?
args + " -bsf:v h264_mp4toannexb" :
args;
}
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
state.SegmentLength.ToString(UsCulture));
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
args+= " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg;
args += " -r 24 -g 24";
// Add resolution params, if specified
if (!hasGraphicalSubs)
{
args += GetOutputSizeParam(state, codec, false);
}
// This is for internal graphical subs
if (hasGraphicalSubs)
{
args += GetGraphicalSubtitleParam(state, codec);
}
return args;
}
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
{
// test url http://192.168.1.2:8096/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3
// Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/
var threads = GetNumberOfThreads(state, false);
var inputModifier = GetInputModifier(state);
// If isEncoding is true we're actually starting ffmpeg
var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
var segmentFilename = Path.GetFileNameWithoutExtension(outputPath) + "%03d" + GetSegmentFileExtension(state);
var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f ssegment -segment_time {6} -segment_list_size {8} -segment_list \"{9}\" {10}",
inputModifier,
GetInputArgument(transcodingJobId, state),
threads,
GetMapArgs(state),
GetVideoArguments(state),
GetAudioArguments(state),
state.SegmentLength.ToString(UsCulture),
startNumberParam,
state.HlsListSize.ToString(UsCulture),
outputPath,
segmentFilename
).Trim();
return args;
}
/// <summary>
/// Gets the segment file extension.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected override string GetSegmentFileExtension(StreamState state)
{
return ".mp4";
}
protected override TranscodingJobType TranscodingJobType
{
get
{
return TranscodingJobType.Dash;
}
}
}
}

View File

@ -1,7 +1,7 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Diagnostics;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
@ -57,7 +57,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary> /// </summary>
public class VideoHlsService : BaseHlsService public class VideoHlsService : BaseHlsService
{ {
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager) public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, processManager, mediaSourceManager, zipClient)
{ {
} }

View File

@ -1,7 +1,7 @@
using MediaBrowser.Controller.Channels; using System;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using ServiceStack; using ServiceStack;
@ -22,51 +22,54 @@ namespace MediaBrowser.Api.Playback
public string UserId { get; set; } public string UserId { get; set; }
} }
[Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
public class GetPlaybackInfo : IReturn<LiveMediaInfoResult>
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string UserId { get; set; }
}
[Authenticated] [Authenticated]
public class MediaInfoService : BaseApiService public class MediaInfoService : BaseApiService
{ {
private readonly ILibraryManager _libraryManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IChannelManager _channelManager;
private readonly IUserManager _userManager;
public MediaInfoService(ILibraryManager libraryManager, IChannelManager channelManager, IUserManager userManager) public MediaInfoService(IMediaSourceManager mediaSourceManager)
{ {
_libraryManager = libraryManager; _mediaSourceManager = mediaSourceManager;
_channelManager = channelManager;
_userManager = userManager;
} }
public async Task<object> Get(GetLiveMediaInfo request) public Task<object> Get(GetPlaybackInfo request)
{
return GetPlaybackInfo(request.Id, request.UserId);
}
public Task<object> Get(GetLiveMediaInfo request)
{
return GetPlaybackInfo(request.Id, request.UserId);
}
private async Task<object> GetPlaybackInfo(string id, string userId)
{ {
var item = _libraryManager.GetItemById(request.Id);
IEnumerable<MediaSourceInfo> mediaSources; IEnumerable<MediaSourceInfo> mediaSources;
var result = new LiveMediaInfoResult();
var channelItem = item as IChannelMediaItem; try
var user = _userManager.GetUserById(request.UserId);
if (channelItem != null)
{ {
mediaSources = await _channelManager.GetChannelItemMediaSources(request.Id, true, CancellationToken.None) mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
.ConfigureAwait(false);
} }
else catch (PlaybackException ex)
{ {
var hasMediaSources = (IHasMediaSources)item; mediaSources = new List<MediaSourceInfo>();
result.ErrorCode = ex.ErrorCode;
if (user == null)
{
mediaSources = hasMediaSources.GetMediaSources(true);
}
else
{
mediaSources = hasMediaSources.GetMediaSources(true, user);
}
} }
return ToOptimizedResult(new LiveMediaInfoResult result.MediaSources = mediaSources.ToList();
{
MediaSources = mediaSources.ToList() return ToOptimizedResult(result);
});
} }
} }
} }

View File

@ -1,8 +1,8 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Diagnostics;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -32,7 +32,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary> /// </summary>
public class AudioService : BaseProgressiveStreamingService public class AudioService : BaseProgressiveStreamingService
{ {
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, imageProcessor, httpClient) public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, processManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
{ {
} }

View File

@ -1,8 +1,8 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Diagnostics;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -15,7 +15,6 @@ using ServiceStack.Web;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -29,7 +28,7 @@ namespace MediaBrowser.Api.Playback.Progressive
protected readonly IImageProcessor ImageProcessor; protected readonly IImageProcessor ImageProcessor;
protected readonly IHttpClient HttpClient; protected readonly IHttpClient HttpClient;
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager) protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, processManager, mediaSourceManager, zipClient)
{ {
ImageProcessor = imageProcessor; ImageProcessor = imageProcessor;
HttpClient = httpClient; HttpClient = httpClient;
@ -153,49 +152,12 @@ namespace MediaBrowser.Api.Playback.Progressive
using (state) using (state)
{ {
var job = string.IsNullOrEmpty(request.TranscodingJobId) ?
null :
ApiEntryPoint.Instance.GetTranscodingJob(request.TranscodingJobId);
var limits = new List<long>();
if (state.InputBitrate.HasValue)
{
// Bytes per second
limits.Add((state.InputBitrate.Value / 8));
}
if (state.InputFileSize.HasValue && state.RunTimeTicks.HasValue)
{
var totalSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds;
if (totalSeconds > 1)
{
var timeBasedLimit = state.InputFileSize.Value / totalSeconds;
limits.Add(Convert.ToInt64(timeBasedLimit));
}
}
// Take the greater of the above to methods, just to be safe
var throttleLimit = limits.Count > 0 ? limits.First() : 0;
// Pad to play it safe
var bytesPerSecond = Convert.ToInt64(1.05 * throttleLimit);
// Don't even start evaluating this until at least two minutes have content have been consumed
var targetGap = throttleLimit * 120;
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{ {
ResponseHeaders = responseHeaders, ResponseHeaders = responseHeaders,
ContentType = contentType, ContentType = contentType,
IsHeadRequest = isHeadRequest, IsHeadRequest = isHeadRequest,
Path = state.MediaPath, Path = state.MediaPath
Throttle = request.Throttle,
ThrottleLimit = bytesPerSecond,
MinThrottlePosition = targetGap,
ThrottleCallback = (l1, l2) => ThrottleCallack(l1, l2, bytesPerSecond, job)
}); });
} }
} }
@ -234,67 +196,6 @@ namespace MediaBrowser.Api.Playback.Progressive
} }
} }
private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(3).Ticks;
private long ThrottleCallack(long currentBytesPerSecond, long bytesWritten, long originalBytesPerSecond, TranscodingJob job)
{
var bytesDownloaded = job.BytesDownloaded ?? 0;
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
var path = job.Path;
if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
{
// Progressive Streaming - byte-based consideration
try
{
var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
// Estimate the bytes the transcoder should be ahead
double gapFactor = _gapLengthInTicks;
gapFactor /= transcodingPositionTicks;
var targetGap = bytesTranscoded * gapFactor;
var gap = bytesTranscoded - bytesDownloaded;
if (gap < targetGap)
{
//Logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
return 0;
}
//Logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
}
catch
{
//Logger.Error("Error getting output size");
}
}
else if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
{
// HLS - time-based consideration
var targetGap = _gapLengthInTicks;
var gap = transcodingPositionTicks - downloadPositionTicks;
if (gap < targetGap)
{
//Logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
return 0;
}
//Logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
}
else
{
//Logger.Debug("No throttle data for " + path);
}
return originalBytesPerSecond;
}
/// <summary> /// <summary>
/// Gets the static remote stream result. /// Gets the static remote stream result.
/// </summary> /// </summary>

View File

@ -1,8 +1,8 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Diagnostics;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -63,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary> /// </summary>
public class VideoService : BaseProgressiveStreamingService public class VideoService : BaseProgressiveStreamingService
{ {
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, imageProcessor, httpClient) public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, processManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
{ {
} }

View File

@ -71,8 +71,8 @@ namespace MediaBrowser.Api.Playback
public string Params { get; set; } public string Params { get; set; }
public string ClientTime { get; set; } public string ClientTime { get; set; }
public string StreamId { get; set; }
public bool Throttle { get; set; }
public string TranscodingJobId { get; set; } public string TranscodingJobId { get; set; }
} }

View File

@ -1,17 +1,16 @@
using MediaBrowser.Common.Net; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using MediaBrowser.Model.Net;
namespace MediaBrowser.Api.Playback namespace MediaBrowser.Api.Playback
{ {
@ -23,6 +22,7 @@ namespace MediaBrowser.Api.Playback
public string RequestedUrl { get; set; } public string RequestedUrl { get; set; }
public StreamRequest Request { get; set; } public StreamRequest Request { get; set; }
public TranscodingThrottler TranscodingThrottler { get; set; }
public VideoStreamRequest VideoRequest public VideoStreamRequest VideoRequest
{ {
@ -52,10 +52,12 @@ namespace MediaBrowser.Api.Playback
public IIsoMount IsoMount { get; set; } public IIsoMount IsoMount { get; set; }
public string MediaPath { get; set; } public string MediaPath { get; set; }
public string WaitForPath { get; set; }
public MediaProtocol InputProtocol { get; set; } public MediaProtocol InputProtocol { get; set; }
public bool IsInputVideo { get; set; } public bool IsInputVideo { get; set; }
public bool IsInputArchive { get; set; }
public VideoType VideoType { get; set; } public VideoType VideoType { get; set; }
public IsoType? IsoType { get; set; } public IsoType? IsoType { get; set; }
@ -64,8 +66,8 @@ namespace MediaBrowser.Api.Playback
public string LiveTvStreamId { get; set; } public string LiveTvStreamId { get; set; }
public int SegmentLength = 6; public int SegmentLength = 3;
public bool EnableGenericHlsSegmenter = false;
public int HlsListSize public int HlsListSize
{ {
get get
@ -112,6 +114,7 @@ namespace MediaBrowser.Api.Playback
public long? EncodingDurationTicks { get; set; } public long? EncodingDurationTicks { get; set; }
public string ItemType { get; set; } public string ItemType { get; set; }
public string ItemId { get; set; }
public string GetMimeType(string outputPath) public string GetMimeType(string outputPath)
{ {
@ -125,6 +128,7 @@ namespace MediaBrowser.Api.Playback
public void Dispose() public void Dispose()
{ {
DisposeTranscodingThrottler();
DisposeLiveStream(); DisposeLiveStream();
DisposeLogStream(); DisposeLogStream();
DisposeIsoMount(); DisposeIsoMount();
@ -147,6 +151,23 @@ namespace MediaBrowser.Api.Playback
} }
} }
private void DisposeTranscodingThrottler()
{
if (TranscodingThrottler != null)
{
try
{
TranscodingThrottler.Dispose();
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing TranscodingThrottler", ex);
}
TranscodingThrottler = null;
}
}
private void DisposeIsoMount() private void DisposeIsoMount()
{ {
if (IsoMount != null) if (IsoMount != null)

View File

@ -0,0 +1,159 @@
using MediaBrowser.Controller.Diagnostics;
using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Threading;
namespace MediaBrowser.Api.Playback
{
public class TranscodingThrottler : IDisposable
{
private readonly TranscodingJob _job;
private readonly ILogger _logger;
private readonly IProcessManager _processManager;
private Timer _timer;
private bool _isPaused;
private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(2).Ticks;
public TranscodingThrottler(TranscodingJob job, ILogger logger, IProcessManager processManager)
{
_job = job;
_logger = logger;
_processManager = processManager;
}
public void Start()
{
_timer = new Timer(TimerCallback, null, 5000, 5000);
}
private void TimerCallback(object state)
{
if (_job.HasExited)
{
DisposeTimer();
return;
}
if (IsThrottleAllowed(_job))
{
PauseTranscoding();
}
else
{
UnpauseTranscoding();
}
}
private void PauseTranscoding()
{
if (!_isPaused)
{
_logger.Debug("Sending pause command to ffmpeg");
try
{
_job.Process.StandardInput.Write("c");
_isPaused = true;
}
catch (Exception ex)
{
_logger.ErrorException("Error pausing transcoding", ex);
}
}
}
private void UnpauseTranscoding()
{
if (_isPaused)
{
_logger.Debug("Sending unpause command to ffmpeg");
try
{
_job.Process.StandardInput.WriteLine();
_isPaused = false;
}
catch (Exception ex)
{
_logger.ErrorException("Error unpausing transcoding", ex);
}
}
}
private bool IsThrottleAllowed(TranscodingJob job)
{
var bytesDownloaded = job.BytesDownloaded ?? 0;
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
var path = job.Path;
if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
{
// HLS - time-based consideration
var targetGap = _gapLengthInTicks;
var gap = transcodingPositionTicks - downloadPositionTicks;
if (gap < targetGap)
{
//_logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
return false;
}
//_logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
return true;
}
if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
{
// Progressive Streaming - byte-based consideration
try
{
var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
// Estimate the bytes the transcoder should be ahead
double gapFactor = _gapLengthInTicks;
gapFactor /= transcodingPositionTicks;
var targetGap = bytesTranscoded * gapFactor;
var gap = bytesTranscoded - bytesDownloaded;
if (gap < targetGap)
{
//_logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
return false;
}
//_logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
return true;
}
catch
{
//_logger.Error("Error getting output size");
return false;
}
}
//_logger.Debug("No throttle data for " + path);
return false;
}
public void Dispose()
{
DisposeTimer();
}
private void DisposeTimer()
{
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
}
}
}

View File

@ -5,6 +5,7 @@ using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Registration;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using ServiceStack; using ServiceStack;
using ServiceStack.Web; using ServiceStack.Web;
@ -106,6 +107,14 @@ namespace MediaBrowser.Api
public string Mb2Equivalent { get; set; } public string Mb2Equivalent { get; set; }
} }
[Route("/Registrations/{Name}", "GET", Summary = "Gets registration status for a feature")]
[Authenticated]
public class GetRegistration : IReturn<RegistrationInfo>
{
[ApiMember(Name = "Name", Description = "Feature Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Name { get; set; }
}
/// <summary> /// <summary>
/// Class PluginsService /// Class PluginsService
/// </summary> /// </summary>
@ -144,13 +153,26 @@ namespace MediaBrowser.Api
/// </summary> /// </summary>
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
public object Get(GetRegistrationStatus request) public async Task<object> Get(GetRegistrationStatus request)
{ {
var result = _securityManager.GetRegistrationStatus(request.Name, request.Mb2Equivalent).Result; var result = await _securityManager.GetRegistrationStatus(request.Name, request.Mb2Equivalent).ConfigureAwait(false);
return ToOptimizedResult(result); return ToOptimizedResult(result);
} }
public async Task<object> Get(GetRegistration request)
{
var result = await _securityManager.GetRegistrationStatus(request.Name).ConfigureAwait(false);
return ToOptimizedResult(new RegistrationInfo
{
ExpirationDate = result.ExpirationDate,
IsRegistered = result.IsRegistered,
IsTrial = result.TrialVersion,
Name = request.Name
});
}
/// <summary> /// <summary>
/// Gets the specified request. /// Gets the specified request.
/// </summary> /// </summary>

View File

@ -3,7 +3,6 @@ using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using ServiceStack; using ServiceStack;
using ServiceStack.Text.Controller;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

View File

@ -211,7 +211,7 @@ namespace MediaBrowser.Api
result.SongCount = album.Tracks.Count(); result.SongCount = album.Tracks.Count();
result.Artists = album.Artists.ToArray(); result.Artists = album.Artists.ToArray();
result.AlbumArtist = album.AlbumArtists.FirstOrDefault(); result.AlbumArtist = album.AlbumArtist;
} }
var song = item as Audio; var song = item as Audio;

View File

@ -418,7 +418,7 @@ namespace MediaBrowser.Api.Session
SeekPositionTicks = request.SeekPositionTicks SeekPositionTicks = request.SeekPositionTicks
}; };
var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); var task = _sessionManager.SendPlaystateCommand(GetSession().Result.Id, request.Id, command, CancellationToken.None);
Task.WaitAll(task); Task.WaitAll(task);
} }
@ -436,7 +436,7 @@ namespace MediaBrowser.Api.Session
ItemType = request.ItemType ItemType = request.ItemType
}; };
var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); var task = _sessionManager.SendBrowseCommand(GetSession().Result.Id, request.Id, command, CancellationToken.None);
Task.WaitAll(task); Task.WaitAll(task);
} }
@ -455,7 +455,7 @@ namespace MediaBrowser.Api.Session
name = commandType.ToString(); name = commandType.ToString();
} }
var currentSession = GetSession(); var currentSession = GetSession().Result;
var command = new GeneralCommand var command = new GeneralCommand
{ {
@ -481,7 +481,7 @@ namespace MediaBrowser.Api.Session
Text = request.Text Text = request.Text
}; };
var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); var task = _sessionManager.SendMessageCommand(GetSession().Result.Id, request.Id, command, CancellationToken.None);
Task.WaitAll(task); Task.WaitAll(task);
} }
@ -500,14 +500,14 @@ namespace MediaBrowser.Api.Session
StartPositionTicks = request.StartPositionTicks StartPositionTicks = request.StartPositionTicks
}; };
var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); var task = _sessionManager.SendPlayCommand(GetSession().Result.Id, request.Id, command, CancellationToken.None);
Task.WaitAll(task); Task.WaitAll(task);
} }
public void Post(SendGeneralCommand request) public void Post(SendGeneralCommand request)
{ {
var currentSession = GetSession(); var currentSession = GetSession().Result;
var command = new GeneralCommand var command = new GeneralCommand
{ {
@ -522,7 +522,7 @@ namespace MediaBrowser.Api.Session
public void Post(SendFullGeneralCommand request) public void Post(SendFullGeneralCommand request)
{ {
var currentSession = GetSession(); var currentSession = GetSession().Result;
request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null;
@ -545,7 +545,7 @@ namespace MediaBrowser.Api.Session
{ {
if (string.IsNullOrWhiteSpace(request.Id)) if (string.IsNullOrWhiteSpace(request.Id))
{ {
request.Id = GetSession().Id; request.Id = GetSession().Result.Id;
} }
_sessionManager.ReportCapabilities(request.Id, new ClientCapabilities _sessionManager.ReportCapabilities(request.Id, new ClientCapabilities
{ {
@ -569,7 +569,7 @@ namespace MediaBrowser.Api.Session
{ {
if (string.IsNullOrWhiteSpace(request.Id)) if (string.IsNullOrWhiteSpace(request.Id))
{ {
request.Id = GetSession().Id; request.Id = GetSession().Result.Id;
} }
_sessionManager.ReportCapabilities(request.Id, request); _sessionManager.ReportCapabilities(request.Id, request);
} }

View File

@ -65,6 +65,7 @@ namespace MediaBrowser.Api
_config.Configuration.MergeMetadataAndImagesByName = true; _config.Configuration.MergeMetadataAndImagesByName = true;
_config.Configuration.EnableStandaloneMetadata = true; _config.Configuration.EnableStandaloneMetadata = true;
_config.Configuration.EnableLibraryMetadataSubFolder = true; _config.Configuration.EnableLibraryMetadataSubFolder = true;
_config.Configuration.EnableUserSpecificUserViews = true;
_config.SaveConfiguration(); _config.SaveConfiguration();
} }

View File

@ -124,20 +124,23 @@ namespace MediaBrowser.Api.Subtitles
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ISubtitleManager _subtitleManager; private readonly ISubtitleManager _subtitleManager;
private readonly ISubtitleEncoder _subtitleEncoder; private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IProviderManager _providerManager;
public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder) public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProviderManager providerManager)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_subtitleManager = subtitleManager; _subtitleManager = subtitleManager;
_subtitleEncoder = subtitleEncoder; _subtitleEncoder = subtitleEncoder;
_mediaSourceManager = mediaSourceManager;
_providerManager = providerManager;
} }
public object Get(GetSubtitlePlaylist request) public object Get(GetSubtitlePlaylist request)
{ {
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
var mediaSource = item.GetMediaSources(false) var mediaSource = _mediaSourceManager.GetStaticMediaSource(item, request.MediaSourceId, false);
.First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
var builder = new StringBuilder(); var builder = new StringBuilder();
@ -255,7 +258,7 @@ namespace MediaBrowser.Api.Subtitles
await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None) await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
await video.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService()), CancellationToken.None).ConfigureAwait(false); _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions());
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -1,7 +1,8 @@
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Sync;
using System.Collections.Generic; using System.Collections.Generic;
namespace MediaBrowser.Model.Sync namespace MediaBrowser.Api.Sync
{ {
public static class SyncHelper public static class SyncHelper
{ {
@ -21,6 +22,7 @@ namespace MediaBrowser.Model.Sync
if (item.IsVideo) if (item.IsVideo)
{ {
options.Add(SyncJobOption.Quality); options.Add(SyncJobOption.Quality);
options.Add(SyncJobOption.Profile);
if (items.Count > 1) if (items.Count > 1)
{ {
options.Add(SyncJobOption.UnwatchedOnly); options.Add(SyncJobOption.UnwatchedOnly);
@ -30,6 +32,7 @@ namespace MediaBrowser.Model.Sync
if (item.IsFolder && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre) if (item.IsFolder && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre)
{ {
options.Add(SyncJobOption.Quality); options.Add(SyncJobOption.Quality);
options.Add(SyncJobOption.Profile);
options.Add(SyncJobOption.UnwatchedOnly); options.Add(SyncJobOption.UnwatchedOnly);
break; break;
} }
@ -64,6 +67,7 @@ namespace MediaBrowser.Model.Sync
options.Add(SyncJobOption.Name); options.Add(SyncJobOption.Name);
options.Add(SyncJobOption.Quality); options.Add(SyncJobOption.Quality);
options.Add(SyncJobOption.Profile);
options.Add(SyncJobOption.UnwatchedOnly); options.Add(SyncJobOption.UnwatchedOnly);
options.Add(SyncJobOption.SyncNewContent); options.Add(SyncJobOption.SyncNewContent);
options.Add(SyncJobOption.ItemLimit); options.Add(SyncJobOption.ItemLimit);

View File

@ -94,6 +94,9 @@ namespace MediaBrowser.Api.Sync
[ApiMember(Name = "ParentId", Description = "ParentId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "ParentId", Description = "ParentId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string ParentId { get; set; } public string ParentId { get; set; }
[ApiMember(Name = "TargetId", Description = "TargetId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string TargetId { get; set; }
[ApiMember(Name = "Category", Description = "Category", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "Category", Description = "Category", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public SyncCategory? Category { get; set; } public SyncCategory? Category { get; set; }
} }
@ -226,6 +229,21 @@ namespace MediaBrowser.Api.Sync
result.Targets = _syncManager.GetSyncTargets(request.UserId) result.Targets = _syncManager.GetSyncTargets(request.UserId)
.ToList(); .ToList();
if (!string.IsNullOrWhiteSpace(request.TargetId))
{
result.Targets = result.Targets
.Where(i => string.Equals(i.Id, request.TargetId, StringComparison.OrdinalIgnoreCase))
.ToList();
result.QualityOptions = _syncManager
.GetQualityOptions(request.TargetId)
.ToList();
result.ProfileOptions = _syncManager
.GetProfileOptions(request.TargetId)
.ToList();
}
if (request.Category.HasValue) if (request.Category.HasValue)
{ {
result.Options = SyncHelper.GetSyncOptions(request.Category.Value); result.Options = SyncHelper.GetSyncOptions(request.Category.Value);

View File

@ -39,6 +39,9 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Person { get; set; } public string Person { get; set; }
[ApiMember(Name = "PersonIds", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string PersonIds { get; set; }
/// <summary> /// <summary>
/// If the Person filter is used, this can also be used to restrict to a specific person type /// If the Person filter is used, this can also be used to restrict to a specific person type
/// </summary> /// </summary>
@ -46,9 +49,6 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string PersonTypes { get; set; } public string PersonTypes { get; set; }
[ApiMember(Name = "AllGenres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string AllGenres { get; set; }
/// <summary> /// <summary>
/// Limit results to items containing specific studios /// Limit results to items containing specific studios
/// </summary> /// </summary>
@ -56,6 +56,9 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] [ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string Studios { get; set; } public string Studios { get; set; }
[ApiMember(Name = "StudioIds", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string StudioIds { get; set; }
/// <summary> /// <summary>
/// Gets or sets the studios. /// Gets or sets the studios.
/// </summary> /// </summary>
@ -63,6 +66,9 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string Artists { get; set; } public string Artists { get; set; }
[ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string ArtistIds { get; set; }
[ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string Albums { get; set; } public string Albums { get; set; }
@ -226,22 +232,27 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? CollapseBoxSetItems { get; set; } public bool? CollapseBoxSetItems { get; set; }
public string[] GetAllGenres()
{
return (AllGenres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
}
public string[] GetStudios() public string[] GetStudios()
{ {
return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
} }
public string[] GetStudioIds()
{
return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
}
public string[] GetPersonTypes() public string[] GetPersonTypes()
{ {
return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
} }
public IEnumerable<VideoType> GetVideoTypes() public string[] GetPersonIds()
{
return (PersonIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
public VideoType[] GetVideoTypes()
{ {
var val = VideoTypes; var val = VideoTypes;
@ -250,7 +261,7 @@ namespace MediaBrowser.Api.UserLibrary
return new VideoType[] { }; return new VideoType[] { };
} }
return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)); return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
} }
} }
@ -471,9 +482,10 @@ namespace MediaBrowser.Api.UserLibrary
Tags = request.GetTags(), Tags = request.GetTags(),
OfficialRatings = request.GetOfficialRatings(), OfficialRatings = request.GetOfficialRatings(),
Genres = request.GetGenres(), Genres = request.GetGenres(),
AllGenres = request.GetAllGenres(),
Studios = request.GetStudios(), Studios = request.GetStudios(),
StudioIds = request.GetStudioIds(),
Person = request.Person, Person = request.Person,
PersonIds = request.GetPersonIds(),
PersonTypes = request.GetPersonTypes(), PersonTypes = request.GetPersonTypes(),
Years = request.GetYears(), Years = request.GetYears(),
ImageTypes = request.GetImageTypes().ToArray(), ImageTypes = request.GetImageTypes().ToArray(),
@ -609,6 +621,8 @@ namespace MediaBrowser.Api.UserLibrary
private bool ApplyAdditionalFilters(GetItems request, BaseItem i, User user, bool isPreFiltered, ILibraryManager libraryManager) private bool ApplyAdditionalFilters(GetItems request, BaseItem i, User user, bool isPreFiltered, ILibraryManager libraryManager)
{ {
var video = i as Video;
if (!isPreFiltered) if (!isPreFiltered)
{ {
var mediaTypes = request.GetMediaTypes(); var mediaTypes = request.GetMediaTypes();
@ -656,7 +670,6 @@ namespace MediaBrowser.Api.UserLibrary
if (request.Is3D.HasValue) if (request.Is3D.HasValue)
{ {
var val = request.Is3D.Value; var val = request.Is3D.Value;
var video = i as Video;
if (video == null || val != video.Video3DFormat.HasValue) if (video == null || val != video.Video3DFormat.HasValue)
{ {
@ -667,7 +680,6 @@ namespace MediaBrowser.Api.UserLibrary
if (request.IsHD.HasValue) if (request.IsHD.HasValue)
{ {
var val = request.IsHD.Value; var val = request.IsHD.Value;
var video = i as Video;
if (video == null || val != video.IsHD) if (video == null || val != video.IsHD)
{ {
@ -809,8 +821,6 @@ namespace MediaBrowser.Api.UserLibrary
{ {
var val = request.HasSubtitles.Value; var val = request.HasSubtitles.Value;
var video = i as Video;
if (video == null || val != video.HasSubtitles) if (video == null || val != video.HasSubtitles)
{ {
return false; return false;
@ -930,24 +940,12 @@ namespace MediaBrowser.Api.UserLibrary
return false; return false;
} }
// Apply genre filter
var allGenres = request.GetAllGenres();
if (allGenres.Length > 0 && !allGenres.All(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))
{
return false;
}
// Filter by VideoType // Filter by VideoType
if (!string.IsNullOrEmpty(request.VideoTypes)) var videoTypes = request.GetVideoTypes();
{ if (videoTypes.Length > 0 && (video == null || !videoTypes.Contains(video.VideoType)))
var types = request.VideoTypes.Split(',');
var video = i as Video;
if (video == null || !types.Contains(video.VideoType.ToString(), StringComparer.OrdinalIgnoreCase))
{ {
return false; return false;
} }
}
var imageTypes = request.GetImageTypes().ToList(); var imageTypes = request.GetImageTypes().ToList();
if (imageTypes.Count > 0) if (imageTypes.Count > 0)
@ -965,6 +963,17 @@ namespace MediaBrowser.Api.UserLibrary
return false; return false;
} }
// Apply studio filter
var studioIds = request.GetStudioIds();
if (studioIds.Length > 0 && !studioIds.Any(id =>
{
var studioItem = libraryManager.GetItemById(id);
return studioItem != null && i.Studios.Contains(studioItem.Name, StringComparer.OrdinalIgnoreCase);
}))
{
return false;
}
// Apply year filter // Apply year filter
var years = request.GetYears(); var years = request.GetYears();
if (years.Length > 0 && !(i.ProductionYear.HasValue && years.Contains(i.ProductionYear.Value))) if (years.Length > 0 && !(i.ProductionYear.HasValue && years.Contains(i.ProductionYear.Value)))
@ -972,6 +981,21 @@ namespace MediaBrowser.Api.UserLibrary
return false; return false;
} }
// Apply person filter
var personIds = request.GetPersonIds();
if (personIds.Length > 0)
{
var names = personIds
.Select(libraryManager.GetItemById)
.Select(p => p == null ? "-1" : p.Name)
.ToList();
if (!(names.Any(v => i.People.Select(p => p.Name).Contains(v, StringComparer.OrdinalIgnoreCase))))
{
return false;
}
}
// Apply person filter // Apply person filter
if (!string.IsNullOrEmpty(request.Person)) if (!string.IsNullOrEmpty(request.Person))
{ {
@ -1030,6 +1054,23 @@ namespace MediaBrowser.Api.UserLibrary
} }
} }
// Artists
if (!string.IsNullOrEmpty(request.ArtistIds))
{
var artistIds = request.ArtistIds.Split('|');
var audio = i as IHasArtist;
if (!(audio != null && artistIds.Any(id =>
{
var artistItem = libraryManager.GetItemById(id);
return artistItem != null && audio.HasAnyArtist(artistItem.Name);
})))
{
return false;
}
}
// Artists // Artists
if (!string.IsNullOrEmpty(request.Artists)) if (!string.IsNullOrEmpty(request.Artists))
{ {
@ -1037,7 +1078,7 @@ namespace MediaBrowser.Api.UserLibrary
var audio = i as IHasArtist; var audio = i as IHasArtist;
if (!(audio != null && artists.Any(audio.HasArtist))) if (!(audio != null && artists.Any(audio.HasAnyArtist)))
{ {
return false; return false;
} }

View File

@ -231,7 +231,7 @@ namespace MediaBrowser.Api.UserLibrary
datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
} }
var session = GetSession(); var session = await GetSession().ConfigureAwait(false);
var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false);
@ -266,7 +266,7 @@ namespace MediaBrowser.Api.UserLibrary
public void Post(ReportPlaybackStart request) public void Post(ReportPlaybackStart request)
{ {
request.SessionId = GetSession().Id; request.SessionId = GetSession().Result.Id;
var task = _sessionManager.OnPlaybackStart(request); var task = _sessionManager.OnPlaybackStart(request);
@ -294,7 +294,7 @@ namespace MediaBrowser.Api.UserLibrary
public void Post(ReportPlaybackProgress request) public void Post(ReportPlaybackProgress request)
{ {
request.SessionId = GetSession().Id; request.SessionId = GetSession().Result.Id;
var task = _sessionManager.OnPlaybackProgress(request); var task = _sessionManager.OnPlaybackProgress(request);
@ -317,7 +317,7 @@ namespace MediaBrowser.Api.UserLibrary
public void Post(ReportPlaybackStopped request) public void Post(ReportPlaybackStopped request)
{ {
request.SessionId = GetSession().Id; request.SessionId = GetSession().Result.Id;
var task = _sessionManager.OnPlaybackStopped(request); var task = _sessionManager.OnPlaybackStopped(request);
@ -339,7 +339,7 @@ namespace MediaBrowser.Api.UserLibrary
{ {
var user = _userManager.GetUserById(request.UserId); var user = _userManager.GetUserById(request.UserId);
var session = GetSession(); var session = await GetSession().ConfigureAwait(false);
var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false);

View File

@ -253,18 +253,14 @@ namespace MediaBrowser.Api
/// The _user manager /// The _user manager
/// </summary> /// </summary>
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IDtoService _dtoService;
private readonly ISessionManager _sessionMananger; private readonly ISessionManager _sessionMananger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly IDeviceManager _deviceManager; private readonly IDeviceManager _deviceManager;
public IAuthorizationContext AuthorizationContext { get; set; } public UserService(IUserManager userManager, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager, IDeviceManager deviceManager)
public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager, IDeviceManager deviceManager)
{ {
_userManager = userManager; _userManager = userManager;
_dtoService = dtoService;
_sessionMananger = sessionMananger; _sessionMananger = sessionMananger;
_config = config; _config = config;
_networkManager = networkManager; _networkManager = networkManager;
@ -464,7 +460,7 @@ namespace MediaBrowser.Api
public async Task PostAsync(UpdateUserPassword request) public async Task PostAsync(UpdateUserPassword request)
{ {
AssertCanUpdateUser(request.Id); AssertCanUpdateUser(_userManager, request.Id);
var user = _userManager.GetUserById(request.Id); var user = _userManager.GetUserById(request.Id);
@ -498,7 +494,7 @@ namespace MediaBrowser.Api
public async Task PostAsync(UpdateUserEasyPassword request) public async Task PostAsync(UpdateUserEasyPassword request)
{ {
AssertCanUpdateUser(request.Id); AssertCanUpdateUser(_userManager, request.Id);
var user = _userManager.GetUserById(request.Id); var user = _userManager.GetUserById(request.Id);
@ -534,7 +530,7 @@ namespace MediaBrowser.Api
// https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
var id = GetPathValue(1); var id = GetPathValue(1);
AssertCanUpdateUser(id); AssertCanUpdateUser(_userManager, id);
var dtoUser = request; var dtoUser = request;
@ -584,29 +580,13 @@ namespace MediaBrowser.Api
public void Post(UpdateUserConfiguration request) public void Post(UpdateUserConfiguration request)
{ {
AssertCanUpdateUser(request.Id); AssertCanUpdateUser(_userManager, request.Id);
var task = _userManager.UpdateConfiguration(request.Id, request); var task = _userManager.UpdateConfiguration(request.Id, request);
Task.WaitAll(task); Task.WaitAll(task);
} }
private void AssertCanUpdateUser(string userId)
{
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
// If they're going to update the record of another user, they must be an administrator
if (!string.Equals(userId, auth.UserId, StringComparison.OrdinalIgnoreCase))
{
var authenticatedUser = _userManager.GetUserById(auth.UserId);
if (!authenticatedUser.Policy.IsAdministrator)
{
throw new SecurityException("Unauthorized access.");
}
}
}
public void Post(UpdateUserPolicy request) public void Post(UpdateUserPolicy request)
{ {
var task = UpdateUserPolicy(request); var task = UpdateUserPolicy(request);

View File

@ -5,6 +5,7 @@ using SharpCompress.Archive.Tar;
using SharpCompress.Common; using SharpCompress.Common;
using SharpCompress.Reader; using SharpCompress.Reader;
using SharpCompress.Reader.Zip; using SharpCompress.Reader.Zip;
using System;
using System.IO; using System.IO;
namespace MediaBrowser.Common.Implementations.Archiving namespace MediaBrowser.Common.Implementations.Archiving

View File

@ -313,7 +313,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// </summary> /// </summary>
/// <param name="sender">The source of the event.</param> /// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
async void trigger_Triggered(object sender, EventArgs e) async void trigger_Triggered(object sender, GenericEventArgs<TaskExecutionOptions> e)
{ {
var trigger = (ITaskTrigger)sender; var trigger = (ITaskTrigger)sender;
@ -340,11 +340,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <summary> /// <summary>
/// Executes the task /// Executes the task
/// </summary> /// </summary>
/// <param name="options">Task options.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
/// <exception cref="System.InvalidOperationException">Cannot execute a Task that is already running</exception> /// <exception cref="System.InvalidOperationException">Cannot execute a Task that is already running</exception>
public async Task Execute() public async Task Execute(TaskExecutionOptions options)
{ {
var task = ExecuteInternal(); var task = ExecuteInternal(options);
_currentTask = task; _currentTask = task;
@ -358,7 +359,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
} }
} }
private async Task ExecuteInternal() private async Task ExecuteInternal(TaskExecutionOptions options)
{ {
// Cancel the current execution, if any // Cancel the current execution, if any
if (CurrentCancellationTokenSource != null) if (CurrentCancellationTokenSource != null)
@ -383,7 +384,14 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
try try
{ {
await ExecuteTask(CurrentCancellationTokenSource.Token, progress).ConfigureAwait(false); var localTask = ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress);
if (options != null && options.MaxRuntimeMs.HasValue)
{
CurrentCancellationTokenSource.CancelAfter(options.MaxRuntimeMs.Value);
}
await localTask.ConfigureAwait(false);
status = TaskCompletionStatus.Completed; status = TaskCompletionStatus.Completed;
} }

View File

@ -29,7 +29,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <summary> /// <summary>
/// The _task queue /// The _task queue
/// </summary> /// </summary>
private readonly List<Type> _taskQueue = new List<Type>(); private readonly SortedDictionary<Type, TaskExecutionOptions> _taskQueue = new SortedDictionary<Type, TaskExecutionOptions>();
/// <summary> /// <summary>
/// Gets or sets the json serializer. /// Gets or sets the json serializer.
@ -69,13 +69,20 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// Cancels if running and queue. /// Cancels if running and queue.
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public void CancelIfRunningAndQueue<T>() /// <param name="options">Task options.</param>
public void CancelIfRunningAndQueue<T>(TaskExecutionOptions options)
where T : IScheduledTask where T : IScheduledTask
{ {
var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
((ScheduledTaskWorker)task).CancelIfRunning(); ((ScheduledTaskWorker)task).CancelIfRunning();
QueueScheduledTask<T>(); QueueScheduledTask<T>(options);
}
public void CancelIfRunningAndQueue<T>()
where T : IScheduledTask
{
CancelIfRunningAndQueue<T>(new TaskExecutionOptions());
} }
/// <summary> /// <summary>
@ -93,30 +100,39 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// Queues the scheduled task. /// Queues the scheduled task.
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public void QueueScheduledTask<T>() /// <param name="options">Task options</param>
public void QueueScheduledTask<T>(TaskExecutionOptions options)
where T : IScheduledTask where T : IScheduledTask
{ {
var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
QueueScheduledTask(scheduledTask); QueueScheduledTask(scheduledTask, options);
}
public void QueueScheduledTask<T>()
where T : IScheduledTask
{
QueueScheduledTask<T>(new TaskExecutionOptions());
} }
/// <summary> /// <summary>
/// Queues the scheduled task. /// Queues the scheduled task.
/// </summary> /// </summary>
/// <param name="task">The task.</param> /// <param name="task">The task.</param>
public void QueueScheduledTask(IScheduledTask task) /// <param name="options">The task options.</param>
public void QueueScheduledTask(IScheduledTask task, TaskExecutionOptions options)
{ {
var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == task.GetType()); var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == task.GetType());
QueueScheduledTask(scheduledTask); QueueScheduledTask(scheduledTask, options);
} }
/// <summary> /// <summary>
/// Queues the scheduled task. /// Queues the scheduled task.
/// </summary> /// </summary>
/// <param name="task">The task.</param> /// <param name="task">The task.</param>
private void QueueScheduledTask(IScheduledTaskWorker task) /// <param name="options">The task options.</param>
private void QueueScheduledTask(IScheduledTaskWorker task, TaskExecutionOptions options)
{ {
var type = task.ScheduledTask.GetType(); var type = task.ScheduledTask.GetType();
@ -125,17 +141,18 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
// If it's idle just execute immediately // If it's idle just execute immediately
if (task.State == TaskState.Idle) if (task.State == TaskState.Idle)
{ {
Execute(task); Execute(task, options);
return; return;
} }
if (!_taskQueue.Contains(type)) if (!_taskQueue.ContainsKey(type))
{ {
Logger.Info("Queueing task {0}", type.Name); Logger.Info("Queueing task {0}", type.Name);
_taskQueue.Add(type); _taskQueue.Add(type, options);
} }
else else
{ {
_taskQueue[type] = options;
Logger.Info("Task already queued: {0}", type.Name); Logger.Info("Task already queued: {0}", type.Name);
} }
} }
@ -181,9 +198,9 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
((ScheduledTaskWorker)task).Cancel(); ((ScheduledTaskWorker)task).Cancel();
} }
public Task Execute(IScheduledTaskWorker task) public Task Execute(IScheduledTaskWorker task, TaskExecutionOptions options)
{ {
return ((ScheduledTaskWorker)task).Execute(); return ((ScheduledTaskWorker)task).Execute(options);
} }
/// <summary> /// <summary>
@ -224,15 +241,15 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
// Execute queued tasks // Execute queued tasks
lock (_taskQueue) lock (_taskQueue)
{ {
foreach (var type in _taskQueue.ToList()) foreach (var enqueuedType in _taskQueue.ToList())
{ {
var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == type); var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Key);
if (scheduledTask.State == TaskState.Idle) if (scheduledTask.State == TaskState.Idle)
{ {
Execute(scheduledTask); Execute(scheduledTask, enqueuedType.Value);
_taskQueue.Remove(type); _taskQueue.Remove(enqueuedType.Key);
} }
} }
} }

View File

@ -118,9 +118,23 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
index++; index++;
} }
DeleteEmptyFolders(directory);
progress.Report(100); progress.Report(100);
} }
private static void DeleteEmptyFolders(string parent)
{
foreach (var directory in Directory.GetDirectories(parent))
{
DeleteEmptyFolders(directory);
if (!Directory.EnumerateFileSystemEntries(directory).Any())
{
Directory.Delete(directory, false);
}
}
}
private void DeleteFile(string path) private void DeleteFile(string path)
{ {
try try

View File

@ -59,6 +59,11 @@ namespace MediaBrowser.Common.Implementations.Serialization
} }
} }
private Stream OpenFile(string path)
{
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
}
/// <summary> /// <summary>
/// Deserializes from file. /// Deserializes from file.
/// </summary> /// </summary>
@ -78,7 +83,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
throw new ArgumentNullException("file"); throw new ArgumentNullException("file");
} }
using (Stream stream = File.OpenRead(file)) using (Stream stream = OpenFile(file))
{ {
return DeserializeFromStream(stream, type); return DeserializeFromStream(stream, type);
} }
@ -99,7 +104,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
throw new ArgumentNullException("file"); throw new ArgumentNullException("file");
} }
using (Stream stream = File.OpenRead(file)) using (Stream stream = OpenFile(file))
{ {
return DeserializeFromStream<T>(stream); return DeserializeFromStream<T>(stream);
} }

View File

@ -86,6 +86,7 @@
<Compile Include="ScheduledTasks\DailyTrigger.cs" /> <Compile Include="ScheduledTasks\DailyTrigger.cs" />
<Compile Include="ScheduledTasks\IntervalTrigger.cs" /> <Compile Include="ScheduledTasks\IntervalTrigger.cs" />
<Compile Include="ScheduledTasks\TaskCompletionEventArgs.cs" /> <Compile Include="ScheduledTasks\TaskCompletionEventArgs.cs" />
<Compile Include="ScheduledTasks\TaskExecutionOptions.cs" />
<Compile Include="ScheduledTasks\WeeklyTrigger.cs" /> <Compile Include="ScheduledTasks\WeeklyTrigger.cs" />
<Compile Include="Security\IRequiresRegistration.cs" /> <Compile Include="Security\IRequiresRegistration.cs" />
<Compile Include="Security\ISecurityManager.cs" /> <Compile Include="Security\ISecurityManager.cs" />

View File

@ -325,7 +325,7 @@ namespace MediaBrowser.Common.Plugins
AssemblyFileName = AssemblyFileName, AssemblyFileName = AssemblyFileName,
ConfigurationDateLastModified = ConfigurationDateLastModified, ConfigurationDateLastModified = ConfigurationDateLastModified,
Description = Description, Description = Description,
Id = Id.ToString("N"), Id = Id.ToString(),
ConfigurationFileName = ConfigurationFileName ConfigurationFileName = ConfigurationFileName
}; };

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Threading; using System.Threading;
using MediaBrowser.Model.Events;
namespace MediaBrowser.Common.ScheduledTasks namespace MediaBrowser.Common.ScheduledTasks
{ {
@ -20,6 +21,14 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <value>The timer.</value> /// <value>The timer.</value>
private Timer Timer { get; set; } private Timer Timer { get; set; }
/// <summary>
/// Gets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
public TaskExecutionOptions TaskOptions { get; set; }
/// <summary> /// <summary>
/// Stars waiting for the trigger action /// Stars waiting for the trigger action
/// </summary> /// </summary>
@ -58,7 +67,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <summary> /// <summary>
/// Occurs when [triggered]. /// Occurs when [triggered].
/// </summary> /// </summary>
public event EventHandler<EventArgs> Triggered; public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary> /// <summary>
/// Called when [triggered]. /// Called when [triggered].
@ -67,7 +76,7 @@ namespace MediaBrowser.Common.ScheduledTasks
{ {
if (Triggered != null) if (Triggered != null)
{ {
Triggered(this, EventArgs.Empty); Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
} }
} }
} }

View File

@ -13,6 +13,14 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <value>The scheduled tasks.</value> /// <value>The scheduled tasks.</value>
IScheduledTaskWorker[] ScheduledTasks { get; } IScheduledTaskWorker[] ScheduledTasks { get; }
/// <summary>
/// Cancels if running and queue.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="options">Task options.</param>
void CancelIfRunningAndQueue<T>(TaskExecutionOptions options)
where T : IScheduledTask;
/// <summary> /// <summary>
/// Cancels if running and queue. /// Cancels if running and queue.
/// </summary> /// </summary>
@ -27,6 +35,14 @@ namespace MediaBrowser.Common.ScheduledTasks
void CancelIfRunning<T>() void CancelIfRunning<T>()
where T : IScheduledTask; where T : IScheduledTask;
/// <summary>
/// Queues the scheduled task.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="options">Task options.</param>
void QueueScheduledTask<T>(TaskExecutionOptions options)
where T : IScheduledTask;
/// <summary> /// <summary>
/// Queues the scheduled task. /// Queues the scheduled task.
/// </summary> /// </summary>
@ -38,7 +54,8 @@ namespace MediaBrowser.Common.ScheduledTasks
/// Queues the scheduled task. /// Queues the scheduled task.
/// </summary> /// </summary>
/// <param name="task">The task.</param> /// <param name="task">The task.</param>
void QueueScheduledTask(IScheduledTask task); /// <param name="options">The task run options.</param>
void QueueScheduledTask(IScheduledTask task, TaskExecutionOptions options = null);
/// <summary> /// <summary>
/// Adds the tasks. /// Adds the tasks.
@ -47,7 +64,7 @@ namespace MediaBrowser.Common.ScheduledTasks
void AddTasks(IEnumerable<IScheduledTask> tasks); void AddTasks(IEnumerable<IScheduledTask> tasks);
void Cancel(IScheduledTaskWorker task); void Cancel(IScheduledTaskWorker task);
Task Execute(IScheduledTaskWorker task); Task Execute(IScheduledTaskWorker task, TaskExecutionOptions options = null);
event EventHandler<GenericEventArgs<IScheduledTaskWorker>> TaskExecuting; event EventHandler<GenericEventArgs<IScheduledTaskWorker>> TaskExecuting;
event EventHandler<TaskCompletionEventArgs> TaskCompleted; event EventHandler<TaskCompletionEventArgs> TaskCompleted;

View File

@ -1,4 +1,5 @@
using System; using System;
using MediaBrowser.Model.Events;
namespace MediaBrowser.Common.ScheduledTasks namespace MediaBrowser.Common.ScheduledTasks
{ {
@ -10,7 +11,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <summary> /// <summary>
/// Fires when the trigger condition is satisfied and the task should run /// Fires when the trigger condition is satisfied and the task should run
/// </summary> /// </summary>
event EventHandler<EventArgs> Triggered; event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary> /// <summary>
/// Stars waiting for the trigger action /// Stars waiting for the trigger action
@ -22,5 +23,13 @@ namespace MediaBrowser.Common.ScheduledTasks
/// Stops waiting for the trigger action /// Stops waiting for the trigger action
/// </summary> /// </summary>
void Stop(); void Stop();
/// <summary>
/// Gets or sets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
TaskExecutionOptions TaskOptions { get; set; }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Threading; using System.Threading;
using MediaBrowser.Model.Events;
namespace MediaBrowser.Common.ScheduledTasks namespace MediaBrowser.Common.ScheduledTasks
{ {
@ -20,6 +21,14 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <value>The timer.</value> /// <value>The timer.</value>
private Timer Timer { get; set; } private Timer Timer { get; set; }
/// <summary>
/// Gets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
public TaskExecutionOptions TaskOptions { get; set; }
/// <summary> /// <summary>
/// Stars waiting for the trigger action /// Stars waiting for the trigger action
/// </summary> /// </summary>
@ -53,7 +62,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <summary> /// <summary>
/// Occurs when [triggered]. /// Occurs when [triggered].
/// </summary> /// </summary>
public event EventHandler<EventArgs> Triggered; public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary> /// <summary>
/// Called when [triggered]. /// Called when [triggered].
@ -62,7 +71,7 @@ namespace MediaBrowser.Common.ScheduledTasks
{ {
if (Triggered != null) if (Triggered != null)
{ {
Triggered(this, EventArgs.Empty); Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
} }
} }
} }

View File

@ -99,6 +99,11 @@ namespace MediaBrowser.Common.ScheduledTasks
info.SystemEvent = systemEventTrigger.SystemEvent; info.SystemEvent = systemEventTrigger.SystemEvent;
} }
if (trigger.TaskOptions != null)
{
info.MaxRuntimeMs = trigger.TaskOptions.MaxRuntimeMs;
}
return info; return info;
} }
@ -111,6 +116,11 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <exception cref="System.ArgumentException">Invalid trigger type: + info.Type</exception> /// <exception cref="System.ArgumentException">Invalid trigger type: + info.Type</exception>
public static ITaskTrigger GetTrigger(TaskTriggerInfo info) public static ITaskTrigger GetTrigger(TaskTriggerInfo info)
{ {
var options = new TaskExecutionOptions
{
MaxRuntimeMs = info.MaxRuntimeMs
};
if (info.Type.Equals(typeof(DailyTrigger).Name, StringComparison.OrdinalIgnoreCase)) if (info.Type.Equals(typeof(DailyTrigger).Name, StringComparison.OrdinalIgnoreCase))
{ {
if (!info.TimeOfDayTicks.HasValue) if (!info.TimeOfDayTicks.HasValue)
@ -120,7 +130,8 @@ namespace MediaBrowser.Common.ScheduledTasks
return new DailyTrigger return new DailyTrigger
{ {
TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value) TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value),
TaskOptions = options
}; };
} }
@ -139,7 +150,8 @@ namespace MediaBrowser.Common.ScheduledTasks
return new WeeklyTrigger return new WeeklyTrigger
{ {
TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value), TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value),
DayOfWeek = info.DayOfWeek.Value DayOfWeek = info.DayOfWeek.Value,
TaskOptions = options
}; };
} }
@ -152,7 +164,8 @@ namespace MediaBrowser.Common.ScheduledTasks
return new IntervalTrigger return new IntervalTrigger
{ {
Interval = TimeSpan.FromTicks(info.IntervalTicks.Value) Interval = TimeSpan.FromTicks(info.IntervalTicks.Value),
TaskOptions = options
}; };
} }
@ -165,7 +178,8 @@ namespace MediaBrowser.Common.ScheduledTasks
return new SystemEventTrigger return new SystemEventTrigger
{ {
SystemEvent = info.SystemEvent.Value SystemEvent = info.SystemEvent.Value,
TaskOptions = options
}; };
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Events;
namespace MediaBrowser.Common.ScheduledTasks namespace MediaBrowser.Common.ScheduledTasks
{ {
@ -10,6 +11,14 @@ namespace MediaBrowser.Common.ScheduledTasks
{ {
public int DelayMs { get; set; } public int DelayMs { get; set; }
/// <summary>
/// Gets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
public TaskExecutionOptions TaskOptions { get; set; }
public StartupTrigger() public StartupTrigger()
{ {
DelayMs = 3000; DelayMs = 3000;
@ -39,7 +48,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <summary> /// <summary>
/// Occurs when [triggered]. /// Occurs when [triggered].
/// </summary> /// </summary>
public event EventHandler<EventArgs> Triggered; public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary> /// <summary>
/// Called when [triggered]. /// Called when [triggered].
@ -48,7 +57,7 @@ namespace MediaBrowser.Common.ScheduledTasks
{ {
if (Triggered != null) if (Triggered != null)
{ {
Triggered(this, EventArgs.Empty); Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
} }
} }
} }

View File

@ -2,6 +2,7 @@
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Events;
namespace MediaBrowser.Common.ScheduledTasks namespace MediaBrowser.Common.ScheduledTasks
{ {
@ -16,6 +17,14 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <value>The system event.</value> /// <value>The system event.</value>
public SystemEvent SystemEvent { get; set; } public SystemEvent SystemEvent { get; set; }
/// <summary>
/// Gets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
public TaskExecutionOptions TaskOptions { get; set; }
/// <summary> /// <summary>
/// Stars waiting for the trigger action /// Stars waiting for the trigger action
/// </summary> /// </summary>
@ -57,7 +66,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <summary> /// <summary>
/// Occurs when [triggered]. /// Occurs when [triggered].
/// </summary> /// </summary>
public event EventHandler<EventArgs> Triggered; public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary> /// <summary>
/// Called when [triggered]. /// Called when [triggered].
@ -66,7 +75,7 @@ namespace MediaBrowser.Common.ScheduledTasks
{ {
if (Triggered != null) if (Triggered != null)
{ {
Triggered(this, EventArgs.Empty); Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
} }
} }
} }

View File

@ -0,0 +1,11 @@

namespace MediaBrowser.Common.ScheduledTasks
{
/// <summary>
/// A class that encomposases all common task run properties.
/// </summary>
public class TaskExecutionOptions
{
public int? MaxRuntimeMs { get; set; }
}
}

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Threading; using System.Threading;
using MediaBrowser.Model.Events;
namespace MediaBrowser.Common.ScheduledTasks namespace MediaBrowser.Common.ScheduledTasks
{ {
@ -20,6 +21,14 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <value>The day of week.</value> /// <value>The day of week.</value>
public DayOfWeek DayOfWeek { get; set; } public DayOfWeek DayOfWeek { get; set; }
/// <summary>
/// Gets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
public TaskExecutionOptions TaskOptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the timer. /// Gets or sets the timer.
/// </summary> /// </summary>
@ -88,7 +97,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// <summary> /// <summary>
/// Occurs when [triggered]. /// Occurs when [triggered].
/// </summary> /// </summary>
public event EventHandler<EventArgs> Triggered; public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary> /// <summary>
/// Called when [triggered]. /// Called when [triggered].
@ -97,7 +106,7 @@ namespace MediaBrowser.Common.ScheduledTasks
{ {
if (Triggered != null) if (Triggered != null)
{ {
Triggered(this, EventArgs.Empty); Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
} }
} }
} }

View File

@ -0,0 +1,19 @@
using MediaBrowser.Model.Connect;
using System.Collections.Generic;
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Connect
{
public class ConnectSupporterSummary
{
public int MaxUsers { get; set; }
public List<ConnectUser> Users { get; set; }
public List<UserDto> EligibleUsers { get; set; }
public ConnectSupporterSummary()
{
Users = new List<ConnectUser>();
EligibleUsers = new List<UserDto>();
}
}
}

View File

@ -69,5 +69,25 @@ namespace MediaBrowser.Controller.Connect
/// <param name="token">The token.</param> /// <param name="token">The token.</param>
/// <returns><c>true</c> if [is authorization token valid] [the specified token]; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if [is authorization token valid] [the specified token]; otherwise, <c>false</c>.</returns>
bool IsAuthorizationTokenValid(string token); bool IsAuthorizationTokenValid(string token);
/// <summary>
/// Gets the connect supporter summary.
/// </summary>
/// <returns>Task&lt;ConnectSupporterSummary&gt;.</returns>
Task<ConnectSupporterSummary> GetConnectSupporterSummary();
/// <summary>
/// Removes the connect supporter.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>Task.</returns>
Task RemoveConnectSupporter(string id);
/// <summary>
/// Adds the connect supporter.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>Task.</returns>
Task AddConnectSupporter(string id);
} }
} }

View File

@ -25,9 +25,10 @@ namespace MediaBrowser.Controller.Devices
/// <param name="reportedId">The reported identifier.</param> /// <param name="reportedId">The reported identifier.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="appName">Name of the application.</param> /// <param name="appName">Name of the application.</param>
/// <param name="appVersion">The application version.</param>
/// <param name="usedByUserId">The used by user identifier.</param> /// <param name="usedByUserId">The used by user identifier.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task<DeviceInfo> RegisterDevice(string reportedId, string name, string appName, string usedByUserId); Task<DeviceInfo> RegisterDevice(string reportedId, string name, string appName, string appVersion, string usedByUserId);
/// <summary> /// <summary>
/// Saves the capabilities. /// Saves the capabilities.

View File

@ -0,0 +1,28 @@
using System.Diagnostics;
namespace MediaBrowser.Controller.Diagnostics
{
/// <summary>
/// Interface IProcessManager
/// </summary>
public interface IProcessManager
{
/// <summary>
/// Gets a value indicating whether [supports suspension].
/// </summary>
/// <value><c>true</c> if [supports suspension]; otherwise, <c>false</c>.</value>
bool SupportsSuspension { get; }
/// <summary>
/// Suspends the process.
/// </summary>
/// <param name="process">The process.</param>
void SuspendProcess(Process process);
/// <summary>
/// Resumes the process.
/// </summary>
/// <param name="process">The process.</param>
void ResumeProcess(Process process);
}
}

View File

@ -2,7 +2,6 @@
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -30,10 +29,9 @@ namespace MediaBrowser.Controller.Drawing
/// <summary> /// <summary>
/// Gets the size of the image. /// Gets the size of the image.
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="info">The information.</param>
/// <param name="imageDateModified">The image date modified.</param>
/// <returns>ImageSize.</returns> /// <returns>ImageSize.</returns>
ImageSize GetImageSize(string path, DateTime imageDateModified); ImageSize GetImageSize(ItemImageInfo info);
/// <summary> /// <summary>
/// Adds the parts. /// Adds the parts.

View File

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -22,7 +21,8 @@ namespace MediaBrowser.Controller.Entities.Audio
IHasLookupInfo<SongInfo>, IHasLookupInfo<SongInfo>,
IHasTags, IHasTags,
IHasMediaSources, IHasMediaSources,
IThemeMedia IThemeMedia,
IArchivable
{ {
public string FormatName { get; set; } public string FormatName { get; set; }
public long? Size { get; set; } public long? Size { get; set; }
@ -171,16 +171,6 @@ namespace MediaBrowser.Controller.Entities.Audio
+ (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name; + (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name;
} }
/// <summary>
/// Determines whether the specified name has artist.
/// </summary>
/// <param name="name">The name.</param>
/// <returns><c>true</c> if the specified name has artist; otherwise, <c>false</c>.</returns>
public bool HasArtist(string name)
{
return AllArtists.Contains(name, StringComparer.OrdinalIgnoreCase);
}
/// <summary> /// <summary>
/// Gets the user data key. /// Gets the user data key.
/// </summary> /// </summary>
@ -239,7 +229,7 @@ namespace MediaBrowser.Controller.Entities.Audio
{ {
Id = i.Id.ToString("N"), Id = i.Id.ToString("N"),
Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File, Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File,
MediaStreams = MediaSourceManager.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(), MediaStreams = MediaSourceManager.GetMediaStreams(i.Id).ToList(),
Name = i.Name, Name = i.Name,
Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path, Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path,
RunTimeTicks = i.RunTimeTicks, RunTimeTicks = i.RunTimeTicks,

View File

@ -1,4 +1,6 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Controller.Entities.Audio namespace MediaBrowser.Controller.Entities.Audio
{ {
@ -9,10 +11,20 @@ namespace MediaBrowser.Controller.Entities.Audio
public interface IHasArtist public interface IHasArtist
{ {
bool HasArtist(string name);
List<string> AllArtists { get; } List<string> AllArtists { get; }
List<string> Artists { get; } List<string> Artists { get; set; }
}
public static class HasArtistExtensions
{
public static bool HasArtist(this IHasArtist hasArtist, string artist)
{
return hasArtist.Artists.Contains(artist, StringComparer.OrdinalIgnoreCase);
}
public static bool HasAnyArtist(this IHasArtist hasArtist, string artist)
{
return hasArtist.AllArtists.Contains(artist, StringComparer.OrdinalIgnoreCase);
}
} }
} }

View File

@ -120,16 +120,6 @@ namespace MediaBrowser.Controller.Entities.Audio
get { return Parent as MusicArtist ?? UnknwonArtist; } get { return Parent as MusicArtist ?? UnknwonArtist; }
} }
/// <summary>
/// Determines whether the specified artist has artist.
/// </summary>
/// <param name="artist">The artist.</param>
/// <returns><c>true</c> if the specified artist has artist; otherwise, <c>false</c>.</returns>
public bool HasArtist(string artist)
{
return AllArtists.Contains(artist, StringComparer.OrdinalIgnoreCase);
}
public List<string> Artists { get; set; } public List<string> Artists { get; set; }
/// <summary> /// <summary>

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users; using MediaBrowser.Model.Users;
@ -148,12 +149,15 @@ namespace MediaBrowser.Controller.Entities.Audio
var totalItems = songs.Count + others.Count; var totalItems = songs.Count + others.Count;
var numComplete = 0; var numComplete = 0;
var childUpdateType = ItemUpdateType.None;
// Refresh songs // Refresh songs
foreach (var item in songs) foreach (var item in songs)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); var updateType = await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
childUpdateType = childUpdateType | updateType;
numComplete++; numComplete++;
double percent = numComplete; double percent = numComplete;
@ -161,15 +165,22 @@ namespace MediaBrowser.Controller.Entities.Audio
progress.Report(percent * 100); progress.Report(percent * 100);
} }
var parentRefreshOptions = refreshOptions;
if (childUpdateType > ItemUpdateType.None)
{
parentRefreshOptions = new MetadataRefreshOptions(refreshOptions);
parentRefreshOptions.MetadataRefreshMode = MetadataRefreshMode.FullRefresh;
}
// Refresh current item // Refresh current item
await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); await RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
// Refresh all non-songs // Refresh all non-songs
foreach (var item in others) foreach (var item in others)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); var updateType = await item.RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
numComplete++; numComplete++;
double percent = numComplete; double percent = numComplete;
@ -202,7 +213,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return i => return i =>
{ {
var hasArtist = i as IHasArtist; var hasArtist = i as IHasArtist;
return hasArtist != null && hasArtist.HasArtist(Name); return hasArtist != null && hasArtist.HasAnyArtist(Name);
}; };
} }
} }

View File

@ -239,6 +239,11 @@ namespace MediaBrowser.Controller.Entities
get { return this.GetImagePath(ImageType.Primary); } get { return this.GetImagePath(ImageType.Primary); }
} }
public virtual bool IsInternetMetadataEnabled()
{
return ConfigurationManager.Configuration.EnableInternetProviders;
}
public virtual bool CanDelete() public virtual bool CanDelete()
{ {
var locationType = LocationType; var locationType = LocationType;
@ -717,7 +722,7 @@ namespace MediaBrowser.Controller.Entities
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>true if a provider reports we changed</returns> /// <returns>true if a provider reports we changed</returns>
public async Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken) public async Task<ItemUpdateType> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
{ {
var locationType = LocationType; var locationType = LocationType;
@ -744,15 +749,16 @@ namespace MediaBrowser.Controller.Entities
} }
} }
var dateLastSaved = DateLastSaved; var refreshOptions = requiresSave
? new MetadataRefreshOptions(options)
await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false);
// If it wasn't saved by the provider process, save now
if (requiresSave && dateLastSaved == DateLastSaved)
{ {
await UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); ForceSave = true
} }
: options;
var result = await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
return result;
} }
[IgnoreDataMember] [IgnoreDataMember]
@ -1245,13 +1251,6 @@ namespace MediaBrowser.Controller.Entities
{ {
if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase)) if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase))
{ {
if (info.ItemYear.HasValue)
{
if (info.ItemYear.Value != (i.ProductionYear ?? -1))
{
return false;
}
}
return true; return true;
} }
} }
@ -1461,7 +1460,8 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task.</returns> /// <returns>Task.</returns>
public virtual Task ChangedExternally() public virtual Task ChangedExternally()
{ {
return RefreshMetadata(CancellationToken.None); ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions());
return Task.FromResult(true);
} }
/// <summary> /// <summary>

View File

@ -1135,11 +1135,8 @@ namespace MediaBrowser.Controller.Entities
foreach (var child in LinkedChildren) foreach (var child in LinkedChildren)
{ {
// Reset the cached value // Reset the cached value
if (child.ItemId.HasValue && child.ItemId.Value == Guid.Empty)
{
child.ItemId = null; child.ItemId = null;
} }
}
return false; return false;
} }

View File

@ -0,0 +1,8 @@

namespace MediaBrowser.Controller.Entities
{
public interface IArchivable
{
bool IsArchive { get; }
}
}

View File

@ -184,6 +184,12 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
/// <value><c>true</c> if [always scan internal metadata path]; otherwise, <c>false</c>.</value> /// <value><c>true</c> if [always scan internal metadata path]; otherwise, <c>false</c>.</value>
bool AlwaysScanInternalMetadataPath { get; } bool AlwaysScanInternalMetadataPath { get; }
/// <summary>
/// Determines whether [is internet metadata enabled].
/// </summary>
/// <returns><c>true</c> if [is internet metadata enabled]; otherwise, <c>false</c>.</returns>
bool IsInternetMetadataEnabled();
} }
public static class HasImagesExtensions public static class HasImagesExtensions

View File

@ -1,9 +1,6 @@
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
{ {
@ -22,53 +19,4 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns> /// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns>
IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution); IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution);
} }
public static class HasMediaSourceExtensions
{
public static IEnumerable<MediaSourceInfo> GetMediaSources(this IHasMediaSources item, bool enablePathSubstitution, User user)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
if (!(item is Video))
{
return item.GetMediaSources(enablePathSubstitution);
}
if (user == null)
{
throw new ArgumentNullException("user");
}
var sources = item.GetMediaSources(enablePathSubstitution).ToList();
var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
? new string[] { }
: new[] { user.Configuration.AudioLanguagePreference };
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
? new List<string> { }
: new List<string> { user.Configuration.SubtitleLanguagePreference };
foreach (var source in sources)
{
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(
source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex == null
? null
: source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams,
preferredSubs,
user.Configuration.SubtitleMode,
audioLangage);
}
return sources;
}
}
} }

View File

@ -0,0 +1,8 @@

namespace MediaBrowser.Controller.Entities
{
public interface IHasOriginalTitle
{
string OriginalTitle { get; set; }
}
}

View File

@ -30,7 +30,6 @@ namespace MediaBrowser.Controller.Entities
public string[] IncludeItemTypes { get; set; } public string[] IncludeItemTypes { get; set; }
public string[] ExcludeItemTypes { get; set; } public string[] ExcludeItemTypes { get; set; }
public string[] Genres { get; set; } public string[] Genres { get; set; }
public string[] AllGenres { get; set; }
public bool? IsMissing { get; set; } public bool? IsMissing { get; set; }
public bool? IsUnaired { get; set; } public bool? IsUnaired { get; set; }
@ -42,6 +41,7 @@ namespace MediaBrowser.Controller.Entities
public string NameLessThan { get; set; } public string NameLessThan { get; set; }
public string Person { get; set; } public string Person { get; set; }
public string[] PersonIds { get; set; }
public string AdjacentTo { get; set; } public string AdjacentTo { get; set; }
public string[] PersonTypes { get; set; } public string[] PersonTypes { get; set; }
@ -66,6 +66,7 @@ namespace MediaBrowser.Controller.Entities
public bool? HasParentalRating { get; set; } public bool? HasParentalRating { get; set; }
public string[] Studios { get; set; } public string[] Studios { get; set; }
public string[] StudioIds { get; set; }
public ImageType[] ImageTypes { get; set; } public ImageType[] ImageTypes { get; set; }
public VideoType[] VideoTypes { get; set; } public VideoType[] VideoTypes { get; set; }
public int[] Years { get; set; } public int[] Years { get; set; }
@ -80,13 +81,14 @@ namespace MediaBrowser.Controller.Entities
MediaTypes = new string[] { }; MediaTypes = new string[] { };
IncludeItemTypes = new string[] { }; IncludeItemTypes = new string[] { };
ExcludeItemTypes = new string[] { }; ExcludeItemTypes = new string[] { };
AllGenres = new string[] { };
Genres = new string[] { }; Genres = new string[] { };
Studios = new string[] { }; Studios = new string[] { };
StudioIds = new string[] { };
ImageTypes = new ImageType[] { }; ImageTypes = new ImageType[] { };
VideoTypes = new VideoType[] { }; VideoTypes = new VideoType[] { };
Years = new int[] { }; Years = new int[] { };
PersonTypes = new string[] { }; PersonTypes = new string[] { };
PersonIds = new string[] { };
} }
} }
} }

View File

@ -5,10 +5,22 @@ namespace MediaBrowser.Controller.Entities
{ {
public class ItemImageInfo public class ItemImageInfo
{ {
/// <summary>
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
public string Path { get; set; } public string Path { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public ImageType Type { get; set; } public ImageType Type { get; set; }
/// <summary>
/// Gets or sets the date modified.
/// </summary>
/// <value>The date modified.</value>
public DateTime DateModified { get; set; } public DateTime DateModified { get; set; }
} }
} }

View File

@ -11,7 +11,6 @@ namespace MediaBrowser.Controller.Entities
public string ItemName { get; set; } public string ItemName { get; set; }
public string ItemType { get; set; } public string ItemType { get; set; }
public int? ItemYear { get; set; }
[IgnoreDataMember] [IgnoreDataMember]
public string Id { get; set; } public string Id { get; set; }

View File

@ -6,7 +6,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -15,10 +14,11 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <summary> /// <summary>
/// Class Movie /// Class Movie
/// </summary> /// </summary>
public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasSpecialFeatures, IHasProductionLocations, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasSpecialFeatures, IHasProductionLocations, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping, IHasOriginalTitle
{ {
public List<Guid> SpecialFeatureIds { get; set; } public List<Guid> SpecialFeatureIds { get; set; }
public string OriginalTitle { get; set; }
public List<Guid> SoundtrackIds { get; set; } public List<Guid> SoundtrackIds { get; set; }
public List<Guid> ThemeSongIds { get; set; } public List<Guid> ThemeSongIds { get; set; }

View File

@ -47,16 +47,6 @@ namespace MediaBrowser.Controller.Entities
} }
} }
/// <summary>
/// Determines whether the specified name has artist.
/// </summary>
/// <param name="name">The name.</param>
/// <returns><c>true</c> if the specified name has artist; otherwise, <c>false</c>.</returns>
public bool HasArtist(string name)
{
return AllArtists.Contains(name, StringComparer.OrdinalIgnoreCase);
}
/// <summary> /// <summary>
/// Gets the user data key. /// Gets the user data key.
/// </summary> /// </summary>

View File

@ -15,11 +15,12 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary> /// <summary>
/// Class Series /// Class Series
/// </summary> /// </summary>
public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>, IHasSpecialFeatures, IMetadataContainer public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>, IHasSpecialFeatures, IMetadataContainer, IHasOriginalTitle
{ {
public List<Guid> SpecialFeatureIds { get; set; } public List<Guid> SpecialFeatureIds { get; set; }
public List<Guid> SoundtrackIds { get; set; } public List<Guid> SoundtrackIds { get; set; }
public string OriginalTitle { get; set; }
public int SeasonCount { get; set; } public int SeasonCount { get; set; }
public int? AnimeSeriesIndex { get; set; } public int? AnimeSeriesIndex { get; set; }
@ -260,7 +261,7 @@ namespace MediaBrowser.Controller.Entities.TV
progress.Report(percent * 100); progress.Report(percent * 100);
} }
await ProviderManager.RefreshMetadata(this, refreshOptions, cancellationToken).ConfigureAwait(false); await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
progress.Report(100); progress.Report(100);
} }

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.TV; using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using System; using System;
@ -15,6 +16,12 @@ namespace MediaBrowser.Controller.Entities
public Guid? UserId { get; set; } public Guid? UserId { get; set; }
public static ITVSeriesManager TVSeriesManager; public static ITVSeriesManager TVSeriesManager;
public static IPlaylistManager PlaylistManager;
public bool ContainsDynamicCategories(User user)
{
return true;
}
public override Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query) public override Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
{ {
@ -25,7 +32,7 @@ namespace MediaBrowser.Controller.Entities
parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent; parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
} }
return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, CollectionManager) return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, CollectionManager, PlaylistManager)
.GetUserItems(parent, this, ViewType, query); .GetUserItems(parent, this, ViewType, query);
} }
@ -45,6 +52,11 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
public override bool IsSaveLocalMetadataEnabled()
{
return true;
}
public override IEnumerable<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter) public override IEnumerable<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter)
{ {
var result = GetItems(new InternalItemsQuery var result = GetItems(new InternalItemsQuery

View File

@ -5,7 +5,7 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.TV; using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Channels; using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -18,7 +18,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MoreLinq;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
{ {
@ -32,8 +31,9 @@ namespace MediaBrowser.Controller.Entities
private readonly IUserDataManager _userDataManager; private readonly IUserDataManager _userDataManager;
private readonly ITVSeriesManager _tvSeriesManager; private readonly ITVSeriesManager _tvSeriesManager;
private readonly ICollectionManager _collectionManager; private readonly ICollectionManager _collectionManager;
private readonly IPlaylistManager _playlistManager;
public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, ICollectionManager collectionManager) public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, ICollectionManager collectionManager, IPlaylistManager playlistManager)
{ {
_userViewManager = userViewManager; _userViewManager = userViewManager;
_liveTvManager = liveTvManager; _liveTvManager = liveTvManager;
@ -43,6 +43,7 @@ namespace MediaBrowser.Controller.Entities
_userDataManager = userDataManager; _userDataManager = userDataManager;
_tvSeriesManager = tvSeriesManager; _tvSeriesManager = tvSeriesManager;
_collectionManager = collectionManager; _collectionManager = collectionManager;
_playlistManager = playlistManager;
} }
public async Task<QueryResult<BaseItem>> GetUserItems(Folder queryParent, Folder displayParent, string viewType, InternalItemsQuery query) public async Task<QueryResult<BaseItem>> GetUserItems(Folder queryParent, Folder displayParent, string viewType, InternalItemsQuery query)
@ -111,12 +112,21 @@ namespace MediaBrowser.Controller.Entities
return GetResult(result, queryParent, query); return GetResult(result, queryParent, query);
} }
case CollectionType.Books:
case CollectionType.Photos:
case CollectionType.HomeVideos:
case CollectionType.MusicVideos:
return GetResult(queryParent.GetChildren(user, true), queryParent, query);
case CollectionType.Folders: case CollectionType.Folders:
return GetResult(user.RootFolder.GetChildren(user, true), queryParent, query); return GetResult(user.RootFolder.GetChildren(user, true), queryParent, query);
case CollectionType.Games: case CollectionType.Games:
return await GetGameView(user, queryParent, query).ConfigureAwait(false); return await GetGameView(user, queryParent, query).ConfigureAwait(false);
case CollectionType.Playlists:
return await GetPlaylistsView(queryParent, user, query).ConfigureAwait(false);
case CollectionType.BoxSets: case CollectionType.BoxSets:
return await GetBoxsetView(queryParent, user, query).ConfigureAwait(false); return await GetBoxsetView(queryParent, user, query).ConfigureAwait(false);
@ -574,6 +584,11 @@ namespace MediaBrowser.Controller.Entities
return GetResult(items, queryParent, query); return GetResult(items, queryParent, query);
} }
private async Task<QueryResult<BaseItem>> GetPlaylistsView(Folder parent, User user, InternalItemsQuery query)
{
return GetResult(_playlistManager.GetPlaylists(user.Id.ToString("N")), parent, query);
}
private async Task<QueryResult<BaseItem>> GetBoxsetView(Folder parent, User user, InternalItemsQuery query) private async Task<QueryResult<BaseItem>> GetBoxsetView(Folder parent, User user, InternalItemsQuery query)
{ {
return GetResult(GetMediaFolders(user).SelectMany(i => return GetResult(GetMediaFolders(user).SelectMany(i =>
@ -937,11 +952,6 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
if (request.AllGenres.Length > 0)
{
return false;
}
if (request.Genres.Length > 0) if (request.Genres.Length > 0)
{ {
return false; return false;
@ -1047,11 +1057,21 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
if (request.PersonIds.Length > 0)
{
return false;
}
if (request.Studios.Length > 0) if (request.Studios.Length > 0)
{ {
return false; return false;
} }
if (request.StudioIds.Length > 0)
{
return false;
}
if (request.VideoTypes.Length > 0) if (request.VideoTypes.Length > 0)
{ {
return false; return false;
@ -1571,12 +1591,6 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
// Apply genre filter
if (query.AllGenres.Length > 0 && !query.AllGenres.All(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))
{
return false;
}
// Filter by VideoType // Filter by VideoType
if (query.VideoTypes.Length > 0) if (query.VideoTypes.Length > 0)
{ {
@ -1598,6 +1612,16 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
// Apply studio filter
if (query.StudioIds.Length > 0 && !query.StudioIds.Any(id =>
{
var studioItem = libraryManager.GetItemById(id);
return studioItem != null && item.Studios.Contains(studioItem.Name, StringComparer.OrdinalIgnoreCase);
}))
{
return false;
}
// Apply year filter // Apply year filter
if (query.Years.Length > 0) if (query.Years.Length > 0)
{ {
@ -1614,7 +1638,22 @@ namespace MediaBrowser.Controller.Entities
} }
// Apply person filter // Apply person filter
if (!string.IsNullOrEmpty(query.Person)) if (query.PersonIds.Length > 0)
{
var names = query.PersonIds
.Select(libraryManager.GetItemById)
.Select(i => i == null ? "-1" : i.Name)
.ToList();
if (!(names.Any(
v => item.People.Select(i => i.Name).Contains(v, StringComparer.OrdinalIgnoreCase))))
{
return false;
}
}
// Apply person filter
if (!string.IsNullOrWhiteSpace(query.Person))
{ {
var personTypes = query.PersonTypes; var personTypes = query.PersonTypes;
@ -1716,7 +1755,7 @@ namespace MediaBrowser.Controller.Entities
var parent = user.RootFolder; var parent = user.RootFolder;
//list.Add(await GetUserView(SpecialFolder.LiveTvNowPlaying, user, "0", parent).ConfigureAwait(false)); //list.Add(await GetUserSubView(SpecialFolder.LiveTvNowPlaying, user, "0", parent).ConfigureAwait(false));
list.Add(await GetUserView(SpecialFolder.LiveTvChannels, user, string.Empty, parent).ConfigureAwait(false)); list.Add(await GetUserView(SpecialFolder.LiveTvChannels, user, string.Empty, parent).ConfigureAwait(false));
list.Add(await GetUserView(SpecialFolder.LiveTvRecordingGroups, user, string.Empty, parent).ConfigureAwait(false)); list.Add(await GetUserView(SpecialFolder.LiveTvRecordingGroups, user, string.Empty, parent).ConfigureAwait(false));
@ -1725,7 +1764,7 @@ namespace MediaBrowser.Controller.Entities
private async Task<UserView> GetUserView(string name, string type, User user, string sortName, BaseItem parent) private async Task<UserView> GetUserView(string name, string type, User user, string sortName, BaseItem parent)
{ {
var view = await _userViewManager.GetUserView(name, parent.Id.ToString("N"), type, user, sortName, CancellationToken.None) var view = await _userViewManager.GetUserSubView(name, parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
return view; return view;
@ -1733,7 +1772,7 @@ namespace MediaBrowser.Controller.Entities
private async Task<UserView> GetUserView(string type, User user, string sortName, BaseItem parent) private async Task<UserView> GetUserView(string type, User user, string sortName, BaseItem parent)
{ {
var view = await _userViewManager.GetUserView(parent.Id.ToString("N"), type, user, sortName, CancellationToken.None) var view = await _userViewManager.GetUserSubView(parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
return view; return view;

View File

@ -24,7 +24,8 @@ namespace MediaBrowser.Controller.Entities
IHasMediaSources, IHasMediaSources,
IHasShortOverview, IHasShortOverview,
IHasPreferredMetadataLanguage, IHasPreferredMetadataLanguage,
IThemeMedia IThemeMedia,
IArchivable
{ {
public Guid? PrimaryVersionId { get; set; } public Guid? PrimaryVersionId { get; set; }
@ -420,12 +421,17 @@ namespace MediaBrowser.Controller.Entities
return base.GetDeletePaths(); return base.GetDeletePaths();
} }
public virtual IEnumerable<MediaStream> GetMediaStreams() public IEnumerable<MediaStream> GetMediaStreams()
{ {
return MediaSourceManager.GetMediaStreams(new MediaStreamQuery var mediaSource = GetMediaSources(false)
.FirstOrDefault();
if (mediaSource == null)
{ {
ItemId = Id return new List<MediaStream>();
}); }
return mediaSource.MediaStreams;
} }
public virtual MediaStream GetDefaultVideoStream() public virtual MediaStream GetDefaultVideoStream()
@ -455,7 +461,7 @@ namespace MediaBrowser.Controller.Entities
return result.OrderBy(i => return result.OrderBy(i =>
{ {
if (item.VideoType == VideoType.VideoFile) if (i.VideoType == VideoType.VideoFile)
{ {
return 0; return 0;
} }
@ -474,7 +480,7 @@ namespace MediaBrowser.Controller.Entities
private static MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, Video i, MediaSourceType type) private static MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, Video i, MediaSourceType type)
{ {
var mediaStreams = MediaSourceManager.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }) var mediaStreams = MediaSourceManager.GetMediaStreams(i.Id)
.ToList(); .ToList();
var locationType = i.LocationType; var locationType = i.LocationType;
@ -551,7 +557,6 @@ namespace MediaBrowser.Controller.Entities
return info; return info;
} }
private static string GetMediaSourceName(Video video, List<MediaStream> mediaStreams) private static string GetMediaSourceName(Video video, List<MediaStream> mediaStreams)
{ {
var terms = new List<string>(); var terms = new List<string>();

View File

@ -302,7 +302,7 @@ namespace MediaBrowser.Controller.Library
IEnumerable<BaseItem> ReplaceVideosWithPrimaryVersions(IEnumerable<BaseItem> items); IEnumerable<BaseItem> ReplaceVideosWithPrimaryVersions(IEnumerable<BaseItem> items);
/// <summary> /// <summary>
/// Gets the special folder. /// Gets the named view.
/// </summary> /// </summary>
/// <param name="user">The user.</param> /// <param name="user">The user.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
@ -311,7 +311,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="sortName">Name of the sort.</param> /// <param name="sortName">Name of the sort.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;UserView&gt;.</returns> /// <returns>Task&lt;UserView&gt;.</returns>
Task<UserView> GetSpecialFolder(User user, Task<UserView> GetNamedView(User user,
string name, string name,
string parentId, string parentId,
string viewType, string viewType,
@ -321,12 +321,14 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// Gets the named view. /// Gets the named view.
/// </summary> /// </summary>
/// <param name="user">The user.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="viewType">Type of the view.</param> /// <param name="viewType">Type of the view.</param>
/// <param name="sortName">Name of the sort.</param> /// <param name="sortName">Name of the sort.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;UserView&gt;.</returns> /// <returns>Task&lt;UserView&gt;.</returns>
Task<UserView> GetNamedView(string name, Task<UserView> GetNamedView(User user,
string name,
string viewType, string viewType,
string sortName, string sortName,
CancellationToken cancellationToken); CancellationToken cancellationToken);

View File

@ -1,11 +1,76 @@
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Library namespace MediaBrowser.Controller.Library
{ {
public interface IMediaSourceManager public interface IMediaSourceManager
{ {
/// <summary>
/// Adds the parts.
/// </summary>
/// <param name="providers">The providers.</param>
void AddParts(IEnumerable<IMediaSourceProvider> providers);
/// <summary>
/// Gets the media streams.
/// </summary>
/// <param name="itemId">The item identifier.</param>
/// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
IEnumerable<MediaStream> GetMediaStreams(Guid itemId);
/// <summary>
/// Gets the media streams.
/// </summary>
/// <param name="mediaSourceId">The media source identifier.</param>
/// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
IEnumerable<MediaStream> GetMediaStreams(string mediaSourceId);
/// <summary>
/// Gets the media streams.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query); IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query);
/// <summary>
/// Gets the playack media sources.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="userId">The user identifier.</param>
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IEnumerable&lt;MediaSourceInfo&gt;.</returns>
Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, string userId, bool enablePathSubstitution, CancellationToken cancellationToken);
/// <summary>
/// Gets the playack media sources.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns>
Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken);
/// <summary>
/// Gets the static media sources.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable&lt;MediaSourceInfo&gt;.</returns>
IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user);
/// <summary>
/// Gets the static media source.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="mediaSourceId">The media source identifier.</param>
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
/// <returns>MediaSourceInfo.</returns>
MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution);
} }
} }

View File

@ -0,0 +1,19 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Library
{
public interface IMediaSourceProvider
{
/// <summary>
/// Gets the media sources.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns>
Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken);
}
}

View File

@ -34,6 +34,7 @@ namespace MediaBrowser.Controller.Library
event EventHandler<GenericEventArgs<User>> UserCreated; event EventHandler<GenericEventArgs<User>> UserCreated;
event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated; event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
event EventHandler<GenericEventArgs<User>> UserPasswordChanged; event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
event EventHandler<GenericEventArgs<User>> UserLockedOut;
/// <summary> /// <summary>
/// Gets a User by Id /// Gets a User by Id

View File

@ -12,12 +12,10 @@ namespace MediaBrowser.Controller.Library
{ {
Task<IEnumerable<Folder>> GetUserViews(UserViewQuery query, CancellationToken cancellationToken); Task<IEnumerable<Folder>> GetUserViews(UserViewQuery query, CancellationToken cancellationToken);
Task<UserView> GetUserView(string name, string parentId, string type, User user, string sortName, Task<UserView> GetUserSubView(string name, string parentId, string type, User user, string sortName,
CancellationToken cancellationToken); CancellationToken cancellationToken);
Task<UserView> GetUserView(string type, string sortName, CancellationToken cancellationToken); Task<UserView> GetUserSubView(string category, string type, User user, string sortName, CancellationToken cancellationToken);
Task<UserView> GetUserView(string category, string type, User user, string sortName, CancellationToken cancellationToken);
List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request); List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request);
} }

View File

@ -0,0 +1,8 @@

namespace MediaBrowser.Controller.LiveTv
{
public interface ILiveTvItem
{
string ServiceName { get; set; }
}
}

View File

@ -14,12 +14,6 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary> /// </summary>
public interface ILiveTvManager public interface ILiveTvManager
{ {
/// <summary>
/// Gets the active service.
/// </summary>
/// <value>The active service.</value>
ILiveTvService ActiveService { get; }
/// <summary> /// <summary>
/// Gets the services. /// Gets the services.
/// </summary> /// </summary>

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Library; using MediaBrowser.Model.Library;
using System.Threading; using System.Threading;
@ -6,10 +7,8 @@ using System.Threading.Tasks;
namespace MediaBrowser.Controller.LiveTv namespace MediaBrowser.Controller.LiveTv
{ {
public interface ILiveTvRecording : IHasImages, IHasMediaSources, IHasUserData public interface ILiveTvRecording : IHasImages, IHasMediaSources, IHasUserData, ILiveTvItem
{ {
string ServiceName { get; set; }
string MediaType { get; } string MediaType { get; }
string Container { get; } string Container { get; }
@ -22,7 +21,7 @@ namespace MediaBrowser.Controller.LiveTv
bool IsParentalAllowed(User user); bool IsParentalAllowed(User user);
Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken); Task<ItemUpdateType> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken);
PlayAccess GetPlayAccess(User user); PlayAccess GetPlayAccess(User user);

View File

@ -11,7 +11,7 @@ using System.Runtime.Serialization;
namespace MediaBrowser.Controller.LiveTv namespace MediaBrowser.Controller.LiveTv
{ {
public class LiveTvChannel : BaseItem, IHasMediaSources public class LiveTvChannel : BaseItem, IHasMediaSources, ILiveTvItem
{ {
/// <summary> /// <summary>
/// Gets the user data key. /// Gets the user data key.
@ -58,6 +58,10 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The type of the channel.</value> /// <value>The type of the channel.</value>
public ChannelType ChannelType { get; set; } public ChannelType ChannelType { get; set; }
/// <summary>
/// Gets or sets the name of the service.
/// </summary>
/// <value>The name of the service.</value>
public string ServiceName { get; set; } public string ServiceName { get; set; }
/// <summary> /// <summary>

View File

@ -1,5 +1,6 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Users; using MediaBrowser.Model.Users;
@ -11,7 +12,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Controller.LiveTv namespace MediaBrowser.Controller.LiveTv
{ {
public class LiveTvProgram : BaseItem public class LiveTvProgram : BaseItem, ILiveTvItem, IHasLookupInfo<LiveTvProgramLookupInfo>
{ {
/// <summary> /// <summary>
/// Gets the user data key. /// Gets the user data key.
@ -220,5 +221,23 @@ namespace MediaBrowser.Controller.LiveTv
{ {
return false; return false;
} }
public override bool IsInternetMetadataEnabled()
{
if (IsMovie)
{
var options = (LiveTvOptions)ConfigurationManager.GetConfiguration("livetv");
return options.EnableMovieProviders;
}
return false;
}
public LiveTvProgramLookupInfo GetLookupInfo()
{
var info = GetItemLookupInfo<LiveTvProgramLookupInfo>();
info.IsMovie = IsMovie;
return info;
}
} }
} }

View File

@ -145,6 +145,12 @@ namespace MediaBrowser.Controller.LiveTv
/// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
public bool IsPremiere { get; set; } public bool IsPremiere { get; set; }
/// <summary>
/// Gets or sets the production year.
/// </summary>
/// <value>The production year.</value>
public int? ProductionYear { get; set; }
public ProgramInfo() public ProgramInfo()
{ {
Genres = new List<string>(); Genres = new List<string>();

View File

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Users; using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.LiveTv namespace MediaBrowser.Controller.LiveTv

View File

@ -99,11 +99,13 @@
<Compile Include="Collections\CollectionCreationOptions.cs" /> <Compile Include="Collections\CollectionCreationOptions.cs" />
<Compile Include="Collections\CollectionEvents.cs" /> <Compile Include="Collections\CollectionEvents.cs" />
<Compile Include="Collections\ICollectionManager.cs" /> <Compile Include="Collections\ICollectionManager.cs" />
<Compile Include="Connect\ConnectSupporterSummary.cs" />
<Compile Include="Connect\IConnectManager.cs" /> <Compile Include="Connect\IConnectManager.cs" />
<Compile Include="Connect\UserLinkResult.cs" /> <Compile Include="Connect\UserLinkResult.cs" />
<Compile Include="Devices\CameraImageUploadInfo.cs" /> <Compile Include="Devices\CameraImageUploadInfo.cs" />
<Compile Include="Devices\IDeviceManager.cs" /> <Compile Include="Devices\IDeviceManager.cs" />
<Compile Include="Devices\IDeviceRepository.cs" /> <Compile Include="Devices\IDeviceRepository.cs" />
<Compile Include="Diagnostics\IProcessManager.cs" />
<Compile Include="Dlna\ControlRequest.cs" /> <Compile Include="Dlna\ControlRequest.cs" />
<Compile Include="Dlna\ControlResponse.cs" /> <Compile Include="Dlna\ControlResponse.cs" />
<Compile Include="Dlna\EventSubscriptionResponse.cs" /> <Compile Include="Dlna\EventSubscriptionResponse.cs" />
@ -129,6 +131,7 @@
<Compile Include="Entities\Game.cs" /> <Compile Include="Entities\Game.cs" />
<Compile Include="Entities\GameGenre.cs" /> <Compile Include="Entities\GameGenre.cs" />
<Compile Include="Entities\GameSystem.cs" /> <Compile Include="Entities\GameSystem.cs" />
<Compile Include="Entities\IArchivable.cs" />
<Compile Include="Entities\IByReferenceItem.cs" /> <Compile Include="Entities\IByReferenceItem.cs" />
<Compile Include="Entities\IHasAspectRatio.cs" /> <Compile Include="Entities\IHasAspectRatio.cs" />
<Compile Include="Entities\IHasBudget.cs" /> <Compile Include="Entities\IHasBudget.cs" />
@ -138,6 +141,7 @@
<Compile Include="Entities\IHasKeywords.cs" /> <Compile Include="Entities\IHasKeywords.cs" />
<Compile Include="Entities\IHasMediaSources.cs" /> <Compile Include="Entities\IHasMediaSources.cs" />
<Compile Include="Entities\IHasMetascore.cs" /> <Compile Include="Entities\IHasMetascore.cs" />
<Compile Include="Entities\IHasOriginalTitle.cs" />
<Compile Include="Entities\IHasPreferredMetadataLanguage.cs" /> <Compile Include="Entities\IHasPreferredMetadataLanguage.cs" />
<Compile Include="Entities\IHasProductionLocations.cs" /> <Compile Include="Entities\IHasProductionLocations.cs" />
<Compile Include="Entities\IHasScreenshots.cs" /> <Compile Include="Entities\IHasScreenshots.cs" />
@ -171,6 +175,7 @@
<Compile Include="Library\DeleteOptions.cs" /> <Compile Include="Library\DeleteOptions.cs" />
<Compile Include="Library\ILibraryPostScanTask.cs" /> <Compile Include="Library\ILibraryPostScanTask.cs" />
<Compile Include="Library\IMediaSourceManager.cs" /> <Compile Include="Library\IMediaSourceManager.cs" />
<Compile Include="Library\IMediaSourceProvider.cs" />
<Compile Include="Library\IMetadataFileSaver.cs" /> <Compile Include="Library\IMetadataFileSaver.cs" />
<Compile Include="Library\IMetadataSaver.cs" /> <Compile Include="Library\IMetadataSaver.cs" />
<Compile Include="Library\IMusicManager.cs" /> <Compile Include="Library\IMusicManager.cs" />
@ -182,6 +187,7 @@
<Compile Include="Library\MetadataConfigurationStore.cs" /> <Compile Include="Library\MetadataConfigurationStore.cs" />
<Compile Include="Library\PlaybackStopEventArgs.cs" /> <Compile Include="Library\PlaybackStopEventArgs.cs" />
<Compile Include="Library\UserDataSaveEventArgs.cs" /> <Compile Include="Library\UserDataSaveEventArgs.cs" />
<Compile Include="LiveTv\ILiveTvItem.cs" />
<Compile Include="LiveTv\RecordingGroup.cs" /> <Compile Include="LiveTv\RecordingGroup.cs" />
<Compile Include="LiveTv\RecordingStatusChangedEventArgs.cs" /> <Compile Include="LiveTv\RecordingStatusChangedEventArgs.cs" />
<Compile Include="LiveTv\ILiveTvRecording.cs" /> <Compile Include="LiveTv\ILiveTvRecording.cs" />
@ -243,22 +249,69 @@
<Compile Include="Persistence\MediaStreamQuery.cs" /> <Compile Include="Persistence\MediaStreamQuery.cs" />
<Compile Include="Playlists\IPlaylistManager.cs" /> <Compile Include="Playlists\IPlaylistManager.cs" />
<Compile Include="Playlists\Playlist.cs" /> <Compile Include="Playlists\Playlist.cs" />
<Compile Include="Plugins\ILocalizablePlugin.cs" />
<Compile Include="Providers\AlbumInfo.cs" />
<Compile Include="Providers\ArtistInfo.cs" />
<Compile Include="Providers\BookInfo.cs" />
<Compile Include="Providers\BoxSetInfo.cs" />
<Compile Include="Providers\ChannelItemLookupInfo.cs" />
<Compile Include="Providers\DirectoryService.cs" /> <Compile Include="Providers\DirectoryService.cs" />
<Compile Include="Providers\DynamicImageInfo.cs" />
<Compile Include="Providers\DynamicImageResponse.cs" />
<Compile Include="Providers\EpisodeIdentity.cs" />
<Compile Include="Providers\EpisodeInfo.cs" />
<Compile Include="Providers\ExtraInfo.cs" />
<Compile Include="Providers\ExtraSource.cs" />
<Compile Include="Providers\GameInfo.cs" />
<Compile Include="Providers\GameSystemInfo.cs" />
<Compile Include="Providers\ICustomMetadataProvider.cs" /> <Compile Include="Providers\ICustomMetadataProvider.cs" />
<Compile Include="Providers\IDirectoryService.cs" />
<Compile Include="Providers\IDynamicImageProvider.cs" />
<Compile Include="Providers\IExternalId.cs" /> <Compile Include="Providers\IExternalId.cs" />
<Compile Include="Providers\IExtrasProvider.cs" /> <Compile Include="Providers\IExtrasProvider.cs" />
<Compile Include="Providers\IForcedProvider.cs" /> <Compile Include="Providers\IForcedProvider.cs" />
<Compile Include="Providers\IHasChangeMonitor.cs" /> <Compile Include="Providers\IHasChangeMonitor.cs" />
<Compile Include="Entities\IHasMetadata.cs" /> <Compile Include="Entities\IHasMetadata.cs" />
<Compile Include="Providers\IHasIdentities.cs" />
<Compile Include="Providers\IHasItemChangeMonitor.cs" />
<Compile Include="Providers\IHasLookupInfo.cs" />
<Compile Include="Providers\IHasOrder.cs" />
<Compile Include="Providers\IImageFileSaver.cs" />
<Compile Include="Providers\IImageProvider.cs" /> <Compile Include="Providers\IImageProvider.cs" />
<Compile Include="Providers\IImageSaver.cs" /> <Compile Include="Providers\IImageSaver.cs" />
<Compile Include="Providers\IItemIdentity.cs" />
<Compile Include="Providers\IItemIdentityConverter.cs" />
<Compile Include="Providers\IItemIdentityProvider.cs" />
<Compile Include="Providers\ILocalImageFileProvider.cs" />
<Compile Include="Providers\ILocalMetadataProvider.cs" /> <Compile Include="Providers\ILocalMetadataProvider.cs" />
<Compile Include="Providers\ImageRefreshMode.cs" />
<Compile Include="Providers\ImageRefreshOptions.cs" />
<Compile Include="Providers\IPreRefreshProvider.cs" />
<Compile Include="Providers\IProviderRepository.cs" /> <Compile Include="Providers\IProviderRepository.cs" />
<Compile Include="Providers\IRemoteImageProvider.cs" /> <Compile Include="Providers\IRemoteImageProvider.cs" />
<Compile Include="Providers\ILocalImageProvider.cs" /> <Compile Include="Providers\ILocalImageProvider.cs" />
<Compile Include="Providers\IMetadataProvider.cs" /> <Compile Include="Providers\IMetadataProvider.cs" />
<Compile Include="Providers\IMetadataService.cs" /> <Compile Include="Providers\IMetadataService.cs" />
<Compile Include="Providers\IRemoteMetadataProvider.cs" /> <Compile Include="Providers\IRemoteMetadataProvider.cs" />
<Compile Include="Providers\IRemoteSearchProvider.cs" />
<Compile Include="Providers\ISeriesOrderProvider.cs" />
<Compile Include="Providers\ItemInfo.cs" />
<Compile Include="Providers\LiveTvProgramLookupInfo.cs" />
<Compile Include="Providers\LocalImageInfo.cs" />
<Compile Include="Providers\LocalMetadataResult.cs" />
<Compile Include="Providers\MetadataRefreshMode.cs" />
<Compile Include="Providers\MetadataResult.cs" />
<Compile Include="Providers\MovieInfo.cs" />
<Compile Include="Providers\MusicVideoInfo.cs" />
<Compile Include="Providers\PersonLookupInfo.cs" />
<Compile Include="Providers\RemoteSearchQuery.cs" />
<Compile Include="Providers\SeasonIdentity.cs" />
<Compile Include="Providers\SeasonInfo.cs" />
<Compile Include="Providers\SeriesIdentity.cs" />
<Compile Include="Providers\SeriesInfo.cs" />
<Compile Include="Providers\SeriesOrderTypes.cs" />
<Compile Include="Providers\SongInfo.cs" />
<Compile Include="Providers\TrailerInfo.cs" />
<Compile Include="Providers\VideoContentType.cs" /> <Compile Include="Providers\VideoContentType.cs" />
<Compile Include="RelatedMedia\IRelatedMediaProvider.cs" /> <Compile Include="RelatedMedia\IRelatedMediaProvider.cs" />
<Compile Include="Security\AuthenticationInfo.cs" /> <Compile Include="Security\AuthenticationInfo.cs" />
@ -341,11 +394,12 @@
<Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" /> <Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" />
<Compile Include="Subtitles\SubtitleResponse.cs" /> <Compile Include="Subtitles\SubtitleResponse.cs" />
<Compile Include="Subtitles\SubtitleSearchRequest.cs" /> <Compile Include="Subtitles\SubtitleSearchRequest.cs" />
<Compile Include="Sync\ICloudSyncProvider.cs" />
<Compile Include="Sync\IServerSyncProvider.cs" /> <Compile Include="Sync\IServerSyncProvider.cs" />
<Compile Include="Sync\ISyncDataProvider.cs" />
<Compile Include="Sync\ISyncManager.cs" /> <Compile Include="Sync\ISyncManager.cs" />
<Compile Include="Sync\ISyncProvider.cs" /> <Compile Include="Sync\ISyncProvider.cs" />
<Compile Include="Sync\ISyncRepository.cs" /> <Compile Include="Sync\ISyncRepository.cs" />
<Compile Include="Sync\SendFileResult.cs" />
<Compile Include="Themes\IAppThemeManager.cs" /> <Compile Include="Themes\IAppThemeManager.cs" />
<Compile Include="Themes\InternalThemeImage.cs" /> <Compile Include="Themes\InternalThemeImage.cs" />
<Compile Include="TV\ITVSeriesManager.cs" /> <Compile Include="TV\ITVSeriesManager.cs" />

View File

@ -70,6 +70,11 @@ namespace MediaBrowser.Controller.Net
/// <returns>Task.</returns> /// <returns>Task.</returns>
public Task ProcessMessage(WebSocketMessageInfo message) public Task ProcessMessage(WebSocketMessageInfo message)
{ {
if (message == null)
{
throw new ArgumentNullException("message");
}
if (message.MessageType.Equals(Name + "Start", StringComparison.OrdinalIgnoreCase)) if (message.MessageType.Equals(Name + "Start", StringComparison.OrdinalIgnoreCase))
{ {
Start(message); Start(message);

View File

@ -44,6 +44,11 @@ namespace MediaBrowser.Controller.Net
/// </summary> /// </summary>
event EventHandler<WebSocketConnectEventArgs> WebSocketConnected; event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
/// <summary>
/// Occurs when [web socket connecting].
/// </summary>
event EventHandler<WebSocketConnectingEventArgs> WebSocketConnecting;
/// <summary> /// <summary>
/// Inits this instance. /// Inits this instance.
/// </summary> /// </summary>

View File

@ -1,4 +1,4 @@
using MediaBrowser.Common.Net; using MediaBrowser.Model.Events;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
@ -58,5 +58,10 @@ namespace MediaBrowser.Controller.Net
/// </summary> /// </summary>
/// <value>The web socket connections.</value> /// <value>The web socket connections.</value>
IEnumerable<IWebSocketConnection> WebSocketConnections { get; } IEnumerable<IWebSocketConnection> WebSocketConnections { get; }
/// <summary>
/// Occurs when [web socket connected].
/// </summary>
event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
} }
} }

View File

@ -1,14 +1,15 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Net namespace MediaBrowser.Controller.Net
{ {
public interface ISessionContext public interface ISessionContext
{ {
SessionInfo GetSession(object requestContext); Task<SessionInfo> GetSession(object requestContext);
User GetUser(object requestContext); Task<User> GetUser(object requestContext);
SessionInfo GetSession(IServiceRequest requestContext); Task<SessionInfo> GetSession(IServiceRequest requestContext);
User GetUser(IServiceRequest requestContext); Task<User> GetUser(IServiceRequest requestContext);
} }
} }

View File

@ -1,5 +1,6 @@
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using System; using System;
using System.Collections.Specialized;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -24,6 +25,17 @@ namespace MediaBrowser.Controller.Net
/// <value>The last activity date.</value> /// <value>The last activity date.</value>
DateTime LastActivityDate { get; } DateTime LastActivityDate { get; }
/// <summary>
/// Gets or sets the URL.
/// </summary>
/// <value>The URL.</value>
string Url { get; set; }
/// <summary>
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
NameValueCollection QueryString { get; set; }
/// <summary> /// <summary>
/// Gets or sets the receive action. /// Gets or sets the receive action.
/// </summary> /// </summary>

View File

@ -18,11 +18,6 @@ namespace MediaBrowser.Controller.Net
public IDictionary<string, string> ResponseHeaders { get; set; } public IDictionary<string, string> ResponseHeaders { get; set; }
public bool Throttle { get; set; }
public long ThrottleLimit { get; set; }
public long MinThrottlePosition { get; set; }
public Func<long, long, long> ThrottleCallback { get; set; }
public Action OnComplete { get; set; } public Action OnComplete { get; set; }
public StaticResultOptions() public StaticResultOptions()

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Specialized;
namespace MediaBrowser.Controller.Net namespace MediaBrowser.Controller.Net
{ {
@ -7,6 +8,16 @@ namespace MediaBrowser.Controller.Net
/// </summary> /// </summary>
public class WebSocketConnectEventArgs : EventArgs public class WebSocketConnectEventArgs : EventArgs
{ {
/// <summary>
/// Gets or sets the URL.
/// </summary>
/// <value>The URL.</value>
public string Url { get; set; }
/// <summary>
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
public NameValueCollection QueryString { get; set; }
/// <summary> /// <summary>
/// Gets or sets the web socket. /// Gets or sets the web socket.
/// </summary> /// </summary>
@ -18,4 +29,35 @@ namespace MediaBrowser.Controller.Net
/// <value>The endpoint.</value> /// <value>The endpoint.</value>
public string Endpoint { get; set; } public string Endpoint { get; set; }
} }
public class WebSocketConnectingEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the URL.
/// </summary>
/// <value>The URL.</value>
public string Url { get; set; }
/// <summary>
/// Gets or sets the endpoint.
/// </summary>
/// <value>The endpoint.</value>
public string Endpoint { get; set; }
/// <summary>
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
public NameValueCollection QueryString { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [allow connection].
/// </summary>
/// <value><c>true</c> if [allow connection]; otherwise, <c>false</c>.</value>
public bool AllowConnection { get; set; }
public WebSocketConnectingEventArgs()
{
QueryString = new NameValueCollection();
AllowConnection = true;
}
}
} }

View File

@ -106,7 +106,7 @@ namespace MediaBrowser.Controller.Playlists
Func<BaseItem, bool> filter = i => Func<BaseItem, bool> filter = i =>
{ {
var audio = i as Audio; var audio = i as Audio;
return audio != null && audio.HasArtist(musicArtist.Name); return audio != null && audio.HasAnyArtist(musicArtist.Name);
}; };
var items = user == null var items = user == null

View File

@ -0,0 +1,20 @@
using System.IO;
using System.Reflection;
namespace MediaBrowser.Controller.Plugins
{
public interface ILocalizablePlugin
{
Stream GetDictionary(string culture);
}
public static class LocalizablePluginHelper
{
public static Stream GetDictionary(Assembly assembly, string manifestPrefix, string culture)
{
// Find all dictionaries using GetManifestResourceNames, start start with the prefix
// Return the one for the culture if exists, otherwise return the default
return null;
}
}
}

Some files were not shown because too many files have changed in this diff Show More