diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..3a5cc350
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+xcuserdata/
+compile/
+build/
+DerivedData/
+*.xccheckout
+release/
+debug.plist
+Examples/debug.html
diff --git a/Aerial Test/AppDelegate.swift b/Aerial Test/AppDelegate.swift
new file mode 100644
index 00000000..2cfd72c3
--- /dev/null
+++ b/Aerial Test/AppDelegate.swift
@@ -0,0 +1,27 @@
+//
+// AppDelegate.swift
+// Aerial Test
+//
+// Created by John Coates on 10/23/15.
+// Copyright © 2015 John Coates. All rights reserved.
+//
+
+import Cocoa
+
+@NSApplicationMain
+class AppDelegate: NSObject, NSApplicationDelegate {
+
+ @IBOutlet weak var window: NSWindow!
+ @IBOutlet var preferencesWindowController:PreferencesWindowController!
+
+
+ func applicationDidFinishLaunching(aNotification: NSNotification) {
+ }
+
+ func applicationWillTerminate(aNotification: NSNotification) {
+ // Insert code here to tear down your application
+ }
+
+
+}
+
diff --git a/Aerial Test/Assets.xcassets/AppIcon.appiconset/Contents.json b/Aerial Test/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..2db2b1c7
--- /dev/null
+++ b/Aerial Test/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,58 @@
+{
+ "images" : [
+ {
+ "idiom" : "mac",
+ "size" : "16x16",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "16x16",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "32x32",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "32x32",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "128x128",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "128x128",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "256x256",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "256x256",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "512x512",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "512x512",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Aerial Test/Base.lproj/MainMenu.xib b/Aerial Test/Base.lproj/MainMenu.xib
new file mode 100644
index 00000000..27cff1d2
--- /dev/null
+++ b/Aerial Test/Base.lproj/MainMenu.xib
@@ -0,0 +1,875 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Aerial Test/Info.plist b/Aerial Test/Info.plist
new file mode 100644
index 00000000..d09bbe17
--- /dev/null
+++ b/Aerial Test/Info.plist
@@ -0,0 +1,39 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIconFile
+
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ LSMinimumSystemVersion
+ $(MACOSX_DEPLOYMENT_TARGET)
+ NSHumanReadableCopyright
+ Copyright © 2015 John Coates. All rights reserved.
+ NSMainNibFile
+ MainMenu
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
+ NSPrincipalClass
+ NSApplication
+
+
diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj
index c86202a0..2a017000 100644
--- a/Aerial.xcodeproj/project.pbxproj
+++ b/Aerial.xcodeproj/project.pbxproj
@@ -7,18 +7,52 @@
objects = {
/* Begin PBXBuildFile section */
- FACAF1A91BD9FC6000E539DC /* AerialView.h in Headers */ = {isa = PBXBuildFile; fileRef = FACAF1A81BD9FC6000E539DC /* AerialView.h */; };
- FACAF1AB1BD9FC6000E539DC /* AerialView.m in Sources */ = {isa = PBXBuildFile; fileRef = FACAF1AA1BD9FC6000E539DC /* AerialView.m */; };
+ FA143CCB1BDA270A0041A82B /* PreferencesWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA143CCA1BDA270A0041A82B /* PreferencesWindow.xib */; settings = {ASSET_TAGS = (); }; };
+ FA143CCD1BDA2EE00041A82B /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA143CCC1BDA2EE00041A82B /* PreferencesWindowController.swift */; settings = {ASSET_TAGS = (); }; };
+ FA143CD91BDA3E880041A82B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA143CD81BDA3E880041A82B /* AppDelegate.swift */; };
+ FA143CDB1BDA3E880041A82B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA143CDA1BDA3E880041A82B /* Assets.xcassets */; };
+ FA143CDE1BDA3E880041A82B /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA143CDC1BDA3E880041A82B /* MainMenu.xib */; };
+ FA143CE31BDA3E910041A82B /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA143CCC1BDA2EE00041A82B /* PreferencesWindowController.swift */; settings = {ASSET_TAGS = (); }; };
+ FA143CE61BDA3EEF0041A82B /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA143CE51BDA3EEF0041A82B /* AVKit.framework */; };
+ FA143CE81BDA49BA0041A82B /* AerialVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA143CE71BDA49BA0041A82B /* AerialVideo.swift */; settings = {ASSET_TAGS = (); }; };
+ FA143CE91BDA4A9E0041A82B /* AerialVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA143CE71BDA49BA0041A82B /* AerialVideo.swift */; settings = {ASSET_TAGS = (); }; };
+ FA6746D01BDB7B2B00769DC6 /* CheckCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6746CF1BDB7B2B00769DC6 /* CheckCellView.swift */; settings = {ASSET_TAGS = (); }; };
+ FA6746D11BDB7B3300769DC6 /* CheckCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6746CF1BDB7B2B00769DC6 /* CheckCellView.swift */; settings = {ASSET_TAGS = (); }; };
+ FA6746D41BDB850000769DC6 /* icon-day.pdf in Resources */ = {isa = PBXBuildFile; fileRef = FA6746D21BDB850000769DC6 /* icon-day.pdf */; settings = {ASSET_TAGS = (); }; };
+ FA6746D51BDB850000769DC6 /* icon-day.pdf in Resources */ = {isa = PBXBuildFile; fileRef = FA6746D21BDB850000769DC6 /* icon-day.pdf */; settings = {ASSET_TAGS = (); }; };
+ FA6746D61BDB850000769DC6 /* icon-night.pdf in Resources */ = {isa = PBXBuildFile; fileRef = FA6746D31BDB850000769DC6 /* icon-night.pdf */; settings = {ASSET_TAGS = (); }; };
+ FA6746D71BDB850000769DC6 /* icon-night.pdf in Resources */ = {isa = PBXBuildFile; fileRef = FA6746D31BDB850000769DC6 /* icon-night.pdf */; settings = {ASSET_TAGS = (); }; };
+ FA6746D81BDB9DCE00769DC6 /* AerialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACAF1B31BD9FEEC00E539DC /* AerialView.swift */; settings = {ASSET_TAGS = (); }; };
+ FACAF1B41BD9FEEC00E539DC /* AerialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACAF1B31BD9FEEC00E539DC /* AerialView.swift */; settings = {ASSET_TAGS = (); }; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ FA143CCA1BDA270A0041A82B /* PreferencesWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = PreferencesWindow.xib; path = Aerial/PreferencesWindow.xib; sourceTree = ""; };
+ FA143CCC1BDA2EE00041A82B /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; };
+ FA143CD61BDA3E880041A82B /* Aerial Test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Aerial Test.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ FA143CD81BDA3E880041A82B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ FA143CDA1BDA3E880041A82B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ FA143CDD1BDA3E880041A82B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
+ FA143CDF1BDA3E880041A82B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ FA143CE51BDA3EEF0041A82B /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; };
+ FA143CE71BDA49BA0041A82B /* AerialVideo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AerialVideo.swift; sourceTree = ""; };
+ FA6746CF1BDB7B2B00769DC6 /* CheckCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckCellView.swift; sourceTree = ""; };
+ FA6746D21BDB850000769DC6 /* icon-day.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = "icon-day.pdf"; path = "Aerial/icon-day.pdf"; sourceTree = ""; };
+ FA6746D31BDB850000769DC6 /* icon-night.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = "icon-night.pdf"; path = "Aerial/icon-night.pdf"; sourceTree = ""; };
FACAF1A51BD9FC6000E539DC /* Aerial.saver */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Aerial.saver; sourceTree = BUILT_PRODUCTS_DIR; };
- FACAF1A81BD9FC6000E539DC /* AerialView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AerialView.h; sourceTree = ""; };
- FACAF1AA1BD9FC6000E539DC /* AerialView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AerialView.m; sourceTree = ""; };
- FACAF1AC1BD9FC6000E539DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ FACAF1AC1BD9FC6000E539DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Aerial/Info.plist; sourceTree = ""; };
+ FACAF1B31BD9FEEC00E539DC /* AerialView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AerialView.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ FA143CD31BDA3E880041A82B /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ FA143CE61BDA3EEF0041A82B /* AVKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
FACAF1A11BD9FC6000E539DC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -29,10 +63,43 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ FA143CD71BDA3E880041A82B /* Aerial Test */ = {
+ isa = PBXGroup;
+ children = (
+ FA143CD81BDA3E880041A82B /* AppDelegate.swift */,
+ FA143CDA1BDA3E880041A82B /* Assets.xcassets */,
+ FA143CDC1BDA3E880041A82B /* MainMenu.xib */,
+ FA143CDF1BDA3E880041A82B /* Info.plist */,
+ );
+ path = "Aerial Test";
+ sourceTree = "";
+ };
+ FA2D7AA01BDD849E009EA54C /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ FA143CE51BDA3EEF0041A82B /* AVKit.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ FA2D7AA11BDD84A7009EA54C /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ FA143CCA1BDA270A0041A82B /* PreferencesWindow.xib */,
+ FA6746D21BDB850000769DC6 /* icon-day.pdf */,
+ FA6746D31BDB850000769DC6 /* icon-night.pdf */,
+ FACAF1AC1BD9FC6000E539DC /* Info.plist */,
+ );
+ name = Resources;
+ sourceTree = "";
+ };
FACAF19B1BD9FC6000E539DC = {
isa = PBXGroup;
children = (
+ FA2D7AA11BDD84A7009EA54C /* Resources */,
+ FA2D7AA01BDD849E009EA54C /* Frameworks */,
FACAF1A71BD9FC6000E539DC /* Aerial */,
+ FA143CD71BDA3E880041A82B /* Aerial Test */,
FACAF1A61BD9FC6000E539DC /* Products */,
);
sourceTree = "";
@@ -41,6 +108,7 @@
isa = PBXGroup;
children = (
FACAF1A51BD9FC6000E539DC /* Aerial.saver */,
+ FA143CD61BDA3E880041A82B /* Aerial Test.app */,
);
name = Products;
sourceTree = "";
@@ -48,9 +116,10 @@
FACAF1A71BD9FC6000E539DC /* Aerial */ = {
isa = PBXGroup;
children = (
- FACAF1A81BD9FC6000E539DC /* AerialView.h */,
- FACAF1AA1BD9FC6000E539DC /* AerialView.m */,
- FACAF1AC1BD9FC6000E539DC /* Info.plist */,
+ FACAF1B31BD9FEEC00E539DC /* AerialView.swift */,
+ FA143CCC1BDA2EE00041A82B /* PreferencesWindowController.swift */,
+ FA143CE71BDA49BA0041A82B /* AerialVideo.swift */,
+ FA6746CF1BDB7B2B00769DC6 /* CheckCellView.swift */,
);
path = Aerial;
sourceTree = "";
@@ -62,13 +131,29 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
- FACAF1A91BD9FC6000E539DC /* AerialView.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
+ FA143CD51BDA3E880041A82B /* Aerial Test */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = FA143CE01BDA3E880041A82B /* Build configuration list for PBXNativeTarget "Aerial Test" */;
+ buildPhases = (
+ FA143CD21BDA3E880041A82B /* Sources */,
+ FA143CD31BDA3E880041A82B /* Frameworks */,
+ FA143CD41BDA3E880041A82B /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Aerial Test";
+ productName = "Aerial Test";
+ productReference = FA143CD61BDA3E880041A82B /* Aerial Test.app */;
+ productType = "com.apple.product-type.application";
+ };
FACAF1A41BD9FC6000E539DC /* Aerial */ = {
isa = PBXNativeTarget;
buildConfigurationList = FACAF1AF1BD9FC6000E539DC /* Build configuration list for PBXNativeTarget "Aerial" */;
@@ -93,9 +178,13 @@
FACAF19C1BD9FC6000E539DC /* Project object */ = {
isa = PBXProject;
attributes = {
+ LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = "John Coates";
TargetAttributes = {
+ FA143CD51BDA3E880041A82B = {
+ CreatedOnToolsVersion = 7.0;
+ };
FACAF1A41BD9FC6000E539DC = {
CreatedOnToolsVersion = 7.0;
};
@@ -107,6 +196,7 @@
hasScannedForEncodings = 0;
knownRegions = (
en,
+ Base,
);
mainGroup = FACAF19B1BD9FC6000E539DC;
productRefGroup = FACAF1A61BD9FC6000E539DC /* Products */;
@@ -114,32 +204,102 @@
projectRoot = "";
targets = (
FACAF1A41BD9FC6000E539DC /* Aerial */,
+ FA143CD51BDA3E880041A82B /* Aerial Test */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ FA143CD41BDA3E880041A82B /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ FA143CDB1BDA3E880041A82B /* Assets.xcassets in Resources */,
+ FA143CDE1BDA3E880041A82B /* MainMenu.xib in Resources */,
+ FA6746D71BDB850000769DC6 /* icon-night.pdf in Resources */,
+ FA6746D51BDB850000769DC6 /* icon-day.pdf in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
FACAF1A31BD9FC6000E539DC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ FA143CCB1BDA270A0041A82B /* PreferencesWindow.xib in Resources */,
+ FA6746D61BDB850000769DC6 /* icon-night.pdf in Resources */,
+ FA6746D41BDB850000769DC6 /* icon-day.pdf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ FA143CD21BDA3E880041A82B /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ FA143CE31BDA3E910041A82B /* PreferencesWindowController.swift in Sources */,
+ FA143CD91BDA3E880041A82B /* AppDelegate.swift in Sources */,
+ FA143CE91BDA4A9E0041A82B /* AerialVideo.swift in Sources */,
+ FA6746D11BDB7B3300769DC6 /* CheckCellView.swift in Sources */,
+ FA6746D81BDB9DCE00769DC6 /* AerialView.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
FACAF1A01BD9FC6000E539DC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- FACAF1AB1BD9FC6000E539DC /* AerialView.m in Sources */,
+ FA143CCD1BDA2EE00041A82B /* PreferencesWindowController.swift in Sources */,
+ FACAF1B41BD9FEEC00E539DC /* AerialView.swift in Sources */,
+ FA143CE81BDA49BA0041A82B /* AerialVideo.swift in Sources */,
+ FA6746D01BDB7B2B00769DC6 /* CheckCellView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
+/* Begin PBXVariantGroup section */
+ FA143CDC1BDA3E880041A82B /* MainMenu.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ FA143CDD1BDA3E880041A82B /* Base */,
+ );
+ name = MainMenu.xib;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
/* Begin XCBuildConfiguration section */
+ FA143CE11BDA3E880041A82B /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "-";
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = "Aerial Test/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.johncoates.Aerial-Test";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ FA143CE21BDA3E880041A82B /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "-";
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = "Aerial Test/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.johncoates.Aerial-Test";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
FACAF1AD1BD9FC6000E539DC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -175,7 +335,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.11;
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@@ -211,7 +371,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.11;
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
};
@@ -220,11 +380,17 @@
FACAF1B01BD9FC6000E539DC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
+ DEFINES_MODULE = YES;
+ EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
INFOPLIST_FILE = Aerial/Info.plist;
INSTALL_PATH = "$(HOME)/Library/Screen Savers";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johncoates.Aerial;
PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Aerial/Aerial-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
WRAPPER_EXTENSION = saver;
};
name = Debug;
@@ -232,11 +398,16 @@
FACAF1B11BD9FC6000E539DC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
+ DEFINES_MODULE = YES;
+ EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
INFOPLIST_FILE = Aerial/Info.plist;
INSTALL_PATH = "$(HOME)/Library/Screen Savers";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johncoates.Aerial;
PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Aerial/Aerial-Bridging-Header.h";
WRAPPER_EXTENSION = saver;
};
name = Release;
@@ -244,6 +415,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ FA143CE01BDA3E880041A82B /* Build configuration list for PBXNativeTarget "Aerial Test" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ FA143CE11BDA3E880041A82B /* Debug */,
+ FA143CE21BDA3E880041A82B /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
FACAF19F1BD9FC6000E539DC /* Build configuration list for PBXProject "Aerial" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -260,6 +440,7 @@
FACAF1B11BD9FC6000E539DC /* Release */,
);
defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
diff --git a/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/Aerial.xcscheme b/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/Aerial.xcscheme
index dbd16b19..904547b4 100644
--- a/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/Aerial.xcscheme
+++ b/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/Aerial.xcscheme
@@ -5,6 +5,22 @@
+
+
+
+
+
+
+
+
+
+
@@ -35,6 +60,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
+
+
+
+
diff --git a/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/xcschememanagement.plist b/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/xcschememanagement.plist
index 895aea8f..b1affd9b 100644
--- a/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Aerial.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -4,6 +4,11 @@
SchemeUserState
+ Aerial Test.xcscheme
+
+ orderHint
+ 1
+
Aerial.xcscheme
orderHint
@@ -12,6 +17,11 @@
SuppressBuildableAutocreation
+ FA143CD51BDA3E880041A82B
+
+ primary
+
+
FACAF1A41BD9FC6000E539DC
primary
diff --git a/Aerial/Aerial-Bridging-Header.h b/Aerial/Aerial-Bridging-Header.h
new file mode 100644
index 00000000..1b2cb5d6
--- /dev/null
+++ b/Aerial/Aerial-Bridging-Header.h
@@ -0,0 +1,4 @@
+//
+// Use this file to import your target's public headers that you would like to expose to Swift.
+//
+
diff --git a/Aerial/AerialVideo.swift b/Aerial/AerialVideo.swift
new file mode 100644
index 00000000..6fabbb1e
--- /dev/null
+++ b/Aerial/AerialVideo.swift
@@ -0,0 +1,28 @@
+//
+// AerialVideo.swift
+// Aerial
+//
+// Created by John Coates on 10/23/15.
+// Copyright © 2015 John Coates. All rights reserved.
+//
+
+import Foundation
+
+
+class AerialVideo {
+ let id:String;
+ let name:String;
+ let type:String;
+ let timeOfDay:String;
+ let url:NSURL;
+ var arrayPosition:Int = 1;
+
+
+ init(id:String, name:String, type:String, timeOfDay:String, url:String) {
+ self.id = id;
+ self.name = name;
+ self.type = type;
+ self.timeOfDay = timeOfDay;
+ self.url = NSURL(string:url)!;
+ }
+}
\ No newline at end of file
diff --git a/Aerial/AerialView.h b/Aerial/AerialView.h
deleted file mode 100644
index 0fc44f90..00000000
--- a/Aerial/AerialView.h
+++ /dev/null
@@ -1,13 +0,0 @@
-//
-// AerialView.h
-// Aerial
-//
-// Created by John Coates on 10/22/15.
-// Copyright © 2015 John Coates. All rights reserved.
-//
-
-#import
-
-@interface AerialView : ScreenSaverView
-
-@end
diff --git a/Aerial/AerialView.m b/Aerial/AerialView.m
deleted file mode 100644
index 0f65b63b..00000000
--- a/Aerial/AerialView.m
+++ /dev/null
@@ -1,52 +0,0 @@
-//
-// AerialView.m
-// Aerial
-//
-// Created by John Coates on 10/22/15.
-// Copyright © 2015 John Coates. All rights reserved.
-//
-
-#import "AerialView.h"
-
-@implementation AerialView
-
-- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
-{
- self = [super initWithFrame:frame isPreview:isPreview];
- if (self) {
- [self setAnimationTimeInterval:1/30.0];
- }
- return self;
-}
-
-- (void)startAnimation
-{
- [super startAnimation];
-}
-
-- (void)stopAnimation
-{
- [super stopAnimation];
-}
-
-- (void)drawRect:(NSRect)rect
-{
- [super drawRect:rect];
-}
-
-- (void)animateOneFrame
-{
- return;
-}
-
-- (BOOL)hasConfigureSheet
-{
- return NO;
-}
-
-- (NSWindow*)configureSheet
-{
- return nil;
-}
-
-@end
diff --git a/Aerial/AerialView.swift b/Aerial/AerialView.swift
new file mode 100644
index 00000000..08243667
--- /dev/null
+++ b/Aerial/AerialView.swift
@@ -0,0 +1,402 @@
+//
+// AerialView.swift
+// Aerial
+//
+// Created by John Coates on 10/22/15.
+// Copyright © 2015 John Coates. All rights reserved.
+//
+
+import Foundation
+import ScreenSaver
+import AVFoundation
+import AVKit
+
+typealias manifestLoadCallback = ([AerialVideo]) -> (Void);
+
+// shuffling thanks to Nate Cook http://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift
+extension CollectionType {
+ /// Return a copy of `self` with its elements shuffled
+ func shuffle() -> [Generator.Element] {
+ var list = Array(self)
+ list.shuffleInPlace()
+ return list
+ }
+}
+
+extension MutableCollectionType where Index == Int {
+ /// Shuffle the elements of `self` in-place.
+ mutating func shuffleInPlace() {
+ // empty and single-element collections don't shuffle
+ if count < 2 { return }
+
+ for i in 0.. 0) {
+ callback(loadedManifest);
+ }
+ else {
+ callbacks.append(callback);
+ }
+ }
+
+ func randomVideo() -> AerialVideo? {
+
+ let shuffled = loadedManifest.shuffle();
+
+ for video in shuffled {
+ let possible = defaults.objectForKey(video.id);
+
+ if let possible = possible as? NSNumber {
+ if possible.boolValue == false {
+ continue;
+ }
+ }
+
+ return video;
+ }
+
+ // nothing available??? return first thing we find
+ return shuffled.first;
+ }
+
+ init() {
+ // start loading right away!
+ let completionHandler = { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
+ guard let data = data else {
+ NSLog("Couldn't load manifest!");
+ return;
+ }
+
+ if let error = error {
+ NSLog("Error! \(error)");
+ return;
+ }
+
+ var videos = [AerialVideo]();
+
+ do {
+ let batches = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as! Array;
+
+ for batch:NSDictionary in batches {
+ let assets = batch["assets"] as! Array;
+
+ for item in assets {
+ let url = item["url"] as! String;
+ let name = item["accessibilityLabel"] as! String;
+ let timeOfDay = item["timeOfDay"] as! String;
+ let id = item["id"] as! String;
+ let type = item["type"] as! String;
+
+ if (type != "video") {
+ continue;
+ }
+
+
+ let video = AerialVideo(id: id, name: name, type: type, timeOfDay: timeOfDay, url: url);
+
+ videos.append(video)
+ }
+ }
+
+ self.loadedManifest = videos;
+ NSLog("loaded videos: \(videos)");
+
+ // callbacks
+ for callback in self.callbacks {
+ callback(videos);
+ }
+ self.callbacks.removeAll()
+
+ }
+ catch {
+ NSLog("Error retrieving content listing.");
+ return;
+ }
+
+
+ };
+ let url = NSURL(string: "http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/videos/entries.json");
+ let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler:completionHandler);
+ task.resume();
+ }
+}
+
+
+@objc(AerialView) class AerialView : ScreenSaverView {
+ var playerView: AVPlayerView!
+ var playerLayer:AVPlayerLayer!
+ var preferencesController:PreferencesWindowController?
+ static var players:[AVPlayer] = [AVPlayer]()
+ static var previewPlayer:AVPlayer?
+
+ var player:AVPlayer?
+ static let defaults:NSUserDefaults = ScreenSaverDefaults(forModuleWithName: "com.JohnCoates.Aerial")! as ScreenSaverDefaults
+
+ static var sharingPlayers:Bool {
+ defaults.synchronize();
+ return !defaults.boolForKey("differentDisplays");
+ }
+
+ static var singlePlayerAlreadySetup:Bool = false;
+ class var sharedPlayer: AVPlayer {
+ struct Static {
+ static let instance: AVPlayer = AVPlayer();
+ static var _player:AVPlayer?;
+ static var player:AVPlayer {
+ if let activePlayer = _player {
+ NSLog("returning existing player: %@", activePlayer);
+ return activePlayer;
+ }
+ NSLog("preview.... constructing new player!");
+// let movieURL = NSURL(string: "http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/videos/b10-2.mov");
+// let asset = AVAsset(URL: movieURL!);
+//
+// let item = AVPlayerItem(asset: asset);
+ _player = AVPlayer();
+ return _player!;
+ }
+ }
+
+ return Static.player;
+ }
+
+ override init?(frame: NSRect, isPreview: Bool) {
+ super.init(frame: frame, isPreview: isPreview)
+
+ self.animationTimeInterval = 1.0 / 30.0
+ setup()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ setup()
+ }
+
+ deinit {
+ NSLog("deinit AerialView");
+ NSNotificationCenter.defaultCenter().removeObserver(self);
+
+ // set player item to nil if not preview player
+ if player != AerialView.previewPlayer {
+ player?.rate = 0;
+ player?.replaceCurrentItemWithPlayerItem(nil);
+ }
+
+ guard let player = self.player else {
+ return;
+ }
+
+ // Remove from player index
+
+ let indexMaybe = AerialView.players.indexOf(player)
+
+ guard let index = indexMaybe else {
+ return;
+ }
+
+ AerialView.players.removeAtIndex(index);
+ }
+
+
+ func setupPlayerLayer(withPlayer player:AVPlayer) {
+ self.layer = CALayer()
+ guard let layer = self.layer else {
+ NSLog("Aerial Errror: Couldn't create CALayer");
+ return;
+ }
+ self.wantsLayer = true
+ layer.backgroundColor = NSColor.blackColor().CGColor
+ layer.delegate = self;
+ layer.needsDisplayOnBoundsChange = true;
+ layer.frame = self.bounds
+
+ playerLayer = AVPlayerLayer(player: player);
+
+ playerLayer.frame = layer.bounds;
+ layer.addSublayer(playerLayer);
+ }
+ func setupPlayerView(withPlayer player:AVPlayer) {
+ playerView = AVPlayerView()
+ playerView.frame = self.bounds
+ playerView.autoresizingMask = [.ViewHeightSizable, .ViewWidthSizable]
+ playerView.controlsStyle = .None
+ playerView.player = player;
+ playerView.videoGravity = AVLayerVideoGravityResizeAspectFill;
+ self.addSubview(playerView);
+ }
+ func setup() {
+
+ var localPlayer:AVPlayer?
+
+ if (!self.preview) {
+ // check if we should share preview's player
+ if (AerialView.players.count == 0) {
+ if AerialView.previewPlayer != nil {
+ localPlayer = AerialView.previewPlayer;
+ }
+ }
+ }
+
+
+ if localPlayer == nil {
+ if AerialView.sharingPlayers {
+ if AerialView.previewPlayer != nil {
+ localPlayer = AerialView.previewPlayer
+ }
+ else {
+ localPlayer = AerialView.sharedPlayer;
+ }
+ }
+ else {
+ localPlayer = AVPlayer();
+ }
+ }
+
+ guard let player = localPlayer else {
+ NSLog("Aerial: Couldn't create AVPlayer!");
+ return;
+ }
+
+ self.player = player;
+
+ if (self.preview) {
+ AerialView.previewPlayer = player;
+ }
+ else if (AerialView.sharingPlayers == false) {
+ // add to player list
+ AerialView.players.append(player);
+ }
+
+// setupPlayerLayer(withPlayer: player);
+ // view allows for video gravity
+ setupPlayerView(withPlayer: player);
+
+
+ if (AerialView.sharingPlayers == true && AerialView.singlePlayerAlreadySetup) {
+ return;
+ }
+
+ AerialView.singlePlayerAlreadySetup = true;
+
+
+ ManifestLoader.instance.addCallback { (videos:[AerialVideo]) -> Void in
+ self.playNextVideo(player);
+ };
+ }
+
+ /*
+public let AVPlayerItemTimeJumpedNotification: String // the item's current time has changed discontinuously
+@available(OSX 10.7, *)
+public let AVPlayerItemDidPlayToEndTimeNotification: String // item has played to its end time
+@available(OSX 10.7, *)
+public let AVPlayerItemFailedToPlayToEndTimeNotification: String // item has failed to play to its end time
+@available(OSX 10.9, *)
+public let AVPlayerItemPlaybackStalledNotification: String // media did not arrive in time to continue playback
+@available(OSX 10.9, *)
+public let AVPlayerItemNewAccessLogEntryNotification: String // a new access log entry has been added
+@available(OSX 10.9, *)
+public let AVPlayerItemNewErrorLogEntryNotification: String // a new error log entry has been added
+
+// notification userInfo key type
+@available(OSX 10.7, *)
+public let AVPlayerItemFailedToPlayToEndTimeErrorKey: String // NSError
+*/
+ func playerItemFailedtoPlayToEnd(aNotification: NSNotification) {
+ NSLog("AVPlayerItemFailedToPlayToEndTimeNotification \(aNotification)");
+ }
+
+ func playerItemNewErrorLogEntryNotification(aNotification: NSNotification) {
+ NSLog("AVPlayerItemNewErrorLogEntryNotification \(aNotification)");
+ }
+
+ func playerItemPlaybackStalledNotification(aNotification: NSNotification) {
+ NSLog("AVPlayerItemPlaybackStalledNotification \(aNotification)");
+ }
+
+ func playerItemDidReachEnd(aNotification: NSNotification) {
+ NSLog("played did reach end");
+ NSLog("notification: \(aNotification)");
+ guard let player = self.player else {
+ return;
+ }
+
+ NSLog("playing next video for player \(player)");
+
+ // play another video
+ playNextVideo(player);
+ }
+
+ func playNextVideo(player:AVPlayer) {
+ let randomVideo = ManifestLoader.instance.randomVideo();
+
+ guard let video = randomVideo else {
+ NSLog("error grabbing random video!");
+ return;
+ }
+ let videoURL = video.url;
+// let videoURL = NSURL(string:"http://localhost/test.mov")!;
+
+ let asset = AVAsset(URL: videoURL);
+
+ let item = AVPlayerItem(asset: asset);
+ player.replaceCurrentItemWithPlayerItem(item);
+
+ NSLog("playing video: \(video.url)");
+ if player.rate == 0 {
+ player.play();
+ }
+
+ guard let currentItem = player.currentItem else {
+ NSLog("no current item!");
+ return;
+ }
+
+ let notificationCenter = NSNotificationCenter.defaultCenter()
+
+ // remove old entries
+ notificationCenter.removeObserver(self);
+
+ NSLog("observing current item \(currentItem)");
+ notificationCenter.addObserver(self, selector: "playerItemDidReachEnd:", name: AVPlayerItemDidPlayToEndTimeNotification, object: currentItem);
+ notificationCenter.addObserver(self, selector: "playerItemNewErrorLogEntryNotification:", name: AVPlayerItemNewErrorLogEntryNotification, object: currentItem);
+ notificationCenter.addObserver(self, selector: "playerItemFailedtoPlayToEnd:", name: AVPlayerItemFailedToPlayToEndTimeNotification, object: currentItem);
+ notificationCenter.addObserver(self, selector: "playerItemPlaybackStalledNotification:", name: AVPlayerItemPlaybackStalledNotification, object: currentItem);
+ player.actionAtItemEnd = AVPlayerActionAtItemEnd.None;
+ }
+
+ override func hasConfigureSheet() -> Bool {
+ return true;
+ }
+
+ override func configureSheet() -> NSWindow? {
+ if let controller = preferencesController {
+ return controller.window
+ }
+
+ let controller = PreferencesWindowController(windowNibName: "PreferencesWindow");
+
+ preferencesController = controller;
+// controller.loadWindow();
+// controller.window?.styleMask
+// guard let window = controller.window else {
+// NSLog("no controller :(");
+// return nil;
+// }
+ return controller.window;
+ }
+}
\ No newline at end of file
diff --git a/Aerial/CheckCellView.swift b/Aerial/CheckCellView.swift
new file mode 100644
index 00000000..c95b77a3
--- /dev/null
+++ b/Aerial/CheckCellView.swift
@@ -0,0 +1,44 @@
+//
+// CheckCellView.swift
+// Aerial
+//
+// Created by John Coates on 10/24/15.
+// Copyright © 2015 John Coates. All rights reserved.
+//
+
+import Cocoa
+
+class CheckCellView: NSTableCellView {
+
+ @IBOutlet var checkButton:NSButton!
+
+ var onCheck:((Bool) -> (Void))?;
+
+ override required init(frame frameRect: NSRect) {
+ super.init(frame: frameRect);
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder:coder);
+ }
+
+ override func awakeFromNib() {
+ checkButton.target = self;
+ checkButton.action = "check:";
+ }
+
+ func check(button:AnyObject?) {
+ NSLog("checked!");
+
+ guard let onCheck = self.onCheck else {
+ return;
+ }
+
+ onCheck(checkButton.state == NSOnState);
+ }
+
+ override func drawRect(dirtyRect: NSRect) {
+ super.drawRect(dirtyRect)
+ }
+
+}
diff --git a/Aerial/Info.plist b/Aerial/Info.plist
index 445ec3d6..a8fb7e75 100644
--- a/Aerial/Info.plist
+++ b/Aerial/Info.plist
@@ -7,7 +7,7 @@
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
+ com.JohnCoates.Aerial
CFBundleInfoDictionaryVersion
6.0
CFBundleName
@@ -20,8 +20,6 @@
????
CFBundleVersion
1
- NSHumanReadableCopyright
- Copyright © 2015 John Coates. All rights reserved.
NSPrincipalClass
AerialView
diff --git a/Aerial/PreferencesWindow.xib b/Aerial/PreferencesWindow.xib
new file mode 100644
index 00000000..a557fad2
--- /dev/null
+++ b/Aerial/PreferencesWindow.xib
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Aerial/PreferencesWindowController.swift b/Aerial/PreferencesWindowController.swift
new file mode 100644
index 00000000..1c8ad90e
--- /dev/null
+++ b/Aerial/PreferencesWindowController.swift
@@ -0,0 +1,427 @@
+//
+// PreferencesWindowController.swift
+// Aerial
+//
+// Created by John Coates on 10/23/15.
+// Copyright © 2015 John Coates. All rights reserved.
+//
+
+import Cocoa
+import AVKit
+import AVFoundation
+import ScreenSaver
+
+class TimeOfDay {
+ let title:String;
+ var videos:[AerialVideo] = [AerialVideo]()
+
+ init(title:String) {
+ self.title = title;
+ }
+
+}
+
+class City {
+ var night:TimeOfDay = TimeOfDay(title: "night");
+ var day:TimeOfDay = TimeOfDay(title: "day");
+ let name:String;
+
+ init(name:String) {
+ self.name = name;
+ }
+
+ func addVideoForTimeOfDay(timeOfDay:String, video:AerialVideo) {
+ if timeOfDay.lowercaseString == "night" {
+ video.arrayPosition = night.videos.count;
+ night.videos.append(video);
+ }
+ else {
+ video.arrayPosition = day.videos.count;
+ day.videos.append(video);
+ }
+ }
+}
+
+@objc(PreferencesWindowController) class PreferencesWindowController: NSWindowController, NSOutlineViewDataSource, NSOutlineViewDelegate {
+
+ @IBOutlet var outlineView:NSOutlineView?
+ @IBOutlet var playerView:AVPlayerView!
+ @IBOutlet var differentAerialCheckbox:NSButton!
+ @IBOutlet var projectPageLink:NSButton!
+
+ var player:AVPlayer = AVPlayer()
+ let defaults:NSUserDefaults = ScreenSaverDefaults(forModuleWithName: "com.JohnCoates.Aerial")! as ScreenSaverDefaults
+
+ var videos:[AerialVideo]?
+ // cities -> time of day -> videos
+ var cities = [City]();
+
+ static var loadedJSON:Bool = false;
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ if let previewPlayer = AerialView.previewPlayer {
+ self.player = previewPlayer;
+ }
+
+ outlineView?.floatsGroupRows = false;
+ loadJSON();
+
+ playerView.player = player;
+ playerView.controlsStyle = .None;
+ playerView.videoGravity = AVLayerVideoGravityResizeAspectFill;
+ defaults.synchronize();
+
+ if (defaults.boolForKey("differentDisplays")) {
+ differentAerialCheckbox.state = NSOnState;
+ }
+
+ // blue link
+ let color = NSColor(calibratedRed:0.18, green:0.39, blue:0.76, alpha:1);
+ let coloredTitle = NSMutableAttributedString(attributedString: projectPageLink.attributedTitle);
+ let titleRange = NSMakeRange(0, coloredTitle.length);
+ coloredTitle.addAttribute(NSForegroundColorAttributeName, value: color, range: titleRange);
+ projectPageLink.attributedTitle = coloredTitle;
+ }
+
+
+ required init?(coder decoder: NSCoder) {
+ super.init(coder: decoder)
+ NSLog("initted!");
+ }
+ override init(window: NSWindow?) {
+ super.init(window: window)
+ NSLog("initted!");
+
+ }
+ override func windowDidLoad() {
+ super.windowDidLoad()
+ }
+
+ @IBAction func outlineViewSettingsClick(button:NSButton) {
+ let menu = NSMenu()
+ menu.insertItemWithTitle("Uncheck All",
+ action: "outlineViewUncheckAll:",
+ keyEquivalent: "",
+ atIndex: 0);
+
+ menu.insertItemWithTitle("Check All",
+ action: "outlineViewCheckAll:",
+ keyEquivalent: "",
+ atIndex: 1);
+
+ let event = NSApp.currentEvent;
+ NSMenu.popUpContextMenu(menu, withEvent: event!, forView: button);
+ }
+
+
+ func outlineViewUncheckAll(button:NSButton) {
+ setChecked(false);
+ }
+
+ func outlineViewCheckAll(button:NSButton) {
+ setChecked(true);
+ }
+
+ func setChecked(checked:Bool) {
+ guard let videos = videos else {
+ return;
+ }
+
+ for video in videos {
+ self.defaults.setBool(checked, forKey: video.id);
+ }
+ self.defaults.synchronize();
+
+ outlineView!.reloadData()
+ }
+
+ @IBAction func differentAerialsOnEachDisplayCheckClick(button:NSButton?) {
+ let state = differentAerialCheckbox.state;
+
+ let onState:Bool = state == NSOnState;
+
+ defaults.setBool(onState, forKey: "differentDisplays");
+ defaults.synchronize();
+
+ NSLog("set differentDisplays to \(onState)");
+ }
+
+ @IBAction func pageProjectClick(button:NSButton?) {
+ NSWorkspace.sharedWorkspace().openURL(NSURL(string: "http://github.com/JohnCoates/Aerial")!);
+ }
+
+ func loadJSON() {
+ if (PreferencesWindowController.loadedJSON) {
+ return;
+ }
+
+ NSLog("loading JSON!");
+
+ PreferencesWindowController.loadedJSON = true;
+
+ let completionHandler = { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
+ guard let data = data else {
+ return;
+ }
+
+ var videos = [AerialVideo]();
+ var cities = [String:City]();
+
+ do {
+ let batches = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as! Array;
+
+ for batch:NSDictionary in batches {
+ let assets = batch["assets"] as! Array;
+
+ for item in assets {
+ let url = item["url"] as! String;
+ let name = item["accessibilityLabel"] as! String;
+ let timeOfDay = item["timeOfDay"] as! String;
+ let id = item["id"] as! String;
+ let type = item["type"] as! String;
+
+ if (type != "video") {
+ continue;
+ }
+
+ // check if city name has dictionary
+ if cities.keys.contains(name) == false {
+ cities[name] = City(name: name);
+ }
+
+ let city:City = cities[name]!;
+
+ let video = AerialVideo(id: id, name: name, type: type, timeOfDay: timeOfDay, url: url);
+
+ city.addVideoForTimeOfDay(timeOfDay, video: video);
+ videos.append(video)
+
+ NSLog("id: \(id), name: \(name), time of day: \(timeOfDay), url: \(url)");
+ }
+ }
+
+ self.videos = videos;
+ // sort cities by name
+ let unsortedCities = cities.values;
+ let sortedCities = unsortedCities.sort { $0.name < $1.name };
+
+ self.cities = sortedCities;
+ dispatch_async(dispatch_get_main_queue(), { () -> Void in
+ self.outlineView?.reloadData();
+ self.outlineView?.expandItem(nil, expandChildren: true);
+ })
+ NSLog("reloading outline view\(self.outlineView)");
+ }
+ catch {
+ NSLog("Error retrieving content listing.");
+ return;
+ }
+
+
+ };
+ let url = NSURL(string: "http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/videos/entries.json");
+ let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler:completionHandler);
+ task.resume();
+ }
+
+ @IBAction func close(sender: AnyObject?) {
+ NSApp.mainWindow?.endSheet(window!);
+ }
+
+
+ // MARK: - Outline View Delegate & Data Source
+
+
+ func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
+ if item == nil {
+ NSLog("count: \(cities.count)");
+ return cities.count;
+ }
+
+ switch item {
+ case is TimeOfDay:
+ let timeOfDay = item as! TimeOfDay;
+ return timeOfDay.videos.count;
+ case is City:
+ let city = item as! City;
+
+ var count = 0;
+
+ if city.night.videos.count > 0 {
+ count++;
+ }
+
+ if city.day.videos.count > 0 {
+ count++;
+ }
+
+ return count;
+ default:
+ return 0;
+ }
+
+ }
+
+ func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
+ switch item {
+ case is TimeOfDay:
+ return true;
+ case is City: // cities
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
+ if item == nil {
+ return cities[index];
+ }
+
+ switch item {
+ case is City:
+ let city = item as! City;
+
+ if (index == 0 && city.day.videos.count > 0) {
+ return city.day;
+ }
+ else {
+ return city.night;
+ }
+
+ case is TimeOfDay:
+ let timeOfDay = item as! TimeOfDay;
+ return timeOfDay.videos[index];
+
+ default:
+ return false;
+ }
+ }
+
+ func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
+ switch item {
+ case is City:
+ let city = item as! City;
+ return city.name;
+ case is TimeOfDay:
+ let timeOfDay = item as! TimeOfDay;
+ return timeOfDay.title;
+
+ default:
+ return "untitled";
+ }
+ }
+
+ func outlineView(outlineView: NSOutlineView, shouldEditTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> Bool {
+ return false;
+ }
+
+ func outlineView(outlineView: NSOutlineView, dataCellForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSCell? {
+ let row = outlineView.rowForItem(item);
+ return tableColumn!.dataCellForRow(row) as? NSCell;
+ }
+
+ func outlineView(outlineView: NSOutlineView, isGroupItem item: AnyObject) -> Bool {
+ switch item {
+ case is TimeOfDay:
+ return true;
+ case is City:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {
+ switch item {
+ case is City:
+
+ let city = item as! City;
+ let view = outlineView.makeViewWithIdentifier("HeaderCell", owner: self) as? NSTableCellView
+
+ view?.textField?.stringValue = city.name;
+
+ return view;
+ case is TimeOfDay:
+ let view = outlineView.makeViewWithIdentifier("DataCell", owner: self) as? NSTableCellView
+
+ let timeOfDay = item as! TimeOfDay;
+ view?.textField?.stringValue = timeOfDay.title.capitalizedString;
+ let bundle = NSBundle(forClass: PreferencesWindowController.self);
+ let imagePath = bundle.pathForResource("icon-\(timeOfDay.title)", ofType:"pdf");
+ let image = NSImage(contentsOfFile: imagePath!);
+ view?.imageView?.image = image;
+ return view;
+ case is AerialVideo:
+ let view = outlineView.makeViewWithIdentifier("CheckCell", owner: self) as? CheckCellView
+
+
+ let video = item as! AerialVideo;
+ let number = video.arrayPosition + 1;
+ let numberFormatter = NSNumberFormatter();
+
+ numberFormatter.numberStyle = NSNumberFormatterStyle.SpellOutStyle;
+ let numberString = numberFormatter.stringFromNumber(number);
+ view?.textField?.stringValue = numberString!.capitalizedString;
+
+ let settingValue = defaults.objectForKey(video.id);
+
+ if let settingValue = settingValue as? NSNumber {
+ if settingValue.boolValue == false {
+ view?.checkButton.state = NSOffState;
+ }
+ else {
+ view?.checkButton.state = NSOnState;
+ }
+ }
+ else {
+ view?.checkButton.state = NSOnState;
+ }
+
+
+ view?.onCheck = { (checked:Bool) in
+ self.defaults.setBool(checked, forKey: video.id);
+ self.defaults.synchronize();
+ };
+
+ return view;
+ default:
+ return nil;
+ }
+ }
+
+ func outlineView(outlineView: NSOutlineView, shouldSelectItem item: AnyObject) -> Bool {
+ switch item {
+ case is AerialVideo:
+ let video = item as! AerialVideo;
+
+ let asset = AVAsset(URL: video.url);
+
+ let item = AVPlayerItem(asset: asset);
+ player.replaceCurrentItemWithPlayerItem(item);
+ player.play();
+
+ return true;
+ case is TimeOfDay:
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ func outlineView(outlineView: NSOutlineView, heightOfRowByItem item: AnyObject) -> CGFloat {
+ switch item {
+ case is AerialVideo:
+ return 18;
+ case is TimeOfDay:
+ return 18;
+ case is City:
+ return 18;
+ default:
+ return 0;
+ }
+ }
+
+}
diff --git a/Aerial/icon-day.pdf b/Aerial/icon-day.pdf
new file mode 100644
index 00000000..27c1445d
Binary files /dev/null and b/Aerial/icon-day.pdf differ
diff --git a/Aerial/icon-night.pdf b/Aerial/icon-night.pdf
new file mode 100644
index 00000000..3a2e1fee
Binary files /dev/null and b/Aerial/icon-night.pdf differ
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..e3882312
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 John Coates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 00000000..b0bea4fe
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,44 @@
+![Screencast](https://raw.githubusercontent.com/JohnCoates/Aerial/master/screencast.gif)
+
+## Aerial - Apple TV Aerial Views Screensaver
+Aerial is a OS X screensaver based on the new Apple TV screensaver that displays the aerial movies Apple shot over New York, San Francisco, Hawaii, China, etc.
+
+Aerial is completely open source, so feel free to contribute to its development!
+
+## Download
+Download from [Github](https://github.com/JohnCoates/Aerial/archive/master.zip)
+
+Two ways to install:
+
+**Option A:** Open Aerial.saver and OS X will ask if you'd like it installed.
+
+**Option B:** Place Aerial.saver in ~/Library/Screen Savers
+
+## Using Aerial
+
+* To enable Aerial open System Preferences -> Desktop & Screen Saver -> Screen Saver
+Choose Aerial and click on Screen Saver Options to select your settings.
+
+
+## Features
+* **Auto Load Latest Aerials:** Aerials are loaded directly from Apple, so you're never out of date.
+* **Play Different Aerial On Each Display:** If you've got multiple monitors, this setting loads a different aerial for each of your displays.
+* **Favorites:** You can choose to only have certain aerials play.
+* **Preview:** Clicking on an aerial in the screen saver options previews that aerial for you.
+
+## Community
+- **Find a bug?** [Open an issue](https://github.com/JohnCoates/Aerial/issues/new). Try to be as specific as possible.
+- **Have a feature request** [Open an issue](https://github.com/JohnCoates/Aerial/issues/new). Tell me why this feature would be useful, and why you and others would want it.
+
+## Contribute
+I appreciate all pull requests. Caching hasn't been added yet.
+
+## Changelog
+
+- October 26th, 2015 - 1.0: first release
+
+## License
+[MIT License](https://raw.githubusercontent.com/JohnCoates/Aerial/master/LICENSE)
+
+## Author
+Maintained and created by John Coates [@punksomething](http://twitter.com/punksomething)
diff --git a/screencast.gif b/screencast.gif
new file mode 100644
index 00000000..1be8a8f7
Binary files /dev/null and b/screencast.gif differ