Merge branch 'master' into TVFix

This commit is contained in:
cvium
2021-11-08 10:38:08 +01:00
549 changed files with 7338 additions and 4194 deletions

View File

@@ -37,8 +37,9 @@ namespace MediaBrowser.Controller.Dlna
/// <summary>
/// Updates the profile.
/// </summary>
/// <param name="profileId">The profile id.</param>
/// <param name="profile">The profile.</param>
void UpdateProfile(DeviceProfile profile);
void UpdateProfile(string profileId, DeviceProfile profile);
/// <summary>
/// Deletes the profile.
@@ -74,6 +75,6 @@ namespace MediaBrowser.Controller.Dlna
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>DlnaIconResponse.</returns>
ImageStream GetIcon(string filename);
ImageStream? GetIcon(string filename);
}
}

View File

@@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>Guid.</returns>
string GetImageCacheTag(BaseItem item, ItemImageInfo image);
string GetImageCacheTag(BaseItem item, ChapterInfo info);
string GetImageCacheTag(BaseItem item, ChapterInfo chapter);
string? GetImageCacheTag(User user);

View File

@@ -8,11 +8,16 @@ namespace MediaBrowser.Controller.Drawing
{
public class ImageStream : IDisposable
{
public ImageStream(Stream stream)
{
Stream = stream;
}
/// <summary>
/// Gets or sets the stream.
/// Gets the stream.
/// </summary>
/// <value>The stream.</value>
public Stream? Stream { get; set; }
public Stream Stream { get; }
/// <summary>
/// Gets or sets the format.

View File

@@ -19,6 +19,7 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
@@ -43,18 +44,10 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// The trailer folder name.
/// </summary>
public const string TrailerFolderName = "trailers";
public const string TrailersFolderName = "trailers";
public const string ThemeSongsFolderName = "theme-music";
public const string ThemeSongFilename = "theme";
public const string ThemeSongFileName = "theme";
public const string ThemeVideosFolderName = "backdrops";
public const string ExtrasFolderName = "extras";
public const string BehindTheScenesFolderName = "behind the scenes";
public const string DeletedScenesFolderName = "deleted scenes";
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";
/// <summary>
/// The supported image extensions.
@@ -91,17 +84,19 @@ namespace MediaBrowser.Controller.Entities
Model.Entities.ExtraType.Scene
};
public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
public static readonly string[] AllExtrasTypesFolderNames =
/// <summary>
/// The supported extra folder names and types. See <see cref="Emby.Naming.Common.NamingOptions" />.
/// </summary>
public static readonly Dictionary<string, ExtraType> AllExtrasTypesFolderNames = new Dictionary<string, ExtraType>(StringComparer.OrdinalIgnoreCase)
{
ExtrasFolderName,
BehindTheScenesFolderName,
DeletedScenesFolderName,
InterviewFolderName,
SceneFolderName,
SampleFolderName,
ShortsFolderName,
FeaturettesFolderName
["extras"] = MediaBrowser.Model.Entities.ExtraType.Unknown,
["behind the scenes"] = MediaBrowser.Model.Entities.ExtraType.BehindTheScenes,
["deleted scenes"] = MediaBrowser.Model.Entities.ExtraType.DeletedScene,
["interviews"] = MediaBrowser.Model.Entities.ExtraType.Interview,
["scenes"] = MediaBrowser.Model.Entities.ExtraType.Scene,
["samples"] = MediaBrowser.Model.Entities.ExtraType.Sample,
["shorts"] = MediaBrowser.Model.Entities.ExtraType.Clip,
["featurettes"] = MediaBrowser.Model.Entities.ExtraType.Clip
};
private string _sortName;
@@ -357,11 +352,6 @@ namespace MediaBrowser.Controller.Entities
{
get
{
// if (IsOffline)
// {
// return LocationType.Offline;
// }
var path = Path;
if (string.IsNullOrEmpty(path))
{
@@ -394,7 +384,7 @@ namespace MediaBrowser.Controller.Entities
}
[JsonIgnore]
public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File);
public bool IsFileProtocol => PathProtocol == MediaProtocol.File;
[JsonIgnore]
public bool HasPathProtocol => PathProtocol.HasValue;
@@ -586,14 +576,7 @@ namespace MediaBrowser.Controller.Entities
}
[JsonIgnore]
public virtual Guid DisplayParentId
{
get
{
var parentId = ParentId;
return parentId;
}
}
public virtual Guid DisplayParentId => ParentId;
[JsonIgnore]
public BaseItem DisplayParent
@@ -856,13 +839,6 @@ namespace MediaBrowser.Controller.Entities
return Id.ToString("N", CultureInfo.InvariantCulture);
}
public bool IsPathProtocol(MediaProtocol protocol)
{
var current = PathProtocol;
return current.HasValue && current.Value == protocol;
}
private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1)
{
var list = new List<Tuple<StringBuilder, bool>>();
@@ -990,7 +966,7 @@ namespace MediaBrowser.Controller.Entities
ReadOnlySpan<char> idString = Id.ToString("N", CultureInfo.InvariantCulture);
return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString);
return System.IO.Path.Join(basePath, "library", idString[..2], idString);
}
/// <summary>
@@ -1305,8 +1281,7 @@ namespace MediaBrowser.Controller.Entities
terms.Add(item.Name);
}
var video = item as Video;
if (video != null)
if (item is Video video)
{
if (video.Video3DFormat.HasValue)
{
@@ -1341,7 +1316,7 @@ namespace MediaBrowser.Controller.Entities
}
}
return string.Join('/', terms.ToArray());
return string.Join('/', terms);
}
/// <summary>
@@ -1357,16 +1332,14 @@ namespace MediaBrowser.Controller.Entities
// Support plex/xbmc convention
files.AddRange(fileSystemChildren
.Where(i => !i.IsDirectory && System.IO.Path.GetFileNameWithoutExtension(i.FullName.AsSpan()).Equals(ThemeSongFilename, StringComparison.OrdinalIgnoreCase)));
.Where(i => !i.IsDirectory && System.IO.Path.GetFileNameWithoutExtension(i.FullName.AsSpan()).Equals(ThemeSongFileName, StringComparison.OrdinalIgnoreCase)));
return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
.OfType<Audio.Audio>()
.Select(audio =>
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
var dbItem = LibraryManager.GetItemById(audio.Id) as Audio.Audio;
if (dbItem != null)
if (LibraryManager.GetItemById(audio.Id) is Audio.Audio dbItem)
{
audio = dbItem;
}
@@ -1416,39 +1389,24 @@ namespace MediaBrowser.Controller.Entities
protected virtual BaseItem[] LoadExtras(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var extras = new List<Video>();
var libraryOptions = new LibraryOptions();
var folders = fileSystemChildren.Where(i => i.IsDirectory).ToList();
foreach (var extraFolderName in AllExtrasTypesFolderNames)
{
var files = folders
.Where(i => string.Equals(i.Name, extraFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => FileSystem.GetFiles(i.FullName));
// Re-using the same instance of LibraryOptions since it looks like it's never being altered.
extras.AddRange(LibraryManager.ResolvePaths(files, directoryService, null, libraryOptions)
return fileSystemChildren
.Where(child => child.IsDirectory && AllExtrasTypesFolderNames.ContainsKey(child.Name))
.SelectMany(folder => LibraryManager
.ResolvePaths(FileSystem.GetFiles(folder.FullName), directoryService, null, new LibraryOptions())
.OfType<Video>()
.Select(item =>
.Select(video =>
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
if (LibraryManager.GetItemById(item.Id) is Video dbItem)
if (LibraryManager.GetItemById(video.Id) is Video dbItem)
{
item = dbItem;
video = dbItem;
}
// Use some hackery to get the extra type based on foldername
item.ExtraType = Enum.TryParse(extraFolderName.Replace(" ", string.Empty, StringComparison.Ordinal), true, out ExtraType extraType)
? extraType
: Model.Entities.ExtraType.Unknown;
return item;
// Sort them so that the list can be easily compared for changes
}).OrderBy(i => i.Path));
}
return extras.ToArray();
video.ExtraType = AllExtrasTypesFolderNames[folder.Name];
return video;
})
.OrderBy(video => video.Path)) // Sort them so that the list can be easily compared for changes
.ToArray();
}
public Task RefreshMetadata(CancellationToken cancellationToken)
@@ -1588,8 +1546,7 @@ namespace MediaBrowser.Controller.Entities
}
}
var hasTrailers = this as IHasTrailers;
if (hasTrailers != null)
if (this is IHasTrailers hasTrailers)
{
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
}
@@ -2286,7 +2243,11 @@ namespace MediaBrowser.Controller.Entities
var existingImage = GetImageInfo(image.Type, index);
if (existingImage != null)
if (existingImage == null)
{
AddImage(image);
}
else
{
existingImage.Path = image.Path;
existingImage.DateModified = image.DateModified;
@@ -2294,15 +2255,6 @@ namespace MediaBrowser.Controller.Entities
existingImage.Height = image.Height;
existingImage.BlurHash = image.BlurHash;
}
else
{
var current = ImageInfos;
var currentCount = current.Length;
var newArr = new ItemImageInfo[currentCount + 1];
current.CopyTo(newArr, 0);
newArr[currentCount] = image;
ImageInfos = newArr;
}
}
public void SetImagePath(ImageType type, int index, FileSystemMetadata file)
@@ -2316,7 +2268,7 @@ namespace MediaBrowser.Controller.Entities
if (image == null)
{
ImageInfos = ImageInfos.Concat(new[] { GetImageInfo(file, type) }).ToArray();
AddImage(GetImageInfo(file, type));
}
else
{
@@ -2360,14 +2312,24 @@ namespace MediaBrowser.Controller.Entities
public void RemoveImage(ItemImageInfo image)
{
RemoveImages(new List<ItemImageInfo> { image });
RemoveImages(new[] { image });
}
public void RemoveImages(List<ItemImageInfo> deletedImages)
public void RemoveImages(IEnumerable<ItemImageInfo> deletedImages)
{
ImageInfos = ImageInfos.Except(deletedImages).ToArray();
}
public void AddImage(ItemImageInfo image)
{
var current = ImageInfos;
var currentCount = current.Length;
var newArr = new ItemImageInfo[currentCount + 1];
current.CopyTo(newArr, 0);
newArr[currentCount] = image;
ImageInfos = newArr;
}
public virtual Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken)
=> LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken);
@@ -2391,7 +2353,7 @@ namespace MediaBrowser.Controller.Entities
if (deletedImages.Count > 0)
{
ImageInfos = ImageInfos.Except(deletedImages).ToArray();
RemoveImages(deletedImages);
}
return deletedImages.Count > 0;
@@ -2439,6 +2401,17 @@ namespace MediaBrowser.Controller.Entities
};
}
// Music albums usually don't have dedicated backdrops, so return one from the artist instead
if (GetType() == typeof(MusicAlbum) && imageType == ImageType.Backdrop)
{
var artist = FindParent<MusicArtist>();
if (artist != null)
{
return artist.GetImages(imageType).ElementAtOrDefault(imageIndex);
}
}
return GetImages(imageType)
.ElementAtOrDefault(imageIndex);
}
@@ -2502,11 +2475,11 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
/// Adds the images.
/// Adds the images, updating metadata if they already are part of this item.
/// </summary>
/// <param name="imageType">Type of the image.</param>
/// <param name="images">The images.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
/// <returns><c>true</c> if images were added or updated, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentException">Cannot call AddImages with chapter images.</exception>
public bool AddImages(ImageType imageType, List<FileSystemMetadata> images)
{
@@ -2519,7 +2492,6 @@ namespace MediaBrowser.Controller.Entities
.ToList();
var newImageList = new List<FileSystemMetadata>();
var imageAdded = false;
var imageUpdated = false;
foreach (var newImage in images)
@@ -2535,7 +2507,6 @@ namespace MediaBrowser.Controller.Entities
if (existing == null)
{
newImageList.Add(newImage);
imageAdded = true;
}
else
{
@@ -2556,19 +2527,6 @@ namespace MediaBrowser.Controller.Entities
}
}
if (imageAdded || images.Count != existingImages.Count)
{
var newImagePaths = images.Select(i => i.FullName).ToList();
var deleted = existingImages
.FindAll(i => i.IsLocalFile && !newImagePaths.Contains(i.Path.AsSpan(), StringComparison.OrdinalIgnoreCase) && !File.Exists(i.Path));
if (deleted.Count > 0)
{
ImageInfos = ImageInfos.Except(deleted).ToArray();
}
}
if (newImageList.Count > 0)
{
ImageInfos = ImageInfos.Concat(newImageList.Select(i => GetImageInfo(i, imageType))).ToArray();
@@ -2619,7 +2577,7 @@ namespace MediaBrowser.Controller.Entities
public bool AllowsMultipleImages(ImageType type)
{
return type == ImageType.Backdrop || type == ImageType.Screenshot || type == ImageType.Chapter;
return type == ImageType.Backdrop || type == ImageType.Chapter;
}
public Task SwapImagesAsync(ImageType type, int index1, int index2)
@@ -2737,7 +2695,7 @@ namespace MediaBrowser.Controller.Entities
protected static string GetMappedPath(BaseItem item, string path, MediaProtocol? protocol)
{
if (protocol.HasValue && protocol.Value == MediaProtocol.File)
if (protocol == MediaProtocol.File)
{
return LibraryManager.GetPathAfterNetworkSubstitution(path, item);
}
@@ -2765,8 +2723,10 @@ namespace MediaBrowser.Controller.Entities
protected Task RefreshMetadataForOwnedItem(BaseItem ownedItem, bool copyTitleMetadata, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
var newOptions = new MetadataRefreshOptions(options);
newOptions.SearchResult = null;
var newOptions = new MetadataRefreshOptions(options)
{
SearchResult = null
};
var item = this;
@@ -2827,8 +2787,10 @@ namespace MediaBrowser.Controller.Entities
protected Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, bool copyTitleMetadata, string path, CancellationToken cancellationToken)
{
var newOptions = new MetadataRefreshOptions(options);
newOptions.SearchResult = null;
var newOptions = new MetadataRefreshOptions(options)
{
SearchResult = null
};
var id = LibraryManager.GetNewItemId(path, typeof(Video));
@@ -2842,14 +2804,6 @@ namespace MediaBrowser.Controller.Entities
newOptions.ForceSave = true;
}
// var parentId = Id;
// if (!video.IsOwnedItem || video.ParentId != parentId)
// {
// video.IsOwnedItem = true;
// video.ParentId = parentId;
// newOptions.ForceSave = true;
// }
if (video == null)
{
return Task.FromResult(true);
@@ -2933,7 +2887,7 @@ namespace MediaBrowser.Controller.Entities
.Select(i => i.OfficialRating)
.Where(i => !string.IsNullOrEmpty(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(i => new Tuple<string, int?>(i, LocalizationManager.GetRatingLevel(i)))
.Select(i => (i, LocalizationManager.GetRatingLevel(i)))
.OrderBy(i => i.Item2 ?? 1000)
.Select(i => i.Item1);
@@ -2980,18 +2934,6 @@ namespace MediaBrowser.Controller.Entities
.Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
}
public IEnumerable<BaseItem> GetTrailers()
{
if (this is IHasTrailers)
{
return ((IHasTrailers)this).LocalTrailerIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName);
}
else
{
return Array.Empty<BaseItem>();
}
}
public virtual long GetRunTimeTicksForPlayState()
{
return RunTimeTicks ?? 0;
@@ -3004,7 +2946,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <inheritdoc />
public bool Equals(BaseItem other) => object.Equals(Id, other?.Id);
public bool Equals(BaseItem other) => Equals(Id, other?.Id);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(Id);

View File

@@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Entities
/// <param name="file">The file.</param>
public static void SetImagePath(this BaseItem item, ImageType imageType, string file)
{
if (file.StartsWith("http", System.StringComparison.OrdinalIgnoreCase))
if (file.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
item.SetImage(
new ItemImageInfo

View File

@@ -1015,17 +1015,17 @@ namespace MediaBrowser.Controller.Entities
if (!string.IsNullOrEmpty(query.NameStartsWithOrGreater))
{
items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1);
items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.InvariantCultureIgnoreCase) < 1);
}
if (!string.IsNullOrEmpty(query.NameStartsWith))
{
items = items.Where(i => i.SortName.StartsWith(query.NameStartsWith, StringComparison.CurrentCultureIgnoreCase));
items = items.Where(i => i.SortName.StartsWith(query.NameStartsWith, StringComparison.InvariantCultureIgnoreCase));
}
if (!string.IsNullOrEmpty(query.NameLessThan))
{
items = items.Where(i => string.Compare(query.NameLessThan, i.SortName, StringComparison.CurrentCultureIgnoreCase) == 1);
items = items.Where(i => string.Compare(query.NameLessThan, i.SortName, StringComparison.InvariantCultureIgnoreCase) == 1);
}
// This must be the last filter

View File

@@ -1,9 +0,0 @@
namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// The item has screenshots.
/// </summary>
public interface IHasScreenshots
{
}
}

View File

@@ -24,6 +24,14 @@ namespace MediaBrowser.Controller.Entities
private readonly object _childIdsLock = new object();
private List<Guid> _childrenIds = null;
/// <summary>
/// Initializes a new instance of the <see cref="UserRootFolder"/> class.
/// </summary>
public UserRootFolder()
{
IsRoot = true;
}
[JsonIgnore]
public override bool SupportsInheritedParentImages => false;

View File

@@ -57,9 +57,7 @@ namespace MediaBrowser.Controller.Entities
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
{
var usCulture = new CultureInfo("en-US");
if (!int.TryParse(Name, NumberStyles.Integer, usCulture, out var year))
if (!int.TryParse(Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
{
return new List<BaseItem>();
}

View File

@@ -2,7 +2,6 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Net;
using MediaBrowser.Common;
@@ -51,11 +50,11 @@ namespace MediaBrowser.Controller
/// <summary>
/// Gets the system info.
/// </summary>
/// <param name="source">The originator of the request.</param>
/// <param name="request">The HTTP request.</param>
/// <returns>SystemInfo.</returns>
SystemInfo GetSystemInfo(IPAddress source);
SystemInfo GetSystemInfo(HttpRequest request);
PublicSystemInfo GetPublicSystemInfo(IPAddress address);
PublicSystemInfo GetPublicSystemInfo(HttpRequest request);
/// <summary>
/// Gets a URL specific for the request.

View File

@@ -0,0 +1,19 @@
using System.IO;
namespace MediaBrowser.Controller.Library
{
/// <summary>
/// The direct live TV stream provider.
/// </summary>
/// <remarks>
/// Deprecated.
/// </remarks>
public interface IDirectStreamProvider
{
/// <summary>
/// Gets the live stream, shared streams seek to the end of the file first.
/// </summary>
/// <returns>The stream.</returns>
Stream GetStream();
}
}

View File

@@ -2,6 +2,7 @@
#pragma warning disable CA1711, CS1591
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Dto;
@@ -25,5 +26,7 @@ namespace MediaBrowser.Controller.Library
Task Open(CancellationToken openCancellationToken);
Task Close();
Stream GetStream();
}
}

View File

@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
@@ -110,6 +109,20 @@ namespace MediaBrowser.Controller.Library
Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken);
/// <summary>
/// Gets the live stream info.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>An instance of <see cref="ILiveStream"/>.</returns>
public ILiveStream GetLiveStreamInfo(string id);
/// <summary>
/// Gets the live stream info using the stream's unique id.
/// </summary>
/// <param name="uniqueId">The unique identifier.</param>
/// <returns>An instance of <see cref="ILiveStream"/>.</returns>
public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId);
/// <summary>
/// Closes the media source.
/// </summary>
@@ -126,14 +139,5 @@ namespace MediaBrowser.Controller.Library
void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user);
Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProbeDelay, bool isLiveStream, CancellationToken cancellationToken);
Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken);
}
public interface IDirectStreamProvider
{
Task CopyToAsync(Stream stream, CancellationToken cancellationToken);
string GetFilePath();
}
}

