diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj
index 7b713f5d..2c502bb9 100644
--- a/Aerial.xcodeproj/project.pbxproj
+++ b/Aerial.xcodeproj/project.pbxproj
@@ -1169,7 +1169,7 @@
CODE_SIGN_IDENTITY = "Developer ID Application: Guillaume Louel (3L54M5L5KK)";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 1.5.1beta2;
+ CURRENT_PROJECT_VERSION = 1.5.1beta3;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 3L54M5L5KK;
ENABLE_HARDENED_RUNTIME = YES;
@@ -1177,7 +1177,7 @@
INSTALL_PATH = "$(HOME)/Library/Screen Savers";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.9;
- MARKETING_VERSION = 1.5.1beta2;
+ MARKETING_VERSION = 1.5.1beta3;
PRODUCT_BUNDLE_IDENTIFIER = com.johncoates.Aerial;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1198,7 +1198,7 @@
CODE_SIGN_IDENTITY = "Developer ID Application: Guillaume Louel (3L54M5L5KK)";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 1.5.1beta2;
+ CURRENT_PROJECT_VERSION = 1.5.1beta3;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 3L54M5L5KK;
ENABLE_HARDENED_RUNTIME = YES;
@@ -1206,7 +1206,7 @@
INSTALL_PATH = "$(HOME)/Library/Screen Savers";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.9;
- MARKETING_VERSION = 1.5.1beta2;
+ MARKETING_VERSION = 1.5.1beta3;
OTHER_CODE_SIGN_FLAGS = "--timestamp";
PRODUCT_BUNDLE_IDENTIFIER = com.johncoates.Aerial;
PRODUCT_NAME = "$(TARGET_NAME)";
diff --git a/Aerial/Source/Controllers/PWC Tabs/PWC+Advanced.swift b/Aerial/Source/Controllers/PWC Tabs/PWC+Advanced.swift
index 7742d0b5..c45163e8 100644
--- a/Aerial/Source/Controllers/PWC Tabs/PWC+Advanced.swift
+++ b/Aerial/Source/Controllers/PWC Tabs/PWC+Advanced.swift
@@ -21,6 +21,9 @@ extension PreferencesWindowController {
if preferences.logMilliseconds {
logMillisecondsButton.state = .on
}
+ if preferences.synchronizedMode {
+ synchronizedModeCheckbox.state = .on
+ }
}
// MARK: - Advanced panel
@@ -103,6 +106,12 @@ extension PreferencesWindowController {
showLogBottomClick.isHidden = false
}
+ @IBAction func synchronizedModeClick(_ sender: NSButton) {
+ let onState = sender.state == .on
+ preferences.synchronizedMode = onState
+ debugLog("UI synchronizedMode \(onState)")
+ }
+
@IBAction func moveOldVideosClick(_ sender: Any) {
ManifestLoader.instance.moveOldVideos()
diff --git a/Aerial/Source/Controllers/PWC Tabs/PWC+Brightness.swift b/Aerial/Source/Controllers/PWC Tabs/PWC+Brightness.swift
index 599a7bb4..c5ae90a1 100644
--- a/Aerial/Source/Controllers/PWC Tabs/PWC+Brightness.swift
+++ b/Aerial/Source/Controllers/PWC Tabs/PWC+Brightness.swift
@@ -29,9 +29,6 @@ extension PreferencesWindowController {
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!)
diff --git a/Aerial/Source/Controllers/PWC Tabs/PWC+Videos.swift b/Aerial/Source/Controllers/PWC Tabs/PWC+Videos.swift
index f90e0b58..30e782d0 100644
--- a/Aerial/Source/Controllers/PWC Tabs/PWC+Videos.swift
+++ b/Aerial/Source/Controllers/PWC Tabs/PWC+Videos.swift
@@ -115,6 +115,15 @@ extension PreferencesWindowController {
fadeInOutModePopup.selectItem(at: preferences.fadeMode!)
+ // We need catalina for HDR !
+ if #available(OSX 10.15, *) {
+ if preferences.useHDR {
+ useHDRCheckbox.state = .off
+ }
+ } else {
+ useHDRCheckbox.state = .off
+ useHDRCheckbox.isEnabled = false
+ }
}
@IBAction func rightArrowKeyPlaysNextClick(_ sender: NSButton) {
@@ -123,12 +132,6 @@ extension PreferencesWindowController {
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
@@ -164,6 +167,12 @@ extension PreferencesWindowController {
outlineView.reloadData()
}
+ @IBAction func useHDRChange(_ sender: NSButton) {
+ let onState = sender.state == .on
+ preferences.useHDR = onState
+ debugLog("UI useHDR \(onState)")
+ }
+
@IBAction func helpButtonClick(_ button: NSButton!) {
popover.show(relativeTo: button.preparedContentRect, of: button, preferredEdge: .maxY)
}
diff --git a/Aerial/Source/Controllers/Preferences.swift b/Aerial/Source/Controllers/Preferences.swift
index d98914c2..67054873 100644
--- a/Aerial/Source/Controllers/Preferences.swift
+++ b/Aerial/Source/Controllers/Preferences.swift
@@ -83,6 +83,7 @@ final class Preferences {
case synchronizedMode = "synchronizedMode"
case aspectMode = "aspectMode"
+ case useHDR = "useHDR"
}
enum AspectMode: Int {
@@ -228,6 +229,7 @@ final class Preferences {
defaultValues[.verticalMargin] = 0
defaultValues[.synchronizedMode] = false
defaultValues[.aspectMode] = AspectMode.fill
+ defaultValues[.useHDR] = true
// Set today's date as default
let dateFormatter = DateFormatter()
@@ -345,6 +347,15 @@ final class Preferences {
}
}
+ var useHDR: Bool {
+ get {
+ return value(forIdentifier: .useHDR)
+ }
+ set {
+ setValue(forIdentifier: .useHDR, value: newValue)
+ }
+ }
+
var synchronizedMode: Bool {
get {
return value(forIdentifier: .synchronizedMode)
diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift
index 9b00aa01..d3fe1b96 100644
--- a/Aerial/Source/Controllers/PreferencesWindowController.swift
+++ b/Aerial/Source/Controllers/PreferencesWindowController.swift
@@ -56,6 +56,8 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo
@IBOutlet var fadeInOutModePopup: NSPopUpButton!
@IBOutlet var popupVideoFormat: NSPopUpButton!
+ @IBOutlet var useHDRCheckbox: NSButton!
+
@IBOutlet var overrideOnBatteryCheckbox: NSButton!
@IBOutlet var alternatePopupVideoFormat: NSPopUpButton!
@IBOutlet var powerSavingOnLowBatteryCheckbox: NSButton!
diff --git a/Aerial/Source/Models/AerialVideo.swift b/Aerial/Source/Models/AerialVideo.swift
index 737a00c8..ad9afd8d 100644
--- a/Aerial/Source/Models/AerialVideo.swift
+++ b/Aerial/Source/Models/AerialVideo.swift
@@ -90,9 +90,13 @@ final class AerialVideo: CustomStringConvertible, Equatable {
let secondaryName: String
let type: String
let timeOfDay: String
+
var url1080pH264: String
let url1080pHEVC: String
+ let url1080pHDR: String
let url4KHEVC: String
+ let url4KHDR: String
+
var sources: [Manifests]
let poi: [String: String]
let communityPoi: [String: String]
@@ -106,6 +110,7 @@ final class AerialVideo: CustomStringConvertible, Equatable {
return VideoCache.isAvailableOffline(video: self)
}
+ // MARK: - Public getter
var url: URL {
let preferences = Preferences.sharedInstance
let timeManagement = TimeManagement.sharedInstance
@@ -117,35 +122,56 @@ final class AerialVideo: CustomStringConvertible, Equatable {
return getClosestAvailable(wanted: preferences.videoFormat!)
}
+ // Returns the closest video we have in the manifests
func getClosestAvailable(wanted: Int) -> URL {
if wanted == Preferences.VideoFormat.v4KHEVC.rawValue {
- if url4KHEVC != "" {
- return URL(string: self.url4KHEVC)!
- } else if url1080pHEVC != "" {
- return URL(string: self.url1080pHEVC)!
- } else {
- return URL(string: self.url1080pH264)!
- }
+ return getVideoFormatFrom(best: .v4KHEVC, option2: .v1080pHEVC, option3: .v1080pH264)
} else if wanted == Preferences.VideoFormat.v1080pHEVC.rawValue {
- if url1080pHEVC != "" {
- return URL(string: self.url1080pHEVC)!
- } else if url1080pH264 != "" {
- return URL(string: self.url1080pH264)!
- } else {
- return URL(string: self.url4KHEVC)!
- }
+ return getVideoFormatFrom(best: .v1080pHEVC, option2: .v1080pH264, option3: .v4KHEVC)
} else {
- if url1080pH264 != "" {
- return URL(string: self.url1080pH264)!
- } else if url1080pHEVC != "" {
- // With the latest versions, we should always have a H.264 fallback so this is just for future proofing
- return URL(string: self.url1080pHEVC)!
- } else {
- return URL(string: self.url4KHEVC)!
- }
+ return getVideoFormatFrom(best: .v1080pH264, option2: .v1080pHEVC, option3: .v4KHEVC)
+ }
+ }
+
+ // Helper to find the best available format from the 3 options given, in that order
+ func getVideoFormatFrom(best: Preferences.VideoFormat, option2: Preferences.VideoFormat, option3: Preferences.VideoFormat) -> URL {
+ if urlFor(videoFormat: best) != "" {
+ return getDynamicRange(wanted: best)
+ } else if urlFor(videoFormat: option2) != "" {
+ return getDynamicRange(wanted: option2)
+ } else {
+ return getDynamicRange(wanted: option3)
+ }
+ }
+
+ // Helper to get the url for a given format
+ private func urlFor(videoFormat: Preferences.VideoFormat) -> String {
+ if videoFormat == .v4KHEVC {
+ return url4KHEVC
+ } else if videoFormat == .v1080pHEVC {
+ return url1080pHEVC
+ } else {
+ return url1080pH264
+ }
+ }
+
+ // Helper to get the correct Dynamic Range version based on Format, preferences, and OS availability
+ func getDynamicRange(wanted: Preferences.VideoFormat) -> URL {
+ let preferences = Preferences.sharedInstance
+ if #available(OSX 10.15, *), preferences.useHDR && wanted == .v4KHEVC {
+ return URL(string: url4KHDR)!
+ } else if wanted == .v4KHEVC {
+ return URL(string: url4KHEVC)!
+ } else if #available(OSX 10.15, *), preferences.useHDR && wanted == .v1080pHEVC {
+ return URL(string: url1080pHDR)!
+ } else if wanted == .v1080pHEVC {
+ return URL(string: url1080pHEVC)!
+ } else {
+ return URL(string: url1080pH264)!
}
}
+ // MARK: - Init
init(id: String,
name: String,
secondaryName: String,
@@ -153,7 +179,9 @@ final class AerialVideo: CustomStringConvertible, Equatable {
timeOfDay: String,
url1080pH264: String,
url1080pHEVC: String,
+ url1080pHDR: String,
url4KHEVC: String,
+ url4KHDR: String,
manifest: Manifests,
poi: [String: String],
communityPoi: [String: String]
@@ -189,13 +217,15 @@ final class AerialVideo: CustomStringConvertible, Equatable {
self.url1080pH264 = url1080pH264
self.url1080pHEVC = url1080pHEVC
+ self.url1080pHDR = url1080pHDR
self.url4KHEVC = url4KHEVC
+ self.url4KHDR = url4KHDR
self.sources = [manifest]
self.poi = poi
self.communityPoi = communityPoi
self.duration = 0
- updateDuration()
+ updateDuration() // We need to have the video duration
}
func updateDuration() {
@@ -255,7 +285,9 @@ final class AerialVideo: CustomStringConvertible, Equatable {
timeofDay=\(timeOfDay),
url1080pH264=\(url1080pH264),
url1080pHEVC=\(url1080pHEVC),
+ url1080pHDR=\(url1080pHDR),
url4KHEVC=\(url4KHEVC)"
+ url4KHDR=\(url4KHDR)"
"""
}
}
diff --git a/Aerial/Source/Models/DisplayDetection.swift b/Aerial/Source/Models/DisplayDetection.swift
index b2c88815..4dfe7288 100644
--- a/Aerial/Source/Models/DisplayDetection.swift
+++ b/Aerial/Source/Models/DisplayDetection.swift
@@ -99,6 +99,7 @@ final class DisplayDetection: NSObject {
for screen in NSScreen.screens {
debugLog("pass2: dict \(screen.deviceDescription)")
debugLog(" bottomLeftFrame \(screen.frame)")
+
let dscreen = findScreenWith(frame: screen.frame)
if dscreen != nil {
diff --git a/Aerial/Source/Models/ManifestLoader.swift b/Aerial/Source/Models/ManifestLoader.swift
index 6741c284..797e3b9e 100644
--- a/Aerial/Source/Models/ManifestLoader.swift
+++ b/Aerial/Source/Models/ManifestLoader.swift
@@ -103,7 +103,9 @@ class ManifestLoader {
let mergeInfo = [
"2F11E857-4F77-4476-8033-4A1E4610AFCC":
["url-1080-SDR": "https://sylvan.apple.com/Aerials/2x/Videos/DB_D011_C009_2K_SDR_HEVC.mov",
- "url-4K-SDR": "https://sylvan.apple.com/Aerials/2x/Videos/DB_D011_C009_4K_SDR_HEVC.mov", ], // Dubai night 2
+ "url-1080-HDR": "https://sylvan.apple.com/Aerials/2x/Videos/DB_D011_C009_2K_HDR_HEVC.mov",
+ "url-4K-SDR": "https://sylvan.apple.com/Aerials/2x/Videos/DB_D011_C009_4K_SDR_HEVC.mov",
+ "url-4K-HDR": "https://sylvan.apple.com/Aerials/2x/Videos/DB_D011_C009_4K_HDR_HEVC.mov", ], // Dubai night 2
]
// Extra POI
@@ -151,8 +153,7 @@ class ManifestLoader {
playlistIsRestricted = isRestricted
playlistRestrictedTo = restrictedTo
- // Start with a shuffled list
- //let shuffled = loadedManifest.shuffled()
+ // Start with a shuffled list, we may have synchronized seed shuffle
var shuffled: [AerialVideo]
let preferences = Preferences.sharedInstance
if preferences.synchronizedMode {
@@ -177,14 +178,12 @@ class ManifestLoader {
let inRotation = preferences.videoIsInRotation(videoID: video.id)
if !inRotation {
- //debugLog("randomVideo: video is disabled: \(video)")
continue
}
// Do we restrict video types by day/night ?
if isRestricted {
if video.timeOfDay != restrictedTo {
- //debugLog("randomVideo: video is excluded as we only play \(restrictTo) (is: \(video.timeOfDay))")
continue
}
}
@@ -192,7 +191,6 @@ class ManifestLoader {
// We may not want to stream
if preferences.neverStreamVideos == true {
if video.isAvailableOffline == false {
- //debugLog("randomVideo: video is excluded because it's not available offline \(video)")
continue
}
}
@@ -201,7 +199,7 @@ class ManifestLoader {
playlist.append(video)
}
- // On regenerating a new playlist, we try to avoid repeating
+ // On regenerating a new playlist, we try to avoid repeating the last thing we played!
while playlist.count > 1 && lastPluckedFromPlaylist == playlist.first {
playlist.shuffle()
}
@@ -210,15 +208,18 @@ class ManifestLoader {
func randomVideo(excluding: [AerialVideo]) -> AerialVideo? {
let timeManagement = TimeManagement.sharedInstance
let (shouldRestrictByDayNight, restrictTo) = timeManagement.shouldRestrictPlaybackToDayNightVideo()
- debugLog("shouldRestrictByDayNight : \(shouldRestrictByDayNight) (\(restrictTo))")
+
+ // We may need to regenerate a playlist!
if playlist.isEmpty || restrictTo != playlistRestrictedTo || shouldRestrictByDayNight != playlistIsRestricted {
generatePlaylist(isRestricted: shouldRestrictByDayNight, restrictedTo: restrictTo)
}
+ // If not pluck one from current playlist and return that
if !playlist.isEmpty {
lastPluckedFromPlaylist = playlist.removeFirst()
return lastPluckedFromPlaylist
} else {
+ // If we don't have any playlist, something's got awfully wrong so deal with that!
return findBestEffortVideo()
}
}
@@ -238,7 +239,7 @@ class ManifestLoader {
warnLog("Empty playlist, not good !")
if lastPluckedFromPlaylist != nil {
- warnLog("returning last played video after condition change not met !")
+ warnLog("Repeating last played video, after condition change not met !")
return lastPluckedFromPlaylist!
} else {
// Start with a shuffled list
@@ -337,6 +338,7 @@ class ManifestLoader {
}
}
+ // MARK: - This will refetch the manifests online
func reloadFiles() {
moveOldManifests()
@@ -364,17 +366,16 @@ class ManifestLoader {
urls.append(URL(string: "http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/videos/entries.json")!)
}
+ // Setup and start async fetching
let completion = BlockOperation {
debugLog("Fetching manifests all done")
// We can now load from the newly cached files
self.loadCachedManifests()
}
-
for url in urls {
let operation = downloadManager.queueDownload(url)
completion.addDependency(operation)
}
-
OperationQueue.main.addOperation(completion)
}
@@ -441,7 +442,9 @@ class ManifestLoader {
timeOfDay: asset.time,
url1080pH264: url1080p,
url1080pHEVC: "",
+ url1080pHDR: "",
url4KHEVC: url4K,
+ url4KHDR: "",
manifest: .customVideos,
poi: [:],
communityPoi: asset.pointsOfInterest)
@@ -526,6 +529,7 @@ class ManifestLoader {
}
}
}
+
// MARK: - Manifests
// Check if the Manifests have been loaded in this class already
@@ -686,7 +690,9 @@ class ManifestLoader {
let id = item["id"] as! String
let url1080pH264 = item["url-1080-H264"] as? String
let url1080pHEVC = item["url-1080-SDR"] as? String
+ let url1080pHDR = item["url-1080-HDR"] as? String
let url4KHEVC = item["url-4K-SDR"] as? String
+ let url4KHDR = item["url-4K-HDR"] as? String
let name = item["accessibilityLabel"] as! String
var secondaryName = ""
// We may have a secondary name
@@ -719,7 +725,9 @@ class ManifestLoader {
timeOfDay: timeOfDay,
url1080pH264: url1080pH264 ?? "",
url1080pHEVC: url1080pHEVC ?? "",
+ url1080pHDR: url1080pHDR ?? "",
url4KHEVC: url4KHEVC ?? "",
+ url4KHDR: url4KHDR ?? "",
manifest: manifest,
poi: poi ?? [:],
communityPoi: communityPoi)
@@ -781,12 +789,16 @@ class ManifestLoader {
}
}
} else {
- var url4khevc = ""
var url1080phevc = ""
+ var url1080phdr = ""
+ var url4khevc = ""
+ var url4khdr = ""
// Check if we have some HEVC urls to merge
if let val = mergeInfo[id] {
url1080phevc = val["url-1080-SDR"]!
+ url1080phdr = val["url-1080-HDR"]!
url4khevc = val["url-4K-SDR"]!
+ url4khdr = val["url-4K-HDR"]!
}
// Now we can finally add...
@@ -797,7 +809,9 @@ class ManifestLoader {
timeOfDay: timeOfDay,
url1080pH264: url,
url1080pHEVC: url1080phevc,
+ url1080pHDR: url1080phdr,
url4KHEVC: url4khevc,
+ url4KHDR: url4khdr,
manifest: manifest,
poi: poi ?? [:],
communityPoi: communityPoi)
diff --git a/Resources/PreferencesWindow.xib b/Resources/PreferencesWindow.xib
index 86574f52..c920367d 100644
--- a/Resources/PreferencesWindow.xib
+++ b/Resources/PreferencesWindow.xib
@@ -141,6 +141,7 @@
+
@@ -170,7 +171,7 @@
-
+
@@ -194,12 +195,12 @@
-
+
-
+
@@ -213,7 +214,7 @@
-
+
@@ -236,7 +237,7 @@
-
+
@@ -255,13 +256,13 @@
-
+
-
+
@@ -270,7 +271,7 @@
-
+
@@ -280,7 +281,7 @@
-
+
@@ -383,10 +384,10 @@ is disabled
-
+
-
-
+
+
@@ -395,7 +396,7 @@ is disabled
-
+
@@ -405,14 +406,14 @@ is disabled
-
+
-
+
@@ -459,7 +460,7 @@ is disabled
-
+
@@ -491,7 +492,7 @@ is disabled
-
+
@@ -573,7 +573,7 @@ is disabled
-
+
@@ -1604,7 +1604,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required)
-
+
@@ -1906,7 +1906,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required)
-
+
@@ -1948,7 +1948,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required)
-
-
-
-
-
- If you are experiencing an issue with Aerial, we may ask you to enable the Debug and Log to disk options below.
-
-
-
-
-
-
-
-
+
@@ -2003,7 +1990,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required)
-
+
+
+
+
+
@@ -2038,7 +2029,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required)
+
+
+
+
+
+
+
+
+
+