mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-24 16:40:25 +01:00
Merge branch 'master' into defer_image_fetching
This commit is contained in:
86
MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
Normal file
86
MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
||||
namespace MediaBrowser.Controller.BaseItemManager
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class BaseItemManager : IBaseItemManager
|
||||
{
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseItemManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||
public BaseItemManager(IServerConfigurationManager serverConfigurationManager)
|
||||
{
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name)
|
||||
{
|
||||
if (baseItem is Channel)
|
||||
{
|
||||
// Hack alert.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (baseItem.SourceType == SourceType.Channel)
|
||||
{
|
||||
// Hack alert.
|
||||
return !baseItem.EnableMediaSourceDisplay;
|
||||
}
|
||||
|
||||
var typeOptions = libraryOptions.GetTypeOptions(GetType().Name);
|
||||
if (typeOptions != null)
|
||||
{
|
||||
return typeOptions.ImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (!libraryOptions.EnableInternetProviders)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name)
|
||||
{
|
||||
if (baseItem is Channel)
|
||||
{
|
||||
// Hack alert.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (baseItem.SourceType == SourceType.Channel)
|
||||
{
|
||||
// Hack alert.
|
||||
return !baseItem.EnableMediaSourceDisplay;
|
||||
}
|
||||
|
||||
var typeOptions = libraryOptions.GetTypeOptions(GetType().Name);
|
||||
if (typeOptions != null)
|
||||
{
|
||||
return typeOptions.ImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (!libraryOptions.EnableInternetProviders)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
Normal file
29
MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
||||
namespace MediaBrowser.Controller.BaseItemManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The <c>BaseItem</c> manager.
|
||||
/// </summary>
|
||||
public interface IBaseItemManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Is metadata fetcher enabled.
|
||||
/// </summary>
|
||||
/// <param name="baseItem">The base item.</param>
|
||||
/// <param name="libraryOptions">The library options.</param>
|
||||
/// <param name="name">The metadata fetcher name.</param>
|
||||
/// <returns><c>true</c> if metadata fetcher is enabled, else false.</returns>
|
||||
bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Is image fetcher enabled.
|
||||
/// </summary>
|
||||
/// <param name="baseItem">The base item.</param>
|
||||
/// <param name="libraryOptions">The library options.</param>
|
||||
/// <param name="name">The image fetcher name.</param>
|
||||
/// <returns><c>true</c> if image fetcher is enabled, else false.</returns>
|
||||
bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ namespace MediaBrowser.Controller.Channels
|
||||
/// Indicates if a sort ascending/descending toggle is supported or not.
|
||||
/// </summary>
|
||||
public bool SupportsSortOrderToggle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the automatic refresh levels.
|
||||
/// </summary>
|
||||
@@ -53,6 +54,7 @@ namespace MediaBrowser.Controller.Channels
|
||||
/// </summary>
|
||||
/// <value>The daily download limit.</value>
|
||||
public int? DailyDownloadLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [supports downloading].
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -63,6 +64,7 @@ namespace MediaBrowser.Controller.Drawing
|
||||
/// Create an image collage.
|
||||
/// </summary>
|
||||
/// <param name="options">The options to use when creating the collage.</param>
|
||||
void CreateImageCollage(ImageCollageOptions options);
|
||||
/// <param name="libraryName">Optional. </param>
|
||||
void CreateImageCollage(ImageCollageOptions options, string? libraryName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -75,7 +76,7 @@ namespace MediaBrowser.Controller.Drawing
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options);
|
||||
Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the supported image output formats.
|
||||
@@ -87,7 +88,8 @@ namespace MediaBrowser.Controller.Drawing
|
||||
/// Creates the image collage.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
void CreateImageCollage(ImageCollageOptions options);
|
||||
/// <param name="libraryName">The library name to draw onto the collage.</param>
|
||||
void CreateImageCollage(ImageCollageOptions options, string? libraryName);
|
||||
|
||||
bool SupportsTransparency(string path);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
@@ -15,9 +16,9 @@ namespace MediaBrowser.Controller.Dto
|
||||
ItemFields.RefreshState
|
||||
};
|
||||
|
||||
public ItemFields[] Fields { get; set; }
|
||||
public IReadOnlyList<ItemFields> Fields { get; set; }
|
||||
|
||||
public ImageType[] ImageTypes { get; set; }
|
||||
public IReadOnlyList<ImageType> ImageTypes { get; set; }
|
||||
|
||||
public int ImageTypeLimit { get; set; }
|
||||
|
||||
|
||||
@@ -90,7 +90,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
|
||||
var songKey = IndexNumber.HasValue ? IndexNumber.Value.ToString("0000") : string.Empty;
|
||||
|
||||
|
||||
if (ParentIndexNumber.HasValue)
|
||||
{
|
||||
songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey;
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
{
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Audio), nameof(MusicVideo), nameof(MusicAlbum) };
|
||||
query.ArtistIds = new[] { Id };
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
|
||||
{
|
||||
query.GenreIds = new[] { Id };
|
||||
query.IncludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(MusicVideo), nameof(Audio), nameof(MusicAlbum), nameof(MusicArtist) };
|
||||
|
||||
return LibraryManager.GetItemList(query);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
public const string InterviewFolderName = "interviews";
|
||||
public const string SceneFolderName = "scenes";
|
||||
public const string SampleFolderName = "samples";
|
||||
public const string ShortsFolderName = "shorts";
|
||||
public const string FeaturettesFolderName = "featurettes";
|
||||
|
||||
public static readonly string[] AllExtrasTypesFolderNames = {
|
||||
ExtrasFolderName,
|
||||
@@ -94,7 +96,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
DeletedScenesFolderName,
|
||||
InterviewFolderName,
|
||||
SceneFolderName,
|
||||
SampleFolderName
|
||||
SampleFolderName,
|
||||
ShortsFolderName,
|
||||
FeaturettesFolderName
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
@@ -197,6 +201,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
public virtual bool SupportsRemoteImageDownloading => true;
|
||||
|
||||
private string _name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
@@ -458,60 +463,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
[JsonIgnore]
|
||||
public string PrimaryImagePath => this.GetImagePath(ImageType.Primary);
|
||||
|
||||
public bool IsMetadataFetcherEnabled(LibraryOptions libraryOptions, string name)
|
||||
{
|
||||
if (SourceType == SourceType.Channel)
|
||||
{
|
||||
// hack alert
|
||||
return !EnableMediaSourceDisplay;
|
||||
}
|
||||
|
||||
var typeOptions = libraryOptions.GetTypeOptions(GetType().Name);
|
||||
if (typeOptions != null)
|
||||
{
|
||||
return typeOptions.MetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (!libraryOptions.EnableInternetProviders)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var itemConfig = ConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return itemConfig == null || !itemConfig.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public bool IsImageFetcherEnabled(LibraryOptions libraryOptions, string name)
|
||||
{
|
||||
if (this is Channel)
|
||||
{
|
||||
// hack alert
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SourceType == SourceType.Channel)
|
||||
{
|
||||
// hack alert
|
||||
return !EnableMediaSourceDisplay;
|
||||
}
|
||||
|
||||
var typeOptions = libraryOptions.GetTypeOptions(GetType().Name);
|
||||
if (typeOptions != null)
|
||||
{
|
||||
return typeOptions.ImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (!libraryOptions.EnableInternetProviders)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var itemConfig = ConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public virtual bool CanDelete()
|
||||
{
|
||||
if (SourceType == SourceType.Channel)
|
||||
@@ -661,6 +612,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
private string _forcedSortName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the forced sort.
|
||||
/// </summary>
|
||||
@@ -2604,7 +2556,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
if (!AllowsMultipleImages(type))
|
||||
{
|
||||
throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots");
|
||||
throw new ArgumentException("The change index operation is only applicable to backdrops and screen shots");
|
||||
}
|
||||
|
||||
var info1 = GetImageInfo(type, index1);
|
||||
|
||||
@@ -45,7 +45,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
if (file.StartsWith("http", System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.SetImage(new ItemImageInfo
|
||||
item.SetImage(
|
||||
new ItemImageInfo
|
||||
{
|
||||
Path = file,
|
||||
Type = imageType
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
/// <summary>
|
||||
/// Loads our children. Validation will occur externally.
|
||||
/// We want this sychronous.
|
||||
/// We want this synchronous.
|
||||
/// </summary>
|
||||
protected virtual List<BaseItem> LoadChildren()
|
||||
{
|
||||
@@ -255,7 +255,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
var id = child.Id;
|
||||
if (dictionary.ContainsKey(id))
|
||||
{
|
||||
Logger.LogError("Found folder containing items with duplicate id. Path: {path}, Child Name: {ChildName}",
|
||||
Logger.LogError(
|
||||
"Found folder containing items with duplicate id. Path: {path}, Child Name: {ChildName}",
|
||||
Path ?? Name,
|
||||
child.Path ?? child.Name);
|
||||
}
|
||||
@@ -717,7 +718,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
private bool RequiresPostFiltering2(InternalItemsQuery query)
|
||||
{
|
||||
if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], typeof(BoxSet).Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.LogDebug("Query requires post-filtering due to BoxSet query");
|
||||
return true;
|
||||
@@ -807,7 +808,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (query.IsPlayed.HasValue)
|
||||
{
|
||||
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(typeof(Series).Name))
|
||||
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(nameof(Series)))
|
||||
{
|
||||
Logger.LogDebug("Query requires post-filtering due to IsPlayed");
|
||||
return true;
|
||||
@@ -979,7 +980,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
return items;
|
||||
}
|
||||
|
||||
private static bool CollapseBoxSetItems(InternalItemsQuery query,
|
||||
private static bool CollapseBoxSetItems(
|
||||
InternalItemsQuery query,
|
||||
BaseItem queryParent,
|
||||
User user,
|
||||
IServerConfigurationManager configurationManager)
|
||||
@@ -1060,12 +1062,12 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.Genres.Length > 0)
|
||||
if (request.Genres.Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.GenreIds.Length > 0)
|
||||
if (request.GenreIds.Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1170,7 +1172,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.GenreIds.Length > 0)
|
||||
if (request.GenreIds.Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1381,7 +1383,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the linked children.
|
||||
/// </summary>
|
||||
@@ -1589,7 +1590,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <param name="datePlayed">The date played.</param>
|
||||
/// <param name="resetPosition">if set to <c>true</c> [reset position].</param>
|
||||
/// <returns>Task.</returns>
|
||||
public override void MarkPlayed(User user,
|
||||
public override void MarkPlayed(
|
||||
User user,
|
||||
DateTime? datePlayed,
|
||||
bool resetPosition)
|
||||
{
|
||||
|
||||
@@ -59,7 +59,13 @@ namespace MediaBrowser.Controller.Entities
|
||||
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
|
||||
{
|
||||
query.GenreIds = new[] { Id };
|
||||
query.ExcludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio.Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
|
||||
query.ExcludeItemTypes = new[]
|
||||
{
|
||||
nameof(MusicVideo),
|
||||
nameof(Entities.Audio.Audio),
|
||||
nameof(MusicAlbum),
|
||||
nameof(MusicArtist)
|
||||
};
|
||||
|
||||
return LibraryManager.GetItemList(query);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,5 @@ namespace MediaBrowser.Controller.Entities
|
||||
List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution);
|
||||
|
||||
List<MediaStream> GetMediaStreams();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public string[] ExcludeInheritedTags { get; set; }
|
||||
|
||||
public string[] Genres { get; set; }
|
||||
public IReadOnlyList<string> Genres { get; set; }
|
||||
|
||||
public bool? IsSpecialSeason { get; set; }
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public Guid[] StudioIds { get; set; }
|
||||
|
||||
public Guid[] GenreIds { get; set; }
|
||||
public IReadOnlyList<Guid> GenreIds { get; set; }
|
||||
|
||||
public ImageType[] ImageTypes { get; set; }
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public double? MinCommunityRating { get; set; }
|
||||
|
||||
public Guid[] ChannelIds { get; set; }
|
||||
public IReadOnlyList<Guid> ChannelIds { get; set; }
|
||||
|
||||
public int? ParentIndexNumber { get; set; }
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using Jellyfin.Data.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
@@ -23,6 +24,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public string NameContains { get; set; }
|
||||
|
||||
public User User { get; set; }
|
||||
|
||||
public bool? IsFavorite { get; set; }
|
||||
|
||||
public InternalPeopleQuery()
|
||||
{
|
||||
PersonTypes = Array.Empty<string>();
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
[JsonIgnore]
|
||||
public override Folder LatestItemsIndexContainer => AlbumEntity;
|
||||
|
||||
|
||||
[JsonIgnore]
|
||||
public PhotoAlbum AlbumEntity
|
||||
{
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
}
|
||||
|
||||
query.IsVirtualItem = false;
|
||||
@@ -207,7 +207,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
query.AncestorWithPresentationUniqueKey = null;
|
||||
query.SeriesPresentationUniqueKey = seriesKey;
|
||||
query.IncludeItemTypes = new[] { typeof(Season).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Season) };
|
||||
query.OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray();
|
||||
|
||||
if (user != null && !user.DisplayMissingEpisodes)
|
||||
@@ -233,7 +233,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode), nameof(Season) };
|
||||
}
|
||||
|
||||
query.IsVirtualItem = false;
|
||||
@@ -253,7 +253,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
{
|
||||
AncestorWithPresentationUniqueKey = null,
|
||||
SeriesPresentationUniqueKey = seriesKey,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode), nameof(Season) },
|
||||
OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
|
||||
DtoOptions = options
|
||||
};
|
||||
@@ -364,7 +364,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
{
|
||||
AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey,
|
||||
SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode) },
|
||||
OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
|
||||
DtoOptions = options
|
||||
};
|
||||
@@ -450,7 +450,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected override bool GetBlockUnratedValue(User user)
|
||||
{
|
||||
return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series.ToString());
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
}
|
||||
|
||||
return parent.QueryRecursive(query);
|
||||
@@ -167,7 +167,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -178,7 +178,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -189,7 +189,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -200,7 +200,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -208,7 +208,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
private QueryResult<BaseItem> GetMovieCollections(Folder parent, User user, InternalItemsQuery query)
|
||||
{
|
||||
query.Parent = null;
|
||||
query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(BoxSet) };
|
||||
query.SetUser(user);
|
||||
query.Recursive = true;
|
||||
|
||||
@@ -223,7 +223,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
}
|
||||
@@ -236,7 +236,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
}
|
||||
@@ -255,10 +255,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
var genres = parent.QueryRecursive(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Movie).Name },
|
||||
IncludeItemTypes = new[] { nameof(Movie) },
|
||||
Recursive = true,
|
||||
EnableTotalRecordCount = false
|
||||
|
||||
}).Items
|
||||
.SelectMany(i => i.Genres)
|
||||
.DistinctNames()
|
||||
@@ -287,7 +286,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.GenreIds = new[] { displayParent.Id };
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -334,7 +333,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
query.IsVirtualItem = false;
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
@@ -344,7 +343,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.TvShows, string.Empty });
|
||||
|
||||
var result = _tvSeriesManager.GetNextUp(new NextUpQuery
|
||||
var result = _tvSeriesManager.GetNextUp(
|
||||
new NextUpQuery
|
||||
{
|
||||
Limit = query.Limit,
|
||||
StartIndex = query.StartIndex,
|
||||
@@ -362,7 +362,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
}
|
||||
@@ -373,7 +373,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -382,7 +382,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
var genres = parent.QueryRecursive(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
IncludeItemTypes = new[] { nameof(Series) },
|
||||
Recursive = true,
|
||||
EnableTotalRecordCount = false
|
||||
}).Items
|
||||
@@ -413,7 +413,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.GenreIds = new[] { displayParent.Id };
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -444,7 +444,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
return Filter(item, query.User, query, BaseItem.UserDataManager, BaseItem.LibraryManager);
|
||||
}
|
||||
|
||||
public static QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
|
||||
public static QueryResult<BaseItem> PostFilterAndSort(
|
||||
IEnumerable<BaseItem> items,
|
||||
BaseItem queryParent,
|
||||
int? totalRecordLimit,
|
||||
InternalItemsQuery query,
|
||||
@@ -790,7 +791,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
// Apply genre filter
|
||||
if (query.Genres.Length > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))
|
||||
if (query.Genres.Count > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -821,7 +822,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
// Apply genre filter
|
||||
if (query.GenreIds.Length > 0 && !query.GenreIds.Any(id =>
|
||||
if (query.GenreIds.Count > 0 && !query.GenreIds.Any(id =>
|
||||
{
|
||||
var genreItem = libraryManager.GetItemById(id);
|
||||
return genreItem != null && item.Genres.Contains(genreItem.Name, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace MediaBrowser.Controller
|
||||
/// <summary>
|
||||
/// Gets the display preferences for the user and client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will create the display preferences if it does not exist, but it will not save automatically.
|
||||
/// </remarks>
|
||||
/// <param name="userId">The user's id.</param>
|
||||
/// <param name="client">The client string.</param>
|
||||
/// <returns>The associated display preferences.</returns>
|
||||
@@ -20,6 +23,9 @@ namespace MediaBrowser.Controller
|
||||
/// <summary>
|
||||
/// Gets the default item display preferences for the user and client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will create the item display preferences if it does not exist, but it will not save automatically.
|
||||
/// </remarks>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="client">The client string.</param>
|
||||
|
||||
@@ -111,5 +111,4 @@ namespace MediaBrowser.Controller.IO
|
||||
return returnResult;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace MediaBrowser.Controller
|
||||
{
|
||||
@@ -56,10 +56,11 @@ namespace MediaBrowser.Controller
|
||||
/// <summary>
|
||||
/// Gets the system info.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param>
|
||||
/// <returns>SystemInfo.</returns>
|
||||
Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken);
|
||||
Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken);
|
||||
Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the local IP addresses of this API instance. Each address is validated by sending a 'ping' request
|
||||
@@ -67,7 +68,7 @@ namespace MediaBrowser.Controller
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param>
|
||||
/// <returns>A list containing all the local IP addresses of the server.</returns>
|
||||
Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken);
|
||||
Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured
|
||||
@@ -75,7 +76,7 @@ namespace MediaBrowser.Controller
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param>
|
||||
/// <returns>The server URL.</returns>
|
||||
Task<string> GetLocalApiUrl(CancellationToken cancellationToken);
|
||||
Task<string> GetLocalApiUrl(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localhost URL that can be used to access the API using the loop-back IP address (127.0.0.1)
|
||||
@@ -119,5 +120,13 @@ namespace MediaBrowser.Controller
|
||||
string ExpandVirtualPath(string path);
|
||||
|
||||
string ReverseVirtualPath(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of local plugins.
|
||||
/// </summary>
|
||||
/// <param name="path">Plugin base directory.</param>
|
||||
/// <param name="cleanup">Cleanup old plugins.</param>
|
||||
/// <returns>Enumerable of local plugins.</returns>
|
||||
IEnumerable<LocalPlugin> GetLocalPlugins(string path, bool cleanup = true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ namespace MediaBrowser.Controller.Library
|
||||
MusicArtist GetArtist(string name);
|
||||
|
||||
MusicArtist GetArtist(string name, DtoOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Studio.
|
||||
/// </summary>
|
||||
@@ -234,6 +235,7 @@ namespace MediaBrowser.Controller.Library
|
||||
/// Occurs when [item updated].
|
||||
/// </summary>
|
||||
event EventHandler<ItemChangeEventArgs> ItemUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [item removed].
|
||||
/// </summary>
|
||||
@@ -564,10 +566,13 @@ namespace MediaBrowser.Controller.Library
|
||||
|
||||
int GetCount(InternalItemsQuery query);
|
||||
|
||||
void AddExternalSubtitleStreams(List<MediaStream> streams,
|
||||
void AddExternalSubtitleStreams(
|
||||
List<MediaStream> streams,
|
||||
string videoPath,
|
||||
string[] files);
|
||||
|
||||
void RunMetadataSavers(IReadOnlyList<BaseItem> items, ItemUpdateType updateReason);
|
||||
|
||||
BaseItem GetParentItem(string parentId, Guid? userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,14 @@ namespace MediaBrowser.Controller.Library
|
||||
/// <param name="itemId">The item identifier.</param>
|
||||
/// <returns>IEnumerable<MediaStream>.</returns>
|
||||
List<MediaStream> GetMediaStreams(Guid itemId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the media streams.
|
||||
/// </summary>
|
||||
/// <param name="mediaSourceId">The media source identifier.</param>
|
||||
/// <returns>IEnumerable<MediaStream>.</returns>
|
||||
List<MediaStream> GetMediaStreams(string mediaSourceId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the media streams.
|
||||
/// </summary>
|
||||
@@ -113,5 +115,7 @@ namespace MediaBrowser.Controller.Library
|
||||
public interface IDirectStreamProvider
|
||||
{
|
||||
Task CopyToAsync(Stream stream, CancellationToken cancellationToken);
|
||||
|
||||
string GetFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,8 @@ namespace MediaBrowser.Controller.Library
|
||||
/// </summary>
|
||||
/// <param name="userId">The user's Id.</param>
|
||||
/// <param name="config">The request containing the new user configuration.</param>
|
||||
void UpdateConfiguration(Guid userId, UserConfiguration config);
|
||||
/// <returns>A task representing the update.</returns>
|
||||
Task UpdateConfigurationAsync(Guid userId, UserConfiguration config);
|
||||
|
||||
/// <summary>
|
||||
/// This method updates the user's policy.
|
||||
@@ -167,12 +168,14 @@ namespace MediaBrowser.Controller.Library
|
||||
/// </summary>
|
||||
/// <param name="userId">The user's Id.</param>
|
||||
/// <param name="policy">The request containing the new user policy.</param>
|
||||
void UpdatePolicy(Guid userId, UserPolicy policy);
|
||||
/// <returns>A task representing the update.</returns>
|
||||
Task UpdatePolicyAsync(Guid userId, UserPolicy policy);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the user's profile image.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
void ClearProfileImage(User user);
|
||||
/// <returns>A task representing the clearing of the profile image.</returns>
|
||||
Task ClearProfileImageAsync(User user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +156,7 @@ namespace MediaBrowser.Controller.Library
|
||||
}
|
||||
|
||||
// REVIEW: @bond
|
||||
|
||||
/// <summary>
|
||||
/// Gets the physical locations.
|
||||
/// </summary>
|
||||
|
||||
@@ -225,12 +225,13 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, ItemFields[] fields, User user = null);
|
||||
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the tuner host.
|
||||
/// </summary>
|
||||
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the listing provider.
|
||||
/// </summary>
|
||||
|
||||
@@ -56,7 +56,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
|
||||
|
||||
Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken);
|
||||
|
||||
}
|
||||
|
||||
public interface IConfigurableTunerHost
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// </summary>
|
||||
/// <value>The tuners.</value>
|
||||
public List<LiveTvTunerInfo> Tuners { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is visible.
|
||||
/// </summary>
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// </summary>
|
||||
/// <value>The overview.</value>
|
||||
public string Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the short overview.
|
||||
/// </summary>
|
||||
@@ -169,31 +170,37 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// </summary>
|
||||
/// <value>The production year.</value>
|
||||
public int? ProductionYear { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the home page URL.
|
||||
/// </summary>
|
||||
/// <value>The home page URL.</value>
|
||||
public string HomePageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the series identifier.
|
||||
/// </summary>
|
||||
/// <value>The series identifier.</value>
|
||||
public string SeriesId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the show identifier.
|
||||
/// </summary>
|
||||
/// <value>The show identifier.</value>
|
||||
public string ShowId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the season number.
|
||||
/// </summary>
|
||||
/// <value>The season number.</value>
|
||||
public int? SeasonNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the episode number.
|
||||
/// </summary>
|
||||
/// <value>The episode number.</value>
|
||||
public int? EpisodeNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the etag.
|
||||
/// </summary>
|
||||
|
||||
@@ -187,6 +187,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// </summary>
|
||||
/// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
|
||||
public bool? HasImage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the show identifier.
|
||||
/// </summary>
|
||||
|
||||
@@ -113,6 +113,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
|
||||
// Program properties
|
||||
public int? SeasonNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the episode number.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -287,6 +287,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return BaseRequest.AudioChannels;
|
||||
}
|
||||
|
||||
if (BaseRequest.TranscodingMaxAudioChannels.HasValue)
|
||||
{
|
||||
return BaseRequest.TranscodingMaxAudioChannels;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(codec))
|
||||
{
|
||||
var value = BaseRequest.GetOption(codec, "audiochannels");
|
||||
@@ -342,7 +347,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
|
||||
|
||||
var newSize = DrawingUtils.Resize(size,
|
||||
var newSize = DrawingUtils.Resize(
|
||||
size,
|
||||
BaseRequest.Width ?? 0,
|
||||
BaseRequest.Height ?? 0,
|
||||
BaseRequest.MaxWidth ?? 0,
|
||||
@@ -368,7 +374,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
|
||||
|
||||
var newSize = DrawingUtils.Resize(size,
|
||||
var newSize = DrawingUtils.Resize(
|
||||
size,
|
||||
BaseRequest.Width ?? 0,
|
||||
BaseRequest.Height ?? 0,
|
||||
BaseRequest.MaxWidth ?? 0,
|
||||
@@ -402,7 +409,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
// Don't exceed what the encoder supports
|
||||
// Seeing issues of attempting to encode to 88200
|
||||
return Math.Min(44100, BaseRequest.AudioSampleRate.Value);
|
||||
return BaseRequest.AudioSampleRate.Value;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -586,6 +593,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (VideoStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (EncodingHelper.IsCopyCodec(OutputVideoCodec))
|
||||
{
|
||||
return VideoStream?.Codec;
|
||||
@@ -599,6 +611,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (AudioStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (EncodingHelper.IsCopyCodec(OutputAudioCodec))
|
||||
{
|
||||
return AudioStream?.Codec;
|
||||
@@ -697,10 +714,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
/// The progressive.
|
||||
/// </summary>
|
||||
Progressive,
|
||||
|
||||
/// <summary>
|
||||
/// The HLS.
|
||||
/// </summary>
|
||||
Hls,
|
||||
|
||||
/// <summary>
|
||||
/// The dash.
|
||||
/// </summary>
|
||||
|
||||
@@ -68,7 +68,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
/// <summary>
|
||||
/// Extracts the video images on interval.
|
||||
/// </summary>
|
||||
Task ExtractVideoImagesOnInterval(string[] inputFiles,
|
||||
Task ExtractVideoImagesOnInterval(
|
||||
string[] inputFiles,
|
||||
string container,
|
||||
MediaStream videoStream,
|
||||
MediaProtocol protocol,
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
else if (part.StartsWith("fps=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var rate = part.Split(new[] { '=' }, 2)[^1];
|
||||
var rate = part.Split('=', 2)[^1];
|
||||
|
||||
if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
|
||||
{
|
||||
@@ -103,7 +103,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
else if (state.RunTimeTicks.HasValue &&
|
||||
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var time = part.Split(new[] { '=' }, 2).Last();
|
||||
var time = part.Split('=', 2)[^1];
|
||||
|
||||
if (TimeSpan.TryParse(time, _usCulture, out var val))
|
||||
{
|
||||
@@ -116,7 +116,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var size = part.Split(new[] { '=' }, 2).Last();
|
||||
var size = part.Split('=', 2)[^1];
|
||||
|
||||
int? scale = null;
|
||||
if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
@@ -135,7 +135,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var rate = part.Split(new[] { '=' }, 2).Last();
|
||||
var rate = part.Split('=', 2)[^1];
|
||||
|
||||
int? scale = null;
|
||||
if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using Jellyfin.Data.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// The request authorization info.
|
||||
/// </summary>
|
||||
public class AuthorizationInfo
|
||||
{
|
||||
/// <summary>
|
||||
@@ -43,6 +44,19 @@ namespace MediaBrowser.Controller.Net
|
||||
/// <value>The token.</value>
|
||||
public string Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the authorization is from an api key.
|
||||
/// </summary>
|
||||
public bool IsApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user making the request.
|
||||
/// </summary>
|
||||
public User User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the token is authenticated.
|
||||
/// </summary>
|
||||
public bool IsAuthenticated { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Controller.Net
|
||||
@@ -28,10 +29,22 @@ namespace MediaBrowser.Controller.Net
|
||||
new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// Gets the type used for the messages sent to the client.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
protected abstract string Name { get; }
|
||||
/// <value>The type.</value>
|
||||
protected abstract SessionMessageType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message type received from the client to start sending messages.
|
||||
/// </summary>
|
||||
/// <value>The type.</value>
|
||||
protected abstract SessionMessageType StartType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message type received from the client to stop sending messages.
|
||||
/// </summary>
|
||||
/// <value>The type.</value>
|
||||
protected abstract SessionMessageType StopType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data to send.
|
||||
@@ -66,12 +79,12 @@ namespace MediaBrowser.Controller.Net
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase))
|
||||
if (message.MessageType == StartType)
|
||||
{
|
||||
Start(message);
|
||||
}
|
||||
|
||||
if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase))
|
||||
if (message.MessageType == StopType)
|
||||
{
|
||||
Stop(message);
|
||||
}
|
||||
@@ -159,7 +172,7 @@ namespace MediaBrowser.Controller.Net
|
||||
new WebSocketMessage<TReturnDataType>
|
||||
{
|
||||
MessageId = Guid.NewGuid(),
|
||||
MessageType = Name,
|
||||
MessageType = Type,
|
||||
Data = data
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
@@ -176,7 +189,7 @@ namespace MediaBrowser.Controller.Net
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error sending web socket message {Name}", Name);
|
||||
Logger.LogError(ex, "Error sending web socket message {Name}", Type);
|
||||
DisposeConnection(tuple);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@ namespace MediaBrowser.Controller.Net
|
||||
/// </summary>
|
||||
event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Inits this instance.
|
||||
/// </summary>
|
||||
/// <param name="listeners">The websocket listeners.</param>
|
||||
void Init(IEnumerable<IWebSocketListener> listeners);
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP request handler.
|
||||
/// </summary>
|
||||
|
||||
@@ -100,6 +100,7 @@ namespace MediaBrowser.Controller.Persistence
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>IEnumerable<Guid>.</returns>
|
||||
QueryResult<Guid> GetItemIds(InternalItemsQuery query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items.
|
||||
/// </summary>
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace MediaBrowser.Controller.Playlists
|
||||
/// <param name="itemIds">The item ids.</param>
|
||||
/// <param name="userId">The user identifier.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task AddToPlaylistAsync(Guid playlistId, ICollection<Guid> itemIds, Guid userId);
|
||||
Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId);
|
||||
|
||||
/// <summary>
|
||||
/// Removes from playlist.
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace MediaBrowser.Controller.Playlists
|
||||
return LibraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
Recursive = true,
|
||||
IncludeItemTypes = new[] { typeof(Audio).Name },
|
||||
IncludeItemTypes = new[] { nameof(Audio) },
|
||||
GenreIds = new[] { musicGenre.Id },
|
||||
OrderBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
|
||||
DtoOptions = options
|
||||
@@ -172,7 +172,7 @@ namespace MediaBrowser.Controller.Playlists
|
||||
return LibraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
Recursive = true,
|
||||
IncludeItemTypes = new[] { typeof(Audio).Name },
|
||||
IncludeItemTypes = new[] { nameof(Audio) },
|
||||
ArtistIds = new[] { musicArtist.Id },
|
||||
OrderBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
|
||||
DtoOptions = options
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace MediaBrowser.Controller.Resolvers
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>BaseItem.</returns>
|
||||
BaseItem ResolvePath(ItemResolveArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
@@ -28,7 +29,8 @@ namespace MediaBrowser.Controller.Resolvers
|
||||
|
||||
public interface IMultiItemResolver
|
||||
{
|
||||
MultiItemResolverResult ResolveMultiple(Folder parent,
|
||||
MultiItemResolverResult ResolveMultiple(
|
||||
Folder parent,
|
||||
List<FileSystemMetadata> files,
|
||||
string collectionType,
|
||||
IDirectoryService directoryService);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
||||
namespace MediaBrowser.Controller.Session
|
||||
{
|
||||
@@ -23,6 +24,6 @@ namespace MediaBrowser.Controller.Session
|
||||
/// <summary>
|
||||
/// Sends the message.
|
||||
/// </summary>
|
||||
Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken);
|
||||
Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,16 +188,16 @@ namespace MediaBrowser.Controller.Session
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken);
|
||||
Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the message to user sessions.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>Task.</returns>
|
||||
Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken);
|
||||
Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, T data, CancellationToken cancellationToken);
|
||||
|
||||
Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, Func<T> dataFn, CancellationToken cancellationToken);
|
||||
Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, Func<T> dataFn, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the message to user device sessions.
|
||||
@@ -208,7 +208,7 @@ namespace MediaBrowser.Controller.Session
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken);
|
||||
Task SendMessageToUserDeviceSessions<T>(string deviceId, SessionMessageType name, T data, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the restart required message.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
@@ -22,7 +23,6 @@ namespace MediaBrowser.Controller.Session
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
||||
private readonly object _progressLock = new object();
|
||||
private Timer _progressTimer;
|
||||
private PlaybackProgressInfo _lastProgressInfo;
|
||||
@@ -55,7 +55,7 @@ namespace MediaBrowser.Controller.Session
|
||||
/// Gets or sets the playable media types.
|
||||
/// </summary>
|
||||
/// <value>The playable media types.</value>
|
||||
public string[] PlayableMediaTypes
|
||||
public IReadOnlyList<string> PlayableMediaTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -231,8 +231,8 @@ namespace MediaBrowser.Controller.Session
|
||||
/// Gets or sets the supported commands.
|
||||
/// </summary>
|
||||
/// <value>The supported commands.</value>
|
||||
public string[] SupportedCommands
|
||||
=> Capabilities == null ? Array.Empty<string>() : Capabilities.SupportedCommands;
|
||||
public IReadOnlyList<GeneralCommandType> SupportedCommands
|
||||
=> Capabilities == null ? Array.Empty<GeneralCommandType>() : Capabilities.SupportedCommands;
|
||||
|
||||
public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -52,6 +53,14 @@ namespace MediaBrowser.Controller.Subtitles
|
||||
/// </summary>
|
||||
Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Upload new subtitle.
|
||||
/// </summary>
|
||||
/// <param name="video">The video the subtitle belongs to.</param>
|
||||
/// <param name="response">The subtitle response.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task UploadSubtitle(Video video, SubtitleResponse response);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remote subtitles.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,12 +14,12 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||
public class GroupInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default ping value used for sessions.
|
||||
/// The default ping value used for sessions.
|
||||
/// </summary>
|
||||
public long DefaultPing { get; } = 500;
|
||||
public const long DefaultPing = 500;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the group identifier.
|
||||
/// Gets the group identifier.
|
||||
/// </summary>
|
||||
/// <value>The group identifier.</value>
|
||||
public Guid GroupId { get; } = Guid.NewGuid();
|
||||
@@ -58,7 +58,8 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||
/// <summary>
|
||||
/// Checks if a session is in this group.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if the session is in this group; <c>false</c> otherwise.</value>
|
||||
/// <param name="sessionId">The session id to check.</param>
|
||||
/// <returns><c>true</c> if the session is in this group; <c>false</c> otherwise.</returns>
|
||||
public bool ContainsSession(string sessionId)
|
||||
{
|
||||
return Participants.ContainsKey(sessionId);
|
||||
@@ -70,16 +71,14 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||
/// <param name="session">The session.</param>
|
||||
public void AddSession(SessionInfo session)
|
||||
{
|
||||
if (ContainsSession(session.Id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var member = new GroupMember();
|
||||
member.Session = session;
|
||||
member.Ping = DefaultPing;
|
||||
member.IsBuffering = false;
|
||||
Participants[session.Id] = member;
|
||||
Participants.TryAdd(
|
||||
session.Id,
|
||||
new GroupMember
|
||||
{
|
||||
Session = session,
|
||||
Ping = DefaultPing,
|
||||
IsBuffering = false
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -88,12 +87,7 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||
/// <param name="session">The session.</param>
|
||||
public void RemoveSession(SessionInfo session)
|
||||
{
|
||||
if (!ContainsSession(session.Id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Participants.Remove(session.Id, out _);
|
||||
Participants.Remove(session.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -103,18 +97,16 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||
/// <param name="ping">The ping.</param>
|
||||
public void UpdatePing(SessionInfo session, long ping)
|
||||
{
|
||||
if (!ContainsSession(session.Id))
|
||||
if (Participants.TryGetValue(session.Id, out GroupMember value))
|
||||
{
|
||||
return;
|
||||
value.Ping = ping;
|
||||
}
|
||||
|
||||
Participants[session.Id].Ping = ping;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the highest ping in the group.
|
||||
/// </summary>
|
||||
/// <value name="session">The highest ping in the group.</value>
|
||||
/// <returns>The highest ping in the group.</returns>
|
||||
public long GetHighestPing()
|
||||
{
|
||||
long max = long.MinValue;
|
||||
@@ -133,18 +125,16 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||
/// <param name="isBuffering">The state.</param>
|
||||
public void SetBuffering(SessionInfo session, bool isBuffering)
|
||||
{
|
||||
if (!ContainsSession(session.Id))
|
||||
if (Participants.TryGetValue(session.Id, out GroupMember value))
|
||||
{
|
||||
return;
|
||||
value.IsBuffering = isBuffering;
|
||||
}
|
||||
|
||||
Participants[session.Id].IsBuffering = isBuffering;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the group buffering state.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if there is a session buffering in the group; <c>false</c> otherwise.</value>
|
||||
/// <returns><c>true</c> if there is a session buffering in the group; <c>false</c> otherwise.</returns>
|
||||
public bool IsBuffering()
|
||||
{
|
||||
foreach (var session in Participants.Values)
|
||||
@@ -161,7 +151,7 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||
/// <summary>
|
||||
/// Checks if the group is empty.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if the group is empty; <c>false</c> otherwise.</value>
|
||||
/// <returns><c>true</c> if the group is empty; <c>false</c> otherwise.</returns>
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return Participants.Count == 0;
|
||||
|
||||
Reference in New Issue
Block a user