View File

@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Diacritics.Extensions;
using MediaBrowser.Controller.Extensions;
namespace MediaBrowser.Controller.Library
{

View File

@@ -14,11 +14,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Diacritics" Version="2.1.20036.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
<PackageReference Include="Diacritics" Version="3.3.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0-rc.2*" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="5.0.0" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0-rc.2*" />
</ItemGroup>
<ItemGroup>
@@ -32,18 +32,13 @@
</ItemGroup>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">

View File

@@ -21,8 +21,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public class EncodingHelper
{
private static readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder;
@@ -37,7 +35,7 @@ namespace MediaBrowser.Controller.MediaEncoding
"ConstrainedHigh"
};
private static readonly Version minVersionForCudaOverlay = new Version(4, 4);
private static readonly Version _minVersionForCudaOverlay = new Version(4, 4);
public EncodingHelper(
IMediaEncoder mediaEncoder,
@@ -647,8 +645,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
if (state.IsVideoRequest
&& ((string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
&& (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder))))
&& string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
&& (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder))
{
if (!isCudaTonemappingSupported && isOpenclTonemappingSupported)
{
@@ -816,7 +814,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
{
if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel))
if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double requestLevel))
{
if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
@@ -911,7 +909,7 @@ namespace MediaBrowser.Controller.MediaEncoding
CultureInfo.InvariantCulture,
"subtitles='{0}:si={1}'{2}",
_mediaEncoder.EscapeSubtitleFilterPath(mediaPath),
state.InternalSubtitleStreamOffset.ToString(_usCulture),
state.InternalSubtitleStreamOffset.ToString(CultureInfo.InvariantCulture),
// fallbackFontParam,
setPtsParam);
}
@@ -1217,7 +1215,7 @@ namespace MediaBrowser.Controller.MediaEncoding
param += string.Format(
CultureInfo.InvariantCulture,
" -speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
profileScore.ToString(_usCulture),
profileScore.ToString(CultureInfo.InvariantCulture),
crf,
qmin,
qmax);
@@ -1289,7 +1287,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var framerate = GetFramerateParam(state);
if (framerate.HasValue)
{
param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(_usCulture));
param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.InvariantCulture));
}
var targetVideoCodec = state.ActualOutputVideoCodec;
@@ -1393,7 +1391,7 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
{
// hevc_qsv use -level 51 instead of -level 153.
if (double.TryParse(level, NumberStyles.Any, _usCulture, out double hevcLevel))
if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double hevcLevel))
{
param += " -level " + (hevcLevel / 3);
}
@@ -1555,7 +1553,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// If a specific level was requested, the source must match or be less than
var level = state.GetRequestedLevel(videoStream.Codec);
if (!string.IsNullOrEmpty(level)
&& double.TryParse(level, NumberStyles.Any, _usCulture, out var requestLevel))
&& double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var requestLevel))
{
if (!videoStream.Level.HasValue)
{
@@ -1803,7 +1801,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.AudioStream.Channels.Value > 5
&& !encodingOptions.DownMixAudioBoost.Equals(1))
{
filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture));
filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
}
var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
@@ -2099,7 +2097,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay;
var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay;
var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
// Tonemapping and burn-in graphical subtitles requires overlay_vaapi.
@@ -2380,7 +2378,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase);
var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay;
var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay;
var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@@ -2434,8 +2432,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isExynosV4L2)
{
var widthParam = requestedWidth.Value.ToString(_usCulture);
var heightParam = requestedHeight.Value.ToString(_usCulture);
var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
filters.Add(
string.Format(
@@ -2453,8 +2451,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
else if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
{
var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture);
var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture);
var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
if (isExynosV4L2)
{
@@ -2486,7 +2484,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else
{
var widthParam = requestedWidth.Value.ToString(_usCulture);
var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
filters.Add(
string.Format(
@@ -2499,7 +2497,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// If a fixed height was requested
else if (requestedHeight.HasValue)
{
var heightParam = requestedHeight.Value.ToString(_usCulture);
var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
if (isExynosV4L2)
{
@@ -2522,7 +2520,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// If a max width was requested
else if (requestedMaxWidth.HasValue)
{
var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture);
var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
if (isExynosV4L2)
{
@@ -2545,7 +2543,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// If a max height was requested
else if (requestedMaxHeight.HasValue)
{
var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture);
var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
if (isExynosV4L2)
{
@@ -2683,7 +2681,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay;
var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay;
var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@@ -4122,12 +4120,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (bitrate.HasValue)
{
args += " -ab " + bitrate.Value.ToString(_usCulture);
args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
}
if (state.OutputAudioSampleRate.HasValue)
{
args += " -ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture);
args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
}
args += GetAudioFilterParam(state, encodingOptions);
@@ -4143,12 +4141,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (bitrate.HasValue)
{
audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(_usCulture));
audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
}
if (state.OutputAudioChannels.HasValue)
{
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(_usCulture));
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
// opus will fail on 44100
@@ -4156,7 +4154,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (state.OutputAudioSampleRate.HasValue)
{
audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture));
audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
}
}

View File

@@ -541,7 +541,12 @@ namespace MediaBrowser.Controller.MediaEncoding
return MimeType;
}
return MimeTypes.GetMimeType(outputPath, enableStreamDefault);
if (enableStreamDefault)
{
return MimeTypes.GetMimeType(outputPath);
}
return MimeTypes.GetMimeType(outputPath, null);
}
public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced)

View File

@@ -95,9 +95,10 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="mediaSource">Media source information.</param>
/// <param name="imageStream">Media stream information.</param>
/// <param name="imageStreamIndex">Index of the stream to extract from.</param>
/// <param name="outputExtension">The extension of the file to write, including the '.'.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>Location of video image.</returns>
Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken);
Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken);
/// <summary>
/// Extracts the video images on interval.

View File

@@ -6,7 +6,6 @@ using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -14,7 +13,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public class JobLogger
{
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private readonly ILogger _logger;
public JobLogger(ILogger logger)
@@ -88,7 +86,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var rate = parts[i + 1];
if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
{
framerate = val;
}
@@ -97,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var rate = part.Split('=', 2)[^1];
if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
{
framerate = val;
}
@@ -107,7 +105,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var time = part.Split('=', 2)[^1];
if (TimeSpan.TryParse(time, _usCulture, out var val))
if (TimeSpan.TryParse(time, CultureInfo.InvariantCulture, out var val))
{
var currentMs = startMs + val.TotalMilliseconds;
@@ -121,7 +119,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var size = part.Split('=', 2)[^1];
int? scale = null;
if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
if (size.Contains("kb", StringComparison.OrdinalIgnoreCase))
{
scale = 1024;
size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase);
@@ -129,7 +127,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (scale.HasValue)
{
if (long.TryParse(size, NumberStyles.Any, _usCulture, out var val))
if (long.TryParse(size, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
{
bytesTranscoded = val * scale.Value;
}
@@ -140,7 +138,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var rate = part.Split('=', 2)[^1];
int? scale = null;
if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
if (rate.Contains("kbits/s", StringComparison.OrdinalIgnoreCase))
{
scale = 1024;
rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase);
@@ -148,7 +146,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (scale.HasValue)
{
if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
{
bitRate = (int)Math.Ceiling(val * scale.Value);
}

View File

@@ -1,5 +1,3 @@
#nullable disable
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
@@ -35,7 +33,7 @@ namespace MediaBrowser.Controller.Providers
/// <summary>
/// Gets the URL format string for this id.
/// </summary>
string UrlFormatString { get; }
string? UrlFormatString { get; }
/// <summary>
/// Determines whether this id supports a given item type.

View File

@@ -1,6 +1,5 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

View File

@@ -10,8 +10,6 @@ using Jellyfin.Data.Entities.Security;
using Jellyfin.Data.Events;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.SyncPlay;
@@ -159,21 +157,21 @@ namespace MediaBrowser.Controller.Session
/// <summary>
/// Sends a SyncPlayCommand to a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="sessionId">The identifier of the session.</param>
/// <param name="command">The command.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task SendSyncPlayCommand(SessionInfo session, SendCommand command, CancellationToken cancellationToken);
Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken);
/// <summary>
/// Sends a SyncPlayGroupUpdate to a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="sessionId">The identifier of the session.</param>
/// <param name="command">The group update.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <typeparam name="T">Type of group.</typeparam>
/// <returns>Task.</returns>
Task SendSyncPlayGroupUpdate<T>(SessionInfo session, GroupUpdate<T> command, CancellationToken cancellationToken);
Task SendSyncPlayGroupUpdate<T>(string sessionId, GroupUpdate<T> command, CancellationToken cancellationToken);
/// <summary>
/// Sends the browse command.

View File

@@ -31,12 +31,14 @@ namespace MediaBrowser.Controller.Subtitles
/// <param name="video">The video.</param>
/// <param name="language">Subtitle language.</param>
/// <param name="isPerfectMatch">Require perfect match.</param>
/// <param name="isAutomated">Request is automated.</param>
/// <param name="cancellationToken">CancellationToken to use for the operation.</param>
/// <returns>Subtitles, wrapped in task.</returns>
Task<RemoteSubtitleInfo[]> SearchSubtitles(
Video video,
string language,
bool? isPerfectMatch,
bool isAutomated,
CancellationToken cancellationToken);
/// <summary>

View File

@@ -51,5 +51,7 @@ namespace MediaBrowser.Controller.Subtitles
public string[] DisabledSubtitleFetchers { get; set; }
public string[] SubtitleFetcherOrder { get; set; }
public bool IsAutomated { get; set; }
}
}

View File

@@ -1,5 +1,6 @@
#nullable disable
using System;
using MediaBrowser.Controller.Session;
namespace MediaBrowser.Controller.SyncPlay
@@ -15,14 +16,28 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
public GroupMember(SessionInfo session)
{
Session = session;
SessionId = session.Id;
UserId = session.UserId;
UserName = session.UserName;
}
/// <summary>
/// Gets the session.
/// Gets the identifier of the session.
/// </summary>
/// <value>The session.</value>
public SessionInfo Session { get; }
/// <value>The session identifier.</value>
public string SessionId { get; }
/// <summary>
/// Gets the identifier of the user.
/// </summary>
/// <value>The user identifier.</value>
public Guid UserId { get; }
/// <summary>
/// Gets the username.
/// </summary>
/// <value>The username.</value>
public string UserName { get; }
/// <summary>
/// Gets or sets the ping, in milliseconds.

View File

@@ -68,7 +68,16 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
/// <inheritdoc />
public virtual void HandleRequest(RemoveFromPlaylistGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
{
var playingItemRemoved = context.RemoveFromPlayQueue(request.PlaylistItemIds);
bool playingItemRemoved;
if (request.ClearPlaylist)
{
context.ClearPlayQueue(request.ClearPlayingItem);
playingItemRemoved = request.ClearPlayingItem;
}
else
{
playingItemRemoved = context.RemoveFromPlayQueue(request.PlaylistItemIds);
}
var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.RemoveItems);
var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);

View File

@@ -162,6 +162,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <returns><c>true</c> if the play queue has been changed; <c>false</c> if something went wrong.</returns>
bool SetPlayingItem(Guid playlistItemId);
/// <summary>
/// Clears the play queue.
/// </summary>
/// <param name="clearPlayingItem">Whether to remove the playing item as well.</param>
void ClearPlayQueue(bool clearPlayingItem);
/// <summary>
/// Removes items from the play queue.
/// </summary>

View File

@@ -17,9 +17,13 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests
/// Initializes a new instance of the <see cref="RemoveFromPlaylistGroupRequest"/> class.
/// </summary>
/// <param name="items">The playlist ids of the items to remove.</param>
public RemoveFromPlaylistGroupRequest(IReadOnlyList<Guid> items)
/// <param name="clearPlaylist">Whether to clear the entire playlist. The items list will be ignored.</param>
/// <param name="clearPlayingItem">Whether to remove the playing item as well. Used only when clearing the playlist.</param>
public RemoveFromPlaylistGroupRequest(IReadOnlyList<Guid> items, bool clearPlaylist = false, bool clearPlayingItem = false)
{
PlaylistItemIds = items ?? Array.Empty<Guid>();
ClearPlaylist = clearPlaylist;
ClearPlayingItem = clearPlayingItem;
}
/// <summary>
@@ -28,6 +32,18 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests
/// <value>The playlist identifiers ot the items.</value>
public IReadOnlyList<Guid> PlaylistItemIds { get; }
/// <summary>
/// Gets a value indicating whether the entire playlist should be cleared.
/// </summary>
/// <value>Whether the entire playlist should be cleared.</value>
public bool ClearPlaylist { get; }
/// <summary>
/// Gets a value indicating whether the playing item should be removed as well.
/// </summary>
/// <value>Whether the playing item should be removed as well.</value>
public bool ClearPlayingItem { get; }
/// <inheritdoc />
public override PlaybackRequestType Action { get; } = PlaybackRequestType.RemoveFromPlaylist;

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Extensions;
using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay.Queue
@@ -19,10 +20,16 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
private const int NoPlayingItemIndex = -1;
/// <summary>
/// Random number generator used to shuffle lists.
/// The sorted playlist.
/// </summary>
/// <value>The random number generator.</value>
private readonly Random _randomNumberGenerator = new Random();
/// <value>The sorted playlist, or play queue of the group.</value>
private List<QueueItem> _sortedPlaylist = new List<QueueItem>();
/// <summary>
/// The shuffled playlist.
/// </summary>
/// <value>The shuffled playlist, or play queue of the group.</value>
private List<QueueItem> _shuffledPlaylist = new List<QueueItem>();
/// <summary>
/// Initializes a new instance of the <see cref="PlayQueueManager" /> class.
@@ -56,18 +63,6 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
/// <value>The repeat mode.</value>
public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone;
/// <summary>
/// Gets or sets the sorted playlist.
/// </summary>
/// <value>The sorted playlist, or play queue of the group.</value>
private List<QueueItem> SortedPlaylist { get; set; } = new List<QueueItem>();
/// <summary>
/// Gets or sets the shuffled playlist.
/// </summary>
/// <value>The shuffled playlist, or play queue of the group.</value>
private List<QueueItem> ShuffledPlaylist { get; set; } = new List<QueueItem>();
/// <summary>
/// Checks if an item is playing.
/// </summary>
@@ -92,14 +87,14 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
/// <param name="items">The new items of the playlist.</param>
public void SetPlaylist(IReadOnlyList<Guid> items)
{
SortedPlaylist.Clear();
ShuffledPlaylist.Clear();
_sortedPlaylist.Clear();
_shuffledPlaylist.Clear();
SortedPlaylist = CreateQueueItemsFromArray(items);
_sortedPlaylist = CreateQueueItemsFromArray(items);
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
ShuffledPlaylist = new List<QueueItem>(SortedPlaylist);
Shuffle(ShuffledPlaylist);
_shuffledPlaylist = new List<QueueItem>(_sortedPlaylist);
_shuffledPlaylist.Shuffle();
}
PlayingItemIndex = NoPlayingItemIndex;
@@ -114,10 +109,10 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
var newItems = CreateQueueItemsFromArray(items);
SortedPlaylist.AddRange(newItems);
_sortedPlaylist.AddRange(newItems);
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
ShuffledPlaylist.AddRange(newItems);
_shuffledPlaylist.AddRange(newItems);
}
LastChange = DateTime.UtcNow;
@@ -130,26 +125,26 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
if (PlayingItemIndex == NoPlayingItemIndex)
{
ShuffledPlaylist = new List<QueueItem>(SortedPlaylist);
Shuffle(ShuffledPlaylist);
_shuffledPlaylist = new List<QueueItem>(_sortedPlaylist);
_shuffledPlaylist.Shuffle();
}
else if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
{
// First time shuffle.
var playingItem = SortedPlaylist[PlayingItemIndex];
ShuffledPlaylist = new List<QueueItem>(SortedPlaylist);
ShuffledPlaylist.RemoveAt(PlayingItemIndex);
Shuffle(ShuffledPlaylist);
ShuffledPlaylist.Insert(0, playingItem);
var playingItem = _sortedPlaylist[PlayingItemIndex];
_shuffledPlaylist = new List<QueueItem>(_sortedPlaylist);
_shuffledPlaylist.RemoveAt(PlayingItemIndex);
_shuffledPlaylist.Shuffle();
_shuffledPlaylist.Insert(0, playingItem);
PlayingItemIndex = 0;
}
else
{
// Re-shuffle playlist.
var playingItem = ShuffledPlaylist[PlayingItemIndex];
ShuffledPlaylist.RemoveAt(PlayingItemIndex);
Shuffle(ShuffledPlaylist);
ShuffledPlaylist.Insert(0, playingItem);
var playingItem = _shuffledPlaylist[PlayingItemIndex];
_shuffledPlaylist.RemoveAt(PlayingItemIndex);
_shuffledPlaylist.Shuffle();
_shuffledPlaylist.Insert(0, playingItem);
PlayingItemIndex = 0;
}
@@ -164,11 +159,11 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
if (PlayingItemIndex != NoPlayingItemIndex)
{
var playingItem = ShuffledPlaylist[PlayingItemIndex];
PlayingItemIndex = SortedPlaylist.IndexOf(playingItem);
var playingItem = _shuffledPlaylist[PlayingItemIndex];
PlayingItemIndex = _sortedPlaylist.IndexOf(playingItem);
}
ShuffledPlaylist.Clear();
_shuffledPlaylist.Clear();
ShuffleMode = GroupShuffleMode.Sorted;
LastChange = DateTime.UtcNow;
@@ -181,16 +176,16 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
public void ClearPlaylist(bool clearPlayingItem)
{
var playingItem = GetPlayingItem();
SortedPlaylist.Clear();
ShuffledPlaylist.Clear();
_sortedPlaylist.Clear();
_shuffledPlaylist.Clear();
LastChange = DateTime.UtcNow;
if (!clearPlayingItem && playingItem != null)
{
SortedPlaylist.Add(playingItem);
_sortedPlaylist.Add(playingItem);
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
ShuffledPlaylist.Add(playingItem);
_shuffledPlaylist.Add(playingItem);
}
PlayingItemIndex = 0;
@@ -212,14 +207,14 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
var playingItem = GetPlayingItem();
var sortedPlayingItemIndex = SortedPlaylist.IndexOf(playingItem);
var sortedPlayingItemIndex = _sortedPlaylist.IndexOf(playingItem);
// Append items to sorted and shuffled playlist as they are.
SortedPlaylist.InsertRange(sortedPlayingItemIndex + 1, newItems);
ShuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
_sortedPlaylist.InsertRange(sortedPlayingItemIndex + 1, newItems);
_shuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
}
else
{
SortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
_sortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
}
LastChange = DateTime.UtcNow;
@@ -298,8 +293,8 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
var playingItem = GetPlayingItem();
SortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
ShuffledPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
_sortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
_shuffledPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
LastChange = DateTime.UtcNow;
@@ -313,7 +308,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
// Was first element, picking next if available.
// Default to no playing item otherwise.
PlayingItemIndex = SortedPlaylist.Count > 0 ? 0 : NoPlayingItemIndex;
PlayingItemIndex = _sortedPlaylist.Count > 0 ? 0 : NoPlayingItemIndex;
}
return true;
@@ -363,8 +358,8 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
/// </summary>
public void Reset()
{
SortedPlaylist.Clear();
ShuffledPlaylist.Clear();
_sortedPlaylist.Clear();
_shuffledPlaylist.Clear();
PlayingItemIndex = NoPlayingItemIndex;
ShuffleMode = GroupShuffleMode.Sorted;
RepeatMode = GroupRepeatMode.RepeatNone;
@@ -460,7 +455,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
PlayingItemIndex++;
if (PlayingItemIndex >= SortedPlaylist.Count)
if (PlayingItemIndex >= _sortedPlaylist.Count)
{
if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
{
@@ -468,7 +463,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
else
{
PlayingItemIndex = SortedPlaylist.Count - 1;
PlayingItemIndex = _sortedPlaylist.Count - 1;
return false;
}
}
@@ -494,7 +489,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
{
PlayingItemIndex = SortedPlaylist.Count - 1;
PlayingItemIndex = _sortedPlaylist.Count - 1;
}
else
{
@@ -507,23 +502,6 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
return true;
}
/// <summary>
/// Shuffles a given list.
/// </summary>
/// <param name="list">The list to shuffle.</param>
private void Shuffle<T>(IList<T> list)
{
int n = list.Count;
while (n > 1)
{
n--;
int k = _randomNumberGenerator.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
/// <summary>
/// Creates a list from the array of items. Each item is given an unique playlist identifier.
/// </summary>
@@ -548,11 +526,11 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
return ShuffledPlaylist;
return _shuffledPlaylist;
}
else
{
return SortedPlaylist;
return _sortedPlaylist;
}
}
@@ -568,11 +546,11 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
else if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
return ShuffledPlaylist[PlayingItemIndex];
return _shuffledPlaylist[PlayingItemIndex];
}
else
{
return SortedPlaylist[PlayingItemIndex];
return _sortedPlaylist[PlayingItemIndex];
}
}
}