diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index bd0a9e1f42..9b2c703940 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -321,6 +321,12 @@ namespace Emby.Dlna.PlayTo AddItemFromId(Guid.Parse(id), items); } + var startIndex = command.StartIndex ?? 0; + if (startIndex > 0) + { + items = items.Skip(startIndex).ToList(); + } + var playlist = new List(); var isFirst = true; diff --git a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj index f2b32d52cb..2b61561cbc 100644 --- a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj +++ b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj @@ -59,9 +59,6 @@ - - - ..\packages\SkiaSharp.1.58.1\lib\portable-net45+win8+wpa81+wp8\SkiaSharp.dll diff --git a/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs b/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs index 48f2da62bc..2417043d60 100644 --- a/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs +++ b/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs @@ -15,7 +15,6 @@ namespace Emby.Drawing.Skia { public class PlayedIndicatorDrawer { - private const int FontSize = 42; private const int OffsetFromTopRightCorner = 38; private readonly IApplicationPaths _appPaths; @@ -44,50 +43,25 @@ namespace Emby.Drawing.Skia { paint.Color = new SKColor(255, 255, 255, 255); paint.Style = SKPaintStyle.Fill; - paint.Typeface = SKTypeface.FromFile(await DownloadFont("webdings.ttf", "https://github.com/MediaBrowser/Emby.Resources/raw/master/fonts/webdings.ttf", - _appPaths, _iHttpClient, _fileSystem).ConfigureAwait(false)); - paint.TextSize = FontSize; + + paint.TextSize = 30; paint.IsAntialias = true; - canvas.DrawText("a", (float)x-20, OffsetFromTopRightCorner + 12, paint); + var text = "✔️"; + var emojiChar = StringUtilities.GetUnicodeCharacterCode(text, SKTextEncoding.Utf32); + // or: + //var emojiChar = 0x1F680; + + // ask the font manager for a font with that character + var fontManager = SKFontManager.Default; + var emojiTypeface = fontManager.MatchCharacter(emojiChar); + + paint.Typeface = emojiTypeface; + + canvas.DrawText(text, (float)x-20, OffsetFromTopRightCorner + 12, paint); } } - internal static string ExtractFont(string name, IApplicationPaths paths, IFileSystem fileSystem) - { - var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name); - - if (fileSystem.FileExists(filePath)) - { - return filePath; - } - - var namespacePath = typeof(PlayedIndicatorDrawer).Namespace + ".fonts." + name; - var tempPath = Path.Combine(paths.TempDirectory, Guid.NewGuid().ToString("N") + ".ttf"); - fileSystem.CreateDirectory(fileSystem.GetDirectoryName(tempPath)); - - using (var stream = typeof(PlayedIndicatorDrawer).GetTypeInfo().Assembly.GetManifestResourceStream(namespacePath)) - { - using (var fileStream = fileSystem.GetFileStream(tempPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) - { - stream.CopyTo(fileStream); - } - } - - fileSystem.CreateDirectory(fileSystem.GetDirectoryName(filePath)); - - try - { - fileSystem.CopyFile(tempPath, filePath, false); - } - catch (IOException) - { - - } - - return tempPath; - } - internal static async Task DownloadFont(string name, string url, IApplicationPaths paths, IHttpClient httpClient, IFileSystem fileSystem) { var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name); diff --git a/Emby.Drawing.Skia/UnplayedCountIndicator.cs b/Emby.Drawing.Skia/UnplayedCountIndicator.cs index 56a2519a3a..b59bc1026a 100644 --- a/Emby.Drawing.Skia/UnplayedCountIndicator.cs +++ b/Emby.Drawing.Skia/UnplayedCountIndicator.cs @@ -40,7 +40,7 @@ namespace Emby.Drawing.Skia { paint.Color = new SKColor(255, 255, 255, 255); paint.Style = SKPaintStyle.Fill; - paint.Typeface = SKTypeface.FromFile(PlayedIndicatorDrawer.ExtractFont("robotoregular.ttf", _appPaths, _fileSystem)); + paint.TextSize = 24; paint.IsAntialias = true; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 745d83eb5f..531e13123a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -2317,12 +2317,17 @@ namespace Emby.Server.Implementations } } - public void LaunchUrl(string url) + public virtual void LaunchUrl(string url) { if (EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows && EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.OSX) { - throw new NotImplementedException(); + throw new NotSupportedException(); + } + + if (!Environment.UserInteractive) + { + throw new NotSupportedException(); } var process = ProcessFactory.Create(new ProcessOptions diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index 05cde91e22..71497f6bf8 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Browser { appHost.LaunchUrl(url); } - catch (NotImplementedException) + catch (NotSupportedException) { } diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 614c04fd22..8d13557952 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -3,6 +3,7 @@ using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; +using MediaBrowser.Controller.Configuration; namespace Emby.Server.Implementations.EntryPoints { @@ -20,15 +21,13 @@ namespace Emby.Server.Implementations.EntryPoints /// private readonly ILogger _logger; - /// - /// Initializes a new instance of the class. - /// - /// The app host. - /// The logger. - public StartupWizard(IServerApplicationHost appHost, ILogger logger) + private IServerConfigurationManager _config; + + public StartupWizard(IServerApplicationHost appHost, ILogger logger, IServerConfigurationManager config) { _appHost = appHost; _logger = logger; + _config = config; } /// @@ -38,16 +37,12 @@ namespace Emby.Server.Implementations.EntryPoints { if (_appHost.IsFirstRun) { - LaunchStartupWizard(); + BrowserLauncher.OpenDashboardPage("wizardstart.html", _appHost); + } + else if (_config.Configuration.IsStartupWizardCompleted) + { + BrowserLauncher.OpenDashboardPage("index.html", _appHost); } - } - - /// - /// Launches the startup wizard. - /// - private void LaunchStartupWizard() - { - BrowserLauncher.OpenDashboardPage("wizardstart.html", _appHost); } /// diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index a0ff294829..3bad69b562 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Globalization; using Emby.Naming.Common; using Emby.Naming.TV; +using MediaBrowser.Model.Logging; namespace Emby.Server.Implementations.Library.Resolvers.TV { @@ -21,16 +22,18 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV private readonly ILibraryManager _libraryManager; private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private readonly ILocalizationManager _localization; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The config. - public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization) + public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, ILogger logger) { _config = config; _libraryManager = libraryManager; _localization = localization; + _logger = logger; } /// @@ -45,20 +48,40 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var series = ((Series)args.Parent); + var path = args.Path; + var season = new Season { - IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber, + IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(path, true, true).SeasonNumber, SeriesId = series.Id, SeriesName = series.Name }; if (season.IndexNumber.HasValue) { + var resolver = new Emby.Naming.TV.EpisodeResolver(namingOptions); + + var episodeInfo = resolver.Resolve(path, true); + + if (episodeInfo != null) + { + if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue) + { + _logger.Info("Found folder underneath series with episode number: {0}. Season {1}. Episode {2}", + path, + episodeInfo.SeasonNumber.Value, + episodeInfo.EpisodeNumber.Value); + + return null; + } + } + var seasonNumber = season.IndexNumber.Value; season.Name = seasonNumber == 0 ? args.LibraryOptions.SeasonZeroDisplayName : string.Format(_localization.GetLocalizedString("NameSeasonNumber"), seasonNumber.ToString(UsCulture), args.GetLibraryOptions().PreferredMetadataLanguage); + } return season; diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 0f48ff46b8..c4e75add89 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -218,7 +218,7 @@ namespace Emby.Server.Implementations.Library return builder.ToString(); } - public async Task AuthenticateUser(string username, string password, string hashedPassword, string passwordMd5, string remoteEndPoint) + public async Task AuthenticateUser(string username, string password, string hashedPassword, string passwordMd5, string remoteEndPoint, bool isUserSession) { if (string.IsNullOrWhiteSpace(username)) { @@ -288,8 +288,11 @@ namespace Emby.Server.Implementations.Library // Update LastActivityDate and LastLoginDate, then save if (success) { - user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - UpdateUser(user); + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + UpdateUser(user); + } UpdateInvalidLoginAttemptCount(user, 0); } else diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 9992c71ecf..a4c5645e74 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1897,7 +1897,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV imageSaveFilenameWithoutExtension = "logo"; break; case ImageType.Thumb: - imageSaveFilenameWithoutExtension = "landscape"; + if (program.IsSeries) + { + imageSaveFilenameWithoutExtension = Path.GetFileNameWithoutExtension(recordingPath) + "-thumb"; + } + else + { + imageSaveFilenameWithoutExtension = "landscape"; + } + break; case ImageType.Backdrop: imageSaveFilenameWithoutExtension = "fanart"; @@ -1921,9 +1929,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private async Task SaveRecordingImages(string recordingPath, LiveTvProgram program) { - var image = program.GetImageInfo(ImageType.Primary, 0); + var image = program.IsSeries ? + (program.GetImageInfo(ImageType.Thumb, 0) ?? program.GetImageInfo(ImageType.Primary, 0)) : + (program.GetImageInfo(ImageType.Primary, 0) ?? program.GetImageInfo(ImageType.Thumb, 0)); - if (image != null && program.IsMovie) + if (image != null) { try { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index c96d1f3592..e6c9b184e5 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -152,6 +152,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts isRemote = !_networkManager.IsInLocalNetwork(uri.Host); } + var supportsDirectPlay = !info.EnableStreamLooping && info.TunerCount == 0; + var mediaSource = new MediaSourceInfo { Path = path, @@ -183,7 +185,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts IsInfiniteStream = true, IsRemote = isRemote, - IgnoreDts = true + IgnoreDts = true, + SupportsDirectPlay = supportsDirectPlay }; mediaSource.InferTotalBitrate(); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index cc2cb3e5ee..b8c7c7b18e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -88,6 +88,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts SetTempFilePath(extension); var taskCompletionSource = new TaskCompletionSource(); + + var now = DateTime.UtcNow; + StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); //OpenedMediaSource.Protocol = MediaProtocol.File; @@ -97,11 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; OpenedMediaSource.Protocol = MediaProtocol.Http; - if (OpenedMediaSource.SupportsProbing) - { - await Task.Delay(3000).ConfigureAwait(false); - } - //OpenedMediaSource.Path = TempFilePath; //OpenedMediaSource.Protocol = MediaProtocol.File; @@ -111,6 +109,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts //OpenedMediaSource.SupportsDirectStream = true; //OpenedMediaSource.SupportsTranscoding = true; await taskCompletionSource.Task.ConfigureAwait(false); + + if (OpenedMediaSource.SupportsProbing) + { + var elapsed = (DateTime.UtcNow - now).TotalMilliseconds; + + var delay = Convert.ToInt32(3000 - elapsed); + + if (delay > 0) + { + Logger.Info("Delaying shared stream by {0}ms to allow the buffer to build.", delay); + + await Task.Delay(delay).ConfigureAwait(false); + } + } } protected override void CloseInternal() diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index e1c1bbe2b8..e852544204 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -117,6 +117,10 @@ namespace Emby.Server.Implementations.Session { dict["SubtitleStreamIndex"] = command.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture); } + if (command.StartIndex.HasValue) + { + dict["StartIndex"] = command.StartIndex.Value.ToString(CultureInfo.InvariantCulture); + } if (!string.IsNullOrWhiteSpace(command.MediaSourceId)) { dict["MediaSourceId"] = command.MediaSourceId; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 30f6e65218..411781b259 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1423,7 +1423,7 @@ namespace Emby.Server.Implementations.Session if (enforcePassword) { - var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false); + var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint, true).ConfigureAwait(false); if (result == null) { diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index ddefb08dff..66d6a024eb 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -473,7 +473,7 @@ namespace MediaBrowser.Api } else { - var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPw, request.CurrentPassword, null, Request.RemoteIp).ConfigureAwait(false); + var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPw, request.CurrentPassword, null, Request.RemoteIp, false).ConfigureAwait(false); if (success == null) { diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 1d09783d1f..ccd0a76369 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -215,23 +215,6 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - [IgnoreDataMember] - public bool ContainsEpisodesWithoutSeasonFolders - { - get - { - var children = Children; - foreach (var child in children) - { - if (child is Video) - { - return true; - } - } - return false; - } - } - public override List GetChildren(User user, bool includeLinkedChildren) { return GetSeasons(user, new DtoOptions(true)); diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index d4232c77e0..03e1d352e2 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -143,7 +143,7 @@ namespace MediaBrowser.Controller.Library /// /// Authenticates the user. /// - Task AuthenticateUser(string username, string password, string passwordSha1, string passwordMd5, string remoteEndPoint); + Task AuthenticateUser(string username, string password, string passwordSha1, string passwordMd5, string remoteEndPoint, bool isUserSession); /// /// Starts the forgot password process. diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs index d50cb59533..3477f2e574 100644 --- a/MediaBrowser.Model/Session/PlayRequest.cs +++ b/MediaBrowser.Model/Session/PlayRequest.cs @@ -37,5 +37,6 @@ namespace MediaBrowser.Model.Session public int? SubtitleStreamIndex { get; set; } public int? AudioStreamIndex { get; set; } public string MediaSourceId { get; set; } + public int? StartIndex { get; set; } } } \ No newline at end of file diff --git a/SharedVersion.cs b/SharedVersion.cs index 580bc70016..99dc764dfd 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.40.0")] +[assembly: AssemblyVersion("3.2.40.1")]