From 22ec62e2941be4fc3b48ec079e1c9e3e8dea59e9 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Thu, 9 May 2019 16:00:10 +0200 Subject: [PATCH 01/28] WIP on screen detection --- Aerial.xcodeproj/project.pbxproj | 14 ++ .../PreferencesWindowController.swift | 3 + Aerial/Source/Models/DisplayDetection.swift | 130 ++++++++++++++++++ Aerial/Source/Views/AerialView.swift | 2 +- Aerial/Source/Views/DisplayView.swift | 62 +++++++++ Resources/Info.plist | 4 +- Resources/PreferencesWindow.xib | 36 ++++- 7 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 Aerial/Source/Models/DisplayDetection.swift create mode 100644 Aerial/Source/Views/DisplayView.swift diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj index fe62e0e0..06ee3947 100644 --- a/Aerial.xcodeproj/project.pbxproj +++ b/Aerial.xcodeproj/project.pbxproj @@ -49,6 +49,11 @@ 0395835621807D1F008E8F9C /* thumbnail.png in Resources */ = {isa = PBXBuildFile; fileRef = 0395835221807D1F008E8F9C /* thumbnail.png */; }; 03A2CB9C216BA9AF0061E8E8 /* VideoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A2CB9B216BA9AF0061E8E8 /* VideoManager.swift */; }; 03A2CB9D216BB1490061E8E8 /* VideoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A2CB9B216BA9AF0061E8E8 /* VideoManager.swift */; }; + 03D1E78722842FB300D10CF7 /* DisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D1E78622842FB300D10CF7 /* DisplayView.swift */; }; + 03D1E7882284367200D10CF7 /* DisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D1E78622842FB300D10CF7 /* DisplayView.swift */; }; + 03D1E78A2284471A00D10CF7 /* DisplayDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D1E7892284471A00D10CF7 /* DisplayDetection.swift */; }; + 03D1E78B22844AFD00D10CF7 /* DisplayDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D1E7892284471A00D10CF7 /* DisplayDetection.swift */; }; + 03D1E78C22844AFE00D10CF7 /* DisplayDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D1E7892284471A00D10CF7 /* DisplayDetection.swift */; }; 03D37FD922145487005A146F /* es.json in Resources */ = {isa = PBXBuildFile; fileRef = 03D37FD722145487005A146F /* es.json */; }; 03D37FDA22145487005A146F /* es.json in Resources */ = {isa = PBXBuildFile; fileRef = 03D37FD722145487005A146F /* es.json */; }; 03D37FDB22145487005A146F /* fr.json in Resources */ = {isa = PBXBuildFile; fileRef = 03D37FD822145487005A146F /* fr.json */; }; @@ -128,6 +133,8 @@ 0395835121807D1F008E8F9C /* thumbnail@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumbnail@2x.png"; sourceTree = ""; }; 0395835221807D1F008E8F9C /* thumbnail.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = thumbnail.png; sourceTree = ""; }; 03A2CB9B216BA9AF0061E8E8 /* VideoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoManager.swift; sourceTree = ""; }; + 03D1E78622842FB300D10CF7 /* DisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DisplayView.swift; path = Aerial/Source/Views/DisplayView.swift; sourceTree = SOURCE_ROOT; }; + 03D1E7892284471A00D10CF7 /* DisplayDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayDetection.swift; sourceTree = ""; }; 03D37FD722145487005A146F /* es.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = es.json; sourceTree = ""; }; 03D37FD822145487005A146F /* fr.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = fr.json; sourceTree = ""; }; 03D3DAC3221F286700BDA52F /* pl.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = pl.json; sourceTree = ""; }; @@ -340,6 +347,7 @@ FAC36F401BE1756D007F2A20 /* AerialVideo.swift */, FAC36F661BE1778C007F2A20 /* ManifestLoader.swift */, 03893CB2217749F0008E7125 /* ErrorLog.swift */, + 03D1E7892284471A00D10CF7 /* DisplayDetection.swift */, ); path = Models; sourceTree = ""; @@ -362,6 +370,7 @@ children = ( FAC36F431BE1756D007F2A20 /* AerialView.swift */, FAC36F441BE1756D007F2A20 /* CheckCellView.swift */, + 03D1E78622842FB300D10CF7 /* DisplayView.swift */, AA7E2E5D1FC62E8B00E5F320 /* AerialPlayerItem.swift */, ); path = Views; @@ -720,9 +729,11 @@ 03233B692172762C0077D3F9 /* PoiStringProvider.swift in Sources */, 03A2CB9D216BB1490061E8E8 /* VideoManager.swift in Sources */, 03E87314216760B7002B469B /* TimeManagement.swift in Sources */, + 03D1E78B22844AFD00D10CF7 /* DisplayDetection.swift in Sources */, 03E8731021662AEB002B469B /* DownloadManager.swift in Sources */, 03E8731121662AEB002B469B /* AsynchronousOperation.swift in Sources */, 03510C7121834FC7008F74F2 /* IOBridge.m in Sources */, + 03D1E7882284367200D10CF7 /* DisplayView.swift in Sources */, 030D9B7C21551A8D00961E95 /* AerialPlayerItem.swift in Sources */, FAC36F5E1BE1756D007F2A20 /* CheckCellView.swift in Sources */, FAC36F5C1BE1756D007F2A20 /* AerialView.swift in Sources */, @@ -744,6 +755,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 03D1E78C22844AFE00D10CF7 /* DisplayDetection.swift in Sources */, 0393857C2175D4B80040B850 /* AVPlayerViewExtension.swift in Sources */, FA7199711D94EC5A00FBC99B /* PreferencesTests.swift in Sources */, ); @@ -753,6 +765,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 03D1E78A2284471A00D10CF7 /* DisplayDetection.swift in Sources */, FAC36F5D1BE1756D007F2A20 /* CheckCellView.swift in Sources */, FAC36F5B1BE1756D007F2A20 /* AerialView.swift in Sources */, 0393857A2175D4B80040B850 /* AVPlayerViewExtension.swift in Sources */, @@ -768,6 +781,7 @@ 03A2CB9C216BA9AF0061E8E8 /* VideoManager.swift in Sources */, FAF450211BE2B45D00C1F98A /* VideoLoader.swift in Sources */, 03E8731321675FE0002B469B /* TimeManagement.swift in Sources */, + 03D1E78722842FB300D10CF7 /* DisplayView.swift in Sources */, FAB22A7E1BE17D7D0065C0F5 /* AssetLoaderDelegate.swift in Sources */, 03958349217F4416008E8F9C /* Solar.swift in Sources */, 03233B68217272640077D3F9 /* PoiStringProvider.swift in Sources */, diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index c407d4ca..1b279b59 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -226,6 +226,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo var locationManager: CLLocationManager? var sparkleUpdater: SUUpdater? + @IBOutlet var displayView: DisplayView! public var appMode: Bool = false private lazy var timeFormatter: DateFormatter = { @@ -308,6 +309,8 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo logTableView.delegate = self logTableView.dataSource = self + + //displayView = new DisplayView() if let version = Bundle(identifier: "com.johncoates.Aerial-Test")?.infoDictionary?["CFBundleShortVersionString"] as? String { versionLabel.stringValue = version diff --git a/Aerial/Source/Models/DisplayDetection.swift b/Aerial/Source/Models/DisplayDetection.swift new file mode 100644 index 00000000..6120e942 --- /dev/null +++ b/Aerial/Source/Models/DisplayDetection.swift @@ -0,0 +1,130 @@ +// +// DisplayDetection.swift +// Aerial +// +// Created by Guillaume Louel on 09/05/2019. +// Copyright © 2019 John Coates. All rights reserved. +// + +import Foundation +import Cocoa + +class Screen { + var id: CGDirectDisplayID + var width: Int + var height: Int + var bottomLeftFrame: CGRect + var topRightCorner: CGPoint + var isMain: Bool + var backingScaleFactor: CGFloat + + init(id: CGDirectDisplayID, width: Int, height: Int, bottomLeftFrame: CGRect, isMain: Bool) { + self.id = id + self.width = width + self.height = height + self.bottomLeftFrame = bottomLeftFrame + // We precalculate the right corner too, as we will need this ! + self.topRightCorner = CGPoint(x: bottomLeftFrame.origin.x + CGFloat(width), + y: bottomLeftFrame.origin.y + CGFloat(height)) + self.isMain = isMain + self.backingScaleFactor = 1 + } + + var description: String { + //swiftlint:disable:next line_length + return "[id=\(self.id), width=\(self.width), height=\(self.height), bottomLeftFrame=\(self.bottomLeftFrame), topRightCorner=\(self.topRightCorner), isMain=\(self.isMain), backingScaleFactor=\(self.backingScaleFactor)]" + } +} + +final class DisplayDetection: NSObject { + static let sharedInstance = DisplayDetection() + + var screens = [Screen]() + + // MARK: - Lifecycle + override init() { + super.init() + debugLog("Display Detection initialized") + _ = detectDisplays() + } + + // MARK: - Detection + func detectDisplays() { + // Display detection is done in two passes : + // - Through CGDisplay, we grab all online screens (connected, but + // may or may not be powered on !) and get most information needed + // - Through NSScreen to get the backingScaleFactor (retinaness of a screen) + + debugLog("***Display Detection***") + // First pass + let maxDisplays: UInt32 = 32 + var onlineDisplays = [CGDirectDisplayID](repeating: 0, count: Int(maxDisplays)) + var displayCount: UInt32 = 0 + + _ = CGGetOnlineDisplayList(maxDisplays, &onlineDisplays, &displayCount) + debugLog("\(displayCount) display(s) detected") + + for currentDisplay in onlineDisplays[0.. Screen? { + for screen in screens where frame == screen.bottomLeftFrame { + return screen + } + + return nil + } + + func findScreenWith(id: CGDirectDisplayID) -> Screen? { + for screen in screens where screen.id == id { + return screen + } + + return nil + } +/* + func getGlobalScreenRect -> CGRect { + var minX, minY, maxX, maxY = 0 + for screen in screens { + if screen.origin.x + } + }*/ + // NSScreen coordinates are with a bottom left origin, whereas CGDisplay + // coordinates are top left origin, this function converts the origin.y value + func convertTopLeftToBottomLeft(rect: CGRect) -> CGRect { + let screenFrame = (NSScreen.main?.frame)! + let newY = 0 - (rect.origin.y - screenFrame.size.height + rect.height) + return CGRect(x: rect.origin.x, y: newY, width: rect.width, height: rect.height) + } + +} diff --git a/Aerial/Source/Views/AerialView.swift b/Aerial/Source/Views/AerialView.swift index bccd7772..549eb6b6 100644 --- a/Aerial/Source/Views/AerialView.swift +++ b/Aerial/Source/Views/AerialView.swift @@ -110,7 +110,7 @@ final class AerialView: ScreenSaverView { // This is the one used by System Preferences override init?(frame: NSRect, isPreview: Bool) { super.init(frame: frame, isPreview: isPreview) - debugLog("avInit1") + debugLog("avInit1 \(frame)") self.animationTimeInterval = 1.0 / 30.0 setup() } diff --git a/Aerial/Source/Views/DisplayView.swift b/Aerial/Source/Views/DisplayView.swift new file mode 100644 index 00000000..26a3e897 --- /dev/null +++ b/Aerial/Source/Views/DisplayView.swift @@ -0,0 +1,62 @@ +// +// DisplayView.swift +// Aerial +// +// Created by Guillaume Louel on 09/05/2019. +// Copyright © 2019 John Coates. All rights reserved. +// + +import Foundation +import Cocoa + +class DisplayView: NSView { + /*override init() { + debugLog("************************DisplayView init") + super.init() + }*/ + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + // We need to handle dark mode + var backgroundColor = NSColor.init(white: 0.9, alpha: 1.0) + var borderColor = NSColor.init(white: 0.8, alpha: 1.0) + + let screenColor = NSColor.init(red: 0.44, green: 0.60, blue: 0.82, alpha: 1.0) + let screenBorderColor = NSColor.black + + let timeManagement = TimeManagement.sharedInstance + if timeManagement.isDarkModeEnabled() { + backgroundColor = NSColor.init(white: 0.2, alpha: 1.0) + borderColor = NSColor.init(white: 0.6, alpha: 1.0) + } + + // Draw background with a 1pt border + borderColor.setFill() + __NSRectFill(dirtyRect) + + let path = NSBezierPath(rect: dirtyRect.insetBy(dx: 1, dy: 1)) + backgroundColor.setFill() + path.fill() + + let displayDetection = DisplayDetection.sharedInstance + + let sRect = NSRect(x: 20, y: 20, width: + 150, height: 50) + let sPath = NSBezierPath(rect: sRect) + screenBorderColor.setFill() + sPath.fill() + + let sInPath = NSBezierPath(rect: sRect.insetBy(dx: 1, dy: 1)) + screenColor.setFill() + sInPath.fill() + } +} diff --git a/Resources/Info.plist b/Resources/Info.plist index 9cf15a28..94920653 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.10beta3 + 1.5.0alpha0 CFBundleSignature ???? CFBundleVersion - 1.4.10beta3 + 1.5.0alpha0 LSApplicationCategoryType LSMinimumSystemVersion diff --git a/Resources/PreferencesWindow.xib b/Resources/PreferencesWindow.xib index 00c7b8b2..36ff8ffd 100644 --- a/Resources/PreferencesWindow.xib +++ b/Resources/PreferencesWindow.xib @@ -44,6 +44,7 @@ + @@ -565,6 +566,32 @@ is disabled + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1523,7 +1550,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required) - + @@ -2278,7 +2305,7 @@ You can enable it when on battery, or only when your battery reaches 20%. - + @@ -470,7 +439,7 @@ is disabled - + @@ -491,7 +460,7 @@ is disabled - + @@ -523,7 +492,7 @@ is disabled + + + + + From 64cc2de42dda28d444239df901ca8b6c250c5a36 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Sat, 11 May 2019 12:26:06 +0200 Subject: [PATCH 09/28] quit confirmation panel --- .../PreferencesWindowController.swift | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index 63f160d0..a034461f 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -210,7 +210,8 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo @IBOutlet var newDisplayModePopup: NSPopUpButton! @IBOutlet var newViewingModePopup: NSPopUpButton! @IBOutlet var displayInstructionLabel: NSTextField! - + @IBOutlet var quitConfirmationPanel: NSPanel! + var player: AVPlayer = AVPlayer() var videos: [AerialVideo]? @@ -660,7 +661,23 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo } @IBAction func close(_ sender: AnyObject?) { - // This seems needed for screensavers as our lifecycle is different from a regular app + // We ask for confirmation in case downloads are ongoing + if !downloadProgressIndicator.isHidden { + quitConfirmationPanel.makeKeyAndOrderFront(self) + } else { + // This seems needed for screensavers as our lifecycle is different from a regular app + preferences.synchronize() + logPanel.close() + if appMode { + NSApplication.shared.terminate(nil) + } else { + window?.sheetParent?.endSheet(window!) + } + } + } + + @IBAction func confirmQuitClick(_ sender: Any) { + quitConfirmationPanel.close() preferences.synchronize() logPanel.close() if appMode { @@ -670,6 +687,10 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo } } + @IBAction func cancelQuitClick(_ sender: Any) { + quitConfirmationPanel.close() + } + // MARK: Video playback // Rewind preview video when reaching end From 492fa29cc2f05a641cd21d05e11c055c537a2890 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Sat, 11 May 2019 12:47:28 +0200 Subject: [PATCH 10/28] Add optional milliseconds to log --- Resources/PreferencesWindow.xib | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/Resources/PreferencesWindow.xib b/Resources/PreferencesWindow.xib index 7850cf32..75d2dc89 100644 --- a/Resources/PreferencesWindow.xib +++ b/Resources/PreferencesWindow.xib @@ -79,6 +79,7 @@ + @@ -161,7 +162,7 @@ - + @@ -1773,12 +1774,12 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required) - + - + @@ -1787,7 +1788,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required) - + @@ -1800,7 +1801,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required) - + @@ -1809,7 +1810,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required) - + @@ -1899,7 +1900,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required) + @@ -2581,7 +2593,7 @@ Unless you want to manually manage your updates, we highly recommand you leave t - + @@ -2595,7 +2607,7 @@ Unless you want to manually manage your updates, we highly recommand you leave t - + From e8a35017dee50d3089143508257d51b1c216539c Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Mon, 13 May 2019 14:00:56 +0200 Subject: [PATCH 11/28] milliseconds, temporarily allow unknown screens --- Aerial.xcodeproj/project.pbxproj | 4 ++-- Aerial/Source/Controllers/Preferences.swift | 11 +++++++++++ .../Controllers/PreferencesWindowController.swift | 15 +++++++++++++-- Aerial/Source/Models/ErrorLog.swift | 6 +++++- Aerial/Source/Views/AerialView.swift | 13 +++++++------ Resources/Info.plist | 4 ++-- 6 files changed, 40 insertions(+), 13 deletions(-) diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj index ebcbb5da..af4e176c 100644 --- a/Aerial.xcodeproj/project.pbxproj +++ b/Aerial.xcodeproj/project.pbxproj @@ -698,7 +698,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if which swiftlint >/dev/null; then\n if [ -z \"$CI\" ]; then\n make --directory=${SRCROOT} xcode-lint\n fi\nelse\n echo \"warning: SwiftLint not installed, install using `brew install swiftlint`\"\nfi\n"; + shellScript = "#if which swiftlint >/dev/null; then\n# if [ -z \"$CI\" ]; then\n# make --directory=${SRCROOT} xcode-lint\n# fi\n#else\n# echo \"warning: SwiftLint not installed, install using `brew install swiftlint`\"\n#fi\nif which swiftlint >/dev/null; then\n swiftlint autocorrect\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; 910394578D35CCEBAEE0D456 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -748,7 +748,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if which swiftlint >/dev/null; then\n if [ -z \"$CI\" ]; then\n make --directory=${SRCROOT} xcode-lint\n fi\nelse\n echo \"warning: SwiftLint not installed, install using `brew install swiftlint`\"\nfi"; + shellScript = "#if which swiftlint >/dev/null; then\n# if [ -z \"$CI\" ]; then\n# make --directory=${SRCROOT} xcode-lint\n# fi\n#else\n# echo \"warning: SwiftLint not installed, install using `brew install swiftlint`\"\n#fi\nif which swiftlint >/dev/null; then\n swiftlint autocorrect\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/Aerial/Source/Controllers/Preferences.swift b/Aerial/Source/Controllers/Preferences.swift index 24dabc68..64cf3654 100644 --- a/Aerial/Source/Controllers/Preferences.swift +++ b/Aerial/Source/Controllers/Preferences.swift @@ -77,6 +77,7 @@ final class Preferences { case newDisplayMode = "newDisplayMode" case newViewingMode = "newViewingMode" case newDisplayDict = "newDisplayDict" + case logMilliseconds = "logMilliseconds" } enum NewDisplayMode: Int { @@ -213,6 +214,7 @@ final class Preferences { defaultValues[.newDisplayMode] = NewDisplayMode.allDisplays defaultValues[.newViewingMode] = NewViewingMode.independent defaultValues[.newDisplayDict] = [String: Bool]() + defaultValues[.logMilliseconds] = false // Set today's date as default let dateFormatter = DateFormatter() @@ -303,6 +305,15 @@ final class Preferences { } } + var logMilliseconds: Bool { + get { + return value(forIdentifier: .logMilliseconds) + } + set { + setValue(forIdentifier: .logMilliseconds, value: newValue) + } + } + var allowBetas: Bool { get { return value(forIdentifier: .allowBetas) diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index a034461f..3b5127e8 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -211,7 +211,8 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo @IBOutlet var newViewingModePopup: NSPopUpButton! @IBOutlet var displayInstructionLabel: NSTextField! @IBOutlet var quitConfirmationPanel: NSPanel! - + + @IBOutlet var logMillisecondsButton: NSButton! var player: AVPlayer = AVPlayer() var videos: [AerialVideo]? @@ -421,13 +422,16 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo rightArrowKeyPlaysNextCheckbox.state = .off } - // Aerial panel + // Advanced panel if preferences.debugMode { debugModeCheckbox.state = .on } if preferences.logToDisk { logToDiskCheckbox.state = .on } + if preferences.logMilliseconds { + logMillisecondsButton.state = .on + } // Text panel if preferences.showClock { @@ -1480,6 +1484,13 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo } // MARK: - Advanced panel + + @IBAction func logMillisecondsClick(_ button: NSButton) { + let onState = button.state == .on + preferences.logMilliseconds = onState + debugLog("UI logMilliseconds: \(onState)") + } + @IBAction func logButtonClick(_ sender: NSButton) { logTableView.reloadData() if logPanel.isVisible { diff --git a/Aerial/Source/Models/ErrorLog.swift b/Aerial/Source/Models/ErrorLog.swift index 1730bb31..b34d288a 100644 --- a/Aerial/Source/Models/ErrorLog.swift +++ b/Aerial/Source/Models/ErrorLog.swift @@ -77,7 +77,11 @@ func Log(level: ErrorLevel, message: String) { if preferences.logToDisk { DispatchQueue.main.async { let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd' 'HH:mm:ss" + if preferences.logMilliseconds { + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" + } else { + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + } let string = dateFormatter.string(from: Date()) + " : " + message + "\n" // tmpOverride diff --git a/Aerial/Source/Views/AerialView.swift b/Aerial/Source/Views/AerialView.swift index 3d682a97..eed49414 100644 --- a/Aerial/Source/Views/AerialView.swift +++ b/Aerial/Source/Views/AerialView.swift @@ -269,9 +269,9 @@ final class AerialView: ScreenSaverView { } } else { // If we don't know this screen, we disable - debugLog("This is an unknown display, disabling") - isDisabled = true - return + //debugLog("This is an unknown display, disabling") + //isDisabled = false + //return } } else { AerialView.previewView = self @@ -356,11 +356,12 @@ final class AerialView: ScreenSaverView { debugLog("\(self.description) setting up player layer with bounds/frame: \(layer.bounds) / \(layer.frame)") playerLayer = AVPlayerLayer(player: player) + if #available(OSX 10.10, *) { playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill } playerLayer.autoresizingMask = [CAAutoresizingMask.layerWidthSizable, CAAutoresizingMask.layerHeightSizable] - + // In case of span mode we need to compute the size of our layer if preferences.newViewingMode == Preferences.NewViewingMode.spanned.rawValue && !isPreview { let zRect = displayDetection.getZeroedActiveSpannedRect() @@ -371,6 +372,7 @@ final class AerialView: ScreenSaverView { width: zRect.width, height: zRect.height) playerLayer.frame = tRect + //playerLayer.bounds = layer.bounds } else { errorLog("This is an unknown screen in span mode, this is not good") playerLayer.frame = layer.bounds @@ -378,7 +380,6 @@ final class AerialView: ScreenSaverView { } else { playerLayer.frame = layer.bounds } - layer.addSublayer(playerLayer) textLayer = CATextLayer() @@ -582,7 +583,7 @@ final class AerialView: ScreenSaverView { debugLog("\(self.description) streaming video (not fully available offline) : \(video.url)") } else { let localurl = URL(fileURLWithPath: VideoCache.cachePath(forVideo: video)!) - let localitem = AVPlayerItem(url: localurl) + var localitem = AVPlayerItem(url: localurl) player.replaceCurrentItem(with: localitem) debugLog("\(self.description) playing video (OFFLINE MODE) : \(localurl)") } diff --git a/Resources/Info.plist b/Resources/Info.plist index 3c5dbb35..66eef456 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.98beta1 + 1.4.99beta2 CFBundleSignature ???? CFBundleVersion - 1.4.98beta1 + 1.4.99beta2 LSApplicationCategoryType LSMinimumSystemVersion From 930038037340342debf706f532dfc3360eb97034 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Mon, 13 May 2019 19:01:03 +0200 Subject: [PATCH 12/28] Add horizontal margin control (simplified) for spanned mode --- Aerial/Source/Controllers/Preferences.swift | 11 +++ .../PreferencesWindowController.swift | 14 ++++ Aerial/Source/Models/DisplayDetection.swift | 38 ++++++++- Aerial/Source/Views/AerialView.swift | 17 ++-- Resources/PreferencesWindow.xib | 84 ++++++++++++++++++- 5 files changed, 152 insertions(+), 12 deletions(-) diff --git a/Aerial/Source/Controllers/Preferences.swift b/Aerial/Source/Controllers/Preferences.swift index 64cf3654..01841ec5 100644 --- a/Aerial/Source/Controllers/Preferences.swift +++ b/Aerial/Source/Controllers/Preferences.swift @@ -78,6 +78,7 @@ final class Preferences { case newViewingMode = "newViewingMode" case newDisplayDict = "newDisplayDict" case logMilliseconds = "logMilliseconds" + case horizontalMargin = "horizontalMargin" } enum NewDisplayMode: Int { @@ -215,6 +216,7 @@ final class Preferences { defaultValues[.newViewingMode] = NewViewingMode.independent defaultValues[.newDisplayDict] = [String: Bool]() defaultValues[.logMilliseconds] = false + defaultValues[.horizontalMargin] = 0 // Set today's date as default let dateFormatter = DateFormatter() @@ -251,6 +253,15 @@ final class Preferences { } } + var horizontalMargin: Double? { + get { + return optionalValue(forIdentifier: .horizontalMargin) + } + set { + setValue(forIdentifier: .horizontalMargin, value: newValue) + } + } + var newDisplayMode: Int? { get { return optionalValue(forIdentifier: .newDisplayMode) diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index 3b5127e8..e7229585 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -213,6 +213,8 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo @IBOutlet var quitConfirmationPanel: NSPanel! @IBOutlet var logMillisecondsButton: NSButton! + @IBOutlet var displayMarginBox: NSBox! + @IBOutlet var horizontalDisplayMarginTextfield: NSTextField! var player: AVPlayer = AVPlayer() var videos: [AerialVideo]? @@ -421,6 +423,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo if !preferences.allowSkips { rightArrowKeyPlaysNextCheckbox.state = .off } + horizontalDisplayMarginTextfield.doubleValue = preferences.horizontalMargin! // Advanced panel if preferences.debugMode { @@ -886,6 +889,17 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo debugLog("UI newViewingModeClick: \(sender.indexOfSelectedItem)") preferences.newViewingMode = sender.indexOfSelectedItem displayView.needsDisplay = true + + if preferences.newViewingMode == Preferences.NewViewingMode.spanned.rawValue { + displayMarginBox.isHidden = false + } else { + displayMarginBox.isHidden = true + } + } + + @IBAction func horizontalDisplayMarginChange(_ sender: NSTextField) { + debugLog("UI horizontalDisplayMarginChange \(sender.stringValue)") + preferences.horizontalMargin = sender.doubleValue } // MARK: - Text panel diff --git a/Aerial/Source/Models/DisplayDetection.swift b/Aerial/Source/Models/DisplayDetection.swift index 8cb06ac7..1cc8795a 100644 --- a/Aerial/Source/Models/DisplayDetection.swift +++ b/Aerial/Source/Models/DisplayDetection.swift @@ -42,6 +42,9 @@ final class DisplayDetection: NSObject { static let sharedInstance = DisplayDetection() var screens = [Screen]() + var cmInPoints: CGFloat = 40 + var maxLeftScreens: CGFloat = 0 + var maxBelowScreens: CGFloat = 0 // MARK: - Lifecycle override init() { @@ -72,8 +75,13 @@ final class DisplayDetection: NSObject { var rect = CGDisplayBounds(currentDisplay) if isMain == 0 { rect = convertTopLeftToBottomLeft(rect: rect) + } else { + // We calculate the equivalent of a centimeter in points on the main screen as a reference + let mmsize = CGDisplayScreenSize(currentDisplay) + let wide = CGDisplayPixelsWide(currentDisplay) + cmInPoints = CGFloat(wide) / CGFloat(mmsize.width) * 10 + debugLog("1cm = \(cmInPoints) points") } - screens.append(Screen(id: currentDisplay, width: CGDisplayPixelsWide(currentDisplay), height: CGDisplayPixelsHigh(currentDisplay), @@ -97,6 +105,7 @@ final class DisplayDetection: NSObject { for screen in screens { debugLog("\(screen)") } + debugLog("\(getGlobalScreenRect())") debugLog("***Display Detection Done***") } @@ -106,12 +115,33 @@ final class DisplayDetection: NSObject { func calculateZeroedOrigins() { let orect = getGlobalScreenRect() + // First we check for the screen relative position and calculate how many screens we have horizontally + // TODO Vertical + H/V mix for screen in screens { - screen.zeroedOrigin = CGPoint(x: screen.bottomLeftFrame.origin.x - orect.origin.x, + debugLog("src orig : \(screen.bottomLeftFrame.origin)") + var leftScreens: CGFloat = 0 + // Very rough, horizontal spans only + for otherScreen in screens { + if otherScreen.bottomLeftFrame.origin.x != screen.bottomLeftFrame.origin.x && + otherScreen.bottomLeftFrame.origin.x < screen.bottomLeftFrame.origin.x { + leftScreens += 1 + } + } + + if leftScreens > maxLeftScreens { + maxLeftScreens = leftScreens + } + + screen.zeroedOrigin = CGPoint(x: screen.bottomLeftFrame.origin.x - orect.origin.x + (leftScreens * leftMargin()), y: screen.bottomLeftFrame.origin.y - orect.origin.y) } } + func leftMargin() -> CGFloat { + let preferences = Preferences.sharedInstance + return cmInPoints * CGFloat(preferences.horizontalMargin!) + } + func findScreenWith(frame: CGRect) -> Screen? { for screen in screens where frame == screen.bottomLeftFrame { return screen @@ -146,7 +176,7 @@ final class DisplayDetection: NSObject { } } - return CGRect(x: minX, y: minY, width: maxX-minX, height: maxY-minY) + return CGRect(x: minX, y: minY, width: maxX-minX+(maxLeftScreens*leftMargin()), height: maxY-minY) } func getZeroedActiveSpannedRect() -> CGRect { @@ -172,7 +202,7 @@ final class DisplayDetection: NSObject { let orect = getGlobalScreenRect() minX -= orect.origin.x minY -= orect.origin.y - return CGRect(x: minX, y: minY, width: width, height: height) + return CGRect(x: minX, y: minY, width: width+(maxLeftScreens*leftMargin()), height: height) } // NSScreen coordinates are with a bottom left origin, whereas CGDisplay diff --git a/Aerial/Source/Views/AerialView.swift b/Aerial/Source/Views/AerialView.swift index eed49414..a931a610 100644 --- a/Aerial/Source/Views/AerialView.swift +++ b/Aerial/Source/Views/AerialView.swift @@ -361,7 +361,7 @@ final class AerialView: ScreenSaverView { playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill } playerLayer.autoresizingMask = [CAAutoresizingMask.layerWidthSizable, CAAutoresizingMask.layerHeightSizable] - + // In case of span mode we need to compute the size of our layer if preferences.newViewingMode == Preferences.NewViewingMode.spanned.rawValue && !isPreview { let zRect = displayDetection.getZeroedActiveSpannedRect() @@ -371,6 +371,7 @@ final class AerialView: ScreenSaverView { y: zRect.origin.y - scr.zeroedOrigin.y, width: zRect.width, height: zRect.height) + debugLog("tRect : \(tRect)") playerLayer.frame = tRect //playerLayer.bounds = layer.bounds } else { @@ -583,7 +584,7 @@ final class AerialView: ScreenSaverView { debugLog("\(self.description) streaming video (not fully available offline) : \(video.url)") } else { let localurl = URL(fileURLWithPath: VideoCache.cachePath(forVideo: video)!) - var localitem = AVPlayerItem(url: localurl) + let localitem = AVPlayerItem(url: localurl) player.replaceCurrentItem(with: localitem) debugLog("\(self.description) playing video (OFFLINE MODE) : \(localurl)") } @@ -631,10 +632,14 @@ final class AerialView: ScreenSaverView { if preferences.allowSkips { if event.keyCode == 124 { - //playNextVideo() - // We need to skip forward all our views - for view in AerialView.instanciatedViews { - view.playNextVideo() + // If we share, just call this on our main view + if AerialView.sharingPlayers { + playNextVideo() + } else { + // If we do independant playback we have to skip all views + for view in AerialView.instanciatedViews { + view.playNextVideo() + } } } else { self.nextResponder!.keyDown(with: event) diff --git a/Resources/PreferencesWindow.xib b/Resources/PreferencesWindow.xib index 75d2dc89..19cbfe11 100644 --- a/Resources/PreferencesWindow.xib +++ b/Resources/PreferencesWindow.xib @@ -45,6 +45,7 @@ + @@ -69,6 +70,7 @@ + @@ -541,7 +543,7 @@ is disabled - + @@ -635,6 +637,84 @@ is disabled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1774,7 +1854,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required) - + From 3afcd6e58452cbae325621d2e773bb879f467b69 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Tue, 14 May 2019 12:22:56 +0200 Subject: [PATCH 13/28] Fix misplaced text in shared player mode on right arrow key, correctly clear old animations Add a 1s fadeout on right arrow key with a lock --- Aerial/Source/Views/AerialView.swift | 58 ++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/Aerial/Source/Views/AerialView.swift b/Aerial/Source/Views/AerialView.swift index a931a610..4c0d0790 100644 --- a/Aerial/Source/Views/AerialView.swift +++ b/Aerial/Source/Views/AerialView.swift @@ -14,7 +14,7 @@ import Sparkle @objc(AerialView) // swiftlint:disable:next type_body_length -final class AerialView: ScreenSaverView { +final class AerialView: ScreenSaverView, CAAnimationDelegate { var playerLayer: AVPlayerLayer! var textLayer: CATextLayer! var clockLayer: CATextLayer! @@ -36,6 +36,8 @@ final class AerialView: ScreenSaverView { var isDisabled = false var timeObserver: Any? + var isQuickFading = false + var brightnessToRestore: Float? static var shouldFade: Bool { @@ -632,14 +634,23 @@ final class AerialView: ScreenSaverView { if preferences.allowSkips { if event.keyCode == 124 { - // If we share, just call this on our main view - if AerialView.sharingPlayers { - playNextVideo() - } else { - // If we do independant playback we have to skip all views - for view in AerialView.instanciatedViews { - view.playNextVideo() + if !isQuickFading { + // If we share, just call this on our main view + if AerialView.sharingPlayers { + // The first view with the player gets the fade and the play next instruction, + // it controls the others + AerialView.sharedViews.first!.fastFadeOut(andPlayNext: true) + for view in AerialView.sharedViews where AerialView.sharedViews.first != view { + view.fastFadeOut(andPlayNext: false) + } + } else { + // If we do independant playback we have to skip all views + for view in AerialView.instanciatedViews { + view.playNextVideo() + } } + } else { + debugLog("Right arrow key currently locked") } } else { self.nextResponder!.keyDown(with: event) @@ -659,6 +670,37 @@ final class AerialView: ScreenSaverView { } // MARK: - Extra Animations + private func fastFadeOut(andPlayNext: Bool) { + // We need to clear the current animations running on playerLayer + isQuickFading = true // Lock the use of keydown + playerLayer.removeAllAnimations() + let fadeOutAnimation = CAKeyframeAnimation(keyPath: "opacity") + fadeOutAnimation.values = [1, 0] as [Int] + fadeOutAnimation.keyTimes = [0, 1] as [NSNumber] + fadeOutAnimation.duration = 1 + fadeOutAnimation.delegate = self + fadeOutAnimation.isRemovedOnCompletion = false + fadeOutAnimation.calculationMode = CAAnimationCalculationMode.cubic + if andPlayNext { + playerLayer.add(fadeOutAnimation, forKey: "quickfadeandnext") + } else { + playerLayer.add(fadeOutAnimation, forKey: "quickfade") + } + } + + // Stop callback for fastFadeOut + func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { + isQuickFading = false // Release our ugly lock + playerLayer.opacity = 0 + if anim == playerLayer.animation(forKey: "quickfadeandnext") { + debugLog("stop and next") + playerLayer.removeAllAnimations() // Make sure we get rid of our anim + playNextVideo() + } else { + debugLog("stop") + playerLayer.removeAllAnimations() // Make sure we get rid of our anim + } + } private func addPlayerFades(player: AVPlayer, playerLayer: AVPlayerLayer, video: AerialVideo) { // We only fade in/out if we have duration From 5e584d8f623a3345300f1c8bb566adb0bd71a1d3 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Tue, 14 May 2019 13:01:26 +0200 Subject: [PATCH 14/28] Fix live update of spanned margins in previews --- Aerial/Source/Controllers/PreferencesWindowController.swift | 6 ++++++ Aerial/Source/Models/DisplayDetection.swift | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index e7229585..86b1efe4 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -888,6 +888,8 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo @IBAction func newViewingModeClick(_ sender: NSPopUpButton) { debugLog("UI newViewingModeClick: \(sender.indexOfSelectedItem)") preferences.newViewingMode = sender.indexOfSelectedItem + let displayDetection = DisplayDetection.sharedInstance + displayDetection.detectDisplays() // Force redetection to update our margin calculations in spanned mode displayView.needsDisplay = true if preferences.newViewingMode == Preferences.NewViewingMode.spanned.rawValue { @@ -900,6 +902,10 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo @IBAction func horizontalDisplayMarginChange(_ sender: NSTextField) { debugLog("UI horizontalDisplayMarginChange \(sender.stringValue)") preferences.horizontalMargin = sender.doubleValue + + let displayDetection = DisplayDetection.sharedInstance + displayDetection.detectDisplays() // Force redetection to update our margin calculations in spanned mode + displayView.needsDisplay = true } // MARK: - Text panel diff --git a/Aerial/Source/Models/DisplayDetection.swift b/Aerial/Source/Models/DisplayDetection.swift index 1cc8795a..a4bce737 100644 --- a/Aerial/Source/Models/DisplayDetection.swift +++ b/Aerial/Source/Models/DisplayDetection.swift @@ -61,6 +61,11 @@ final class DisplayDetection: NSObject { // - Through NSScreen to get the backingScaleFactor (retinaness of a screen) debugLog("***Display Detection***") + // Cleanup a bit in case of redetection + screens = [Screen]() + maxLeftScreens = 0 + maxBelowScreens = 0 + // First pass let maxDisplays: UInt32 = 32 var onlineDisplays = [CGDirectDisplayID](repeating: 0, count: Int(maxDisplays)) From 6a45171a13b7999eb0a783ce6ec2079cbe746e53 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Tue, 14 May 2019 14:00:35 +0200 Subject: [PATCH 15/28] Add vertical margin control for spanned mode --- Aerial/Source/Controllers/Preferences.swift | 11 +++++ .../PreferencesWindowController.swift | 11 +++++ Aerial/Source/Models/DisplayDetection.swift | 48 ++++++++++++++----- Aerial/Source/Views/AerialView.swift | 2 +- Resources/Info.plist | 4 +- Resources/PreferencesWindow.xib | 10 ++-- 6 files changed, 68 insertions(+), 18 deletions(-) diff --git a/Aerial/Source/Controllers/Preferences.swift b/Aerial/Source/Controllers/Preferences.swift index 01841ec5..fc6cbc4a 100644 --- a/Aerial/Source/Controllers/Preferences.swift +++ b/Aerial/Source/Controllers/Preferences.swift @@ -79,6 +79,7 @@ final class Preferences { case newDisplayDict = "newDisplayDict" case logMilliseconds = "logMilliseconds" case horizontalMargin = "horizontalMargin" + case verticalMargin = "verticalMargin" } enum NewDisplayMode: Int { @@ -217,6 +218,7 @@ final class Preferences { defaultValues[.newDisplayDict] = [String: Bool]() defaultValues[.logMilliseconds] = false defaultValues[.horizontalMargin] = 0 + defaultValues[.verticalMargin] = 0 // Set today's date as default let dateFormatter = DateFormatter() @@ -262,6 +264,15 @@ final class Preferences { } } + var verticalMargin: Double? { + get { + return optionalValue(forIdentifier: .verticalMargin) + } + set { + setValue(forIdentifier: .verticalMargin, value: newValue) + } + } + var newDisplayMode: Int? { get { return optionalValue(forIdentifier: .newDisplayMode) diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index 86b1efe4..f5076342 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -215,6 +215,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo @IBOutlet var logMillisecondsButton: NSButton! @IBOutlet var displayMarginBox: NSBox! @IBOutlet var horizontalDisplayMarginTextfield: NSTextField! + @IBOutlet var verticalDisplayMarginTextfield: NSTextField! var player: AVPlayer = AVPlayer() var videos: [AerialVideo]? @@ -424,6 +425,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo rightArrowKeyPlaysNextCheckbox.state = .off } horizontalDisplayMarginTextfield.doubleValue = preferences.horizontalMargin! + verticalDisplayMarginTextfield.doubleValue = preferences.verticalMargin! // Advanced panel if preferences.debugMode { @@ -908,6 +910,15 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo displayView.needsDisplay = true } + @IBAction func verticalDisplayMarginChange(_ sender: NSTextField) { + debugLog("UI verticalDisplayMarginChange \(sender.stringValue)") + preferences.verticalMargin = sender.doubleValue + + let displayDetection = DisplayDetection.sharedInstance + displayDetection.detectDisplays() // Force redetection to update our margin calculations in spanned mode + displayView.needsDisplay = true + } + // MARK: - Text panel // We have a secondary panel for entering margins as a workaround on < Mojave diff --git a/Aerial/Source/Models/DisplayDetection.swift b/Aerial/Source/Models/DisplayDetection.swift index a4bce737..8fbe566a 100644 --- a/Aerial/Source/Models/DisplayDetection.swift +++ b/Aerial/Source/Models/DisplayDetection.swift @@ -121,32 +121,56 @@ final class DisplayDetection: NSObject { let orect = getGlobalScreenRect() // First we check for the screen relative position and calculate how many screens we have horizontally - // TODO Vertical + H/V mix for screen in screens { debugLog("src orig : \(screen.bottomLeftFrame.origin)") - var leftScreens: CGFloat = 0 - // Very rough, horizontal spans only - for otherScreen in screens { - if otherScreen.bottomLeftFrame.origin.x != screen.bottomLeftFrame.origin.x && - otherScreen.bottomLeftFrame.origin.x < screen.bottomLeftFrame.origin.x { - leftScreens += 1 - } - } + + let (leftScreens, belowScreens) = detectBorders(forScreen: screen) if leftScreens > maxLeftScreens { maxLeftScreens = leftScreens } + if belowScreens > maxBelowScreens { + maxBelowScreens = belowScreens + } screen.zeroedOrigin = CGPoint(x: screen.bottomLeftFrame.origin.x - orect.origin.x + (leftScreens * leftMargin()), - y: screen.bottomLeftFrame.origin.y - orect.origin.y) + y: screen.bottomLeftFrame.origin.y - orect.origin.y + (belowScreens * belowMargin())) } } + // Border detection + // This will work for most cases, but will fail in some grid/tetris like arrangements + func detectBorders(forScreen: Screen) -> (CGFloat, CGFloat) { + var leftScreens: CGFloat = 0 + var belowScreens: CGFloat = 0 + + for screen in screens where screen != forScreen { + if screen.bottomLeftFrame.origin.x < forScreen.bottomLeftFrame.origin.x && + screen.bottomLeftFrame.origin.x + CGFloat(screen.width) <= + forScreen.bottomLeftFrame.origin.x { + leftScreens += 1 + } + if screen.bottomLeftFrame.origin.y < forScreen.bottomLeftFrame.origin.y && + screen.bottomLeftFrame.origin.y + CGFloat(screen.height) <= + forScreen.bottomLeftFrame.origin.y { + belowScreens += 1 + } + } + debugLog("left \(leftScreens) below \(belowScreens)") + + return (leftScreens, belowScreens) + } + func leftMargin() -> CGFloat { let preferences = Preferences.sharedInstance return cmInPoints * CGFloat(preferences.horizontalMargin!) } + func belowMargin() -> CGFloat { + let preferences = Preferences.sharedInstance + return cmInPoints * CGFloat(preferences.verticalMargin!) + } + func findScreenWith(frame: CGRect) -> Screen? { for screen in screens where frame == screen.bottomLeftFrame { return screen @@ -181,7 +205,7 @@ final class DisplayDetection: NSObject { } } - return CGRect(x: minX, y: minY, width: maxX-minX+(maxLeftScreens*leftMargin()), height: maxY-minY) + return CGRect(x: minX, y: minY, width: maxX-minX+(maxLeftScreens*leftMargin()), height: maxY-minY+(maxBelowScreens*belowMargin())) } func getZeroedActiveSpannedRect() -> CGRect { @@ -207,7 +231,7 @@ final class DisplayDetection: NSObject { let orect = getGlobalScreenRect() minX -= orect.origin.x minY -= orect.origin.y - return CGRect(x: minX, y: minY, width: width+(maxLeftScreens*leftMargin()), height: height) + return CGRect(x: minX, y: minY, width: width+(maxLeftScreens*leftMargin()), height: height+(maxBelowScreens*belowMargin())) } // NSScreen coordinates are with a bottom left origin, whereas CGDisplay diff --git a/Aerial/Source/Views/AerialView.swift b/Aerial/Source/Views/AerialView.swift index 4c0d0790..75e391c8 100644 --- a/Aerial/Source/Views/AerialView.swift +++ b/Aerial/Source/Views/AerialView.swift @@ -646,7 +646,7 @@ final class AerialView: ScreenSaverView, CAAnimationDelegate { } else { // If we do independant playback we have to skip all views for view in AerialView.instanciatedViews { - view.playNextVideo() + view.fastFadeOut(andPlayNext: true) } } } else { diff --git a/Resources/Info.plist b/Resources/Info.plist index 66eef456..2b8df054 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.99beta2 + 1.4.99beta3 CFBundleSignature ???? CFBundleVersion - 1.4.99beta2 + 1.4.99beta3 LSApplicationCategoryType LSMinimumSystemVersion diff --git a/Resources/PreferencesWindow.xib b/Resources/PreferencesWindow.xib index 19cbfe11..549a792d 100644 --- a/Resources/PreferencesWindow.xib +++ b/Resources/PreferencesWindow.xib @@ -137,6 +137,7 @@ + @@ -687,7 +688,7 @@ is disabled - + @@ -696,17 +697,20 @@ is disabled - + + + + - + From 7ae50cd8655929bb592917e89f2994cb01891ef2 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Tue, 14 May 2019 14:06:13 +0200 Subject: [PATCH 16/28] Right arrow key transition now use the correct video fade time --- Aerial/Source/Views/AerialView.swift | 4 ++-- Resources/PreferencesWindow.xib | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Aerial/Source/Views/AerialView.swift b/Aerial/Source/Views/AerialView.swift index 75e391c8..d88cd3f5 100644 --- a/Aerial/Source/Views/AerialView.swift +++ b/Aerial/Source/Views/AerialView.swift @@ -676,8 +676,8 @@ final class AerialView: ScreenSaverView, CAAnimationDelegate { playerLayer.removeAllAnimations() let fadeOutAnimation = CAKeyframeAnimation(keyPath: "opacity") fadeOutAnimation.values = [1, 0] as [Int] - fadeOutAnimation.keyTimes = [0, 1] as [NSNumber] - fadeOutAnimation.duration = 1 + fadeOutAnimation.keyTimes = [0, AerialView.fadeDuration] as [NSNumber] + fadeOutAnimation.duration = AerialView.fadeDuration fadeOutAnimation.delegate = self fadeOutAnimation.isRemovedOnCompletion = false fadeOutAnimation.calculationMode = CAAnimationCalculationMode.cubic diff --git a/Resources/PreferencesWindow.xib b/Resources/PreferencesWindow.xib index 549a792d..62481fe3 100644 --- a/Resources/PreferencesWindow.xib +++ b/Resources/PreferencesWindow.xib @@ -165,7 +165,7 @@ - + @@ -544,7 +544,7 @@ is disabled - + From b2385b0fcbcf5eb92a82acabccbae88459e77f62 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Tue, 14 May 2019 17:50:17 +0200 Subject: [PATCH 17/28] Add new actions to right click menu, download and move to trash --- .../PreferencesWindowController.swift | 30 +++++++++++++++++-- Aerial/Source/Models/Cache/VideoCache.swift | 16 ++++++++++ Aerial/Source/Models/Cache/VideoManager.swift | 6 ++++ Resources/PreferencesWindow.xib | 30 ++++++++++++++----- 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index f5076342..6f990dce 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -216,6 +216,9 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo @IBOutlet var displayMarginBox: NSBox! @IBOutlet var horizontalDisplayMarginTextfield: NSTextField! @IBOutlet var verticalDisplayMarginTextfield: NSTextField! + @IBOutlet var rightClickOpenQuickTimeMenuItem: NSMenuItem! + @IBOutlet var rightClickDownloadVideoMenuItem: NSMenuItem! + @IBOutlet var rightClickMoveToTrashMenuItem: NSMenuItem! var player: AVPlayer = AVPlayer() var videos: [AerialVideo]? @@ -820,6 +823,23 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo } } + @IBAction func rightClickDownloadVideo(_ sender: NSMenuItem) { + if let video = sender.representedObject as? AerialVideo { + let videoManager = VideoManager.sharedInstance + if !videoManager.isVideoQueued(id: video.id) { + videoManager.queueDownload(video) + } + } + } + + @IBAction func rightClickMoveToTrash(_ sender: NSMenuItem) { + if let video = sender.representedObject as? AerialVideo { + VideoCache.moveToTrash(video: video) + let videoManager = VideoManager.sharedInstance + videoManager.updateAllCheckCellView() + } + } + // MARK: - Mac Model detection and HEVC Main10 detection private func getMacModel() -> String { var size = 0 @@ -1959,7 +1979,6 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo } // MARK: - Outline View Delegate & Data Source - func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { guard let item = item else { return cities.count } @@ -2255,13 +2274,18 @@ extension PreferencesWindowController: NSMenuDelegate { if let video = rowItem as? AerialVideo { if video.isAvailableOffline { + rightClickOpenQuickTimeMenuItem.isHidden = false + rightClickMoveToTrashMenuItem.isHidden = false + rightClickDownloadVideoMenuItem.isHidden = true for item in menu.items { - item.isHidden = false item.representedObject = rowItem } } else { + rightClickOpenQuickTimeMenuItem.isHidden = true + rightClickMoveToTrashMenuItem.isHidden = true + rightClickDownloadVideoMenuItem.isHidden = false for item in menu.items { - item.isHidden = true + item.representedObject = rowItem } } } else { diff --git a/Aerial/Source/Models/Cache/VideoCache.swift b/Aerial/Source/Models/Cache/VideoCache.swift index afd61fbb..d1552c75 100644 --- a/Aerial/Source/Models/Cache/VideoCache.swift +++ b/Aerial/Source/Models/Cache/VideoCache.swift @@ -10,6 +10,7 @@ import Foundation import AVFoundation import ScreenSaver +// swiftlint:disable:next type_body_length final class VideoCache { var videoData: Data var mutableVideoData: NSMutableData? @@ -123,6 +124,21 @@ final class VideoCache { return fileManager.fileExists(atPath: videoCachePath) } + static func moveToTrash(video: AerialVideo) { + guard let videoCachePath = cachePath(forVideo: video) else { + errorLog("Couldn't get video cache path to trash!") + return + } + + let vurl = Foundation.URL(fileURLWithPath: videoCachePath as String) + debugLog("trashing \(vurl))") + do { + try FileManager.default.trashItem(at: vurl, resultingItemURL: nil) + } catch let error { + errorLog("Could not move \(video.url) to trash \(error)") + } + } + static func cachePath(forVideo video: AerialVideo) -> String? { let vurl = video.url let filename = vurl.lastPathComponent diff --git a/Aerial/Source/Models/Cache/VideoManager.swift b/Aerial/Source/Models/Cache/VideoManager.swift index 1ec9ce1b..10c6a114 100644 --- a/Aerial/Source/Models/Cache/VideoManager.swift +++ b/Aerial/Source/Models/Cache/VideoManager.swift @@ -53,6 +53,12 @@ final class VideoManager: NSObject { progressCallbacks.append(callback) } + func updateAllCheckCellView() { + for view in checkCells { + view.value.adaptIndicators() + } + } + // Is the video queued for download ? func isVideoQueued(id: String) -> Bool { if queuedVideos.firstIndex(of: id) != nil { diff --git a/Resources/PreferencesWindow.xib b/Resources/PreferencesWindow.xib index 62481fe3..b8f78c8b 100644 --- a/Resources/PreferencesWindow.xib +++ b/Resources/PreferencesWindow.xib @@ -117,6 +117,9 @@ + + + @@ -165,7 +168,7 @@ - + @@ -1858,7 +1861,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required) - + @@ -2389,7 +2392,7 @@ You can enable it when on battery, or only when your battery reaches 20%. - + @@ -2461,7 +2464,7 @@ You can enable it when on battery, or only when your battery reaches 20%. - + @@ -2506,7 +2509,7 @@ You can enable it when on battery, or only when your battery reaches 20%. - + @@ -2586,11 +2589,24 @@ You can enable it when on battery, or only when your battery reaches 20%. + + + + + + + + + + + + + - + - + From 76a77263a350b62c2ba643082e77ca4cfb8c4f14 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Tue, 21 May 2019 17:31:22 +0200 Subject: [PATCH 18/28] Add some foundation for custom videos Add synchronized mode --- Aerial.xcodeproj/project.pbxproj | 18 ++ .../Controllers/CustomVideoController.swift | 31 +++ Aerial/Source/Controllers/Preferences.swift | 12 ++ .../PreferencesWindowController.swift | 29 ++- Aerial/Source/Models/ManifestLoader.swift | 21 ++- Aerial/Source/Models/SeededGenerator.swift | 33 ++++ Aerial/Source/Views/AerialView.swift | 9 +- Resources/CustomVideos.xib | 177 ++++++++++++++++++ Resources/Info.plist | 4 +- Resources/PreferencesWindow.xib | 41 ++-- 10 files changed, 356 insertions(+), 19 deletions(-) create mode 100644 Aerial/Source/Controllers/CustomVideoController.swift create mode 100644 Aerial/Source/Models/SeededGenerator.swift create mode 100644 Resources/CustomVideos.xib diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj index af4e176c..d28e65b0 100644 --- a/Aerial.xcodeproj/project.pbxproj +++ b/Aerial.xcodeproj/project.pbxproj @@ -8,6 +8,12 @@ /* Begin PBXBuildFile section */ 030D9B7C21551A8D00961E95 /* AerialPlayerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E2E5D1FC62E8B00E5F320 /* AerialPlayerItem.swift */; }; + 0313F9E622942AA500B074BB /* CustomVideos.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0313F9E522942AA500B074BB /* CustomVideos.xib */; }; + 0313F9E822942B4500B074BB /* CustomVideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313F9E722942B4500B074BB /* CustomVideoController.swift */; }; + 0313F9E92294337F00B074BB /* CustomVideos.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0313F9E522942AA500B074BB /* CustomVideos.xib */; }; + 0313F9EA2294338300B074BB /* CustomVideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313F9E722942B4500B074BB /* CustomVideoController.swift */; }; + 0313F9EC2294468600B074BB /* SeededGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313F9EB2294468600B074BB /* SeededGenerator.swift */; }; + 0313F9ED2294468600B074BB /* SeededGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313F9EB2294468600B074BB /* SeededGenerator.swift */; }; 03233B68217272640077D3F9 /* PoiStringProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03233B67217272640077D3F9 /* PoiStringProvider.swift */; }; 03233B692172762C0077D3F9 /* PoiStringProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03233B67217272640077D3F9 /* PoiStringProvider.swift */; }; 033192E1217B78240073B580 /* en.json in Resources */ = {isa = PBXBuildFile; fileRef = 033192E0217B78240073B580 /* en.json */; }; @@ -123,6 +129,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 0313F9E522942AA500B074BB /* CustomVideos.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CustomVideos.xib; sourceTree = ""; }; + 0313F9E722942B4500B074BB /* CustomVideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomVideoController.swift; sourceTree = ""; }; + 0313F9EB2294468600B074BB /* SeededGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeededGenerator.swift; sourceTree = ""; }; 03233B67217272640077D3F9 /* PoiStringProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoiStringProvider.swift; sourceTree = ""; }; 033192E0217B78240073B580 /* en.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = en.json; sourceTree = ""; }; 033D62AA216CADCD00F3AF83 /* icon-day-dark.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "icon-day-dark.pdf"; sourceTree = ""; }; @@ -336,6 +345,7 @@ 0395835221807D1F008E8F9C /* thumbnail.png */, 0395835121807D1F008E8F9C /* thumbnail@2x.png */, FAC36F3A1BE1756D007F2A20 /* PreferencesWindow.xib */, + 0313F9E522942AA500B074BB /* CustomVideos.xib */, ); path = Resources; sourceTree = ""; @@ -355,6 +365,7 @@ isa = PBXGroup; children = ( FAC36F3E1BE1756D007F2A20 /* PreferencesWindowController.swift */, + 0313F9E722942B4500B074BB /* CustomVideoController.swift */, FA6F81DB1D939455007975FE /* Preferences.swift */, ); path = Controllers; @@ -371,6 +382,7 @@ FAC36F661BE1778C007F2A20 /* ManifestLoader.swift */, 03893CB2217749F0008E7125 /* ErrorLog.swift */, 03D1E7892284471A00D10CF7 /* DisplayDetection.swift */, + 0313F9EB2294468600B074BB /* SeededGenerator.swift */, ); path = Models; sourceTree = ""; @@ -569,6 +581,7 @@ 033D62B0216CAE2C00F3AF83 /* icon-night-dark.pdf in Resources */, 0395835621807D1F008E8F9C /* thumbnail.png in Resources */, 035A92AD226754760095AB85 /* ar.json in Resources */, + 0313F9E92294337F00B074BB /* CustomVideos.xib in Resources */, 03D3DAC5221F286D00BDA52F /* pl.json in Resources */, FAC36F481BE1756D007F2A20 /* Assets.xcassets in Resources */, 033D62AC216CADCD00F3AF83 /* icon-day-dark.pdf in Resources */, @@ -618,6 +631,7 @@ 033D62AF216CAE2C00F3AF83 /* icon-night-dark.pdf in Resources */, 0369985D2196103300E359D3 /* missingvideos.json in Resources */, FAC36F4F1BE1756D007F2A20 /* icon-night.pdf in Resources */, + 0313F9E622942AA500B074BB /* CustomVideos.xib in Resources */, 033192E1217B78240073B580 /* en.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -777,7 +791,9 @@ FA36BD401BE57F8E00D5E03B /* VideoDownload.swift in Sources */, FA6F81DD1D939455007975FE /* Preferences.swift in Sources */, FAF450221BE2B45D00C1F98A /* VideoLoader.swift in Sources */, + 0313F9EA2294338300B074BB /* CustomVideoController.swift in Sources */, FAC36F5A1BE1756D007F2A20 /* AerialVideo.swift in Sources */, + 0313F9ED2294468600B074BB /* SeededGenerator.swift in Sources */, 0393857B2175D4B80040B850 /* AVPlayerViewExtension.swift in Sources */, FAF450251BE2D2FD00C1F98A /* VideoCache.swift in Sources */, ); @@ -806,6 +822,7 @@ FA6F81DC1D939455007975FE /* Preferences.swift in Sources */, FAF450241BE2D2FD00C1F98A /* VideoCache.swift in Sources */, FAC36F571BE1756D007F2A20 /* PreferencesWindowController.swift in Sources */, + 0313F9EC2294468600B074BB /* SeededGenerator.swift in Sources */, FAC36F671BE1778C007F2A20 /* ManifestLoader.swift in Sources */, FAC36F591BE1756D007F2A20 /* AerialVideo.swift in Sources */, 03893CB3217749F0008E7125 /* ErrorLog.swift in Sources */, @@ -819,6 +836,7 @@ 03233B68217272640077D3F9 /* PoiStringProvider.swift in Sources */, FA36BD3F1BE57F8E00D5E03B /* VideoDownload.swift in Sources */, 03E8730C2165013C002B469B /* DownloadManager.swift in Sources */, + 0313F9E822942B4500B074BB /* CustomVideoController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Aerial/Source/Controllers/CustomVideoController.swift b/Aerial/Source/Controllers/CustomVideoController.swift new file mode 100644 index 00000000..715a0067 --- /dev/null +++ b/Aerial/Source/Controllers/CustomVideoController.swift @@ -0,0 +1,31 @@ +// +// CustomVideoController.swift +// Aerial +// +// Created by Guillaume Louel on 21/05/2019. +// Copyright © 2019 John Coates. All rights reserved. +// + +import Foundation +import AppKit + +class CustomVideoController: NSWindowController { + @IBOutlet var mainPanel: NSWindow! + required init?(coder: NSCoder) { + super.init(coder: coder) + debugLog("cvcinit") + } + + override init(window: NSWindow?) { + super.init(window: window) + //self.init(windowNibName: NSNib.Name("CustomVideos")) + debugLog("cvcinit2") + } + + func show() { + if !mainPanel.isVisible { + mainPanel.makeKeyAndOrderFront(self) + } + } + +} diff --git a/Aerial/Source/Controllers/Preferences.swift b/Aerial/Source/Controllers/Preferences.swift index fc6cbc4a..160d561c 100644 --- a/Aerial/Source/Controllers/Preferences.swift +++ b/Aerial/Source/Controllers/Preferences.swift @@ -80,6 +80,8 @@ final class Preferences { case logMilliseconds = "logMilliseconds" case horizontalMargin = "horizontalMargin" case verticalMargin = "verticalMargin" + + case synchronizedMode = "synchronizedMode" } enum NewDisplayMode: Int { @@ -219,6 +221,7 @@ final class Preferences { defaultValues[.logMilliseconds] = false defaultValues[.horizontalMargin] = 0 defaultValues[.verticalMargin] = 0 + defaultValues[.synchronizedMode] = false // Set today's date as default let dateFormatter = DateFormatter() @@ -327,6 +330,15 @@ final class Preferences { } } + var synchronizedMode: Bool { + get { + return value(forIdentifier: .synchronizedMode) + } + set { + setValue(forIdentifier: .synchronizedMode, value: newValue) + } + } + var logMilliseconds: Bool { get { return value(forIdentifier: .logMilliseconds) diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index 6f990dce..c7a297bb 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -50,6 +50,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo enum HEVCMain10Support: Int { case notsupported, unsure, partial, supported } + lazy var customVideosController: CustomVideoController = CustomVideoController() @IBOutlet weak var prefTabView: NSTabView! @IBOutlet var outlineView: NSOutlineView! @@ -219,6 +220,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo @IBOutlet var rightClickOpenQuickTimeMenuItem: NSMenuItem! @IBOutlet var rightClickDownloadVideoMenuItem: NSMenuItem! @IBOutlet var rightClickMoveToTrashMenuItem: NSMenuItem! + @IBOutlet var synchronizedModeCheckbox: NSButton! var player: AVPlayer = AVPlayer() var videos: [AerialVideo]? @@ -502,6 +504,9 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo if preferences.dimOnlyAtNight { dimOnlyAtNight.state = .on } + if preferences.synchronizedMode { + synchronizedModeCheckbox.state = .on + } dimStartFrom.doubleValue = preferences.startDim ?? 0.5 dimFadeTo.doubleValue = preferences.endDim ?? 0.1 dimFadeInMinutes.stringValue = String(preferences.dimInMinutes!) @@ -661,6 +666,13 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo longitudeTextField.isEnabled = false } + // We also load our CustomVideos nib + let bundle = Bundle.main + var topLevelObjects: NSArray? = NSArray() + bundle.loadNibNamed(NSNib.Name("CustomVideos"), + owner: customVideosController, + topLevelObjects: &topLevelObjects) + debugLog("appMode : \(appMode)") } @@ -743,6 +755,12 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo debugLog("UI allowSkips \(onState)") } + @IBAction func synchronizedModeClick(_ sender: NSButton) { + let onState = sender.state == .on + preferences.synchronizedMode = onState + debugLog("UI synchronizedMode \(onState)") + } + @IBAction func overrideOnBatteryClick(_ sender: NSButton) { let onState = sender.state == .on preferences.overrideOnBattery = onState @@ -1680,11 +1698,20 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo action: #selector(PreferencesWindowController.outlineViewDownloadAll(button:)), keyEquivalent: "", at: 6) - + /*menu.insertItem(NSMenuItem.separator(), at: 7) + menu.insertItem(withTitle: "Custom Videos...", + action: #selector(PreferencesWindowController.outlineViewCustomVideos(button:)), + keyEquivalent: "", + at: 8) +*/ let event = NSApp.currentEvent NSMenu.popUpContextMenu(menu, with: event!, for: button) } + @objc func outlineViewCustomVideos(button: NSButton) { + customVideosController.show() + } + @objc func outlineViewUncheckAll(button: NSButton) { setAllVideos(inRotation: false) } diff --git a/Aerial/Source/Models/ManifestLoader.swift b/Aerial/Source/Models/ManifestLoader.swift index 68f382fe..63ca1562 100644 --- a/Aerial/Source/Models/ManifestLoader.swift +++ b/Aerial/Source/Models/ManifestLoader.swift @@ -8,6 +8,7 @@ import Foundation import ScreenSaver +import GameplayKit typealias ManifestLoadCallback = ([AerialVideo]) -> Void @@ -149,7 +150,25 @@ class ManifestLoader { playlistRestrictedTo = restrictedTo // Start with a shuffled list - let shuffled = loadedManifest.shuffled() + //let shuffled = loadedManifest.shuffled() + var shuffled: [AerialVideo] + let preferences = Preferences.sharedInstance + if preferences.synchronizedMode { + if #available(OSX 10.11, *) { + let date = Date() + let calendar = NSCalendar.current + let minutes = calendar.component(.minute, from: date) + debugLog("seed : \(minutes)") + + var generator = SeededGenerator(seed: UInt64(minutes)) + shuffled = loadedManifest.shuffled(using: &generator) + } else { + // Fallback on earlier versions + shuffled = loadedManifest.shuffled() + } + } else { + shuffled = loadedManifest.shuffled() + } for video in shuffled { // We exclude videos not in rotation diff --git a/Aerial/Source/Models/SeededGenerator.swift b/Aerial/Source/Models/SeededGenerator.swift new file mode 100644 index 00000000..d350667a --- /dev/null +++ b/Aerial/Source/Models/SeededGenerator.swift @@ -0,0 +1,33 @@ +// +// SeededGenerator.swift +// Aerial +// +// Created by Guillaume Louel on 21/05/2019. +// Copyright © 2019 John Coates. All rights reserved. +// + +import Foundation +import GameplayKit + +@available(OSX 10.11, *) +class SeededGenerator: RandomNumberGenerator { + let seed: UInt64 + private let generator: GKMersenneTwisterRandomSource + + convenience init() { + self.init(seed: 0) + } + + init(seed: UInt64) { + self.seed = seed + generator = GKMersenneTwisterRandomSource(seed: seed) + } + + func next(upperBound: T) -> T where T: FixedWidthInteger, T: UnsignedInteger { + return T(abs(generator.nextInt(upperBound: Int(upperBound)))) + } + + func next() -> T where T: FixedWidthInteger, T: UnsignedInteger { + return T(abs(generator.nextInt())) + } +} diff --git a/Aerial/Source/Views/AerialView.swift b/Aerial/Source/Views/AerialView.swift index d88cd3f5..08eb4edd 100644 --- a/Aerial/Source/Views/AerialView.swift +++ b/Aerial/Source/Views/AerialView.swift @@ -504,7 +504,12 @@ final class AerialView: ScreenSaverView, CAAnimationDelegate { of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { debugLog("\(self.description) observeValue \(String(describing: keyPath))") - if self.playerLayer.isReadyForDisplay { + + if keyPath == "rate" { + if self.player!.rate > 0 { + debugLog("video started playing") + } + } else if self.playerLayer.isReadyForDisplay { self.player!.play() hasStartedPlaying = true @@ -544,6 +549,8 @@ final class AerialView: ScreenSaverView, CAAnimationDelegate { // play another video let oldPlayer = self.player self.player = player + player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil) + self.playerLayer.player = self.player if AerialView.shouldFade { self.playerLayer.opacity = 0 diff --git a/Resources/CustomVideos.xib b/Resources/CustomVideos.xib new file mode 100644 index 00000000..2e93f81e --- /dev/null +++ b/Resources/CustomVideos.xib @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/Info.plist b/Resources/Info.plist index 2b8df054..5356c011 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.99beta3 + 1.4.99beta4 CFBundleSignature ???? CFBundleVersion - 1.4.99beta3 + 1.4.99beta4 LSApplicationCategoryType LSMinimumSystemVersion diff --git a/Resources/PreferencesWindow.xib b/Resources/PreferencesWindow.xib index b8f78c8b..4ce99a70 100644 --- a/Resources/PreferencesWindow.xib +++ b/Resources/PreferencesWindow.xib @@ -132,6 +132,7 @@ + @@ -168,7 +169,7 @@ - + @@ -368,17 +369,6 @@ - @@ -1861,7 +1874,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required) - + @@ -2176,7 +2189,7 @@ Gw - + From a7a6d37b8c4c3d9281a22066381ce2ad047759c5 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Wed, 22 May 2019 13:37:24 +0200 Subject: [PATCH 19/28] WIP custom videos --- .swiftlint.yml | 1 + Aerial.xcodeproj/project.pbxproj | 6 + Aerial/App/Resources/Info.plist | 2 +- .../Controllers/CustomVideoController.swift | 1 + .../PreferencesWindowController.swift | 4 +- Aerial/Source/Models/CustomVideoFolders.swift | 196 ++++++++++++++++++ Aerial/Source/Models/ManifestLoader.swift | 13 ++ Resources/CustomVideos.xib | 58 ++---- Resources/Info.plist | 4 +- 9 files changed, 234 insertions(+), 51 deletions(-) create mode 100644 Aerial/Source/Models/CustomVideoFolders.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index 1f5427fa..aef19e03 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -14,6 +14,7 @@ disabled_rules: - function_body_length # Allow declaring operators without extra whitespace, like so: `func ==(_ lhs, ...)` - operator_whitespace + - redundant_string_enum_value opt_in_rules: # Prefer checking `isEmpty` over `count > 0` diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj index d28e65b0..a5f22175 100644 --- a/Aerial.xcodeproj/project.pbxproj +++ b/Aerial.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 0313F9EA2294338300B074BB /* CustomVideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313F9E722942B4500B074BB /* CustomVideoController.swift */; }; 0313F9EC2294468600B074BB /* SeededGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313F9EB2294468600B074BB /* SeededGenerator.swift */; }; 0313F9ED2294468600B074BB /* SeededGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313F9EB2294468600B074BB /* SeededGenerator.swift */; }; + 0313F9EF22955F3B00B074BB /* CustomVideoFolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313F9EE22955F3B00B074BB /* CustomVideoFolders.swift */; }; + 0313F9F022955F3B00B074BB /* CustomVideoFolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313F9EE22955F3B00B074BB /* CustomVideoFolders.swift */; }; 03233B68217272640077D3F9 /* PoiStringProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03233B67217272640077D3F9 /* PoiStringProvider.swift */; }; 03233B692172762C0077D3F9 /* PoiStringProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03233B67217272640077D3F9 /* PoiStringProvider.swift */; }; 033192E1217B78240073B580 /* en.json in Resources */ = {isa = PBXBuildFile; fileRef = 033192E0217B78240073B580 /* en.json */; }; @@ -132,6 +134,7 @@ 0313F9E522942AA500B074BB /* CustomVideos.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CustomVideos.xib; sourceTree = ""; }; 0313F9E722942B4500B074BB /* CustomVideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomVideoController.swift; sourceTree = ""; }; 0313F9EB2294468600B074BB /* SeededGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeededGenerator.swift; sourceTree = ""; }; + 0313F9EE22955F3B00B074BB /* CustomVideoFolders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomVideoFolders.swift; sourceTree = ""; }; 03233B67217272640077D3F9 /* PoiStringProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoiStringProvider.swift; sourceTree = ""; }; 033192E0217B78240073B580 /* en.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = en.json; sourceTree = ""; }; 033D62AA216CADCD00F3AF83 /* icon-day-dark.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "icon-day-dark.pdf"; sourceTree = ""; }; @@ -383,6 +386,7 @@ 03893CB2217749F0008E7125 /* ErrorLog.swift */, 03D1E7892284471A00D10CF7 /* DisplayDetection.swift */, 0313F9EB2294468600B074BB /* SeededGenerator.swift */, + 0313F9EE22955F3B00B074BB /* CustomVideoFolders.swift */, ); path = Models; sourceTree = ""; @@ -789,6 +793,7 @@ FAB22A7F1BE17D7D0065C0F5 /* AssetLoaderDelegate.swift in Sources */, FAC36F601BE175CF007F2A20 /* AppDelegate.swift in Sources */, FA36BD401BE57F8E00D5E03B /* VideoDownload.swift in Sources */, + 0313F9F022955F3B00B074BB /* CustomVideoFolders.swift in Sources */, FA6F81DD1D939455007975FE /* Preferences.swift in Sources */, FAF450221BE2B45D00C1F98A /* VideoLoader.swift in Sources */, 0313F9EA2294338300B074BB /* CustomVideoController.swift in Sources */, @@ -819,6 +824,7 @@ 0393857A2175D4B80040B850 /* AVPlayerViewExtension.swift in Sources */, 03E8730F216501ED002B469B /* AsynchronousOperation.swift in Sources */, 03510C6F21834F38008F74F2 /* IOBridge.m in Sources */, + 0313F9EF22955F3B00B074BB /* CustomVideoFolders.swift in Sources */, FA6F81DC1D939455007975FE /* Preferences.swift in Sources */, FAF450241BE2D2FD00C1F98A /* VideoCache.swift in Sources */, FAC36F571BE1756D007F2A20 /* PreferencesWindowController.swift in Sources */, diff --git a/Aerial/App/Resources/Info.plist b/Aerial/App/Resources/Info.plist index b6ebb6f6..67b3f049 100644 --- a/Aerial/App/Resources/Info.plist +++ b/Aerial/App/Resources/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.7beta6 + 1.5.0 CFBundleSignature ???? CFBundleVersion diff --git a/Aerial/Source/Controllers/CustomVideoController.swift b/Aerial/Source/Controllers/CustomVideoController.swift index 715a0067..f8aa193e 100644 --- a/Aerial/Source/Controllers/CustomVideoController.swift +++ b/Aerial/Source/Controllers/CustomVideoController.swift @@ -11,6 +11,7 @@ import AppKit class CustomVideoController: NSWindowController { @IBOutlet var mainPanel: NSWindow! + @IBOutlet var folderOutlineView: NSOutlineView! required init?(coder: NSCoder) { super.init(coder: coder) debugLog("cvcinit") diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index c7a297bb..62ec11fc 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -1698,12 +1698,12 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo action: #selector(PreferencesWindowController.outlineViewDownloadAll(button:)), keyEquivalent: "", at: 6) - /*menu.insertItem(NSMenuItem.separator(), at: 7) + menu.insertItem(NSMenuItem.separator(), at: 7) menu.insertItem(withTitle: "Custom Videos...", action: #selector(PreferencesWindowController.outlineViewCustomVideos(button:)), keyEquivalent: "", at: 8) -*/ + let event = NSApp.currentEvent NSMenu.popUpContextMenu(menu, with: event!, for: button) } diff --git a/Aerial/Source/Models/CustomVideoFolders.swift b/Aerial/Source/Models/CustomVideoFolders.swift new file mode 100644 index 00000000..6760a9ac --- /dev/null +++ b/Aerial/Source/Models/CustomVideoFolders.swift @@ -0,0 +1,196 @@ +// This file was generated from JSON Schema using quicktype, do not modify it directly. +// To parse the JSON, add this file to your project and do: +// +// let customVideoFolders = try CustomVideoFolders(json) + +import Foundation + +// MARK: - CustomVideoFolders +class CustomVideoFolders: Codable { + let folders: [Folder] + + enum CodingKeys: String, CodingKey { + case folders = "folders" + } + + init(folders: [Folder]) { + self.folders = folders + } +} + +// MARK: CustomVideoFolders convenience initializers and mutators + +extension CustomVideoFolders { + convenience init(data: Data) throws { + let me = try newJSONDecoder().decode(CustomVideoFolders.self, from: data) + self.init(folders: me.folders) + } + + convenience init(_ json: String, using encoding: String.Encoding = .utf8) throws { + guard let data = json.data(using: encoding) else { + throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil) + } + try self.init(data: data) + } + + convenience init(fromURL url: URL) throws { + try self.init(data: try Data(contentsOf: url)) + } + + func with( + folders: [Folder]? = nil + ) -> CustomVideoFolders { + return CustomVideoFolders( + folders: folders ?? self.folders + ) + } + + func jsonData() throws -> Data { + return try newJSONEncoder().encode(self) + } + + func jsonString(encoding: String.Encoding = .utf8) throws -> String? { + return String(data: try self.jsonData(), encoding: encoding) + } +} + +// MARK: - Folder +class Folder: Codable { + let url: String + let assets: [Asset] + + enum CodingKeys: String, CodingKey { + case url = "url" + case assets = "assets" + } + + init(url: String, assets: [Asset]) { + self.url = url + self.assets = assets + } +} + +// MARK: Folder convenience initializers and mutators + +extension Folder { + convenience init(data: Data) throws { + let me = try newJSONDecoder().decode(Folder.self, from: data) + self.init(url: me.url, assets: me.assets) + } + + convenience init(_ json: String, using encoding: String.Encoding = .utf8) throws { + guard let data = json.data(using: encoding) else { + throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil) + } + try self.init(data: data) + } + + convenience init(fromURL url: URL) throws { + try self.init(data: try Data(contentsOf: url)) + } + + func with( + url: String? = nil, + assets: [Asset]? = nil + ) -> Folder { + return Folder( + url: url ?? self.url, + assets: assets ?? self.assets + ) + } + + func jsonData() throws -> Data { + return try newJSONEncoder().encode(self) + } + + func jsonString(encoding: String.Encoding = .utf8) throws -> String? { + return String(data: try self.jsonData(), encoding: encoding) + } +} + +// MARK: - Asset +class Asset: Codable { + let pointsOfInterest: [String: String] + let filename: String + let accessibilityLabel: String + let id: String + let time: String + + enum CodingKeys: String, CodingKey { + case pointsOfInterest = "pointsOfInterest" + case filename = "filename" + case accessibilityLabel = "accessibilityLabel" + case id = "id" + case time = "time" + } + + init(pointsOfInterest: [String: String], filename: String, accessibilityLabel: String, id: String, time: String) { + self.pointsOfInterest = pointsOfInterest + self.filename = filename + self.accessibilityLabel = accessibilityLabel + self.id = id + self.time = time + } +} + +// MARK: Asset convenience initializers and mutators + +extension Asset { + convenience init(data: Data) throws { + let me = try newJSONDecoder().decode(Asset.self, from: data) + self.init(pointsOfInterest: me.pointsOfInterest, filename: me.filename, accessibilityLabel: me.accessibilityLabel, id: me.id, time: me.time) + } + + convenience init(_ json: String, using encoding: String.Encoding = .utf8) throws { + guard let data = json.data(using: encoding) else { + throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil) + } + try self.init(data: data) + } + + convenience init(fromURL url: URL) throws { + try self.init(data: try Data(contentsOf: url)) + } + + func with( + pointsOfInterest: [String: String]? = nil, + filename: String? = nil, + accessibilityLabel: String? = nil, + id: String? = nil, + time: String? = nil + ) -> Asset { + return Asset( + pointsOfInterest: pointsOfInterest ?? self.pointsOfInterest, + filename: filename ?? self.filename, + accessibilityLabel: accessibilityLabel ?? self.accessibilityLabel, + id: id ?? self.id, + time: time ?? self.time + ) + } + + func jsonData() throws -> Data { + return try newJSONEncoder().encode(self) + } + + func jsonString(encoding: String.Encoding = .utf8) throws -> String? { + return String(data: try self.jsonData(), encoding: encoding) + } +} + +// MARK: - Helper functions for creating encoders and decoders + +func newJSONDecoder() -> JSONDecoder { + let decoder = JSONDecoder() + if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { + decoder.dateDecodingStrategy = .iso8601 + } + return decoder +} + +func newJSONEncoder() -> JSONEncoder { + let encoder = JSONEncoder() + if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { + encoder.dateEncodingStrategy = .iso8601 + } + return encoder +} diff --git a/Aerial/Source/Models/ManifestLoader.swift b/Aerial/Source/Models/ManifestLoader.swift index 63ca1562..8fad8ca2 100644 --- a/Aerial/Source/Models/ManifestLoader.swift +++ b/Aerial/Source/Models/ManifestLoader.swift @@ -21,6 +21,7 @@ class ManifestLoader { var loadedManifest = [AerialVideo]() var processedVideos = [AerialVideo]() var lastPluckedFromPlaylist: AerialVideo? + var customVideoFolders: CustomVideoFolders? var manifestTvOS10: Data? var manifestTvOS11: Data? @@ -268,6 +269,18 @@ class ManifestLoader { init() { debugLog("Manifest init") + + do { + if let cacheDirectory = VideoCache.cacheDirectory { + // tvOS12 + var cacheFileUrl = URL(fileURLWithPath: cacheDirectory as String) + cacheFileUrl.appendPathComponent("testmodel.json") + debugLog("custom file : \(cacheFileUrl)") + customVideoFolders = try CustomVideoFolders(cacheFileUrl.absoluteString) + } + } catch { + debugLog("No testmodel.json \(error)") + } // We try to load our video manifests in 3 steps : // - reload from local variables (unused for now, maybe with previews+screensaver // in some weird edge case on some systems) diff --git a/Resources/CustomVideos.xib b/Resources/CustomVideos.xib index 2e93f81e..a33f8b98 100644 --- a/Resources/CustomVideos.xib +++ b/Resources/CustomVideos.xib @@ -8,6 +8,7 @@ + @@ -26,17 +27,17 @@ - + - - + + - + @@ -49,7 +50,7 @@ - + @@ -69,39 +70,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -110,14 +78,10 @@ - @@ -147,12 +111,14 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -108,6 +381,9 @@ + + + @@ -135,9 +411,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 310c19ad4b368f73cbf27dbb1eadd57c970ee446 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Fri, 24 May 2019 17:06:46 +0200 Subject: [PATCH 22/28] beta 6 includes initial release of custom videos ! --- Aerial.xcodeproj/project.pbxproj | 6 ++ .../Controllers/CustomVideoController.swift | 70 ++++++++++++++++--- .../PreferencesWindowController.swift | 12 ++-- .../Models/CustomVideoFolders+helpers.swift | 47 +++++++++++++ Resources/CustomVideos.xib | 27 ++----- 5 files changed, 129 insertions(+), 33 deletions(-) create mode 100644 Aerial/Source/Models/CustomVideoFolders+helpers.swift diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj index 065e9e13..a6ea69ea 100644 --- a/Aerial.xcodeproj/project.pbxproj +++ b/Aerial.xcodeproj/project.pbxproj @@ -56,6 +56,8 @@ 0395835621807D1F008E8F9C /* thumbnail.png in Resources */ = {isa = PBXBuildFile; fileRef = 0395835221807D1F008E8F9C /* thumbnail.png */; }; 03A2CB9C216BA9AF0061E8E8 /* VideoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A2CB9B216BA9AF0061E8E8 /* VideoManager.swift */; }; 03A2CB9D216BB1490061E8E8 /* VideoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A2CB9B216BA9AF0061E8E8 /* VideoManager.swift */; }; + 03AD45FF22981B0C00261325 /* CustomVideoFolders+helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AD45FE22981B0C00261325 /* CustomVideoFolders+helpers.swift */; }; + 03AD460022981B1C00261325 /* CustomVideoFolders+helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AD45FE22981B0C00261325 /* CustomVideoFolders+helpers.swift */; }; 03D1E78722842FB300D10CF7 /* DisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D1E78622842FB300D10CF7 /* DisplayView.swift */; }; 03D1E7882284367200D10CF7 /* DisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D1E78622842FB300D10CF7 /* DisplayView.swift */; }; 03D1E78A2284471A00D10CF7 /* DisplayDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D1E7892284471A00D10CF7 /* DisplayDetection.swift */; }; @@ -152,6 +154,7 @@ 0395835121807D1F008E8F9C /* thumbnail@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumbnail@2x.png"; sourceTree = ""; }; 0395835221807D1F008E8F9C /* thumbnail.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = thumbnail.png; sourceTree = ""; }; 03A2CB9B216BA9AF0061E8E8 /* VideoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoManager.swift; sourceTree = ""; }; + 03AD45FE22981B0C00261325 /* CustomVideoFolders+helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomVideoFolders+helpers.swift"; sourceTree = ""; }; 03D1E78622842FB300D10CF7 /* DisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DisplayView.swift; path = Aerial/Source/Views/DisplayView.swift; sourceTree = SOURCE_ROOT; }; 03D1E7892284471A00D10CF7 /* DisplayDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayDetection.swift; sourceTree = ""; }; 03D1E78E22848F7F00D10CF7 /* screen2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = screen2.jpg; sourceTree = ""; }; @@ -385,6 +388,7 @@ 03D1E7892284471A00D10CF7 /* DisplayDetection.swift */, 0313F9EB2294468600B074BB /* SeededGenerator.swift */, 0313F9EE22955F3B00B074BB /* CustomVideoFolders.swift */, + 03AD45FE22981B0C00261325 /* CustomVideoFolders+helpers.swift */, ); path = Models; sourceTree = ""; @@ -790,6 +794,7 @@ 0395834A217F442A008E8F9C /* Solar.swift in Sources */, FAB22A7F1BE17D7D0065C0F5 /* AssetLoaderDelegate.swift in Sources */, FAC36F601BE175CF007F2A20 /* AppDelegate.swift in Sources */, + 03AD460022981B1C00261325 /* CustomVideoFolders+helpers.swift in Sources */, FA36BD401BE57F8E00D5E03B /* VideoDownload.swift in Sources */, 0313F9F022955F3B00B074BB /* CustomVideoFolders.swift in Sources */, FA6F81DD1D939455007975FE /* Preferences.swift in Sources */, @@ -830,6 +835,7 @@ 03893CB3217749F0008E7125 /* ErrorLog.swift in Sources */, AA7E2E5E1FC62E8B00E5F320 /* AerialPlayerItem.swift in Sources */, 03A2CB9C216BA9AF0061E8E8 /* VideoManager.swift in Sources */, + 03AD45FF22981B0C00261325 /* CustomVideoFolders+helpers.swift in Sources */, FAF450211BE2B45D00C1F98A /* VideoLoader.swift in Sources */, 03E8731321675FE0002B469B /* TimeManagement.swift in Sources */, 03D1E78722842FB300D10CF7 /* DisplayView.swift in Sources */, diff --git a/Aerial/Source/Controllers/CustomVideoController.swift b/Aerial/Source/Controllers/CustomVideoController.swift index 70f5bd81..c0edd1e2 100644 --- a/Aerial/Source/Controllers/CustomVideoController.swift +++ b/Aerial/Source/Controllers/CustomVideoController.swift @@ -35,6 +35,7 @@ class CustomVideoController: NSWindowController, NSWindowDelegate { var currentAsset: Asset? var hasAwokenAlready = false var sw: NSWindow? + var controller: PreferencesWindowController? // MARK: - Lifecycle required init?(coder: NSCoder) { @@ -51,7 +52,6 @@ class CustomVideoController: NSWindowController, NSWindowDelegate { override func awakeFromNib() { if !hasAwokenAlready { debugLog("cvcawake") - folderOutlineView.dataSource = self folderOutlineView.delegate = self poiTableView.dataSource = self @@ -70,19 +70,27 @@ class CustomVideoController: NSWindowController, NSWindowDelegate { // We will receive this notification for every panel/window so we need to ensure it's the correct one func windowWillClose(_ notification: Notification) { - if let wobj = notification.object as? NSWindow { + if let wobj = notification.object as? NSPanel { if wobj.title == "Manage Custom Videos" { debugLog("Closing cvc") let manifestInstance = ManifestLoader.instance manifestInstance.saveCustomVideos() + + manifestInstance.addCallback { manifestVideos in + if let contr = self.controller { + contr.loaded(manifestVideos: manifestVideos) + } + } + manifestInstance.loadManifestsFromLoadedFiles() } } } // This is the public function to make this visible - func show() { + func show(sender: NSButton, controller: PreferencesWindowController) { + self.controller = controller if !mainPanel.isVisible { - mainPanel.makeKeyAndOrderFront(self) + mainPanel.makeKeyAndOrderFront(sender) folderOutlineView.expandItem(nil, expandChildren: true) folderOutlineView.deselectAll(self) folderView.isHidden = true @@ -90,6 +98,7 @@ class CustomVideoController: NSWindowController, NSWindowDelegate { topPathControl.isHidden = true } } + // MARK: - Edit Folders @IBAction func folderNameChange(_ sender: NSTextField) { if let folder = currentFolder { @@ -106,15 +115,60 @@ class CustomVideoController: NSWindowController, NSWindowDelegate { addFolderPanel.canCreateDirectories = false addFolderPanel.canChooseFiles = false addFolderPanel.title = "Select a folder containing videos" - //addFolderPanel.allowedFileTypes = ["jpg","png"] - addFolderPanel.beginSheetModal(for: sw!) { (response) in + addFolderPanel.begin { (response) in if response.rawValue == NSFileHandlingPanelOKButton { - let selectedPath = addFolderPanel.url!.path - // do whatever you what with the file path + self.processPathForVideos(url: addFolderPanel.url!) } addFolderPanel.close() } + + } + + func processPathForVideos(url: URL) { + debugLog("processing url for videos : \(url) ") + let folderName = url.lastPathComponent + let manifestInstance = ManifestLoader.instance + + do { + let urls = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) + var assets = [Asset]() + + for lurl in urls { + if lurl.path.lowercased().hasSuffix(".mp4") || lurl.path.lowercased().hasSuffix(".mov") { + assets.append(Asset(pointsOfInterest: [:], + url: lurl.path, + accessibilityLabel: lurl.lastPathComponent, + id: NSUUID().uuidString, + time: "day")) + } + } + + if let cvf = manifestInstance.customVideoFolders { + // check if we have this folder already ? + if !cvf.hasFolder(withUrl: url.path) && !assets.isEmpty { + cvf.folders.append(Folder(url: url.path, label: folderName, assets: assets)) + } else if !assets.isEmpty { + // We need to append in place those that don't exist yet + let folderIndex = cvf.getFolderIndex(withUrl: url.path) + for asset in assets { + if !cvf.folders[folderIndex].hasAsset(withUrl: asset.url) { + cvf.folders[folderIndex].assets.append(asset) + } + } + } + } else { + // Create our initial CVF with the parsed folder + manifestInstance.customVideoFolders = CustomVideoFolders(folders: [Folder(url: url.path, label: folderName, assets: assets)]) + } + + folderOutlineView.reloadData() + folderOutlineView.expandItem(nil, expandChildren: true) + folderOutlineView.deselectAll(self) + + } catch { + errorLog("Could not process directory") + } } // MARK: - Edit Files diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index 62ec11fc..ed14f0cb 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -667,11 +667,15 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo } // We also load our CustomVideos nib - let bundle = Bundle.main + //if appMode { + //let bundle = Bundle.main + let bundle = Bundle(for: PreferencesWindowController.self) var topLevelObjects: NSArray? = NSArray() - bundle.loadNibNamed(NSNib.Name("CustomVideos"), + if !bundle.loadNibNamed(NSNib.Name("CustomVideos"), owner: customVideosController, - topLevelObjects: &topLevelObjects) + topLevelObjects: &topLevelObjects) { + errorLog("Could not load nib for CustomVideos, please report") + } debugLog("appMode : \(appMode)") } @@ -1709,7 +1713,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo } @objc func outlineViewCustomVideos(button: NSButton) { - customVideosController.show() + customVideosController.show(sender: button, controller: self) } @objc func outlineViewUncheckAll(button: NSButton) { diff --git a/Aerial/Source/Models/CustomVideoFolders+helpers.swift b/Aerial/Source/Models/CustomVideoFolders+helpers.swift new file mode 100644 index 00000000..2d1f3a58 --- /dev/null +++ b/Aerial/Source/Models/CustomVideoFolders+helpers.swift @@ -0,0 +1,47 @@ +// +// CustomVideoFolders+helpers.swift +// Aerial +// +// Created by Guillaume Louel on 24/05/2019. +// Copyright © 2019 John Coates. All rights reserved. +// + +import Foundation + +// Helpers added on top of our generated json class +extension CustomVideoFolders { + func hasFolder(withUrl: String) -> Bool { + for folder in folders where folder.url == withUrl { + return true + } + + return false + } + + func getFolderIndex(withUrl: String) -> Int { + var index = 0 + for folder in folders { + if folder.url == withUrl { + return index + } + index += 1 + } + return -1 + } + + func getFolder(withUrl: String) -> Folder? { + for folder in folders where folder.url == withUrl { + return folder + } + + return nil + } +} +extension Folder { + func hasAsset(withUrl: String) -> Bool { + for asset in assets where asset.url == withUrl { + return true + } + return false + } +} diff --git a/Resources/CustomVideos.xib b/Resources/CustomVideos.xib index c556f005..4a2ccdd1 100644 --- a/Resources/CustomVideos.xib +++ b/Resources/CustomVideos.xib @@ -28,7 +28,7 @@ - + @@ -117,7 +117,7 @@ - + @@ -126,7 +126,7 @@ - + @@ -138,7 +138,7 @@ - + @@ -300,7 +300,7 @@ - - - - - - - @@ -497,7 +483,6 @@ DQ - From d7a078a81bef1cdd02275868537e58209a3792ec Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Tue, 28 May 2019 13:55:47 +0200 Subject: [PATCH 23/28] Videos are now muted --- Aerial/Source/Controllers/CustomVideoController.swift | 9 +++++++++ .../Source/Controllers/PreferencesWindowController.swift | 1 + Aerial/Source/Views/AerialView.swift | 1 + Resources/CustomVideos.xib | 9 +++++++++ 4 files changed, 20 insertions(+) diff --git a/Aerial/Source/Controllers/CustomVideoController.swift b/Aerial/Source/Controllers/CustomVideoController.swift index c0edd1e2..e25a0bca 100644 --- a/Aerial/Source/Controllers/CustomVideoController.swift +++ b/Aerial/Source/Controllers/CustomVideoController.swift @@ -338,6 +338,9 @@ extension CustomVideoController: NSOutlineViewDelegate { if let player = editPlayerView.player { let localitem = AVPlayerItem(url: URL(fileURLWithPath: file.url)) + // let currentAssetDuration = localitem.asset.duration.convertScale(1, method: .default).value + debugLog("resolution \(getResolution(asset: localitem.asset))") + player.replaceCurrentItem(with: localitem) } @@ -350,6 +353,12 @@ extension CustomVideoController: NSOutlineViewDelegate { return true } + + func getResolution(asset: AVAsset) -> CGSize { + guard let track = asset.tracks(withMediaType: AVMediaType.video).first else { return CGSize.zero } + let size = track.naturalSize.applying(track.preferredTransform) + return CGSize(width: abs(size.width), height: abs(size.height)) + } } // MARK: - Extension for poi table view diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index ed14f0cb..a6d18e66 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -2183,6 +2183,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo case let video as AerialVideo: player = AVPlayer() playerView.player = player + player.isMuted = true debugLog("Playing this preview \(video)") // Workaround for cached videos generating online traffic diff --git a/Aerial/Source/Views/AerialView.swift b/Aerial/Source/Views/AerialView.swift index 08eb4edd..f332dd71 100644 --- a/Aerial/Source/Views/AerialView.swift +++ b/Aerial/Source/Views/AerialView.swift @@ -549,6 +549,7 @@ final class AerialView: ScreenSaverView, CAAnimationDelegate { // play another video let oldPlayer = self.player self.player = player + player.isMuted = true player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil) self.playerLayer.player = self.player diff --git a/Resources/CustomVideos.xib b/Resources/CustomVideos.xib index 4a2ccdd1..11d66ff5 100644 --- a/Resources/CustomVideos.xib +++ b/Resources/CustomVideos.xib @@ -346,6 +346,15 @@ + + + + + + + + + From 2bb5a8142d910bb469ca0554278ffea8356af895 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Tue, 28 May 2019 15:54:52 +0200 Subject: [PATCH 24/28] Add new duration and resolution info, fix stepper --- .../Controllers/CustomVideoController.swift | 32 ++++++- Resources/CustomVideos.xib | 87 +++++++++++++++---- 2 files changed, 98 insertions(+), 21 deletions(-) diff --git a/Aerial/Source/Controllers/CustomVideoController.swift b/Aerial/Source/Controllers/CustomVideoController.swift index e25a0bca..8fbfa9db 100644 --- a/Aerial/Source/Controllers/CustomVideoController.swift +++ b/Aerial/Source/Controllers/CustomVideoController.swift @@ -29,10 +29,17 @@ class CustomVideoController: NSWindowController, NSWindowDelegate { @IBOutlet var addPoiPopover: NSPopover! @IBOutlet var timeTextField: NSTextField! + @IBOutlet var timeTextStepper: NSStepper! + @IBOutlet var timeTextFormatter: NumberFormatter! @IBOutlet var descriptionTextField: NSTextField! + @IBOutlet var durationLabel: NSTextField! + @IBOutlet var resolutionLabel: NSTextField! + var currentFolder: Folder? var currentAsset: Asset? + var currentAssetDuration: Int? + var hasAwokenAlready = false var sw: NSWindow? var controller: PreferencesWindowController? @@ -219,6 +226,18 @@ class CustomVideoController: NSWindowController, NSWindowDelegate { } } + @IBAction func timeStepperChange(_ sender: NSStepper) { + if let player = editPlayerView.player { + player.seek(to: CMTime(seconds: Double(sender.intValue), preferredTimescale: 1)) + } + } + + @IBAction func timeTextChange(_ sender: NSTextField) { + if let player = editPlayerView.player { + player.seek(to: CMTime(seconds: Double(sender.intValue), preferredTimescale: 1)) + } + } + @IBAction func tableViewTimeField(_ sender: NSTextField) { if let asset = currentAsset { if poiTableView.selectedRow != -1 { @@ -338,8 +357,17 @@ extension CustomVideoController: NSOutlineViewDelegate { if let player = editPlayerView.player { let localitem = AVPlayerItem(url: URL(fileURLWithPath: file.url)) - // let currentAssetDuration = localitem.asset.duration.convertScale(1, method: .default).value - debugLog("resolution \(getResolution(asset: localitem.asset))") + currentAssetDuration = Int(localitem.asset.duration.convertScale(1, method: .default).value) + let currentResolution = getResolution(asset: localitem.asset) + let crString = String(Int(currentResolution.width)) + "x" + String(Int(currentResolution.height)) + + timeTextStepper.minValue = 0 + timeTextStepper.maxValue = Double(currentAssetDuration!) + timeTextFormatter.minimum = 0 + timeTextFormatter.maximum = NSNumber(value: currentAssetDuration!) + + durationLabel.stringValue = String(currentAssetDuration!) + " seconds" + resolutionLabel.stringValue = crString player.replaceCurrentItem(with: localitem) } diff --git a/Resources/CustomVideos.xib b/Resources/CustomVideos.xib index 11d66ff5..1d83ff48 100644 --- a/Resources/CustomVideos.xib +++ b/Resources/CustomVideos.xib @@ -12,6 +12,7 @@ + @@ -20,8 +21,11 @@ + + + @@ -294,10 +298,6 @@ - - - - + + + + + + + + + + From b5d67f41c4ce34942e1eeca6250df794076e00fd Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Tue, 28 May 2019 16:07:54 +0200 Subject: [PATCH 25/28] Fix custom 4K videos showing as 1080p --- Aerial/Source/Models/ManifestLoader.swift | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Aerial/Source/Models/ManifestLoader.swift b/Aerial/Source/Models/ManifestLoader.swift index 2a812aab..ebeb1c2e 100644 --- a/Aerial/Source/Models/ManifestLoader.swift +++ b/Aerial/Source/Models/ManifestLoader.swift @@ -9,6 +9,7 @@ import Foundation import ScreenSaver import GameplayKit +import AVFoundation typealias ManifestLoadCallback = ([AerialVideo]) -> Void @@ -422,14 +423,24 @@ class ManifestLoader { if let cvf = customVideoFolders { for folder in cvf.folders { for asset in folder.assets { + let avResolution = getResolution(asset: AVAsset(url: URL(fileURLWithPath: asset.url))) + var url1080p = "" + var url4K = "" + + if avResolution.height > 1080 { + url4K = URL(fileURLWithPath: asset.url).absoluteString + } else { + url1080p = URL(fileURLWithPath: asset.url).absoluteString + } + let video = AerialVideo(id: asset.id, name: folder.label, secondaryName: asset.accessibilityLabel, type: "video", timeOfDay: asset.time, - url1080pH264: URL(fileURLWithPath: asset.url).absoluteString, + url1080pH264: url1080p, url1080pHEVC: "", - url4KHEVC: "", + url4KHEVC: url4K, manifest: .customVideos, poi: [:], communityPoi: asset.pointsOfInterest) @@ -439,6 +450,12 @@ class ManifestLoader { } } + func getResolution(asset: AVAsset) -> CGSize { + guard let track = asset.tracks(withMediaType: AVMediaType.video).first else { return CGSize.zero } + let size = track.naturalSize.applying(track.preferredTransform) + return CGSize(width: abs(size.width), height: abs(size.height)) + } + // MARK: - Periodically check for new videos func checkIfShouldRedownloadFiles() { let dateFormatter = DateFormatter() From 74d6d5b630e5fd3946e90e6ca73fd1cf5a008621 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Wed, 29 May 2019 12:01:33 +0200 Subject: [PATCH 26/28] make the custom json pretty printed and not a garbled mess --- Aerial/Source/Controllers/CustomVideoController.swift | 4 +++- Aerial/Source/Controllers/PreferencesWindowController.swift | 6 +++++- Aerial/Source/Models/CustomVideoFolders.swift | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Aerial/Source/Controllers/CustomVideoController.swift b/Aerial/Source/Controllers/CustomVideoController.swift index 8fbfa9db..0fa8593a 100644 --- a/Aerial/Source/Controllers/CustomVideoController.swift +++ b/Aerial/Source/Controllers/CustomVideoController.swift @@ -84,8 +84,10 @@ class CustomVideoController: NSWindowController, NSWindowDelegate { manifestInstance.saveCustomVideos() manifestInstance.addCallback { manifestVideos in + debugLog("addCallback") if let contr = self.controller { - contr.loaded(manifestVideos: manifestVideos) + debugLog("addLoadedCallback") + contr.loaded(manifestVideos: []) } } manifestInstance.loadManifestsFromLoadedFiles() diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index a6d18e66..26fc5335 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -1971,8 +1971,12 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo var videos = [AerialVideo]() var cities = [String: City]() + // Grab a fresh version, because our callback can be feeding us wrong data in CVC + let freshManifestVideos = ManifestLoader.instance.loadedManifest + debugLog("freshManifestVideos count : \(freshManifestVideos.count)") + // First day, then night - for video in manifestVideos { + for video in freshManifestVideos { let name = video.name if cities.keys.contains(name) == false { diff --git a/Aerial/Source/Models/CustomVideoFolders.swift b/Aerial/Source/Models/CustomVideoFolders.swift index e9a36ef7..0ae7a27a 100644 --- a/Aerial/Source/Models/CustomVideoFolders.swift +++ b/Aerial/Source/Models/CustomVideoFolders.swift @@ -194,6 +194,7 @@ func newJSONDecoder() -> JSONDecoder { func newJSONEncoder() -> JSONEncoder { let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { encoder.dateEncodingStrategy = .iso8601 } From c88a856bf200e575cdf038cb25e097899984306f Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Wed, 29 May 2019 12:17:14 +0200 Subject: [PATCH 27/28] Fix reload bug after CVC --- Aerial/Source/Controllers/CustomVideoController.swift | 2 -- Aerial/Source/Controllers/PreferencesWindowController.swift | 2 +- Aerial/Source/Models/ManifestLoader.swift | 3 ++- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Aerial/Source/Controllers/CustomVideoController.swift b/Aerial/Source/Controllers/CustomVideoController.swift index 0fa8593a..6a09cb03 100644 --- a/Aerial/Source/Controllers/CustomVideoController.swift +++ b/Aerial/Source/Controllers/CustomVideoController.swift @@ -84,9 +84,7 @@ class CustomVideoController: NSWindowController, NSWindowDelegate { manifestInstance.saveCustomVideos() manifestInstance.addCallback { manifestVideos in - debugLog("addCallback") if let contr = self.controller { - debugLog("addLoadedCallback") contr.loaded(manifestVideos: []) } } diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index 26fc5335..2be22d43 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -1973,7 +1973,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo // Grab a fresh version, because our callback can be feeding us wrong data in CVC let freshManifestVideos = ManifestLoader.instance.loadedManifest - debugLog("freshManifestVideos count : \(freshManifestVideos.count)") + //debugLog("freshManifestVideos count : \(freshManifestVideos.count)") // First day, then night for video in freshManifestVideos { diff --git a/Aerial/Source/Models/ManifestLoader.swift b/Aerial/Source/Models/ManifestLoader.swift index ebeb1c2e..6741c284 100644 --- a/Aerial/Source/Models/ManifestLoader.swift +++ b/Aerial/Source/Models/ManifestLoader.swift @@ -411,6 +411,7 @@ class ManifestLoader { if let encodedData = try? cvf.jsonData() { try encodedData.write(to: cacheFileUrl) debugLog("customvideos.json saved successfully!") + loadedManifest.removeAll() // we remove our previously loaded manifest, it's invalid } } catch let error as NSError { errorLog("customvideos.json could not be saved: \(error.localizedDescription)") @@ -658,7 +659,7 @@ class ManifestLoader { } }*/ - debugLog("Total videos processed : \(processedVideos.count)") + debugLog("Total videos processed : \(processedVideos.count) callbacks : \(callbacks.count)") // callbacks for callback in self.callbacks { callback(self.loadedManifest) From 6fa488cab35917f0ae761de8b5103ee5119ec6a0 Mon Sep 17 00:00:00 2001 From: Guillaume Louel Date: Wed, 29 May 2019 13:00:10 +0200 Subject: [PATCH 28/28] beta7 --- Aerial/Source/Controllers/CustomVideoController.swift | 2 ++ Resources/CustomVideos.xib | 1 - Resources/Info.plist | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Aerial/Source/Controllers/CustomVideoController.swift b/Aerial/Source/Controllers/CustomVideoController.swift index 6a09cb03..50ec9c9e 100644 --- a/Aerial/Source/Controllers/CustomVideoController.swift +++ b/Aerial/Source/Controllers/CustomVideoController.swift @@ -365,6 +365,8 @@ extension CustomVideoController: NSOutlineViewDelegate { timeTextStepper.maxValue = Double(currentAssetDuration!) timeTextFormatter.minimum = 0 timeTextFormatter.maximum = NSNumber(value: currentAssetDuration!) + //timeTableFormatter.minimum = 0 + //timeTableFormatter.maximum = NSNumber(value: currentAssetDuration!) durationLabel.stringValue = String(currentAssetDuration!) + " seconds" resolutionLabel.stringValue = crString diff --git a/Resources/CustomVideos.xib b/Resources/CustomVideos.xib index 1d83ff48..a0af20bb 100644 --- a/Resources/CustomVideos.xib +++ b/Resources/CustomVideos.xib @@ -225,7 +225,6 @@ - diff --git a/Resources/Info.plist b/Resources/Info.plist index 35b27f35..5097b15a 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.99beta6 + 1.4.99beta7 CFBundleSignature ???? CFBundleVersion - 1.4.99beta6 + 1.4.99beta7 LSApplicationCategoryType LSMinimumSystemVersion