diff --git a/Aerial.saver/Contents/Frameworks/libswiftAVFoundation.dylib b/Aerial.saver/Contents/Frameworks/libswiftAVFoundation.dylib
deleted file mode 100755
index de05f8c4..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftAVFoundation.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftAppKit.dylib b/Aerial.saver/Contents/Frameworks/libswiftAppKit.dylib
deleted file mode 100755
index 1180a54e..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftAppKit.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftCore.dylib b/Aerial.saver/Contents/Frameworks/libswiftCore.dylib
deleted file mode 100755
index 3ed375dd..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftCore.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftCoreAudio.dylib b/Aerial.saver/Contents/Frameworks/libswiftCoreAudio.dylib
deleted file mode 100755
index 895367df..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftCoreAudio.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftCoreData.dylib b/Aerial.saver/Contents/Frameworks/libswiftCoreData.dylib
deleted file mode 100755
index 05013325..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftCoreData.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftCoreGraphics.dylib b/Aerial.saver/Contents/Frameworks/libswiftCoreGraphics.dylib
deleted file mode 100755
index 60d5d821..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftCoreGraphics.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftCoreImage.dylib b/Aerial.saver/Contents/Frameworks/libswiftCoreImage.dylib
deleted file mode 100755
index 45292d33..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftCoreImage.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftCoreMedia.dylib b/Aerial.saver/Contents/Frameworks/libswiftCoreMedia.dylib
deleted file mode 100755
index 56dc0158..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftCoreMedia.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftDarwin.dylib b/Aerial.saver/Contents/Frameworks/libswiftDarwin.dylib
deleted file mode 100755
index 15e5db90..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftDarwin.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftDispatch.dylib b/Aerial.saver/Contents/Frameworks/libswiftDispatch.dylib
deleted file mode 100755
index 81edc42c..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftDispatch.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftFoundation.dylib b/Aerial.saver/Contents/Frameworks/libswiftFoundation.dylib
deleted file mode 100755
index ac63bc71..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftFoundation.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Frameworks/libswiftObjectiveC.dylib b/Aerial.saver/Contents/Frameworks/libswiftObjectiveC.dylib
deleted file mode 100755
index 0cc95355..00000000
Binary files a/Aerial.saver/Contents/Frameworks/libswiftObjectiveC.dylib and /dev/null differ
diff --git a/Aerial.saver/Contents/Info.plist b/Aerial.saver/Contents/Info.plist
deleted file mode 100644
index 7495e3c9..00000000
--- a/Aerial.saver/Contents/Info.plist
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
- BuildMachineOSBuild
- 15B42
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- Aerial
- CFBundleIdentifier
- com.JohnCoates.Aerial
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- Aerial
- CFBundlePackageType
- BNDL
- CFBundleShortVersionString
- 1.2
- CFBundleSignature
- ????
- CFBundleSupportedPlatforms
-
- MacOSX
-
- CFBundleVersion
- 1.2.0
- DTCompiler
- com.apple.compilers.llvm.clang.1_0
- DTPlatformBuild
- 7B91b
- DTPlatformVersion
- GM
- DTSDKBuild
- 15A278
- DTSDKName
- macosx10.11
- DTXcode
- 0710
- DTXcodeBuild
- 7B91b
- LSMinimumSystemVersion
- 10.9
- NSAppTransportSecurity
-
- NSAllowsArbitraryLoads
-
-
- NSPrincipalClass
- AerialView
-
-
diff --git a/Aerial.saver/Contents/MacOS/Aerial b/Aerial.saver/Contents/MacOS/Aerial
deleted file mode 100755
index 7650971b..00000000
Binary files a/Aerial.saver/Contents/MacOS/Aerial and /dev/null differ
diff --git a/Aerial.saver/Contents/Resources/PreferencesWindow.nib b/Aerial.saver/Contents/Resources/PreferencesWindow.nib
deleted file mode 100644
index 6737559f..00000000
Binary files a/Aerial.saver/Contents/Resources/PreferencesWindow.nib and /dev/null differ
diff --git a/Aerial.saver/Contents/Resources/icon-day.pdf b/Aerial.saver/Contents/Resources/icon-day.pdf
deleted file mode 100644
index 27c1445d..00000000
Binary files a/Aerial.saver/Contents/Resources/icon-day.pdf and /dev/null differ
diff --git a/Aerial.saver/Contents/Resources/icon-night.pdf b/Aerial.saver/Contents/Resources/icon-night.pdf
deleted file mode 100644
index 3a2e1fee..00000000
Binary files a/Aerial.saver/Contents/Resources/icon-night.pdf and /dev/null differ
diff --git a/Aerial.saver/Contents/Resources/thumbnail.png b/Aerial.saver/Contents/Resources/thumbnail.png
deleted file mode 100644
index d86a1918..00000000
Binary files a/Aerial.saver/Contents/Resources/thumbnail.png and /dev/null differ
diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj
index 5fec6c9a..16eeab9f 100644
--- a/Aerial.xcodeproj/project.pbxproj
+++ b/Aerial.xcodeproj/project.pbxproj
@@ -10,12 +10,16 @@
030D9B7C21551A8D00961E95 /* AerialPlayerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E2E5D1FC62E8B00E5F320 /* AerialPlayerItem.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 */; };
+ 033192E2217B78240073B580 /* en.json in Resources */ = {isa = PBXBuildFile; fileRef = 033192E0217B78240073B580 /* en.json */; };
033D62AB216CADCD00F3AF83 /* icon-day-dark.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 033D62AA216CADCD00F3AF83 /* icon-day-dark.pdf */; };
033D62AC216CADCD00F3AF83 /* icon-day-dark.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 033D62AA216CADCD00F3AF83 /* icon-day-dark.pdf */; };
033D62AD216CADCD00F3AF83 /* icon-day-dark.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 033D62AA216CADCD00F3AF83 /* icon-day-dark.pdf */; };
033D62AF216CAE2C00F3AF83 /* icon-night-dark.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 033D62AE216CAE2C00F3AF83 /* icon-night-dark.pdf */; };
033D62B0216CAE2C00F3AF83 /* icon-night-dark.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 033D62AE216CAE2C00F3AF83 /* icon-night-dark.pdf */; };
033D62B1216CAE2C00F3AF83 /* icon-night-dark.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 033D62AE216CAE2C00F3AF83 /* icon-night-dark.pdf */; };
+ 03893CB3217749F0008E7125 /* ErrorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03893CB2217749F0008E7125 /* ErrorLog.swift */; };
+ 03893CB4217753AC008E7125 /* ErrorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03893CB2217749F0008E7125 /* ErrorLog.swift */; };
0393857A2175D4B80040B850 /* AVPlayerViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039385792175D4B80040B850 /* AVPlayerViewExtension.swift */; };
0393857B2175D4B80040B850 /* AVPlayerViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039385792175D4B80040B850 /* AVPlayerViewExtension.swift */; };
0393857C2175D4B80040B850 /* AVPlayerViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039385792175D4B80040B850 /* AVPlayerViewExtension.swift */; };
@@ -59,8 +63,6 @@
FAC36F651BE1772F007F2A20 /* CollectionType+Shuffling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC36F631BE1772F007F2A20 /* CollectionType+Shuffling.swift */; };
FAC36F671BE1778C007F2A20 /* ManifestLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC36F661BE1778C007F2A20 /* ManifestLoader.swift */; };
FAC36F681BE1778C007F2A20 /* ManifestLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC36F661BE1778C007F2A20 /* ManifestLoader.swift */; };
- FAC36F6A1BE1780B007F2A20 /* Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC36F691BE1780B007F2A20 /* Debug.swift */; };
- FAC36F6B1BE1780B007F2A20 /* Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC36F691BE1780B007F2A20 /* Debug.swift */; };
FAF450211BE2B45D00C1F98A /* VideoLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF450201BE2B45D00C1F98A /* VideoLoader.swift */; };
FAF450221BE2B45D00C1F98A /* VideoLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF450201BE2B45D00C1F98A /* VideoLoader.swift */; };
FAF450241BE2D2FD00C1F98A /* VideoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF450231BE2D2FD00C1F98A /* VideoCache.swift */; };
@@ -79,8 +81,10 @@
/* Begin PBXFileReference section */
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 = ""; };
033D62AE216CAE2C00F3AF83 /* icon-night-dark.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "icon-night-dark.pdf"; sourceTree = ""; };
+ 03893CB2217749F0008E7125 /* ErrorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLog.swift; sourceTree = ""; };
039385792175D4B80040B850 /* AVPlayerViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerViewExtension.swift; sourceTree = ""; };
03A2CB9B216BA9AF0061E8E8 /* VideoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoManager.swift; sourceTree = ""; };
03E8730B2165013C002B469B /* DownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = ""; };
@@ -110,7 +114,6 @@
FAC36F441BE1756D007F2A20 /* CheckCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CheckCellView.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
FAC36F631BE1772F007F2A20 /* CollectionType+Shuffling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CollectionType+Shuffling.swift"; sourceTree = ""; };
FAC36F661BE1778C007F2A20 /* ManifestLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ManifestLoader.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
- FAC36F691BE1780B007F2A20 /* Debug.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Debug.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
FACAF1A51BD9FC6000E539DC /* Aerial.saver */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Aerial.saver; sourceTree = BUILT_PRODUCTS_DIR; };
FAF450201BE2B45D00C1F98A /* VideoLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = VideoLoader.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
FAF450231BE2D2FD00C1F98A /* VideoCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = VideoCache.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
@@ -142,6 +145,14 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 033192DF217B77E90073B580 /* Community */ = {
+ isa = PBXGroup;
+ children = (
+ 033192E0217B78240073B580 /* en.json */,
+ );
+ path = Community;
+ sourceTree = "";
+ };
03E8730D216501B3002B469B /* Downloads */ = {
isa = PBXGroup;
children = (
@@ -191,6 +202,7 @@
FAC36F361BE1756D007F2A20 /* Resources */ = {
isa = PBXGroup;
children = (
+ 033192DF217B77E90073B580 /* Community */,
033D62AA216CADCD00F3AF83 /* icon-day-dark.pdf */,
FAC36F371BE1756D007F2A20 /* icon-day.pdf */,
FAC36F381BE1756D007F2A20 /* icon-night.pdf */,
@@ -231,7 +243,7 @@
FAC36F621BE17701007F2A20 /* Extensions */,
FAC36F401BE1756D007F2A20 /* AerialVideo.swift */,
FAC36F661BE1778C007F2A20 /* ManifestLoader.swift */,
- FAC36F691BE1780B007F2A20 /* Debug.swift */,
+ 03893CB2217749F0008E7125 /* ErrorLog.swift */,
03E8731221675FE0002B469B /* TimeManagement.swift */,
);
path = Models;
@@ -409,6 +421,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 033192E2217B78240073B580 /* en.json in Resources */,
FAC36F541BE1756D007F2A20 /* PreferencesWindow.xib in Resources */,
FAC36F4E1BE1756D007F2A20 /* icon-day.pdf in Resources */,
033D62B0216CAE2C00F3AF83 /* icon-night-dark.pdf in Resources */,
@@ -439,6 +452,7 @@
FAC36F551BE1756D007F2A20 /* thumbnail.png in Resources */,
033D62AF216CAE2C00F3AF83 /* icon-night-dark.pdf in Resources */,
FAC36F4F1BE1756D007F2A20 /* icon-night.pdf in Resources */,
+ 033192E1217B78240073B580 /* en.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -465,6 +479,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 03893CB4217753AC008E7125 /* ErrorLog.swift in Sources */,
03233B692172762C0077D3F9 /* PoiStringProvider.swift in Sources */,
03A2CB9D216BB1490061E8E8 /* VideoManager.swift in Sources */,
03E87314216760B7002B469B /* TimeManagement.swift in Sources */,
@@ -476,7 +491,6 @@
FAC36F5C1BE1756D007F2A20 /* AerialView.swift in Sources */,
FAC36F681BE1778C007F2A20 /* ManifestLoader.swift in Sources */,
FAC36F581BE1756D007F2A20 /* PreferencesWindowController.swift in Sources */,
- FAC36F6B1BE1780B007F2A20 /* Debug.swift in Sources */,
FAB22A7F1BE17D7D0065C0F5 /* AssetLoaderDelegate.swift in Sources */,
FAC36F601BE175CF007F2A20 /* AppDelegate.swift in Sources */,
FA36BD401BE57F8E00D5E03B /* VideoDownload.swift in Sources */,
@@ -510,10 +524,10 @@
FAC36F571BE1756D007F2A20 /* PreferencesWindowController.swift in Sources */,
FAC36F671BE1778C007F2A20 /* ManifestLoader.swift in Sources */,
FAC36F591BE1756D007F2A20 /* AerialVideo.swift in Sources */,
+ 03893CB3217749F0008E7125 /* ErrorLog.swift in Sources */,
AA7E2E5E1FC62E8B00E5F320 /* AerialPlayerItem.swift in Sources */,
03A2CB9C216BA9AF0061E8E8 /* VideoManager.swift in Sources */,
FAF450211BE2B45D00C1F98A /* VideoLoader.swift in Sources */,
- FAC36F6A1BE1780B007F2A20 /* Debug.swift in Sources */,
03E8731321675FE0002B469B /* TimeManagement.swift in Sources */,
FAB22A7E1BE17D7D0065C0F5 /* AssetLoaderDelegate.swift in Sources */,
03233B68217272640077D3F9 /* PoiStringProvider.swift in Sources */,
diff --git a/Aerial/App/AppDelegate.swift b/Aerial/App/AppDelegate.swift
index 59623b20..c3174c51 100644
--- a/Aerial/App/AppDelegate.swift
+++ b/Aerial/App/AppDelegate.swift
@@ -16,14 +16,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
let objects = objectsFromNib(loadNibNamed: "PreferencesWindow")
-
- guard let windowIndex = objects.index(where: { $0 is NSWindow }),
- let preferencesWindow = objects[windowIndex] as? NSWindow
- else {
- fatalError("Missing window object")
+
+ // We need to find the correct window in our nib
+ for object in objects {
+ if object is NSWindow {
+ if (object as! NSWindow).identifier!.rawValue == "preferencesWindow" {
+ setUp(preferencesWindow: (object as! NSWindow))
+ }
+ }
}
-
- setUp(preferencesWindow: preferencesWindow)
}
private func setUp(preferencesWindow window: NSWindow) {
diff --git a/Aerial/App/Resources/Info.plist b/Aerial/App/Resources/Info.plist
index 4772a79e..6e49dcc9 100644
--- a/Aerial/App/Resources/Info.plist
+++ b/Aerial/App/Resources/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.4.1
+ 1.4.2test9
CFBundleSignature
????
CFBundleVersion
diff --git a/Aerial/Resources/Community/en.json b/Aerial/Resources/Community/en.json
new file mode 100644
index 00000000..7f7f946a
--- /dev/null
+++ b/Aerial/Resources/Community/en.json
@@ -0,0 +1,358 @@
+{
+ "version" : "0",
+ "language" : "en",
+ "assets" : [
+ {
+ "id" : "6C3D54AE-0871-498A-81D0-56ED24E5FE9F",
+ "name" : "Korea and Japan Night"
+ },
+ {
+ "id" : "B876B645-3955-420E-99DF-60139E451CF3",
+ "name" :"Wulingyuan National Park 1"
+ },
+ {
+ "id" : "9CCB8297-E9F5-4699-AE1F-890CFBD5E29C",
+ "name" : "Longji Rice Terraces"
+ },
+ {
+ "id" : "D5E76230-81A3-4F65-A1BA-51B8CADED625",
+ "name" : "Wulingyuan National Park 2"
+ },
+ {
+ "id" : "b6-1",
+ "name" : "Great Wall 1"
+ },
+ {
+ "id" : "b2-1",
+ "name" :"Great Wall 2"
+ },
+ {
+ "id" : "b5-1",
+ "name" : "Great Wall 3"
+ },
+
+ {
+ "id" : "AC9C09DD-1D97-4013-A09F-B0F5259E64C3",
+ "name" : "Sheikh Zayed Road"
+ },
+ {
+ "id" : "49790B7C-7D8C-466C-A09E-83E38B6BE87A",
+ "name" :"Marina 1"
+ },
+ {
+ "id" : "02EA5DBE-3A67-4DFA-8528-12901DFD6CC1",
+ "name" : "Downtown"
+ },
+ {
+ "id" : "802866E6-4AAF-4A69-96EA-C582651391F1",
+ "name" : "Marina 2"
+ },
+
+ {
+ "id" : "BAF76353-3475-4855-B7E1-CE96CC9BC3A7",
+ "name" : "Approaching Burj Khalifa"
+ },
+ {
+ "id" : "2F11E857-4F77-4476-8033-4A1E4610AFCC",
+ "name" : "Sheikh Zayed Road"
+ },
+
+ {
+ "id" : "E4ED0B22-EB81-4D4F-A29E-7E1EA6B6D980",
+ "name" : "Nuussuaq Peninsula"
+ },
+ {
+ "id" : "30047FDA-3AE3-4E74-9575-3520AD77865B",
+ "name" : "Ilulissat Icefjord"
+ },
+
+ {
+ "id" : "7D4710EB-5BA4-42E6-AA60-68D77F67D9B9",
+ "name" : "Ilulissat Icefjord"
+ },
+
+ {
+ "id" : "b7-1",
+ "name" : "Laupāhoehoe Nui"
+ },
+ {
+ "id" : "b1-1",
+ "name" : "Waimanu Valley"
+ },
+ {
+ "id" : "b2-2",
+ "name" : "Honopū Valley"
+ },
+ {
+ "id" : "b4-1",
+ "name" : "Pu‘u O ‘Umi"
+ },
+
+ {
+ "id" : "b6-2",
+ "name" : "Kohala coastline"
+ },
+ {
+ "id" : "b8-1",
+ "name" : "Pu‘u O ‘Umi"
+ },
+
+ {
+ "id" : "102C19D1-9D9F-48EC-B492-074C985C4D9F",
+ "name" : "Victoria Harbour 1"
+ },
+ {
+ "id" : "560E09E8-E89D-4ADB-8EEA-4754415383D4",
+ "name" : "Victoria Peak"
+ },
+ {
+ "id" : "024891DE-B7F6-4187-BFE0-E6D237702EF0",
+ "name" : "Wan Chai"
+ },
+ {
+ "id" : "786E674C-BB22-4AA9-9BD3-114D2020EC4D",
+ "name" : "Victoria Harbour 2"
+ },
+
+ {
+ "id" : "30313BC1-BF20-45EB-A7B1-5A6FFDBD2488",
+ "name" : "Victoria Harbour"
+ },
+
+ {
+ "id" : "6E2FC8AC-832D-46CF-B306-BB2A05030C17",
+ "name" : "Liwa Oasis"
+ },
+
+ {
+ "id" : "b6-3",
+ "name" : "River Thames",
+ "pointsOfInterest" : {
+ "0" : "Passing the Gherkin in the City of London, on the right",
+ "15" : "Approaching Tower Bridge on the River Thames, with City Hall behind and the Tower of London to the right",
+ "40" : "Approaching HMS Belfast in the River Thames, passing the Walkie-Talkie on the right",
+ "75" : "Traveling west up the River Thames, with The Shard on the left and St Paul’s Cathedral on the right",
+ "95" : "Passing over Southwark Cathedral, with St Paul's Cathedral on the right",
+ "130" : "Passing Shakespeare’s Globe in Southwark (right foreground)",
+ "195" : "Passing Waterloo Station on the left, with the Palace of Westminster (Houses of Parliament) across the Thames behind",
+ "210" : "Heading toward Westminster over the River Thames"
+ }
+ },
+ {
+ "id" : "b5-2",
+ "name" :"Buckingham Palace",
+ "pointsOfInterest" : {
+ "0" : "Flying over Buckingham Palace and the Victoria Memorial, London",
+ "25" : "Passing Wellington Barracks on the right, following Birdcage Walk along St James’s Park",
+ "65" : "Passing over the Foreign & Commonwealth Office in Whitehall, with Horse Guards Parade on the left and Her Majesty’s Treasury on the right",
+ "75" : "Passing over the Cenotaph, with the Ministy of Defence on the left and Big Ben and the Palace of Westmintser (Houses of Parliament) on the right",
+ "95" : "Crossing the River Thames, flying over the London Eye",
+ "130" : "Passing over Waterloo Station in South London",
+ "242" : "Passing Southwark Cathedral on the left",
+ "255" : "Passing The Shard on the right",
+ "275" : "Passing the cruiser HMS Belfast in the River Thames",
+ "300" : "Crossing the River Thames at Tower Bridge, with City Hall on the right and the Tower of London on the left"
+ }
+ },
+
+ {
+ "id" : "b1-2",
+ "name" : "River Thames near Sunset",
+ "pointsOfInterest" : {
+ "0" : "Heading up the River Thames in London toward Tower Bridge",
+ "25" : "Crossing the River Thames and passing over City Hall",
+ "35" : "Passing the cruiser HMS Belfast in the River Thames",
+ "60" : "Approaching The Shard",
+ "92" : "Flying west over Southwark toward Lambeth in South London",
+ "200" : "Passing Lambeth Palace on the left and Waterloo Station on the right",
+ "225" : "Crossing the River Thames toward the Palace of Westminster (Houses of Parliament)"
+ }
+ },
+ {
+ "id" : "b3-1",
+ "name" : "River Thames at Dusk",
+ "pointsOfInterest" : {
+ "0": "Heading up the River Thames in London toward Tower Bridge",
+ "28" : "Traveling west up the River Thames, past City Hall on the left",
+ "40" : "Passing HMS Belfast in the River Thames, with The Shard on the left and the Walkie-Talkie on the right",
+ "75" : "Passing Southwark Cathedral on the left",
+ "110" : "Passing St Paul’s Cathedral on the right",
+ "130" : "Passing Shakespeare’s Globe in the left foreground",
+ "210" : "Continuing up the River Thames toward Westminster",
+ "310" : "Passing the London Eye on the River Thames",
+ "325" : "Passing Big Ben and the Houses of Parliament, with Westminster Abbey on the right"
+ }
+ },
+
+ {
+ "id" : "829E69BA-BB53-4841-A138-4DF0C2A74236",
+ "name" : "Los Angeles Int’l Airport",
+ "pointsOfInterest" : {
+ "0" : "Heading west over Los Angeles International Airport",
+ "80" : "Passing over the Theme Building",
+ "210" : "Passing over the Tom Bradley International Terminal"
+ }
+ },
+ {
+ "id" : "30A2A488-E708-42E7-9A90-B749A407AE1C",
+ "name" : "Harbor Freeway (Interstate 110)",
+ "pointsOfInterest" : {
+ "0" :"Following the Harbor Freeway (Interstate 110) north through South Los Angeles",
+ "50" : "Crossing the Century Freeway (Interstate 105)",
+ "65" : "Passing over the Harbor Freeway Station on the Metro Green Line, which runs along the Century Freeway",
+ "170" : "Crossing Imperial Highway"
+ }
+ },
+ {
+ "id" : "B730433D-1B3B-4B99-9500-A286BF7A9940",
+ "name" : "Santa Monica Beach"
+ },
+
+ {
+ "id" : "89B1643B-06DD-4DEC-B1B0-774493B0F7B7",
+ "name" : "Griffith Observatory"
+ },
+ {
+ "id" : "EC67726A-8212-4C5E-83CF-8412932740D2",
+ "name" : "Hollywood Hills",
+ "pointsOfInterest" : {
+ "0" : "Traveling north along Beachwood Drive through the Hollywood Hills in Los Angeles",
+ "60" : "Continuing north through Beachwood Canyon",
+ "250" : "Passing the Mount Lee Communications Center, heading toward Burbank, California"
+ }
+ },
+ {
+ "id" : "A284F0BF-E690-4C13-92E2-4672D93E8DE5",
+ "name" : "Downtown",
+ "pointsOfInterest" : {
+ "0" : "Approaching Downtown Los Angeles, with the MTA Building (in yellow) and Union Station (in pink) in the left mid-ground",
+ "60": "Passing the Cathedral of Our Lady of the Angels (in the center)",
+ "80": "Passing the Los Angeles County Music Center (in the center)",
+ "95": "Crossing the Hollywood Freeway, traveling south through Downtown along the Harbor Freeway",
+ "140": "Passing Disney Hall (in the center)",
+ "155": "Passing City Hall (in pale green, in the left mid-ground)",
+ "215": "Passing the Library Tower (in the center)",
+ "235": "Passing the Bonaventure Hotel (in the center)",
+ "265": "The Los Angeles Central Library, brightly lit, appears from behind the Bonaventure Hotel",
+ "310": "Passing the Wilshire Grand Center (under construction)",
+ "400": "Passing L.A. Live",
+ "435": "Passing the Ritz-Carlton Hotel and Staples Center",
+ "465": "Passing the Los Angeles Convention Center",
+ "520": "Approaching the Santa Monica Freeway"
+ }
+ },
+
+ {
+ "id" : "b7-2",
+ "name" : "Central Park"
+ },
+ {
+ "id" : "b1-3",
+ "name" : "Lower Manhattan"
+ },
+ {
+ "id" : "b3-2",
+ "name" : "Upper East Side"
+ },
+
+ {
+ "id" : "b2-3",
+ "name" : "Seventh avenue"
+ },
+ {
+ "id" : "b4-2",
+ "name" : "Lower Manhattan"
+ },
+
+ {
+ "id" : "b8-2",
+ "name" : "Marin Headlands"
+ },
+ {
+ "id" : "b10-3",
+ "name" : "Marin to Golden Gate",
+ "pointsOfInterest" : {
+ "0" : "Moving over the Marin Headlands along Highway 101 toward the Golden Gate Bridge",
+ "20" : "Horseshoe Cove appearing on the left",
+ "130" : "Passing over Battery Spencer",
+ "192" : "Crossing the Golden Gate Bridge toward San Francisco"
+ }
+ },
+ {
+ "id" : "b9-3",
+ "name" : "Bay and Golden Gate"
+ },
+ {
+ "id" : "b8-3",
+ "name" : "Alamo Square",
+ "pointsOfInterest" : {
+ "0" : "Painted Ladies across from Alamo Square in San Francisco",
+ "35" : "St Mary’s Cathedral in the left mid-ground, with downtown SF in the background",
+ "70" : "The dome of City Hall appearing in the right mid-ground",
+ "210" : "The dome of City Hall clear in the right mid-ground"
+ }
+ },
+ {
+ "id" : "b3-3",
+ "name" : "Embarcadero, Market Street",
+ "pointsOfInterest" : {
+ "0" : "Heading southwest over San Francisco Bay to the San Francisco Ferry Building",
+ "110" : "Approaching the Port of San Francisco Ferry Building and the Embarcadero",
+ "160" : "Heading southwest along Market Street in downtown San Francisco"
+ }
+ },
+ {
+ "id" : "b4-3",
+ "name" : "Presidio to Golden Gate",
+ "pointsOfInterest" : {
+ "0" : "Moving along the Presidio of San Francisco toward the Golden Gate Bridge",
+ "190" : "Crossing the Golden Gate Bridge toward the Marin Headlands"
+ }
+ },
+
+ {
+ "id" : "b6-4",
+ "name" : "Downtown and Coit Tower",
+ "pointsOfInterest" : {
+ "0" : "Downtown San Francisco with the Transamerica Pyramid in the center",
+ "100" : "Downtown San Francisco with Coit Tower coming into view"
+ }
+ },
+ {
+ "id" : "b7-3",
+ "name" : "Fisherman’s Wharf",
+ "pointsOfInterest" : {
+ "0" : "Heading southeast over Fisherman’s Wharf in San Francisco",
+ "30" : "Passing the Liberty ship SS Jeremiah O’Brien",
+ "50" : "Passing over the submarine USS Pampanito",
+ "80" : "Passing over North Beach in San Francisco",
+ "120" : "Approaching Coit Tower on Telegraph Hill, with the Bay Bridge and downtown SF in the background",
+ "150" : "Passing Coit Tower on Telegraph Hill, with Sts Peter and Paul Church in the right mid-ground"
+ }
+ },
+ {
+ "id" : "b5-3",
+ "name" : "Embarcadero, Market Street",
+ "pointsOfInterest" : {
+ "0" : "Heading southwest over San Francisco Bay to the San Francisco Ferry Building",
+ "150" : "Approaching the Port of San Francisco Ferry Building and the Embarcadero",
+ "210" : "Heading southwest along Market Street in San Francisco"
+ }
+ },
+ {
+ "id" : "b1-4",
+ "name" : "Bay Bridge"
+ },
+ {
+ "id" : "b2-4",
+ "name" : "Downtown and Sutro Tower",
+ "pointsOfInterest" : {
+ "0" : "Heading into San Francisco’s Financial District, with Sutro Tower in the background",
+ "10" : "Passing the Transamerica Pyramid",
+ "50" : "Former Bank of America headquarters blotting out the sun"
+ }
+ }
+ ]
+}
+
+
diff --git a/Aerial/Resources/Info.plist b/Aerial/Resources/Info.plist
index 467f233c..3a3fec1e 100644
--- a/Aerial/Resources/Info.plist
+++ b/Aerial/Resources/Info.plist
@@ -15,11 +15,11 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.4.1
+ 1.4.2beta3
CFBundleSignature
????
CFBundleVersion
- 1.4.1
+ 1.4.2beta3
LSMinimumSystemVersion
${MACOSX_DEPLOYMENT_TARGET}
NSAppTransportSecurity
diff --git a/Aerial/Resources/PreferencesWindow.xib b/Aerial/Resources/PreferencesWindow.xib
index fe9d48b5..d6d879a7 100644
--- a/Aerial/Resources/PreferencesWindow.xib
+++ b/Aerial/Resources/PreferencesWindow.xib
@@ -11,7 +11,7 @@
-
+
@@ -19,8 +19,11 @@
-
+
+
+
+
@@ -31,6 +34,9 @@
+
+
+
@@ -44,21 +50,24 @@
+
+
-
+
+
@@ -81,7 +90,7 @@
-
+
@@ -169,7 +178,7 @@
-
+
@@ -395,7 +404,7 @@ is disabled
@@ -629,7 +638,7 @@ should appear
-
+
@@ -640,17 +649,8 @@ should appear
-
-
-
-
-
-
-
-
-
-
+
@@ -658,7 +658,7 @@ should appear
-
+
@@ -698,6 +698,37 @@ should appear
+
+
+
+
+
+
+
+
+
+
+
@@ -707,11 +738,11 @@ should appear
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ If you are experiencing an issue with Aerial, we may ask you to enable the Debug and Log to disk options below.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1042,14 +1111,20 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required)
+
+
+
@@ -1149,12 +1224,147 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Aerial/Source/Controllers/Preferences.swift b/Aerial/Source/Controllers/Preferences.swift
index 32ba7265..85e27321 100644
--- a/Aerial/Source/Controllers/Preferences.swift
+++ b/Aerial/Source/Controllers/Preferences.swift
@@ -18,11 +18,9 @@ class Preferences {
case multiMonitorMode = "multiMonitorMode"
case cacheAerials = "cacheAerials"
case customCacheDirectory = "cacheDirectory"
- case manifestTvOS10 = "manifestTvOS10"
- case manifestTvOS11 = "manifestTvOS11"
- case manifestTvOS12 = "manifestTvOS12"
case videoFormat = "videoFormat"
case showDescriptions = "showDescriptions"
+ case useCommunityDescriptions = "useCommunityDescriptions"
case showDescriptionsMode = "showDescriptionsMode"
case neverStreamVideos = "neverStreamVideos"
case neverStreamPreviews = "neverStreamPreviews"
@@ -36,13 +34,22 @@ class Preferences {
case fontName = "fontName"
case fontSize = "fontSize"
case showClock = "showClock"
+ case withSeconds = "withSeconds"
case showMessage = "showMessage"
case showMessageString = "showMessageString"
case extraFontName = "extraFontName"
case extraFontSize = "extraFontSize"
case extraCorner = "extraCorner"
+ case debugMode = "debugMode"
+ case logToDisk = "logToDisk"
+ case versionCheck = "versionCheck"
+ case alsoVersionCheckBeta = "alsoVersionCheckBeta"
}
-
+
+ enum VersionCheck : Int {
+ case never, daily, weekly, monthly
+ }
+
enum ExtraCorner : Int {
case same, hOpposed, dOpposed
}
@@ -76,7 +83,7 @@ class Preferences {
let module = "com.JohnCoates.Aerial"
guard let userDefaults = ScreenSaverDefaults(forModuleWithName: module) else {
- print("Couldn't create ScreenSaverDefaults, creating generic UserDefaults")
+ warnLog("Couldn't create ScreenSaverDefaults, creating generic UserDefaults")
return UserDefaults()
}
@@ -95,6 +102,7 @@ class Preferences {
defaultValues[.cacheAerials] = true
defaultValues[.videoFormat] = VideoFormat.v1080pH264
defaultValues[.showDescriptions] = true
+ defaultValues[.useCommunityDescriptions] = true
defaultValues[.showDescriptionsMode] = DescriptionMode.fade10seconds
defaultValues[.neverStreamVideos] = false
defaultValues[.neverStreamPreviews] = false
@@ -109,12 +117,16 @@ class Preferences {
defaultValues[.fontName] = "Helvetica Neue Medium"
defaultValues[.fontSize] = 28
defaultValues[.showClock] = false
+ defaultValues[.withSeconds] = false
defaultValues[.showMessage] = false
defaultValues[.showMessageString] = ""
- defaultValues[.extraFontName] = "Helvetica Neue Medium"
+ defaultValues[.extraFontName] = "Monaco"
defaultValues[.extraFontSize] = 28
defaultValues[.extraCorner] = ExtraCorner.same
-
+ defaultValues[.debugMode] = false
+ defaultValues[.logToDisk] = false
+ defaultValues[.versionCheck] = VersionCheck.weekly
+ defaultValues[.alsoVersionCheckBeta] = false
let defaults = defaultValues.reduce([String: Any]()) {
(result, pair:(key: Identifiers, value: Any)) -> [String: Any] in
@@ -127,6 +139,42 @@ class Preferences {
}
// MARK: - Variables
+
+ var useCommunityDescriptions: Bool {
+ get {
+ return value(forIdentifier: .useCommunityDescriptions)
+ }
+ set {
+ setValue(forIdentifier: .useCommunityDescriptions, value: newValue)
+ }
+ }
+
+ var debugMode: Bool {
+ get {
+ return value(forIdentifier: .debugMode)
+ }
+ set {
+ setValue(forIdentifier: .debugMode, value: newValue)
+ }
+ }
+
+ var logToDisk: Bool {
+ get {
+ return value(forIdentifier: .logToDisk)
+ }
+ set {
+ setValue(forIdentifier: .logToDisk, value: newValue)
+ }
+ }
+
+ var alsoVersionCheckBeta : Bool {
+ get {
+ return value(forIdentifier: .alsoVersionCheckBeta)
+ }
+ set {
+ setValue(forIdentifier: .alsoVersionCheckBeta, value: newValue)
+ }
+ }
var showClock: Bool {
get {
@@ -136,7 +184,16 @@ class Preferences {
setValue(forIdentifier: .showClock, value: newValue)
}
}
-
+
+ var withSeconds: Bool {
+ get {
+ return value(forIdentifier: .withSeconds)
+ }
+ set {
+ setValue(forIdentifier: .withSeconds, value: newValue)
+ }
+ }
+
var showMessage: Bool {
get {
return value(forIdentifier: .showMessage)
@@ -264,33 +321,15 @@ class Preferences {
}
}
- var manifestTvOS10: Data? {
- get {
- return optionalValue(forIdentifier: .manifestTvOS10)
- }
- set {
- setValue(forIdentifier: .manifestTvOS10, value: newValue)
- }
- }
-
- var manifestTvOS11: Data? {
+ var versionCheck: Int? {
get {
- return optionalValue(forIdentifier: .manifestTvOS11)
+ return optionalValue(forIdentifier: .versionCheck)
}
set {
- setValue(forIdentifier: .manifestTvOS11, value: newValue)
+ setValue(forIdentifier: .versionCheck, value: newValue)
}
}
- var manifestTvOS12: Data? {
- get {
- return optionalValue(forIdentifier: .manifestTvOS12)
- }
- set {
- setValue(forIdentifier: .manifestTvOS12, value: newValue)
- }
- }
-
var descriptionCorner: Int? {
get {
return optionalValue(forIdentifier: .descriptionCorner)
@@ -412,12 +451,6 @@ class Preferences {
return userDefaults.double(forKey: key)
}
- fileprivate func optionalValue(forIdentifier
- identifier: Identifiers) -> Data? {
- let key = identifier.rawValue
- return userDefaults.data(forKey: key)
- }
-
fileprivate func setValue(forIdentifier identifier: Identifiers, value: Any?) {
let key = identifier.rawValue
if value == nil {
diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift
index f396a79d..8aec4297 100644
--- a/Aerial/Source/Controllers/PreferencesWindowController.swift
+++ b/Aerial/Source/Controllers/PreferencesWindowController.swift
@@ -31,11 +31,7 @@ class City {
init(name: String) {
self.name = name
}
- /*func addVideo(video: AerialVideo)
- {
- video.arrayPosition = videos.count
- videos.append(video)
- }*/
+
func addVideoForTimeOfDay(_ timeOfDay: String, video: AerialVideo) {
if timeOfDay.lowercased() == "night" {
video.arrayPosition = night.videos.count
@@ -49,12 +45,14 @@ class City {
@objc(PreferencesWindowController)
class PreferencesWindowController: NSWindowController, NSOutlineViewDataSource,
-NSOutlineViewDelegate, VideoDownloadDelegate {
+NSOutlineViewDelegate {
+ @IBOutlet weak var prefTabView: NSTabView!
@IBOutlet var outlineView: NSOutlineView!
@IBOutlet var outlineViewSettings: NSButton!
@IBOutlet var playerView: AVPlayerView!
@IBOutlet var showDescriptionsCheckbox: NSButton!
+ @IBOutlet weak var useCommunityCheckbox: NSButton!
@IBOutlet var localizeForTvOS12Checkbox: NSButton!
@IBOutlet var projectPageLink: NSButton!
@IBOutlet var secondProjectPageLink: NSButton!
@@ -62,6 +60,7 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
@IBOutlet var cacheAerialsAsTheyPlayCheckbox: NSButton!
@IBOutlet var neverStreamVideosCheckbox: NSButton!
@IBOutlet var neverStreamPreviewsCheckbox: NSButton!
+ @IBOutlet weak var downloadNowButton: NSButton!
@IBOutlet var multiMonitorModePopup: NSPopUpButton!
@IBOutlet var popupVideoFormat: NSPopUpButton!
@@ -69,7 +68,10 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
@IBOutlet var fadeInOutModePopup: NSPopUpButton!
@IBOutlet weak var fadeInOutTextModePopup: NSPopUpButton!
+ @IBOutlet weak var downloadProgressIndicator: NSProgressIndicator!
+ @IBOutlet weak var downloadStopButton: NSButton!
@IBOutlet var versionLabel: NSTextField!
+
@IBOutlet var popover: NSPopover!
@IBOutlet var popoverH264Indicator: NSButton!
@IBOutlet var popoverHEVCIndicator: NSButton!
@@ -100,11 +102,20 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
@IBOutlet var currentLocaleLabel: NSTextField!
@IBOutlet var showClockCheckbox: NSButton!
+ @IBOutlet weak var withSecondsCheckbox: NSButton!
@IBOutlet var showExtraMessage: NSButton!
@IBOutlet var extraMessageTextField: NSTextField!
@IBOutlet var extraMessageFontLabel: NSTextField!
@IBOutlet weak var extraCornerPopup: NSPopUpButton!
+ @IBOutlet var logPanel: NSPanel!
+ @IBOutlet weak var showLogBottomClick: NSButton!
+ @IBOutlet weak var logTableView: NSTableView!
+ @IBOutlet weak var debugModeCheckbox: NSButton!
+ @IBOutlet weak var logToDiskCheckbox: NSButton!
+
+ @IBOutlet weak var cacheSizeTextField: NSTextField!
+
var player: AVPlayer = AVPlayer()
var videos: [AerialVideo]?
@@ -118,13 +129,19 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
let fontManager: NSFontManager
var fontEditing = 0 // To track the font we are changing
+ var highestLevel : ErrorLevel? // To track the largest level of error received
+
// MARK: - Init
required init?(coder decoder: NSCoder) {
self.fontManager = NSFontManager.shared
+ debugLog("pwc init1")
super.init(coder: decoder)
}
+
+ // We start here from SysPref and App mode
override init(window: NSWindow?) {
self.fontManager = NSFontManager.shared
+ debugLog("pwc init2")
super.init(window: window)
}
@@ -132,17 +149,30 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
override func awakeFromNib() {
super.awakeFromNib()
-
+ let logger = Logger.sharedInstance
+ logger.addCallback {level in
+ self.updateLogs(level:level)
+ }
+ let videoManager = VideoManager.sharedInstance
+ videoManager.addCallback { done,total in
+ self.updateDownloads(done: done,total: total)
+ }
+
self.fontManager.target = self
- if let previewPlayer = AerialView.previewPlayer {
+ // This used to grab the preview player and put it in our own video preview thing.
+ // While kinda cool, it showed a random video that wasn't selected, and with new lifecycle, it was paused
+ /*if let previewPlayer = AerialView.previewPlayer {
self.player = previewPlayer
- }
-
+ }*/
+ updateCacheSize()
outlineView.floatsGroupRows = false
loadJSON() // Async loading
+ logTableView.delegate = self
+ logTableView.dataSource = self
+
if let version = Bundle(identifier: "com.johncoates.Aerial-Test")?.infoDictionary?["CFBundleShortVersionString"] as? String {
versionLabel.stringValue = version
}
@@ -150,7 +180,7 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
versionLabel.stringValue = version
}
- // Some icons are 10.12.2+ only
+ // Some better icons are 10.12.2+ only
if #available(OSX 10.12.2, *) {
iconTime1.image = NSImage(named: NSImage.touchBarHistoryTemplateName)
iconTime2.image = NSImage(named: NSImage.touchBarComposeTemplateName)
@@ -208,12 +238,26 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
} else {
showClockCheckbox.isEnabled = false
}
+
+ // Aerial panel
+ if preferences.debugMode {
+ debugModeCheckbox.state = NSControl.StateValue.on
+ }
+
+ if preferences.logToDisk {
+ logToDiskCheckbox.state = NSControl.StateValue.on
+ }
// Text panel
if preferences.showClock {
showClockCheckbox.state = NSControl.StateValue.on
+ withSecondsCheckbox.isEnabled = true
}
-
+
+ if preferences.withSeconds {
+ withSecondsCheckbox.state = NSControl.StateValue.on
+ }
+
if preferences.showMessage {
showExtraMessage.state = NSControl.StateValue.on
extraMessageTextField.isEnabled = true
@@ -236,6 +280,10 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
neverStreamPreviewsCheckbox.state = NSControl.StateValue.on
}
+ if !preferences.useCommunityDescriptions {
+ useCommunityCheckbox.state = NSControl.StateValue.off
+ }
+
if !preferences.cacheAerials {
cacheAerialsAsTheyPlayCheckbox.state = NSControl.StateValue.off
}
@@ -308,9 +356,11 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
if let cacheDirectory = VideoCache.cacheDirectory {
cacheLocation.url = URL(fileURLWithPath: cacheDirectory as String)
+ } else {
+ cacheLocation.url = nil
}
- cacheStatusLabel.isEditable = false
+ //cacheStatusLabel.isEditable = false
}
override func windowDidLoad() {
@@ -321,6 +371,7 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
}
@IBAction func close(_ sender: AnyObject?) {
+ logPanel.close()
window?.sheetParent?.endSheet(window!)
}
@@ -358,19 +409,94 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
secondProjectPageLink.attributedTitle = coloredLink
}
- // MARK: - Preferences
+
+ // MARK: - Video panel
+
+ @IBAction func popupVideoFormatChange(_ sender:NSPopUpButton) {
+ debugLog("UI popupVideoFormat: \(sender.indexOfSelectedItem)")
+ preferences.videoFormat = sender.indexOfSelectedItem
+ preferences.synchronize()
+
+ outlineView.reloadData()
+ }
+
+ @IBAction func helpButtonClick(_ button: NSButton!) {
+ popover.show(relativeTo: button.preparedContentRect, of: button, preferredEdge: .maxY)
+ }
+
+ @IBAction func multiMonitorModePopupChange(_ sender:NSPopUpButton) {
+ debugLog("UI multiMonitorMode: \(sender.indexOfSelectedItem)")
+ preferences.multiMonitorMode = sender.indexOfSelectedItem
+ preferences.synchronize()
+ }
+
+ @IBAction func fadeInOutModePopupChange(_ sender:NSPopUpButton) {
+ debugLog("UI fadeInOutMode: \(sender.indexOfSelectedItem)")
+ preferences.fadeMode = sender.indexOfSelectedItem
+ preferences.synchronize()
+ }
+
+ func updateDownloads(done: Int, total: Int) {
+ print("VMQueue: done : \(done) \(total)")
+ if (total == 0) {
+ downloadProgressIndicator.isHidden = true
+ downloadStopButton.isHidden = true
+ downloadNowButton.isEnabled = true
+ } else {
+ downloadNowButton.isEnabled = false
+ downloadProgressIndicator.isHidden = false
+ downloadStopButton.isHidden = false
+ downloadProgressIndicator.doubleValue = Double(done)
+ downloadProgressIndicator.maxValue = Double(total)
+ }
+ }
+ @IBAction func cancelDownloadsClick(_ sender: Any) {
+ debugLog("UI cancelDownloadsClick")
+ let videoManager = VideoManager.sharedInstance
+ videoManager.cancelAll()
+ }
+
+ // MARK: - Text panel
+
+ @IBAction func showDescriptionsClick(button: NSButton?) {
+ let state = showDescriptionsCheckbox.state
+ let onState = (state == NSControl.StateValue.on)
+ preferences.showDescriptions = onState
+ debugLog("UI showDescriptions: \(onState)")
+ }
+
+ @IBAction func useCommunityClick(_ button: NSButton) {
+ let state = useCommunityCheckbox.state
+ let onState = (state == NSControl.StateValue.on)
+ preferences.useCommunityDescriptions = onState
+ debugLog("UI useCommunity: \(onState)")
+ }
+
+ @IBAction func localizeForTvOS12Click(button: NSButton?) {
+ let state = localizeForTvOS12Checkbox.state
+ let onState = (state == NSControl.StateValue.on)
+ preferences.localizeDescriptions = onState
+ debugLog("UI localizeDescriptions: \(onState)")
+ }
+
+ @IBAction func descriptionModePopupChange(_ sender:NSPopUpButton) {
+ debugLog("UI descriptionMode: \(sender.indexOfSelectedItem)")
+ preferences.showDescriptionsMode = sender.indexOfSelectedItem
+ preferences.synchronize()
+ }
+
@IBAction func fontPickerClick(_ sender:NSButton?) {
// Make a panel
let fp = self.fontManager.fontPanel(true)
-
+
// Set current font
if let font = NSFont(name: preferences.fontName!,size: CGFloat(preferences.fontSize!)) {
fp?.setPanelFont(font, isMultiple: false)
-
+
} else {
fp?.setPanelFont(NSFont(name: "Helvetica Neue Medium", size: 28)!, isMultiple: false)
}
-
+
// push the panel but mark which one we are editing
fontEditing = 0
fp?.makeKeyAndOrderFront(sender)
@@ -383,7 +509,7 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
// Update our label
currentFontLabel.stringValue = preferences.fontName! + ", \(preferences.fontSize!) pt"
}
-
+
@IBAction func extraFontPickerClick(_ sender:NSButton?) {
// Make a panel
let fp = self.fontManager.fontPanel(true)
@@ -400,7 +526,7 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
fontEditing = 1
fp?.makeKeyAndOrderFront(sender)
}
-
+
@IBAction func extraFontResetClick(_ sender:NSButton?) {
preferences.extraFontName = "Helvetica Neue Medium"
preferences.extraFontSize = 28
@@ -408,25 +534,13 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
// Update our label
extraMessageFontLabel.stringValue = preferences.extraFontName! + ", \(preferences.extraFontSize!) pt"
}
-
+
@IBAction func extraTextFieldChange(_ sender: NSTextField) {
- print("changed message \(sender.stringValue)")
+ debugLog("UI extraTextField \(sender.stringValue)")
preferences.showMessageString = sender.stringValue
}
- @IBAction func timeModeChange(_ sender:NSButton?) {
- if sender == timeDisabledRadio {
- preferences.timeMode = Preferences.TimeMode.disabled.rawValue
- } else if sender == timeNightShiftRadio {
- preferences.timeMode = Preferences.TimeMode.nightShift.rawValue
- } else if sender == timeManualRadio {
- preferences.timeMode = Preferences.TimeMode.manual.rawValue
- } else if sender == timeLightDarkModeRadio {
- preferences.timeMode = Preferences.TimeMode.lightDarkMode.rawValue
- }
- }
-
@IBAction func descriptionCornerChange(_ sender:NSButton?) {
if sender == cornerTopLeft {
preferences.descriptionCorner = Preferences.DescriptionCorner.topLeft.rawValue
@@ -440,67 +554,86 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
preferences.descriptionCorner = Preferences.DescriptionCorner.random.rawValue
}
}
-
- @IBAction func sunriseChange(_ sender:NSDatePicker?) {
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "HH:mm"
- let sunriseString = dateFormatter.string(from: (sender?.dateValue)!)
- preferences.manualSunrise = sunriseString
- }
- @IBAction func sunsetChange(_ sender:NSDatePicker?) {
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "HH:mm"
- let sunsetString = dateFormatter.string(from: (sender?.dateValue)!)
- preferences.manualSunset = sunsetString
- }
-
- @IBAction func helpButtonClick(_ button: NSButton!) {
- popover.show(relativeTo: button.preparedContentRect, of: button, preferredEdge: .maxY)
- }
-
- @IBAction func showInFinder(_ button: NSButton!) {
- NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: VideoCache.cacheDirectory!)
-
- }
-
@IBAction func showClockClick(_ sender: NSButton) {
- debugLog("show clock click: \(convertFromNSControlStateValue(sender.state))")
-
let onState = (sender.state == NSControl.StateValue.on)
preferences.showClock = onState
-
+ withSecondsCheckbox.isEnabled = onState
+ debugLog("UI showClock: \(onState)")
+ }
+
+ @IBAction func withSecondsClick(_ sender: NSButton) {
+ let onState = (sender.state == NSControl.StateValue.on)
+ preferences.withSeconds = onState
+ debugLog("UI withSeconds: \(onState)")
}
@IBAction func showExtraMessageClick(_ sender: NSButton) {
- debugLog("show extra message: \(convertFromNSControlStateValue(sender.state))")
-
let onState = (sender.state == NSControl.StateValue.on)
// We also need to enable/disable our message field
extraMessageTextField.isEnabled = onState
preferences.showMessage = onState
+ debugLog("UI showExtraMessage: \(onState)")
+
}
- @IBAction func neverStreamVideosClick(_ button: NSButton!) {
- debugLog("never stream videos: \(convertFromNSControlStateValue(button.state))")
+ @IBAction func fadeInOutTextModePopupChange(_ sender: NSPopUpButton) {
+ debugLog("UI fadeInOutTextMode: \(sender.indexOfSelectedItem)")
+ preferences.fadeModeText = sender.indexOfSelectedItem
+ preferences.synchronize()
+ }
+
+ @IBAction func extraCornerPopupChange(_ sender: NSPopUpButton) {
+ debugLog("UI extraCorner: \(sender.indexOfSelectedItem)")
+ preferences.extraCorner = sender.indexOfSelectedItem
+ preferences.synchronize()
+ }
+
+ // MARK: - Cache panel
+
+ func updateCacheSize() {
+ // get your directory url
+ let documentsDirectoryURL = URL(fileURLWithPath: VideoCache.cacheDirectory!)
+ // FileManager.default.urls(for: VideoCache.cacheDirectory, in: .userDomainMask).first!
+
+ // check if the url is a directory
+ if (try? documentsDirectoryURL.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true {
+ var folderSize = 0
+ (FileManager.default.enumerator(at: documentsDirectoryURL, includingPropertiesForKeys: nil)?.allObjects as? [URL])?.lazy.forEach {
+ folderSize += (try? $0.resourceValues(forKeys: [.totalFileAllocatedSizeKey]))?.totalFileAllocatedSize ?? 0
+ }
+ let byteCountFormatter = ByteCountFormatter()
+ byteCountFormatter.allowedUnits = .useGB
+ byteCountFormatter.countStyle = .file
+ let sizeToDisplay = byteCountFormatter.string(for: folderSize) ?? ""
+ debugLog("Cache size : \(sizeToDisplay)")
+ cacheSizeTextField.stringValue = "Cache all videos (current cache size \(sizeToDisplay))"
+ }
+ }
+
+ @IBAction func cacheAerialsAsTheyPlayClick(_ button: NSButton!) {
+ let onState = (button.state == NSControl.StateValue.on)
+ preferences.cacheAerials = onState
+ debugLog("UI cacheAerialAsTheyPlay: \(onState)")
+ }
+
+ @IBAction func neverStreamVideosClick(_ button: NSButton!) {
let onState = (button.state == NSControl.StateValue.on)
preferences.neverStreamVideos = onState
+ debugLog("UI neverStreamVideos: \(onState)")
}
@IBAction func neverStreamPreviewsClick(_ button: NSButton!) {
- debugLog("never stream previews: \(convertFromNSControlStateValue(button.state))")
-
let onState = (button.state == NSControl.StateValue.on)
preferences.neverStreamPreviews = onState
- }
- @IBAction func cacheAerialsAsTheyPlayClick(_ button: NSButton!) {
- debugLog("cache aerials as they play: \(convertFromNSControlStateValue(button.state))")
-
- let onState = (button.state == NSControl.StateValue.on)
- preferences.cacheAerials = onState
+ debugLog("UI neverStreamPreviews: \(onState)")
}
+ @IBAction func showInFinder(_ button: NSButton!) {
+ NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: VideoCache.cacheDirectory!)
+ }
+
@IBAction func userSetCacheLocation(_ button: NSButton?) {
let openPanel = NSOpenPanel()
@@ -515,7 +648,7 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
openPanel.begin { result in
guard result.rawValue == NSFileHandlingPanelOKButton,
openPanel.urls.count > 0 else {
- return
+ return
}
let cacheDirectory = openPanel.urls[0]
@@ -523,76 +656,132 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
self.cacheLocation.url = cacheDirectory
}
}
+
@IBAction func resetCacheLocation(_ button: NSButton?) {
preferences.customCacheDirectory = nil
if let cacheDirectory = VideoCache.cacheDirectory {
cacheLocation.url = URL(fileURLWithPath: cacheDirectory as String)
}
}
-
- @IBAction func popupVideoFormatChange(_ sender:NSPopUpButton) {
- debugLog("index change : \(sender.indexOfSelectedItem)")
- preferences.videoFormat = sender.indexOfSelectedItem
- preferences.synchronize()
-
- outlineView.reloadData()
+
+ @IBAction func downloadNowButton(_ sender: Any) {
+ downloadNowButton.isEnabled = false
+ prefTabView.selectTabViewItem(at: 0)
+ downloadAllVideos()
}
-
- @IBAction func descriptionModePopupChange(_ sender:NSPopUpButton) {
- debugLog("dindex change : \(sender.indexOfSelectedItem)")
-
- preferences.showDescriptionsMode = sender.indexOfSelectedItem
- preferences.synchronize()
+
+ // MARK: - Time panel
+
+ @IBAction func timeModeChange(_ sender:NSButton?) {
+ if sender == timeDisabledRadio {
+ preferences.timeMode = Preferences.TimeMode.disabled.rawValue
+ } else if sender == timeNightShiftRadio {
+ preferences.timeMode = Preferences.TimeMode.nightShift.rawValue
+ } else if sender == timeManualRadio {
+ preferences.timeMode = Preferences.TimeMode.manual.rawValue
+ } else if sender == timeLightDarkModeRadio {
+ preferences.timeMode = Preferences.TimeMode.lightDarkMode.rawValue
+ }
}
- @IBAction func multiMonitorModePopupChange(_ sender:NSPopUpButton) {
- debugLog("mm index change : \(sender.indexOfSelectedItem)")
-
- preferences.multiMonitorMode = sender.indexOfSelectedItem
- preferences.synchronize()
+ @IBAction func sunriseChange(_ sender:NSDatePicker?) {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "HH:mm"
+ let sunriseString = dateFormatter.string(from: (sender?.dateValue)!)
+ preferences.manualSunrise = sunriseString
+ }
+
+ @IBAction func sunsetChange(_ sender:NSDatePicker?) {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "HH:mm"
+ let sunsetString = dateFormatter.string(from: (sender?.dateValue)!)
+ preferences.manualSunset = sunsetString
+ }
+
+ // MARK: - Aerial panel
+
+ @IBAction func logButtonClick(_ sender: NSButton) {
+ logTableView.reloadData()
+ if logPanel.isVisible {
+ logPanel.close()
+ } else {
+ logPanel.makeKeyAndOrderFront(sender)
+ }
+ }
+
+ @IBAction func logCopyToClipboardClick(_ sender: NSButton) {
+ if (errorMessages.count > 0) {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateStyle = .none
+ dateFormatter.timeStyle = .medium
+
+ var clipboard = ""
+ for log in errorMessages {
+ clipboard += dateFormatter.string(from:log.date) + " : " + log.message + "\n"
+ }
+
+ let pasteBoard = NSPasteboard.general
+ pasteBoard.clearContents()
+ pasteBoard.setString(clipboard, forType: NSPasteboard.PasteboardType.string)
+ }
+ }
+
+ @IBAction func logRefreshClick(_ sender: NSButton) {
+ logTableView.reloadData()
}
- @IBAction func fadeInOutModePopupChange(_ sender:NSPopUpButton) {
- debugLog("fm index change : \(sender.indexOfSelectedItem)")
-
- preferences.fadeMode = sender.indexOfSelectedItem
- preferences.synchronize()
+ @IBAction func debugModeClick(_ button: NSButton) {
+ let onState = (button.state == NSControl.StateValue.on)
+ preferences.debugMode = onState
+ debugLog("UI debugMode: \(onState)")
}
- @IBAction func fadeInOutTextModePopupChange(_ sender: NSPopUpButton) {
- debugLog("fmt index change : \(sender.indexOfSelectedItem)")
-
- preferences.fadeModeText = sender.indexOfSelectedItem
- preferences.synchronize()
+ @IBAction func logToDiskClick(_ button: NSButton) {
+ let onState = (button.state == NSControl.StateValue.on)
+ preferences.logToDisk = onState
+ debugLog("UI logToDisk: \(onState)")
}
- @IBAction func extraCornerPopupChange(_ sender: NSPopUpButton) {
- debugLog("ec index change : \(sender.indexOfSelectedItem)")
-
- preferences.extraCorner = sender.indexOfSelectedItem
- preferences.synchronize()
+ @IBAction func showLogInFinder(_ button: NSButton!) {
+ let logfile = VideoCache.cacheDirectory!.appending("/AerialLog.txt")
+ // If we don't have a log, just show the folder
+ if FileManager.default.fileExists(atPath: logfile) == false {
+ NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: VideoCache.cacheDirectory!)
+ }
+ else {
+ NSWorkspace.shared.selectFile(logfile, inFileViewerRootedAtPath: VideoCache.cacheDirectory!)
+ }
}
- @IBAction func showDescriptionsClick(button: NSButton?) {
- let state = showDescriptionsCheckbox.state
- let onState = (state == NSControl.StateValue.on)
-
- preferences.showDescriptions = onState
+ func updateLogs(level:ErrorLevel)
+ {
+ logTableView.reloadData()
+ if (highestLevel == nil) {
+ highestLevel = level
+ } else if (level.rawValue > highestLevel!.rawValue) {
+ highestLevel = level
+ }
- debugLog("set showDescriptions to \(onState)")
- }
-
- @IBAction func localizeForTvOS12Click(button: NSButton?) {
- let state = localizeForTvOS12Checkbox.state
- let onState = (state == NSControl.StateValue.on)
+ switch highestLevel! {
+ case ErrorLevel.debug:
+ showLogBottomClick.title = "Show Debug"
+ showLogBottomClick.image = NSImage.init(named: NSImage.actionTemplateName)
+ case ErrorLevel.info:
+ showLogBottomClick.title = "Show Info"
+ showLogBottomClick.image = NSImage.init(named: NSImage.infoName)
+ case ErrorLevel.warning:
+ showLogBottomClick.title = "Show Warning"
+ showLogBottomClick.image = NSImage.init(named: NSImage.cautionName)
+ default:
+ showLogBottomClick.title = "Show Error"
+ showLogBottomClick.image = NSImage.init(named: NSImage.stopProgressFreestandingTemplateName)
+ }
- preferences.localizeDescriptions = onState
- debugLog("set localizeDescriptions to \(onState)")
+ showLogBottomClick.isHidden = false
}
-
- // MARK: Menu
+ // MARK: - Menu
@IBAction func outlineViewSettingsClick(_ button: NSButton) {
let menu = NSMenu()
@@ -692,6 +881,10 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
}
@objc func outlineViewDownloadAll(button: NSButton) {
+ downloadAllVideos()
+ }
+
+ func downloadAllVideos() {
guard let videos = videos else {
return
}
@@ -886,14 +1079,14 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
switch item {
case is City:
let city = item as! City
- let view = outlineView.makeView(withIdentifier: convertToNSUserInterfaceItemIdentifier("HeaderCell"),
+ let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "HeaderCell"),
owner: nil) as! NSTableCellView // if owner = self, awakeFromNib will be called for each created cell !
view.textField?.stringValue = city.name
return view
case is TimeOfDay:
let timeOfDay = item as! TimeOfDay
- let view = outlineView.makeView(withIdentifier: convertToNSUserInterfaceItemIdentifier("DataCell"),
+ let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DataCell"),
owner: nil) as! NSTableCellView // if owner = self, awakeFromNib will be called for each created cell !
view.textField?.stringValue = timeOfDay.title.capitalized
@@ -916,13 +1109,13 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
// TODO, change the icons for dark mode
} else {
- NSLog("\(#file) failed to find time of day icon")
+ errorLog("\(#file) failed to find time of day icon")
}
return view
case is AerialVideo:
let video = item as! AerialVideo
- let view = outlineView.makeView(withIdentifier: convertToNSUserInterfaceItemIdentifier("CheckCell"),
+ let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CheckCell"),
owner: nil) as! CheckCellView // if owner = self, awakeFromNib will be called for each created cell !
// Mark the new view for this video for subsequent callbacks
let videoManager = VideoManager.sharedInstance
@@ -943,7 +1136,7 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
guard
let numberString = numberFormatter.string(from: number as NSNumber)
else {
- print("failed to create number with formatter")
+ errorLog("outlineView: failed to create number with formatter")
return nil
}
@@ -978,7 +1171,7 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
playerView.player = player
let video = item as! AerialVideo
- debugLog("playing this preview \(video)")
+ debugLog("Playing this preview \(video)")
// Workaround for cached videos generating online traffic
if video.isAvailableOffline {
previewDisabledTextfield.isHidden = true
@@ -1022,10 +1215,7 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
}
// MARK: - Caching
-
- @IBOutlet var totalProgress: NSProgressIndicator!
- @IBOutlet var currentProgress: NSProgressIndicator!
- @IBOutlet var cacheStatusLabel: NSTextField!
+ /*
var currentVideoDownload: VideoDownload?
var manifestVideos: [AerialVideo]?
@@ -1037,7 +1227,6 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
DispatchQueue.main.async(execute: { () -> Void in
self.manifestVideos = manifestVideos
self.cacheNextVideo()
-
})
}
}
@@ -1052,11 +1241,11 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
return video.isAvailableOffline == false
}
- NSLog("uncached: \(uncached)")
+ debugLog("uncached: \(uncached)")
totalProgress.maxValue = Double(manifestVideos.count)
totalProgress.doubleValue = Double(manifestVideos.count) - Double(uncached.count)
- NSLog("total process max value: \(totalProgress.maxValue), current value: \(totalProgress.doubleValue)")
+ debugLog("total process max value: \(totalProgress.maxValue), current value: \(totalProgress.doubleValue)")
if uncached.count == 0 {
cacheStatusLabel.stringValue = "All videos have been cached"
@@ -1086,23 +1275,22 @@ NSOutlineViewDelegate, VideoDownloadDelegate {
preferences.synchronize()
outlineView.reloadData()
- NSLog("video download finished with success: \(success))")
+ debugLog("video download finished with success: \(success))")
}
func videoDownload(_ videoDownload: VideoDownload, receivedBytes: Int, progress: Float) {
currentProgress.doubleValue = Double(progress)
-// NSLog("received bytes: \(receivedBytes), progress: \(progress)")
- }
-
+ }*/
}
+// MARK: - Font Panel Delegates
+
extension PreferencesWindowController : NSFontChanging {
func validModesForFontPanel(_ fontPanel: NSFontPanel) -> NSFontPanel.ModeMask {
return [.size, .collection, .face]
}
func changeFont(_ sender: NSFontManager?) {
- print("change font")
// Set current font
var oldFont = NSFont(name: "Helvetica Neue Medium", size: 28)
@@ -1116,7 +1304,6 @@ extension PreferencesWindowController : NSFontChanging {
}
}
-
let newFont = sender?.convert(oldFont!)
if (fontEditing == 0) {
@@ -1137,14 +1324,56 @@ extension PreferencesWindowController : NSFontChanging {
}
+// MARK: - Log TableView Delegates
-// Helper function inserted by Swift 4.2 migrator.
-fileprivate func convertFromNSControlStateValue(_ input: NSControl.StateValue) -> Int {
- return input.rawValue
+extension PreferencesWindowController: NSTableViewDataSource {
+ func numberOfRows(in tableView: NSTableView) -> Int {
+ return errorMessages.count
+ }
}
-// Helper function inserted by Swift 4.2 migrator.
-fileprivate func convertToNSUserInterfaceItemIdentifier(_ input: String) -> NSUserInterfaceItemIdentifier {
- return NSUserInterfaceItemIdentifier(rawValue: input)
+extension PreferencesWindowController : NSTableViewDelegate {
+ fileprivate enum CellIdentifiers {
+ static let DateCell = "DateCellID"
+ static let MessageCell = "MessageCellID"
+ }
+
+ func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
+ var image: NSImage?
+ var text: String = ""
+ var cellIdentifier: String = ""
+
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateStyle = .none
+ dateFormatter.timeStyle = .medium
+
+ let item = errorMessages[row]
+
+ if tableColumn == tableView.tableColumns[0] {
+ text = dateFormatter.string(from: item.date)
+ cellIdentifier = CellIdentifiers.DateCell
+ } else if tableColumn == tableView.tableColumns[1] {
+ switch item.level {
+ case ErrorLevel.info:
+ image = NSImage.init(named: NSImage.infoName)
+ case ErrorLevel.warning:
+ image = NSImage.init(named: NSImage.cautionName)
+ case ErrorLevel.error:
+ image = NSImage.init(named: NSImage.stopProgressFreestandingTemplateName)
+ default:
+ image = NSImage.init(named: NSImage.actionTemplateName)
+ }
+ //image =
+ text = item.message
+ cellIdentifier = CellIdentifiers.MessageCell
+ }
+
+ if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: nil) as? NSTableCellView {
+ cell.textField?.stringValue = text
+ cell.imageView?.image = image ?? nil
+ return cell
+ }
+
+ return nil
+ }
}
-
diff --git a/Aerial/Source/Models/AerialVideo.swift b/Aerial/Source/Models/AerialVideo.swift
index 18ec0403..79c00b56 100644
--- a/Aerial/Source/Models/AerialVideo.swift
+++ b/Aerial/Source/Models/AerialVideo.swift
@@ -62,6 +62,7 @@ class AerialVideo: CustomStringConvertible, Equatable {
let url4KHEVC: String
var sources: [Manifests]
let poi: [String: String]
+ let communityPoi: [String: String]
var duration: Double
var arrayPosition = 1
@@ -85,11 +86,9 @@ class AerialVideo: CustomStringConvertible, Equatable {
return URL(string: self.url4KHEVC)!
}
else if (url1080pHEVC != "") {
- //debugLog("4K NOT AVAILABLE, retunring 1080P HEVC as closest available")
return URL(string: self.url1080pHEVC)!
}
else {
- //debugLog("4K NOT AVAILABLE, retunring 1080P H264 as closest available")
return URL(string: self.url1080pH264)!
}
}
@@ -99,11 +98,9 @@ class AerialVideo: CustomStringConvertible, Equatable {
return URL(string: self.url1080pHEVC)!
}
else if (url1080pH264 != "") {
- //debugLog("1080pHEVC NOT AVAILABLE, retunring 1080P H264 as closest available")
return URL(string: self.url1080pH264)!
}
else {
- //debugLog("1080pHEVC NOT AVAILABLE, retunring 4K HEVC as closest available")
return URL(string: self.url4KHEVC)!
}
}
@@ -113,32 +110,17 @@ class AerialVideo: CustomStringConvertible, Equatable {
return URL(string: self.url1080pH264)!
}
else if (url1080pHEVC != "") {
- //debugLog("1080pH264 NOT AVAILABLE, retunring 1080P HEVC as closest available")
- return URL(string: self.url1080pHEVC)!
+ return URL(string: self.url1080pHEVC)! // With the latest versions, we should always have a H.264 fallback so this is just for future proofing
}
else {
- //debugLog("1080pHEVC NOT AVAILABLE, retunring 4K HEVC as closest available")
return URL(string: self.url4KHEVC)!
}
}
-
-
- /*switch preferences.videoFormat {
- case Preferences.VideoFormat.v1080pH264.rawValue:
- return URL(string: self.url1080pH264)!
- case Preferences.VideoFormat.v1080pHEVC.rawValue:
- return URL(string: self.url1080pHEVC)!
- case Preferences.VideoFormat.v4KHEVC.rawValue:
- return URL(string: self.url4KHEVC)!
- default:
- return URL(string: url1080pH264)!
- }*/
-
}
}
init(id: String, name: String, secondaryName: String, type: String,
- timeOfDay: String, url1080pH264: String, url1080pHEVC: String, url4KHEVC: String, manifest: Manifests, poi: [String: String]) {
+ timeOfDay: String, url1080pH264: String, url1080pHEVC: String, url4KHEVC: String, manifest: Manifests, poi: [String: String], communityPoi: [String:String]) {
self.id = id
// We override names for known space videos
@@ -151,7 +133,7 @@ class AerialVideo: CustomStringConvertible, Equatable {
}
} else {
self.name = name
- self.secondaryName = secondaryName // We may have a secondary name from our merges
+ self.secondaryName = secondaryName // We may have a secondary name from our merges too now !
}
self.type = type
@@ -169,8 +151,9 @@ class AerialVideo: CustomStringConvertible, Equatable {
self.url4KHEVC = url4KHEVC
self.sources = [manifest]
self.poi = poi
-
+ self.communityPoi = communityPoi
self.duration = 0
+
updateDuration()
}
@@ -212,7 +195,7 @@ class AerialVideo: CustomStringConvertible, Equatable {
}
else
{
- print("Could not determine duration, video is not cached")
+ debugLog("Could not determine duration, video is not cached in any format")
self.duration = 0
}
}
diff --git a/Aerial/Source/Models/Cache/AssetLoaderDelegate.swift b/Aerial/Source/Models/Cache/AssetLoaderDelegate.swift
index 8c4615ad..6f43651e 100644
--- a/Aerial/Source/Models/Cache/AssetLoaderDelegate.swift
+++ b/Aerial/Source/Models/Cache/AssetLoaderDelegate.swift
@@ -13,12 +13,11 @@ import AVFoundation
/// then returns the cached asset.
func CachedOrCachingAsset(_ URL: Foundation.URL) -> AVURLAsset {
let assetLoader = AssetLoaderDelegate(URL: URL)
-
let asset = AVURLAsset(url: assetLoader.URLWithCustomScheme)
let queue = DispatchQueue.main
asset.resourceLoader.setDelegate(assetLoader, queue: queue)
objc_setAssociatedObject(asset, "assetLoader", assetLoader, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
-
+ //debugLog("\(asset)")
return asset
}
@@ -36,7 +35,6 @@ class AssetLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, VideoLoaderD
init(URL: Foundation.URL) {
self.URL = URL
-// self.URL = NSURL(string:"http://localhost/test.mov")!
videoCache = VideoCache(URL: URL)
}
@@ -83,20 +81,20 @@ class AssetLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, VideoLoaderD
func resourceLoader(_ resourceLoader: AVAssetResourceLoader,
shouldWaitForLoadingOfRequestedResource
loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
-
+ /*
// check if cache can fulfill this without a request
if videoCache.canFulfillLoadingRequest(loadingRequest) {
if videoCache.fulfillLoadingRequest(loadingRequest) {
+ debugLog("fullfilling loading request")
return true
}
- }
+ }*/
// assign request to VideoLoader
-
+ //debugLog("request to loader \(loadingRequest)")
let videoLoader = VideoLoader(url: URL, loadingRequest: loadingRequest, delegate: self)
videoLoaders.append(videoLoader)
return true
}
-
}
diff --git a/Aerial/Source/Models/Cache/PoiStringProvider.swift b/Aerial/Source/Models/Cache/PoiStringProvider.swift
index 90a8f6ea..c65f9d8b 100644
--- a/Aerial/Source/Models/Cache/PoiStringProvider.swift
+++ b/Aerial/Source/Models/Cache/PoiStringProvider.swift
@@ -8,6 +8,18 @@
import Foundation
+class CommunityStrings {
+ let id : String
+ let name : String
+ let poi : [String:String]
+
+ init(id: String, name: String, poi: [String:String]) {
+ self.id = id
+ self.name = name
+ self.poi = poi
+ }
+}
+
class PoiStringProvider {
static let sharedInstance = PoiStringProvider()
var loadedDescriptions = false
@@ -15,10 +27,17 @@ class PoiStringProvider {
var stringBundle: Bundle?
var stringDict: NSDictionary?
+
+ var communityStrings = [CommunityStrings]()
+
+ // MARK: - Lifecycle
init() {
+ debugLog("Poi Strings Provider initialized")
loadBundle()
+ loadCommunity()
}
-
+
+ // MARK: - Bundle management
private func loadBundle() {
// Idle string bundle
let preferences = Preferences.sharedInstance
@@ -30,7 +49,6 @@ class PoiStringProvider {
else {
bundlePath.append(contentsOf: "/TVIdleScreenStrings.bundle/en.lproj/")
}
-
if let sb = Bundle.init(path: bundlePath) {
let dictPath = VideoCache.cacheDirectory!.appending("/TVIdleScreenStrings.bundle/en.lproj/Localizable.nocache.strings")
@@ -43,6 +61,8 @@ class PoiStringProvider {
self.stringBundle = sb
self.loadedDescriptions = true
self.loadedDescriptionsWasLocalized = preferences.localizeDescriptions
+ } else {
+ errorLog("TVIdleScreenStrings.bundle is missing, please remove entries.json in Cache folder to fix the issue")
}
}
@@ -59,11 +79,24 @@ class PoiStringProvider {
}
// Return the Localized (or english) string for a key from the Strings Bundle
- func getString(key:String) -> String {
+ func getString(key:String, video:AerialVideo) -> String {
if !ensureLoadedBundle() {
return ""
}
- return stringBundle!.localizedString(forKey: key, value: "", table: "Localizable.nocache")
+ let preferences = Preferences.sharedInstance
+ let locale: NSLocale = NSLocale(localeIdentifier: Locale.preferredLanguages[0])
+
+ if #available(OSX 10.12, *) {
+ if (preferences.localizeDescriptions && locale.languageCode != "en") {
+ return stringBundle!.localizedString(forKey: key, value: "", table: "Localizable.nocache")
+ }
+ }
+
+ if preferences.useCommunityDescriptions && video.communityPoi.count > 0 {
+ return key // We directly store the string in the key
+ } else {
+ return stringBundle!.localizedString(forKey: key, value: "", table: "Localizable.nocache")
+ }
}
// Return all POIs for an id
@@ -83,5 +116,89 @@ class PoiStringProvider {
return found
}
+
+ //
+ func getPoiKeys(video: AerialVideo) -> [String:String] {
+ let preferences = Preferences.sharedInstance
+ let locale: NSLocale = NSLocale(localeIdentifier: Locale.preferredLanguages[0])
+
+ if #available(OSX 10.12, *) {
+ if (preferences.localizeDescriptions && locale.languageCode != "en") {
+ return video.poi
+ }
+ }
+
+ if preferences.useCommunityDescriptions && video.communityPoi.count > 0 {
+ return video.communityPoi
+ } else {
+ return video.poi
+ }
+ }
+
+
+ // MARK: - Community data
+
+ // Load the community strings
+ private func loadCommunity()
+ {
+ let preferences = Preferences.sharedInstance
+
+ var bundlePath: String
+ if (preferences.localizeDescriptions) {
+ bundlePath = Bundle(for: PoiStringProvider.self).path(forResource: "en", ofType: "json")!
+ //bundlePath = Bundle.main.path(forResource: "en", ofType: "json")!
+ }
+ else {
+ // TODO
+ bundlePath = Bundle(for: PoiStringProvider.self).path(forResource: "en", ofType: "json")!
+ //bundlePath = Bundle.main.path(forResource: "en", ofType: "json")!
+ }
+ debugLog("path : \(bundlePath)")
+ do {
+ let data = try Data(contentsOf: URL(fileURLWithPath: bundlePath), options: .mappedIfSafe)
+ let batches = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
+
+ guard let batch = batches as? NSDictionary else {
+ errorLog("Community : Encountered unexpected content type for batch, please report !")
+ return
+ }
+
+ let assets = batch["assets"] as! Array
+
+ for item in assets {
+ let id = item["id"] as! String
+ let name = item["name"] as! String
+ let poi = item["pointsOfInterest"] as? [String: String]
+
+ communityStrings.append(CommunityStrings(id: id, name: name, poi: poi ?? [:]))
+ }
+ } catch {
+ // handle error
+ errorLog("Community JSON ERROR")
+ }
+ debugLog("Community JSON : \(communityStrings.count)")
+ }
+
+ func getCommunityName(id: String) -> String? {
+ for obj in communityStrings {
+ if obj.id == id {
+ return obj.name
+ }
+ }
+
+ return nil
+ }
+
+ func getCommunityPoi(id:String) -> [String:String]
+ {
+ for obj in communityStrings {
+ if obj.id == id {
+ return obj.poi
+ }
+ }
+
+ return [:]
+ }
+
}
diff --git a/Aerial/Source/Models/Cache/VideoCache.swift b/Aerial/Source/Models/Cache/VideoCache.swift
index 474aded1..9e6f0d40 100644
--- a/Aerial/Source/Models/Cache/VideoCache.swift
+++ b/Aerial/Source/Models/Cache/VideoCache.swift
@@ -30,7 +30,7 @@ class VideoCache {
.userDomainMask,
true)
if cachePaths.count == 0 {
- NSLog("Aerial Error: Couldn't find cache paths!")
+ errorLog("Couldn't find cache paths!")
return nil
}
@@ -50,7 +50,7 @@ class VideoCache {
try fileManager.createDirectory(atPath: appCacheDirectory as String,
withIntermediateDirectories: false, attributes: nil)
} catch let error {
- NSLog("Aerial Error: Couldn't create cache directory: \(error)")
+ errorLog("Couldn't create cache directory: \(error)")
return nil
}
}
@@ -60,7 +60,7 @@ class VideoCache {
static func isAvailableOffline(video: AerialVideo) -> Bool {
guard let videoCachePath = cachePath(forVideo: video) else {
- NSLog("Aerial Error: Couldn't get video cache path!")
+ errorLog("Couldn't get video cache path!")
return false
}
@@ -86,6 +86,7 @@ class VideoCache {
}
init(URL: Foundation.URL) {
+ debugLog("initvideocache")
videoData = Data()
loading = true
self.URL = URL
@@ -109,7 +110,7 @@ class VideoCache {
func receivedData(_ data: Data, atRange range: NSRange) {
guard let mutableVideoData = mutableVideoData else {
- NSLog("Aerial Error: Received data without having mutable video data")
+ errorLog("Received data without having mutable video data")
return
}
@@ -149,25 +150,25 @@ class VideoCache {
let fileManager = FileManager.default
guard let videoCachePath = videoCachePath else {
- NSLog("Aerial Error: Couldn't save cache file")
+ errorLog("Couldn't save cache file")
return
}
guard fileManager.fileExists(atPath: videoCachePath) == false else {
- NSLog("Aerial Error: Cache file \(videoCachePath) already exists.")
+ errorLog("Cache file \(videoCachePath) already exists.")
return
}
loading = false
guard let mutableVideoData = mutableVideoData else {
- NSLog("Aerial Error: Missing video data for save.")
+ errorLog("Missing video data for save.")
return
}
do {
try mutableVideoData.write(toFile: videoCachePath, options: .atomicWrite)
} catch let error {
- NSLog("Aerial Error: Couldn't write cache file: \(error)")
+ errorLog("Couldn't write cache file: \(error)")
}
}
@@ -175,7 +176,7 @@ class VideoCache {
let fileManager = FileManager.default
guard let videoCachePath = self.videoCachePath else {
- NSLog("Aerial Error: Couldn't load cache file.")
+ errorLog("Couldn't load cache file.")
return
}
@@ -184,7 +185,7 @@ class VideoCache {
}
guard let videoData = try? Data(contentsOf: Foundation.URL(fileURLWithPath: videoCachePath)) else {
- NSLog("Aerial Error: NSData failed to load cache file \(videoCachePath)")
+ errorLog("NSData failed to load cache file \(videoCachePath)")
return
}
@@ -197,7 +198,7 @@ class VideoCache {
func fulfillLoadingRequest(_ loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
guard let dataRequest = loadingRequest.dataRequest else {
- NSLog("Aerial Error: Missing data request for \(loadingRequest)")
+ errorLog("Missing data request for \(loadingRequest)")
return false
}
@@ -239,7 +240,7 @@ class VideoCache {
}
guard let dataRequest = loadingRequest.dataRequest else {
- NSLog("Aerial Error: Missing data request for \(loadingRequest)")
+ errorLog("Missing data request for \(loadingRequest)")
return false
}
diff --git a/Aerial/Source/Models/Cache/VideoDownload.swift b/Aerial/Source/Models/Cache/VideoDownload.swift
index 593e7844..dca1afc8 100644
--- a/Aerial/Source/Models/Cache/VideoDownload.swift
+++ b/Aerial/Source/Models/Cache/VideoDownload.swift
@@ -57,6 +57,14 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
startDownloadForChunk(nil)
}
+ func cancel() {
+ for stream in streams {
+ stream.connection.cancel()
+ }
+ infoLog("Video download cancelled")
+ delegate.videoDownload(self, finished: false, errorMessage: nil)
+ }
+
func startDownloadForChunk(_ chunk: NSRange?) {
let request = NSMutableURLRequest(url: video.url as URL)
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
@@ -70,7 +78,7 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
guard let connection = NSURLConnection(request: request as URLRequest,
delegate: self, startImmediately: false) else {
- NSLog("Aerial: Error creating connection with request: \(request)")
+ errorLog("Error creating connection with request: \(request)")
return
}
@@ -140,7 +148,7 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
func receiveDataForStream(_ stream: VideoDownloadStream, receivedData: Data) {
guard let videoData = self.data else {
- NSLog("Aerial error: video data missing!")
+ errorLog("Aerial error: video data missing!")
return
}
@@ -152,13 +160,13 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
func finishedDownload() {
guard let videoCachePath = VideoCache.cachePath(forVideo: video) else {
- print("Aerial Error: Couldn't save video because couldn't get cache path\n")
+ errorLog("Couldn't save video because couldn't get cache path\n")
failedDownload("Couldn't get cache path")
return
}
guard let videoData = self.data else {
- print("Aerial error: video data missing!\n")
+ errorLog("video data missing!\n")
return
}
@@ -167,7 +175,7 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
do {
try videoData.write(toFile: videoCachePath, options: .atomicWrite)
} catch let error {
- NSLog("Aerial Error: Couldn't write cache file: \(error)")
+ errorLog("Couldn't write cache file: \(error)")
errorMessage = "Couldn't write to cache file!"
success = false
}
@@ -186,7 +194,7 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
func connection(_ connection: NSURLConnection, didReceive response: URLResponse) {
guard let stream = streamForConnection(connection) else {
- NSLog("Aerial Error: No matching stream for connection: \(connection) with response: \(response)")
+ errorLog("No matching stream for connection: \(connection) with response: \(response)")
return
}
@@ -206,7 +214,7 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
queue.async(execute: { () -> Void in
guard let offset = self.startOffsetFromResponse(response) else {
- NSLog("Aerial Error: Couldn't get start offset from response: \(response)")
+ errorLog("Couldn't get start offset from response: \(response)")
return
}
@@ -226,7 +234,7 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
delegate.videoDownload(self, receivedBytes: data.count, progress: progress)
guard let stream = self.streamForConnection(connection) else {
- NSLog("Aerial Error: No matching stream for connection: \(connection)")
+ errorLog("No matching stream for connection: \(connection)")
return
}
@@ -239,12 +247,12 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
debugLog("connectionDidFinishLoading")
guard let stream = self.streamForConnection(connection) else {
- NSLog("Aerial Error: No matching stream for connection: \(connection)")
+ errorLog("No matching stream for connection: \(connection)")
return
}
guard let index = self.streams.index(where: { $0.connection == stream.connection }) else {
- NSLog("Aerial Error: Couldn't find index of stream for finished connection!")
+ errorLog("Couldn't find index of stream for finished connection!")
return
}
@@ -258,14 +266,14 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
}
func connection(_ connection: NSURLConnection, didFailWithError error: Error) {
- NSLog("Aerial Error: Couldn't download video: \(error)")
+ errorLog("Couldn't download video: \(error.localizedDescription)")
queue.async { () -> Void in
- self.failedDownload("Connection fail: \(error)")
+ self.failedDownload("Connection fail: \(error.localizedDescription)")
}
}
func connection(_ connection: NSURLConnection, didReceive challenge: URLAuthenticationChallenge) {
- NSLog("Aerial Error: Didn't expect authentication challenge while downloading videos!")
+ errorLog("Didn't expect authentication challenge while downloading videos!")
queue.async { () -> Void in
self.failedDownload("Connection fail: Received authentication request!")
}
@@ -280,21 +288,21 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
regex = try NSRegularExpression(pattern: "bytes (\\d+)-\\d+/\\d+",
options: NSRegularExpression.Options.caseInsensitive)
} catch let error as NSError {
- NSLog("Aerial: Error formatting regex: \(error)")
+ errorLog("Error formatting regex: \(error)")
return nil
}
let httpResponse = response as! HTTPURLResponse
guard let contentRange = httpResponse.allHeaderFields["Content-Range"] as? NSString else {
- debugLog("Weird, no byte response: \(response)")
+ errorLog("Weird, no byte response: \(response)")
return nil
}
guard let match = regex.firstMatch(in: contentRange as String,
options: NSRegularExpression.MatchingOptions.anchored,
range: NSRange(location:0, length: contentRange.length)) else {
- debugLog("Weird, couldn't make a regex match for byte offset: \(contentRange)")
+ errorLog("Weird, couldn't make a regex match for byte offset: \(contentRange)")
return nil
}
let offsetMatchRange = match.range(at: 1)
@@ -302,8 +310,6 @@ class VideoDownload: NSObject, NSURLConnectionDataDelegate {
let offset = offsetString.longLongValue
-// debugLog("content range: \(contentRange), start offset: \(offset)")
-
return Int(offset)
}
}
diff --git a/Aerial/Source/Models/Cache/VideoLoader.swift b/Aerial/Source/Models/Cache/VideoLoader.swift
index a07179a8..8d9a61f5 100644
--- a/Aerial/Source/Models/Cache/VideoLoader.swift
+++ b/Aerial/Source/Models/Cache/VideoLoader.swift
@@ -28,11 +28,12 @@ class VideoLoader: NSObject, NSURLConnectionDataDelegate {
let queue = DispatchQueue.main
init(url: URL, loadingRequest: AVAssetResourceLoadingRequest, delegate: VideoLoaderDelegate) {
+ //debugLog("videoloader init")
self.delegate = delegate
self.loadingRequest = loadingRequest
let request = NSMutableURLRequest(url: url)
- request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
+ request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
loadRange = false
loadedRange = NSRange(location: 0, length: 0)
@@ -51,21 +52,22 @@ class VideoLoader: NSObject, NSURLConnectionDataDelegate {
request.setValue(requestRange, forHTTPHeaderField: "Range")
}
}
-
+ //debugLog("loadedRange \(loadedRange)")
+ //debugLog("requestedRange \(requestedRange)")
super.init()
connection = NSURLConnection(request: request as URLRequest, delegate: self, startImmediately: false)
guard let connection = connection else {
- NSLog("Aerial error: Couldn't instantiate connection.")
+ errorLog("Couldn't instantiate connection.")
return
}
connection.setDelegateQueue(OperationQueue.main)
loadedRange = NSRange(location: requestedRange.location, length: 0)
-
+
connection.start()
-// debugLog("Starting request: \(request)")
+ //debugLog("Starting request: \(request)")
}
deinit {
@@ -93,42 +95,52 @@ class VideoLoader: NSObject, NSURLConnectionDataDelegate {
func connection(_ connection: NSURLConnection, didReceive data: Data) {
queue.async { () -> Void in
-
self.fillInContentInformation(self.loadingRequest)
guard let dataRequest = self.loadingRequest.dataRequest else {
- NSLog("Aerial Error: Data request missing for \(self.loadingRequest)")
+ errorLog("Data request missing for \(self.loadingRequest)")
return
}
+ //debugLog("drl \(dataRequest.requestedLength) dro \(dataRequest.requestedOffset)")
+ //debugLog("\(dataRequest)")
+
+ /*if (data.count > 100000) {
+ debugLog("NOTGOOD")
+ dataLog(data)
+ }*/
let requestedRange = self.requestedRange
let loadedRange = self.loadedRange
let loadedLocation = loadedRange.location + loadedRange.length
let dataRange = NSRange(location: loadedRange.location + loadedRange.length,
length: data.count)
+ //debugLog("\(dataRange)")
+
self.delegate?.videoLoader(self, receivedData: data, forRange: dataRange)
// check if we've already been sending content, or we're at right byte offset
if loadedLocation >= requestedRange.location {
-
+ //debugLog("case1")
let requestedEndOffset = Int(dataRequest.requestedOffset + Int64(dataRequest.requestedLength))
-
+
let pendingDataEndOffset = loadedLocation + data.count
+ //debugLog("r \(requestedEndOffset) p \(pendingDataEndOffset)")
if pendingDataEndOffset > requestedEndOffset {
let truncateDataLength = pendingDataEndOffset - requestedEndOffset
let truncatedData = data.subdata(in: 0..= requestedRange.location {
+ //debugLog("case2")
// calculate how far along we need to be into the data before it's part of what
// was requested
let inset = requestedRange.location - loadedRange.location
@@ -145,12 +157,12 @@ class VideoLoader: NSObject, NSURLConnectionDataDelegate {
self.connection?.cancel()
}
} else if inset < 1 {
- NSLog("Aerial Error: Inset is invalid value: \(inset)")
+ errorLog("Inset is invalid value: \(inset)")
}
}
-// debugLog("Received data with length: \(data.count)")
+ //debugLog("Received data with length: \(data.count)")
self.loadedRange.length += data.count
@@ -186,7 +198,7 @@ class VideoLoader: NSObject, NSURLConnectionDataDelegate {
return
}
- // debugLog("Processsing contentInformationRequest")
+ debugLog("Processsing contentInformationRequest")
let contentType: String = uti.takeRetainedValue() as String
@@ -194,7 +206,7 @@ class VideoLoader: NSObject, NSURLConnectionDataDelegate {
contentInformationRequest.contentType = contentType
contentInformationRequest.contentLength = response.expectedContentLength
- //debugLog("expected content length: \(response.expectedContentLength)")
+ debugLog("expected content length: \(response.expectedContentLength) type:\(contentType)")
}
// MARK: - Range
@@ -208,21 +220,21 @@ class VideoLoader: NSObject, NSURLConnectionDataDelegate {
regex = try NSRegularExpression(pattern: "bytes (\\d+)-\\d+/\\d+",
options: NSRegularExpression.Options.caseInsensitive)
} catch let error as NSError {
- NSLog("Aerial: Error formatting regex: \(error)")
+ errorLog("Error formatting regex: \(error)")
return nil
}
let httpResponse = response as! HTTPURLResponse
guard let contentRange = httpResponse.allHeaderFields["Content-Range"] as? NSString else {
- debugLog("Weird, no byte response: \(response)")
+ errorLog("Weird, no byte response: \(response)")
return nil
}
guard let match = regex.firstMatch(in: contentRange as String,
options: NSRegularExpression.MatchingOptions.anchored,
range: NSRange(location:0, length: contentRange.length)) else {
- debugLog("Weird, couldn't make a regex match for byte offset: \(contentRange)")
+ errorLog("Weird, couldn't make a regex match for byte offset: \(contentRange)")
return nil
}
let offsetMatchRange = match.range(at: 1)
@@ -230,7 +242,7 @@ class VideoLoader: NSObject, NSURLConnectionDataDelegate {
let offset = offsetString.longLongValue
-// debugLog("content range: \(contentRange), start offset: \(offset)")
+ //debugLog("content range: \(contentRange), start offset: \(offset)")
return Int(offset)
}
diff --git a/Aerial/Source/Models/Cache/VideoManager.swift b/Aerial/Source/Models/Cache/VideoManager.swift
index fde61f46..028ca199 100644
--- a/Aerial/Source/Models/Cache/VideoManager.swift
+++ b/Aerial/Source/Models/Cache/VideoManager.swift
@@ -7,19 +7,25 @@
//
import Foundation
+typealias VideoManagerCallback = (Int,Int) -> (Void)
class VideoManager : NSObject {
static let sharedInstance = VideoManager()
+ var managerCallbacks = [VideoManagerCallback]()
/// Dictionary of CheckCellView, keyed by the video.id
private var checkCells = [String: CheckCellView]()
-
+
/// List of queued videos, by video.id
private var queuedVideos = [String]()
/// Dictionary of operations, keyed by the video.id
fileprivate var operations = [String: VideoDownloadOperation]()
+ /// Number of videos that were queued
+ private var totalQueued = 0
+ var stopAll = false
+
//var downloadItems: [VideoDownloadItem]
/// Serial OperationQueue for downloads
@@ -36,6 +42,10 @@ class VideoManager : NSObject {
checkCells[id] = checkCellView
}
+ func addCallback(_ callback:@escaping VideoManagerCallback) {
+ managerCallbacks.append(callback)
+ }
+
// Is the video queued for download ?
func isVideoQueued(id: String) -> Bool {
if queuedVideos.firstIndex(of: id) != nil {
@@ -47,28 +57,55 @@ class VideoManager : NSObject {
@discardableResult
func queueDownload(_ video: AerialVideo) -> VideoDownloadOperation {
-
+ print(queue.isSuspended)
+ if stopAll {
+ stopAll = false
+ }
+
+ print(queue.operations)
+
let operation = VideoDownloadOperation(video:video, delegate: self)
operations[video.id] = operation
queue.addOperation(operation)
queuedVideos.append(video.id) // Our Internal List of queued videos
markAsQueued(id: video.id) // Callback the CheckCellView
-
+ totalQueued = totalQueued+1 // Increment our count
+
+ DispatchQueue.main.async {
+ // Callback the callbacks
+ for callback in self.managerCallbacks {
+ callback(self.totalQueued-self.queuedVideos.count, self.totalQueued)
+ }
+ }
return operation
}
// Callbacks for Items
- func finishedDownload(id: String)
+ func finishedDownload(id: String, success: Bool)
{
// Manage our queuedVideo index
if let index = queuedVideos.firstIndex(of: id) {
queuedVideos.remove(at: index)
}
+ if queuedVideos.isEmpty {
+ totalQueued = 0
+ }
+
+ DispatchQueue.main.async {
+ // Callback the callbacks
+ for callback in self.managerCallbacks {
+ callback(self.totalQueued-self.queuedVideos.count, self.totalQueued)
+ }
+ }
// Then callback the CheckCellView
if let cell = checkCells[id] {
- cell.markAsDownloaded()
+ if success {
+ cell.markAsDownloaded()
+ } else {
+ cell.markAsNotDownloaded()
+ }
}
}
@@ -83,6 +120,13 @@ class VideoManager : NSObject {
cell.updateProgressIndicator(progress: progress)
}
}
+
+ /// Cancel all queued operations
+
+ func cancelAll() {
+ stopAll = true
+ queue.cancelAllOperations()
+ }
}
@@ -97,23 +141,50 @@ class VideoDownloadOperation : AsynchronousOperation {
}
override func main() {
- print("start \(video.name)")
+ let videoManager = VideoManager.sharedInstance
+ if videoManager.stopAll {
+ print("was cancelled and mained")
+ return
+ }
+
+ debugLog("Starting download for \(video.name)")
DispatchQueue.main.async {
self.download = VideoDownload(video: self.video, delegate: self)
self.download!.startDownload()
}
}
+
+ override func cancel() {
+ defer { finish() }
+ let videoManager = VideoManager.sharedInstance
+
+ if ((self.download) != nil) {
+ self.download!.cancel()
+ } else {
+ videoManager.finishedDownload(id: self.video.id, success: false)
+ }
+ super.cancel()
+ //finish()
+ }
}
extension VideoDownloadOperation : VideoDownloadDelegate {
func videoDownload(_ videoDownload: VideoDownload,
finished success: Bool, errorMessage: String?) {
- print("finished")
+ debugLog("Finished")
defer { finish() }
- // Call up to clean the view
let videoManager = VideoManager.sharedInstance
- videoManager.finishedDownload(id: videoDownload.video.id)
+ if success {
+ // Call up to clean the view
+ videoManager.finishedDownload(id: videoDownload.video.id, success: true)
+ } else {
+ if (errorMessage != nil) {
+ errorLog(errorMessage!)
+ }
+
+ videoManager.finishedDownload(id: videoDownload.video.id, success: false)
+ }
}
func videoDownload(_ videoDownload: VideoDownload, receivedBytes: Int, progress: Float) {
diff --git a/Aerial/Source/Models/Debug.swift b/Aerial/Source/Models/Debug.swift
deleted file mode 100644
index 2aed247f..00000000
--- a/Aerial/Source/Models/Debug.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-//
-// Debug.swift
-// Aerial
-//
-// Created by John Coates on 10/28/15.
-// Copyright © 2015 John Coates. All rights reserved.
-//
-
-import Foundation
-
-func debugLog(_ message: String) {
- #if DEBUG
- print("\(message)\n")
- #endif
-}
diff --git a/Aerial/Source/Models/Downloads/DownloadManager.swift b/Aerial/Source/Models/Downloads/DownloadManager.swift
index 6fc9eae3..85741cb7 100644
--- a/Aerial/Source/Models/Downloads/DownloadManager.swift
+++ b/Aerial/Source/Models/Downloads/DownloadManager.swift
@@ -110,28 +110,28 @@ extension DownloadOperation: URLSessionDownloadDelegate {
// tvOS11 and tvOS10 JSONs are named entries.json, so we rename them here
if downloadTask.originalRequest!.url!.absoluteString.contains("2x/entries.json") {
- NSLog("Aerial: Caching tvos11.json")
+ debugLog("Caching tvos11.json")
destinationURL.appendPathComponent("tvos11.json")
}
else if downloadTask.originalRequest!.url!.absoluteString.contains("Autumn") {
- NSLog("Aerial: Caching tvos10.json")
+ debugLog("Caching tvos10.json")
destinationURL.appendPathComponent("tvos10.json")
}
else {
- NSLog("Aerial: Caching \(downloadTask.originalRequest!.url!.lastPathComponent)")
+ debugLog("Caching \(downloadTask.originalRequest!.url!.lastPathComponent)")
destinationURL.appendPathComponent(downloadTask.originalRequest!.url!.lastPathComponent)
}
try? manager.removeItem(at: destinationURL)
try manager.moveItem(at: location, to: destinationURL)
} catch {
- print(error)
+ errorLog("\(error)")
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
- let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
- print("\(downloadTask.originalRequest!.url!.absoluteString) \(progress)")
+ //let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
+ //print("\(downloadTask.originalRequest!.url!.absoluteString) \(progress)")
}
}
@@ -143,13 +143,13 @@ extension DownloadOperation: URLSessionTaskDelegate {
defer { finish() }
if let error = error {
- print(error)
+ errorLog("\(error)")
return
}
// We need to untar the resources.tar
if task.originalRequest!.url!.absoluteString.contains("resources.tar") {
- print("untaring resources.tar")
+ debugLog("untaring resources.tar")
// Extract json
let process:Process = Process()
@@ -167,8 +167,6 @@ extension DownloadOperation: URLSessionTaskDelegate {
process.waitUntilExit()
}
- print("Finished \(task.originalRequest!.url!.absoluteString)")
-
+ debugLog("Finished downloading \(task.originalRequest!.url!.absoluteString)")
}
-
}
diff --git a/Aerial/Source/Models/ErrorLog.swift b/Aerial/Source/Models/ErrorLog.swift
new file mode 100644
index 00000000..8fb6236e
--- /dev/null
+++ b/Aerial/Source/Models/ErrorLog.swift
@@ -0,0 +1,154 @@
+//
+// ErrorLog.swift
+// Aerial
+//
+// Created by Guillaume Louel on 17/10/2018.
+// Copyright © 2018 John Coates. All rights reserved.
+//
+
+import Cocoa
+import os.log
+
+enum ErrorLevel : Int {
+ case info, debug, warning, error
+}
+
+class LogMessage {
+ let date : Date
+ let level : ErrorLevel
+ let message : String
+ var actionName : String?
+ var actionBlock : BlockOperation?
+
+ init(level: ErrorLevel, message: String) {
+ self.level = level
+ self.message = message
+ self.date = Date()
+ }
+}
+
+typealias LoggerCallback = (ErrorLevel) -> (Void)
+
+class Logger {
+ static let sharedInstance = Logger()
+
+ var callbacks = [LoggerCallback]()
+
+ func addCallback(_ callback:@escaping LoggerCallback) {
+ callbacks.append(callback)
+ }
+
+ func callBack(level: ErrorLevel) {
+ DispatchQueue.main.async {
+ for callback in self.callbacks {
+ callback(level)
+ }
+ }
+ }
+}
+var errorMessages = [LogMessage]()
+
+func Log(level: ErrorLevel, message: String) {
+ errorMessages.append(LogMessage(level: level, message: message))
+
+
+ // We throw errors to console, they always matter
+ if (level == .error) {
+ if #available(OSX 10.12, *) {
+ // This is faster when available
+ let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Screensaver")
+ os_log("AerialError: %@", log: log, type: .error, message)
+ } else {
+ // Fallback on earlier versions
+ NSLog("AerialError: \(message)")
+ }
+ }
+
+ let preferences = Preferences.sharedInstance
+
+ // We may callback
+ if (level == .warning || level == .error || (level == .debug && preferences.debugMode)) {
+ let logger = Logger.sharedInstance
+ logger.callBack(level: level)
+ }
+
+ // We may log to disk
+ if (preferences.logToDisk) {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateStyle = .none
+ dateFormatter.timeStyle = .medium
+ let string = dateFormatter.string(from: Date()) + " : " + message + "\n"
+ //let string = message + "\n"
+ if let cacheDirectory = VideoCache.cacheDirectory {
+ var cacheFileUrl = URL(fileURLWithPath: cacheDirectory as String)
+ cacheFileUrl.appendPathComponent("AerialLog.txt")
+
+ let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false)!
+ //let data = message.data(using: String.Encoding.utf8, allowLossyConversion: false)!
+
+ if FileManager.default.fileExists(atPath: cacheFileUrl.path) {
+ do {
+ let fileHandle = try FileHandle(forWritingTo: cacheFileUrl)
+ fileHandle.seekToEndOfFile()
+ fileHandle.write(data)
+ fileHandle.closeFile()
+ } catch {
+ print("Can't open handle")
+ }
+ } else {
+ do {
+ try data.write(to: cacheFileUrl, options: .atomic)
+ } catch {
+ print("Can't write to file")
+ }
+ }
+ }
+ }
+}
+
+func debugLog(_ message: String) {
+ #if DEBUG
+ print("\(message)\n")
+ #endif
+
+ let preferences = Preferences.sharedInstance
+ if (preferences.debugMode) {
+ Log(level:.debug, message:message)
+ }
+}
+
+func infoLog(_ message: String) {
+ Log(level:.info, message:message)
+}
+
+func warnLog(_ message: String) {
+ Log(level:.warning, message:message)
+}
+
+func errorLog(_ message: String) {
+ Log(level:.error, message:message)
+}
+
+func dataLog(_ data:Data) {
+ let cacheDirectory = VideoCache.cacheDirectory!
+ var cacheFileUrl = URL(fileURLWithPath: cacheDirectory as String)
+ cacheFileUrl.appendPathComponent("AerialData.txt")
+
+ if FileManager.default.fileExists(atPath: cacheFileUrl.path) {
+ do {
+ let fileHandle = try FileHandle(forWritingTo: cacheFileUrl)
+ fileHandle.seekToEndOfFile()
+ fileHandle.write(data)
+ fileHandle.closeFile()
+ } catch {
+ print("Can't open handle")
+ }
+ } else {
+ do {
+ try data.write(to: cacheFileUrl, options: .atomic)
+ } catch {
+ print("Can't write to file")
+ }
+ }
+
+}
diff --git a/Aerial/Source/Models/ManifestLoader.swift b/Aerial/Source/Models/ManifestLoader.swift
index 84a82d5e..5a68c19a 100644
--- a/Aerial/Source/Models/ManifestLoader.swift
+++ b/Aerial/Source/Models/ManifestLoader.swift
@@ -20,6 +20,10 @@ class ManifestLoader {
var processedVideos = [AerialVideo]()
var lastPluckedFromPlaylist: AerialVideo?
+ var manifestTvOS10: Data?
+ var manifestTvOS11: Data?
+ var manifestTvOS12: Data?
+
// Playlist management
var playlistIsRestricted = false
var playlistRestrictedTo = ""
@@ -42,81 +46,6 @@ class ManifestLoader {
["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
- // Better Descriptions
- let mergeName = [
- "6C3D54AE-0871-498A-81D0-56ED24E5FE9F":"Korea and Japan Night", // Fixint Typo
- "B876B645-3955-420E-99DF-60139E451CF3":"Wulingyuan National Park 1", // China day 1
- "9CCB8297-E9F5-4699-AE1F-890CFBD5E29C":"Longji Rice Terraces", // China day 2
- "D5E76230-81A3-4F65-A1BA-51B8CADED625":"Wulingyuan National Park 2", // China day 3
- "b6-1":"Great Wall 1", // China day 4
- "b2-1":"Great Wall 2", // China day 5
- "b5-1":"Great Wall 3", // China day 6
-
- "AC9C09DD-1D97-4013-A09F-B0F5259E64C3":"Sheikh Zayed Road", // Dubai day 1
- "49790B7C-7D8C-466C-A09E-83E38B6BE87A":"Marina 1", // Dubai day 2
- "02EA5DBE-3A67-4DFA-8528-12901DFD6CC1":"Downtown", // Dubai day 3
- "802866E6-4AAF-4A69-96EA-C582651391F1":"Marina 2", // Dubai day 4
-
- "BAF76353-3475-4855-B7E1-CE96CC9BC3A7":"Approaching Burj Khalifa", // Dubai night 1
- "2F11E857-4F77-4476-8033-4A1E4610AFCC":"Sheikh Zayed Road", // Dubai night 2
-
- "E4ED0B22-EB81-4D4F-A29E-7E1EA6B6D980":"Nuussuaq Peninsula", // Greenland day 1
- "30047FDA-3AE3-4E74-9575-3520AD77865B":"Ilulissat Icefjord", // Greenland day 2
-
- "7D4710EB-5BA4-42E6-AA60-68D77F67D9B9":"Ilulissat Icefjord", // Greenland night 1
-
- "b7-1":"Laupāhoehoe Nui", // Hawaii day 1
- "b1-1":"Waimanu Valley", // Hawaii day 2
- "b2-2":"Honopū Valley", // Hawaii day 3
- "b4-1":"Pu‘u O ‘Umi", // Hawaii day 4
-
- "b6-2":"Kohala coastline", // Hawaii night 1
- "b8-1":"Pu‘u O ‘Umi", // Hawaii night 2
-
- "102C19D1-9D9F-48EC-B492-074C985C4D9F":"Victoria Harbour 1", // Hong Kong day 1
- "560E09E8-E89D-4ADB-8EEA-4754415383D4":"Victoria Peak", // Hong Kong day 2
- "024891DE-B7F6-4187-BFE0-E6D237702EF0":"Wan Chai", // Hong Kong day 3
- "786E674C-BB22-4AA9-9BD3-114D2020EC4D":"Victoria Harbour 2", // Hong Kong day 4
-
- "30313BC1-BF20-45EB-A7B1-5A6FFDBD2488":"Victoria Harbour", // Hong Kong night 1
-
- "6E2FC8AC-832D-46CF-B306-BB2A05030C17":"Liwa Oasis", // Liwa day 1
-
- "b6-3":"Tower Bridge", // London day 1
- "b5-2":"Buckingham Palace", // London day 2
-
- "b1-2":"Tower Bridge 1", // London night 1
- "b3-1":"Tower Bridge 2", // London night 2
-
- "829E69BA-BB53-4841-A138-4DF0C2A74236":"LAX", // Los Angeles day 1
- "30A2A488-E708-42E7-9A90-B749A407AE1C":"Interstate 110", // Los Angeles day 2
- "B730433D-1B3B-4B99-9500-A286BF7A9940":"Santa Monica Beach", // Los Angeles day 3
-
- "89B1643B-06DD-4DEC-B1B0-774493B0F7B7":"Griffith Observatory", // Los Angeles night 1
- "EC67726A-8212-4C5E-83CF-8412932740D2":"Hollywood Sign", // Los Angeles night 2
- "A284F0BF-E690-4C13-92E2-4672D93E8DE5":"Downtown", // Los Angeles night 3
-
- "b7-2":"Central Park", // New York day 1
- "b1-3":"Lower Manhattan", // New York day 2
- "b3-2":"Upper East Side", // New York day 3
-
- "b2-3":"7th avenue", // New York night 1
- "b4-2":"Lower Manhattan", // New York night 2
-
-
- "b8-2":"Marin Headlands", // San Francisco day 1
- "b10-3":"Presidio to Golden Gate", // San Francisco day 2
- "b9-3":"Bay and Golden Gate", // San Francisco day 3
- "b8-3":"Downtown", // San Francisco day 4
- "b3-3":"Embarcadero/Market Street", // San Francisco day 5
- "b4-3":"Golden Gate from SF", // San Francisco day 6
-
- "b6-4":"Downtown/Coit Tower", // San Francisco night 1
- "b7-3":"Fisherman's Wharf", // San Francisco night 2
- "b5-3":"Embarcadero/Market Street", // San Francisco night 3
- "b1-4":"Bay Bridge", // San Francisco night 4
- "b2-4":"Downtown/Sutro Tower" // San Francisco night 5
- ]
// Extra POI
let mergePOI = [
@@ -156,14 +85,8 @@ class ManifestLoader {
"b2-4":"A018_C014_" // San Francisco night 5
]
- func addCallback(_ callback:@escaping manifestLoadCallback) {
- if loadedManifest.count > 0 {
- callback(loadedManifest)
- } else {
- callbacks.append(callback)
- }
- }
+ // MARK: - Playlist generation
func generatePlaylist(isRestricted:Bool, restrictedTo:String) {
// Start fresh
playlist = [AerialVideo]()
@@ -204,7 +127,6 @@ class ManifestLoader {
// On regenerating a new playlist, we try to avoid repeating
while (playlist.count > 1 && lastPluckedFromPlaylist == playlist.first) {
- //NSLog("AerialDBG: Reshuffle")
playlist.shuffle()
}
}
@@ -214,7 +136,6 @@ class ManifestLoader {
let (shouldRestrictByDayNight,restrictTo) = timeManagement.shouldRestrictPlaybackToDayNightVideo()
if (playlist.count == 0 || (restrictTo != playlistRestrictedTo) || (shouldRestrictByDayNight != playlistIsRestricted)) {
- //NSLog("AerialDBG: Generating new playlist")
generatePlaylist(isRestricted: shouldRestrictByDayNight, restrictedTo: restrictTo)
}
@@ -224,7 +145,6 @@ class ManifestLoader {
} else {
return findBestEffortVideo()
}
-
}
// Find a backup plan when conditions are not met
@@ -239,10 +159,10 @@ class ManifestLoader {
// - return a random one from the manifest that is cached
// - return a random video that is not cached (slight betrayal of the Never stream videos)
- NSLog("AerialDBG: empty playlist, not good !")
+ warnLog("Empty playlist, not good !")
if lastPluckedFromPlaylist != nil {
- NSLog("AerialDBG: returning last played video after condition change not met !")
+ warnLog("returning last played video after condition change not met !")
return lastPluckedFromPlaylist!
} else {
// Start with a shuffled list
@@ -251,7 +171,7 @@ class ManifestLoader {
if (shuffled.count == 0)
{
// This is super bad, no manifest at all
- NSLog("AerialDBG: No manifest, nothing to play !")
+ errorLog("No manifest, nothing to play !")
return nil
}
@@ -261,42 +181,44 @@ class ManifestLoader {
// If we find anything cached and in rotation, we send that back
if video.isAvailableOffline && inRotation {
- NSLog("AerialDBG: returning random cached in rotation video after condition change not met !")
+ warnLog("returning random cached in rotation video after condition change not met !")
return video
}
}
// Nothing ? Sorry but you'll get a non cached file
- NSLog("AerialDBG: returning random video after condition change not met !")
+ warnLog("returning random video after condition change not met !")
return shuffled.first!
}
}
+ // MARK: - Lifecycle
+
init() {
- NSLog("AerialML: Manifest init")
+ debugLog("Manifest init")
// We try to load our video manifests in 3 steps :
- // - use locally saved data in preferences plist
+ // - reload from local variables (unused for now)
// - reprocess the saved files in cache directory (full offline mode)
// - download the manifests from servers
- NSLog("AerialML: 10 \(isManifestCached(manifest: .tvOS10))")
- NSLog("AerialML: 11 \(isManifestCached(manifest: .tvOS11))")
- NSLog("AerialML: 12 \(isManifestCached(manifest: .tvOS12))")
+ debugLog("isManifestCached 10 \(isManifestCached(manifest: .tvOS10))")
+ debugLog("isManifestCached 11 \(isManifestCached(manifest: .tvOS11))")
+ debugLog("isManifestCached 12 \(isManifestCached(manifest: .tvOS12))")
- if areManifestsSaved() {
- NSLog("AerialML: Loading from plist")
- loadSavedManifests()
+ if areManifestsFilesLoaded() {
+ debugLog("Files were already loaded")
+ loadManifestsFromLoadedFiles()
}
else
{
- NSLog("AerialML: Not available from plist")
+ debugLog("Files were not already loaded")
// Manifests are not in our preferences plist, are they cached on disk ?
if areManifestsCached() {
- NSLog("AerialML: Manifests are cached on disk, loading")
+ debugLog("Manifests are cached on disk, loading")
loadCachedManifests()
}
else {
// Ok then, we fetch them...
- NSLog("AerialML: fetching missing manifests online")
+ debugLog("Fetching missing manifests online")
let downloadManager = DownloadManager()
var urls: [URL] = []
@@ -315,7 +237,7 @@ class ManifestLoader {
}
let completion = BlockOperation {
- NSLog("AerialML: fetching all done")
+ debugLog("Fetching manifests all done")
// We can now load from the newly cached files
self.loadCachedManifests()
@@ -331,14 +253,24 @@ class ManifestLoader {
}
}
- // Check if the Manifests have been saved in our preferences plist
- func areManifestsSaved() -> Bool {
- if (preferences.manifestTvOS12 != nil && preferences.manifestTvOS11 != nil && preferences.manifestTvOS10 != nil) {
- NSLog("AerialML: manifests are saved in preferences")
+ func addCallback(_ callback:@escaping manifestLoadCallback) {
+ if loadedManifest.count > 0 {
+ callback(loadedManifest)
+ } else {
+ callbacks.append(callback)
+ }
+ }
+
+ // MARK: - Manifests
+
+ // Check if the Manifests have been loaded in this class already
+ func areManifestsFilesLoaded() -> Bool {
+ if (manifestTvOS12 != nil && manifestTvOS11 != nil && manifestTvOS10 != nil) {
+ debugLog("Manifests files were loaded in class")
return true
}
else {
- NSLog("AerialML: manifests are NOT saved in preferences")
+ debugLog("Manifests files were not loaded in class")
return false
}
}
@@ -359,8 +291,6 @@ class ManifestLoader {
if !fileManager.fileExists(atPath: cacheResourcesString) {
return false
}
-
- NSLog("AerialML: \(manifest.rawValue) manifest is cached")
}
else
{
@@ -376,98 +306,102 @@ class ManifestLoader {
// tvOS12
var cacheFileUrl = URL(fileURLWithPath: cacheDirectory as String)
cacheFileUrl.appendPathComponent("entries.json")
- NSLog("AerialML: 12path : \(cacheFileUrl)")
do {
let ndata = try Data(contentsOf: cacheFileUrl)
- self.preferences.manifestTvOS12 = ndata
+ manifestTvOS12 = ndata
}
catch {
- NSLog("Aerial: Error can't load entries.json from cached directory (tvOS12)")
+ errorLog("Can't load entries.json from cached directory (tvOS12)")
}
// tvOS11
cacheFileUrl = URL(fileURLWithPath: cacheDirectory as String)
cacheFileUrl.appendPathComponent("tvos11.json")
- NSLog("AerialML: 11path : \(cacheFileUrl)")
-
do {
let ndata = try Data(contentsOf: cacheFileUrl)
- self.preferences.manifestTvOS11 = ndata
+ manifestTvOS11 = ndata
}
catch {
- NSLog("Aerial: Error can't load tvos11.json from cached directory ")
+ errorLog("Can't load tvos11.json from cached directory")
}
// tvOS10
cacheFileUrl = URL(fileURLWithPath: cacheDirectory as String)
cacheFileUrl.appendPathComponent("tvos10.json")
- NSLog("AerialML: 10path : \(cacheFileUrl)")
-
do {
let ndata = try Data(contentsOf: cacheFileUrl)
- self.preferences.manifestTvOS10 = ndata
+ manifestTvOS10 = ndata
}
catch {
- NSLog("Aerial: Error can't load tvos10.json from cached directory")
+ errorLog("Can't load tvos10.json from cached directory")
}
- if self.preferences.manifestTvOS10 != nil || self.preferences.manifestTvOS11 != nil || self.preferences.manifestTvOS12 != nil {
- loadSavedManifests()
+ if manifestTvOS10 != nil || manifestTvOS11 != nil || manifestTvOS12 != nil {
+ loadManifestsFromLoadedFiles()
} else {
// No internet, no anything, nothing to do
- NSLog("AerialDBG: No video to load, no internet connexion ?")
+ errorLog("No video to load, no internet connexion ?")
}
}
}
// Load Manifests from the saved preferences
- func loadSavedManifests() {
- NSLog("AerialML: LSM")
-
+ func loadManifestsFromLoadedFiles() {
// Reset our array
processedVideos = []
- if (preferences.manifestTvOS12 != nil) {
- NSLog("AerialML: lsm12")
+ if (manifestTvOS12 != nil) {
// We start with the more recent one, it has more information (poi, etc)
- readJSONFromData(preferences.manifestTvOS12!, manifest: .tvOS12)
+ readJSONFromData(manifestTvOS12!, manifest: .tvOS12)
+ } else {
+ warnLog("tvOS12 manifest is absent")
}
- if (preferences.manifestTvOS11 != nil) {
- NSLog("AerialML: lsm11")
+
+ if (manifestTvOS11 != nil) {
// This one has a couple videos not in the tvOS12 JSON. No H264 for these !
- readJSONFromData(preferences.manifestTvOS11!, manifest: .tvOS11)
+ readJSONFromData(manifestTvOS11!, manifest: .tvOS11)
+ } else {
+ warnLog("tvOS11 manifest is absent")
}
- if (preferences.manifestTvOS10 != nil) {
- NSLog("AerialML: lsm10")
+
+ if (manifestTvOS10 != nil) {
// The original manifest is in another format
- readOldJSONFromData(preferences.manifestTvOS10!, manifest: .tvOS10)
+ readOldJSONFromData(manifestTvOS10!, manifest: .tvOS10)
+ } else {
+ warnLog("tvOS10 manifest is absent")
}
- NSLog("AerialML: post json loading")
-
- processedVideos = processedVideos.sorted { $0.secondaryName < $1.secondaryName } // Only matters for Space videos, this way they show sorted in the Space category
+ processedVideos = processedVideos.sorted { $0.secondaryName < $1.secondaryName } // We sort videos by secondary names, so they can display sorted in our view later
self.loadedManifest = processedVideos
-
- NSLog("AerialML: \(processedVideos.count) videos processed !")
+ /*
+ // POI Extracter code
+ infoLog("\(processedVideos.count) videos processed !")
+ let poiStringProvider = PoiStringProvider.sharedInstance
+ for video in processedVideos {
+ infoLog(video.name + " " + video.secondaryName)
+ for poi in video.poi {
+ infoLog(poi.key + ": " + poiStringProvider.getString(key: poi.value))
+ }
+ }*/
// callbacks
for callback in self.callbacks {
- NSLog("AerialML: Calling back")
callback(self.loadedManifest)
}
self.callbacks.removeAll()
}
+ // MARK: - JSON
func readJSONFromData(_ data: Data, manifest: Manifests) {
- //var videos = [AerialVideo]()
-
do {
+ let poiStringProvider = PoiStringProvider.sharedInstance
+
let options = JSONSerialization.ReadingOptions.allowFragments
let batches = try JSONSerialization.jsonObject(with: data, options: options)
guard let batch = batches as? NSDictionary else {
- NSLog("Aerial: Encountered unexpected content type for batch, please report !")
+ errorLog("Encountered unexpected content type for batch, please report !")
return
}
@@ -481,27 +415,29 @@ class ManifestLoader {
let name = item["accessibilityLabel"] as! String
var secondaryName = ""
// We may have a secondary name
- if let mergeName = mergeName[id] {
- secondaryName = mergeName
+ if let mergename = poiStringProvider.getCommunityName(id: id) {
+ secondaryName = mergename
}
+/* if let mergeName = mergeName[id] {
+ secondaryName = mergeName
+ }*/
let timeOfDay = "day" // TODO, this is hardcoded as it's no longer available in the modern JSONs
let type = "video"
var poi : [String:String]?
-
if let mergeId = mergePOI[id] {
- let poiStringProvider = PoiStringProvider.sharedInstance
poi = poiStringProvider.fetchExtraPoiForId(id: mergeId)
- }
- else {
+ } else {
poi = item["pointsOfInterest"] as? [String: String]
}
+
+ let communityPoi = poiStringProvider.getCommunityPoi(id: id)
+
+
let (isDupe,foundDupe) = findDuplicate(id: id, url1080pH264: url1080pH264 ?? "")
if (isDupe) {
- //debugLog("duplicate found, adding \(manifest) as source to \(name)")
foundDupe!.sources.append(manifest)
- }
- else {
+ } else {
let video = AerialVideo(id: id, // Must have
name: name, // Must have
secondaryName: secondaryName, // Optional
@@ -511,20 +447,22 @@ class ManifestLoader {
url1080pHEVC: url1080pHEVC ?? "",
url4KHEVC: url4KHEVC ?? "",
manifest: manifest,
- poi: poi ?? [:] ) // tvOS12 only
+ poi: poi ?? [:],
+ communityPoi: communityPoi)
processedVideos.append(video)
- //checkContentLength(video)
}
}
} catch {
- NSLog("Aerial: Error retrieving content listing.")
+ errorLog("Error retrieving content listing")
return
}
}
func readOldJSONFromData(_ data: Data, manifest: Manifests) {
do {
+ let poiStringProvider = PoiStringProvider.sharedInstance
+
let options = JSONSerialization.ReadingOptions.allowFragments
let batches = try JSONSerialization.jsonObject(with: data,
options: options) as! Array
@@ -543,32 +481,32 @@ class ManifestLoader {
continue
}
- var secondaryName = ""
// We may have a secondary name
- if let mergeName = mergeName[id] {
- secondaryName = mergeName
+ var secondaryName = ""
+ if let mergename = poiStringProvider.getCommunityName(id: id) {
+ secondaryName = mergename
}
-
+
+ // We may have POIs to merge
var poi : [String:String]?
if let mergeId = mergePOI[id] {
let poiStringProvider = PoiStringProvider.sharedInstance
poi = poiStringProvider.fetchExtraPoiForId(id: mergeId)
}
-
+
+ let communityPoi = poiStringProvider.getCommunityPoi(id: id)
+
+ // We may have dupes...
let (isDupe,foundDupe) = findDuplicate(id: id, url1080pH264: url)
if isDupe {
if (foundDupe != nil) {
- //debugLog("duplicate found, adding \(manifest) as source to \(name)")
foundDupe!.sources.append(manifest)
if (foundDupe?.url1080pH264 == "") {
- //debugLog("merging urls for \(url)")
foundDupe?.url1080pH264 = url
}
-
}
- }
- else {
+ } else {
var url4khevc = ""
var url1080phevc = ""
// Check if we have some HEVC urls to merge
@@ -577,6 +515,7 @@ class ManifestLoader {
url4khevc = val["url-4K-SDR"]!
}
+ // Now we can finally add...
let video = AerialVideo(id: id, // Must have
name: name, // Must have
secondaryName: secondaryName,
@@ -586,26 +525,15 @@ class ManifestLoader {
url1080pHEVC: url1080phevc,
url4KHEVC: url4khevc,
manifest: manifest,
- poi: poi ?? [:]) // tvOS12 only
+ poi: poi ?? [:],
+ communityPoi: communityPoi)
processedVideos.append(video)
- //checkContentLength(video)
}
- /*let video = AerialVideo(id: id,
- name: name,
- type: type,
- timeOfDay: timeOfDay,
- url: url)
-
- videos.append(video)
-
- checkContentLength(video)*/
}
}
-
- //self.loadedManifest = videos
} catch {
- NSLog("Aerial: Error retrieving content listing.")
+ errorLog("Error retrieving content listing")
return
}
}
@@ -623,16 +551,13 @@ class ManifestLoader {
if (url1080pH264 != "") {
if (blacklist.contains((URL(string:url1080pH264)?.lastPathComponent)!))
{
- //debugLog("Blacklisted video : \(url1080pH264)")
return (true,nil)
}
}
// We also have a Dictionary of duplicates that need source merging
for (pid,replace) in dupePairs {
- if (id == pid)
- {
- //debugLog("duplicate found by dupePairs \(id)")
+ if (id == pid) {
for vid in processedVideos {
if vid.id == replace {
return (true,vid)
@@ -643,12 +568,9 @@ class ManifestLoader {
for video in processedVideos {
if id == video.id {
- //debugLog("duplicate found by ID")
return (true,video)
- }
- else if (url1080pH264 != "" && video.url1080pH264 != "") {
+ } else if (url1080pH264 != "" && video.url1080pH264 != "") {
if (URL(string:url1080pH264)?.lastPathComponent == URL(string:video.url1080pH264)?.lastPathComponent) {
- //debugLog("duplicate found by filename")
return (true,video)
}
}
@@ -656,99 +578,4 @@ class ManifestLoader {
return (false,nil)
}
-
-/* func checkContentLength(_ video: AerialVideo) {
- let config = URLSessionConfiguration.default
- let session = URLSession(configuration: config)
- let request = NSMutableURLRequest(url: video.url as URL)
-
- request.httpMethod = "HEAD"
-
- let task = session.dataTask(with: request as URLRequest,
- completionHandler: {
- data, response, error in
- video.contentLengthChecked = true
-
- if let error = error {
- NSLog("error fetching content length: \(error)")
- DispatchQueue.main.async(execute: { () -> Void in
- self.receivedContentLengthResponse()
- })
- return
- }
-
- guard let response = response else {
- return
- }
-
- video.contentLength = Int(response.expectedContentLength)
-// NSLog("content length: \(response.expectedContentLength)")
- DispatchQueue.main.async(execute: { () -> Void in
- self.receivedContentLengthResponse()
- })
- })
-
- task.resume()
- }
-
- func receivedContentLengthResponse() {
- // check if content length on all videos has been checked
- for video in loadedManifest {
- if video.contentLengthChecked == false {
- return
- }
- }
-
- filterVideoAndProcessCallbacks()
- }
-
- func filterVideoAndProcessCallbacks() {
- let unfiltered = loadedManifest
-
- var filtered = [AerialVideo]()
- for video in unfiltered {
- // offline? eror? just put it through
- if video.contentLength == 0 {
- filtered.append(video)
- continue
- }
-
- // check to see if we find another video with the same content length
- var isDuplicate = false
- for videoCheck in filtered {
- if videoCheck.id == video.id {
- isDuplicate = true
- continue
- }
-
- if videoCheck.name != video.name {
- continue
- }
-
- if videoCheck.timeOfDay != video.timeOfDay {
- continue
- }
-
- if videoCheck.contentLength == video.contentLength {
-// NSLog("removing duplicate video \(videoCheck.name) \(videoCheck.timeOfDay)")
- isDuplicate = true
- break
- }
- } // dupe check
-
- if isDuplicate == true {
- continue
- }
-
- filtered.append(video)
- }
-
- loadedManifest = filtered
-
- // callbacks
- for callback in self.callbacks {
- callback(filtered)
- }
- self.callbacks.removeAll()
- }*/
}
diff --git a/Aerial/Source/Models/TimeManagement.swift b/Aerial/Source/Models/TimeManagement.swift
index b63b1491..f2006ac9 100644
--- a/Aerial/Source/Models/TimeManagement.swift
+++ b/Aerial/Source/Models/TimeManagement.swift
@@ -37,7 +37,7 @@ class TimeManagement {
else if preferences.timeMode == Preferences.TimeMode.nightShift.rawValue {
let (isNSCapable, sunrise, sunset, _) = getNightShiftInformation()
if (!isNSCapable) {
- NSLog("Aerial : Trying to use Night Shift on a non capable Mac")
+ errorLog("Trying to use Night Shift on a non capable Mac")
return (false,"")
}
@@ -49,11 +49,11 @@ class TimeManagement {
dateFormatter.dateFormat = "HH:mm"
guard let dateSunrise = dateFormatter.date(from: preferences.manualSunrise!) else {
- NSLog("Aerial : Invalid sunrise time in preferences")
+ errorLog("Invalid sunrise time in preferences")
return(false,"")
}
guard let dateSunset = dateFormatter.date(from: preferences.manualSunset!) else {
- NSLog("Aerial : Invalid sunrise time in preferences")
+ errorLog("Invalid sunset time in preferences")
return(false,"")
}
@@ -130,7 +130,6 @@ class TimeManagement {
func isDarkModeEnabled() -> Bool {
if #available(OSX 10.14, *) {
let modeString = UserDefaults.standard.string(forKey: "AppleInterfaceStyle")
-
return (modeString == "Dark")
}
else {
@@ -218,7 +217,7 @@ class TimeManagement {
}
// /usr/bin/corebrightnessdiag nightshift-internal | grep nextSunset | cut -d \" -f2
-
+ warnLog("Location services may be disabled, Night Shift can't detect Sunrise and Sunset times without them")
return (false,nil,nil,"Location services may be disabled")
}
@@ -239,5 +238,4 @@ class TimeManagement {
return (output, task.terminationStatus)
}
-
}
diff --git a/Aerial/Source/Views/AerialView.swift b/Aerial/Source/Views/AerialView.swift
index de95a8a5..e4848f03 100644
--- a/Aerial/Source/Views/AerialView.swift
+++ b/Aerial/Source/Views/AerialView.swift
@@ -29,7 +29,11 @@ class AerialView: ScreenSaverView {
var currentVideo: AerialVideo?
var observerWasSet = false
-
+ var hasStartedPlaying = false
+ var wasStopped = false
+ var isDisabled = false
+ var timeObserver : Any?
+
static var shouldFade: Bool {
let preferences = Preferences.sharedInstance
return (preferences.fadeMode != Preferences.FadeMode.disabled.rawValue)
@@ -69,10 +73,11 @@ class AerialView: ScreenSaverView {
}
static var sharedViews: [AerialView] = []
-
// MARK: - Shared Player
static var singlePlayerAlreadySetup: Bool = false
+ static var sharedPlayerIndex: Int?
+
class var sharedPlayer: AVPlayer {
struct Static {
static let instance: AVPlayer = AVPlayer()
@@ -91,21 +96,23 @@ class AerialView: ScreenSaverView {
}
// MARK: - Init / Setup
-
+ // This is the one used by System Preferences
override init?(frame: NSRect, isPreview: Bool) {
super.init(frame: frame, isPreview: isPreview)
-
+ debugLog("avInit1")
self.animationTimeInterval = 1.0 / 30.0
setup()
}
+ // This is the one used by App
required init?(coder: NSCoder) {
super.init(coder: coder)
+ debugLog("avInit2")
setup()
}
deinit {
- debugLog("deinit AerialView")
+ debugLog("\(self.description) deinit AerialView")
NotificationCenter.default.removeObserver(self)
// set player item to nil if not preview player
@@ -129,85 +136,39 @@ class AerialView: ScreenSaverView {
AerialView.players.remove(at: index)
}
- func setupPlayerLayer(withPlayer player: AVPlayer) {
- self.layer = CALayer()
- guard let layer = self.layer else {
- NSLog("Aerial Error: Couldn't create CALayer")
- return
- }
- self.wantsLayer = true
- layer.backgroundColor = NSColor.black.cgColor
- layer.needsDisplayOnBoundsChange = true
- layer.frame = self.bounds
-
- debugLog("setting up player layer with frame: \(self.bounds) / \(self.frame)")
-
- playerLayer = AVPlayerLayer(player: player)
- if #available(OSX 10.10, *) {
- playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
+ func setup() {
+ debugLog("\(self.description) AerialView setup init")
+ if (AerialView.singlePlayerAlreadySetup) {
+ debugLog("\(AerialView.sharedViews[AerialView.sharedPlayerIndex!].wasStopped)")
+ // On previews, it's possible that our shared player was stopped and is not reusable
+ if AerialView.sharedViews[AerialView.sharedPlayerIndex!].wasStopped {
+ debugLog("Purging previous singlePlayer")
+ AerialView.singlePlayerAlreadySetup = false
+ AerialView.sharedPlayerIndex = nil
+ }
}
- playerLayer.autoresizingMask = [CAAutoresizingMask.layerWidthSizable, CAAutoresizingMask.layerHeightSizable]
- playerLayer.frame = layer.bounds
- playerLayer.contentsScale = NSScreen.main?.backingScaleFactor ?? 1.0
-
- layer.addSublayer(playerLayer)
-
- textLayer = CATextLayer()
-/* textLayer.frame = CGRect(x: 20, y: layer.bounds.height-60, width: layer.bounds.width-40, height: 40)
- textLayer.font = NSFont(name: "Helvetica Neue Medium", size: 28)
- if self.frame.height < 400 {
- textLayer.fontSize = 12 // Seems needed despite line above
-
- } else {
- textLayer.fontSize = 28 // Seems needed despite line above
- }*/
- textLayer.string = ""
- textLayer.opacity = 0
- // Add a bit of shadow to give an outline and better readability
- textLayer.shadowRadius = 10
- textLayer.shadowOpacity = 1.0
- textLayer.shadowColor = CGColor.black
- textLayer.contentsScale = NSScreen.main?.backingScaleFactor ?? 1.0
- layer.addSublayer(textLayer)
- // Clock Layer
- clockLayer = CATextLayer()
- clockLayer.opacity = 0
- // Add a bit of shadow to give an outline and better readability
- clockLayer.shadowRadius = 10
- clockLayer.shadowOpacity = 1.0
- clockLayer.contentsScale = NSScreen.main?.backingScaleFactor ?? 1.0
- layer.addSublayer(clockLayer)
-
- // Message Layer
- messageLayer = CATextLayer()
- messageLayer.opacity = 0
- // Add a bit of shadow to give an outline and better readability
- messageLayer.shadowRadius = 10
- messageLayer.shadowOpacity = 1.0
- messageLayer.contentsScale = NSScreen.main?.backingScaleFactor ?? 1.0
- layer.addSublayer(messageLayer)
- }
-
- func setup() {
- NSLog("AerialMM : setup init")
var localPlayer: AVPlayer?
let notPreview = !isPreview
+ debugLog("\(self.description) isPreview : \(isPreview)")
if notPreview {
let preferences = Preferences.sharedInstance
-
+ debugLog("\(self.description) singlePlayerAlreadySetup \(AerialView.singlePlayerAlreadySetup)")
if (AerialView.singlePlayerAlreadySetup && preferences.multiMonitorMode == Preferences.MultiMonitorMode.mainOnly.rawValue) {
+ isDisabled = true
return
}
// check if we should share preview's player
- let noPlayers = (AerialView.players.count == 0)
+ //let noPlayers = (AerialView.players.count == 0)
let previewPlayerExists = (AerialView.previewPlayer != nil)
- if noPlayers && previewPlayerExists {
+ debugLog("\(self.description) nbPlayers \(AerialView.players.count) previewPlayerExists \(previewPlayerExists)")
+ /*if noPlayers && previewPlayerExists {
+
localPlayer = AerialView.previewPlayer
- }
+ }*/
} else {
AerialView.previewView = self
}
@@ -217,19 +178,22 @@ class AerialView: ScreenSaverView {
}
if localPlayer == nil {
+ debugLog("\(self.description) no local player")
+
if AerialView.sharingPlayers {
- if AerialView.previewPlayer != nil {
+ /*if AerialView.previewPlayer != nil {
localPlayer = AerialView.previewPlayer
- } else {
- localPlayer = AerialView.sharedPlayer
- }
+ } else {*/
+
+ localPlayer = AerialView.sharedPlayer
+ //}
} else {
localPlayer = AVPlayer()
}
}
guard let player = localPlayer else {
- NSLog("Aerial Error: Couldn't create AVPlayer!")
+ errorLog("\(self.description) Couldn't create AVPlayer!")
return
}
@@ -245,46 +209,175 @@ class AerialView: ScreenSaverView {
setupPlayerLayer(withPlayer: player)
if AerialView.sharingPlayers && AerialView.singlePlayerAlreadySetup {
- self.playerLayer.player = AerialView.sharedViews[0].player
+ self.playerLayer.player = AerialView.sharedViews[AerialView.sharedPlayerIndex!].player
self.playerLayer.opacity = 0
return
}
-
- AerialView.singlePlayerAlreadySetup = true
+
+ // We're NOT sharing the preview !!!!!
+ if !isPreview {
+ AerialView.singlePlayerAlreadySetup = true
+ AerialView.sharedPlayerIndex = AerialView.sharedViews.count-1
+ }
ManifestLoader.instance.addCallback { videos in
self.playNextVideo()
}
}
+ override func viewDidChangeBackingProperties() {
+ debugLog("\(self.description) backing change \((self.window?.backingScaleFactor) ?? 1.0) isDisabled: \(isDisabled)")
+ if (!isDisabled)
+ {
+ self.layer!.contentsScale = (self.window?.backingScaleFactor) ?? 1.0
+ self.playerLayer.contentsScale = (self.window?.backingScaleFactor) ?? 1.0
+ self.textLayer.contentsScale = (self.window?.backingScaleFactor) ?? 1.0
+ self.clockLayer.contentsScale = (self.window?.backingScaleFactor) ?? 1.0
+ self.messageLayer.contentsScale = (self.window?.backingScaleFactor) ?? 1.0
+ }
+ }
+
+ func setupPlayerLayer(withPlayer player: AVPlayer) {
+ debugLog("\(self.description) setupPlayerLayer")
+
+ self.layer = CALayer()
+ guard let layer = self.layer else {
+ errorLog("\(self.description) Couldn't create CALayer")
+ return
+ }
+ self.wantsLayer = true
+ layer.backgroundColor = NSColor.black.cgColor
+ layer.needsDisplayOnBoundsChange = true
+ layer.frame = self.bounds
+
+ //self.
+ debugLog("\(self.description) setting up player layer with frame: \(self.bounds) / \(self.frame)")
+
+ playerLayer = AVPlayerLayer(player: player)
+ if #available(OSX 10.10, *) {
+ playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
+ }
+ playerLayer.autoresizingMask = [CAAutoresizingMask.layerWidthSizable, CAAutoresizingMask.layerHeightSizable]
+ playerLayer.frame = layer.bounds
+ //playerLayer.contentsScale = 1.0 // NSScreen.main?.backingScaleFactor ?? 1.0
+ layer.addSublayer(playerLayer)
+
+ textLayer = CATextLayer()
+ textLayer.frame = layer.bounds
+ textLayer.opacity = 0
+ // Add a bit of shadow to give an outline and better readability
+ textLayer.shadowRadius = 10
+ textLayer.shadowOpacity = 1.0
+ textLayer.shadowColor = CGColor.black
+ //textLayer.contentsScale = 1.0 // NSScreen.main?.backingScaleFactor ?? 1.0
+ layer.addSublayer(textLayer)
+
+ // Clock Layer
+ clockLayer = CATextLayer()
+ clockLayer.opacity = 0
+ // Add a bit of shadow to give an outline and better readability
+ clockLayer.shadowRadius = 10
+ clockLayer.shadowOpacity = 1.0
+ textLayer.shadowColor = CGColor.black
+ //clockLayer.contentsScale = 1.0 // NSScreen.main?.backingScaleFactor ?? 1.0
+ layer.addSublayer(clockLayer)
+
+ // Message Layer
+ messageLayer = CATextLayer()
+ messageLayer.opacity = 0
+ // Add a bit of shadow to give an outline and better readability
+ messageLayer.shadowRadius = 10
+ messageLayer.shadowOpacity = 1.0
+ textLayer.shadowColor = CGColor.black
+ //messageLayer.contentsScale = 1.0 // NSScreen.main?.backingScaleFactor ?? 1.0
+ layer.addSublayer(messageLayer)
+ }
+
+ // MARK: - Lifecycle stuff
+/* override func draw(_ rect: NSRect) {
+ }*/
+ override func startAnimation() {
+ super.startAnimation()
+ debugLog("\(self.description) startAnimation")
+
+ if !isDisabled{
+ // Previews may be restarted, but our layer will get hidden (somehow) so show it back
+ if (isPreview && player?.currentTime() != CMTime.zero) {
+ playerLayer.opacity = 1
+ player?.play()
+ }
+
+ /*if player?.rate == 0 {
+
+ }*/
+ }
+ }
+
+ override func stopAnimation() {
+ super.stopAnimation()
+ wasStopped = true
+ debugLog("\(self.description) stopAnimation")
+ if !isDisabled {
+ player?.pause()
+ }
+ }
+
// MARK: - AVPlayerItem Notifications
@objc func playerItemFailedtoPlayToEnd(_ aNotification: Notification) {
- NSLog("AVPlayerItemFailedToPlayToEndTimeNotification \(aNotification)")
-
+ warnLog("\(self.description) AVPlayerItemFailedToPlayToEndTimeNotification \(aNotification)")
playNextVideo()
}
@objc func playerItemNewErrorLogEntryNotification(_ aNotification: Notification) {
- NSLog("AVPlayerItemNewErrorLogEntryNotification \(aNotification)")
+ warnLog("\(self.description) AVPlayerItemNewErrorLogEntryNotification \(aNotification)")
}
@objc func playerItemPlaybackStalledNotification(_ aNotification: Notification) {
- NSLog("AVPlayerItemPlaybackStalledNotification \(aNotification)")
+ warnLog("\(self.description) AVPlayerItemPlaybackStalledNotification \(aNotification)")
}
@objc func playerItemDidReachEnd(_ aNotification: Notification) {
- debugLog("played did reach end")
- debugLog("notification: \(aNotification)")
+ debugLog("\(self.description) played did reach end")
+ debugLog("\(self.description) notification: \(aNotification)")
playNextVideo()
+ debugLog("\(self.description) playing next video for player \(String(describing: player))")
+ }
+
+ // Wait for the player to be ready
+ internal override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
+ debugLog("\(self.description) observeValue \(String(describing: keyPath))")
+ if self.playerLayer.isReadyForDisplay {
+ self.player!.play()
+ hasStartedPlaying = true
- debugLog("playing next video for player \(String(describing: player))")
+ // All playerLayers should fade, we only have one shared player
+ if AerialView.sharingPlayers {
+ for view in AerialView.sharedViews {
+ self.addPlayerFades(player: self.player!, playerLayer: view.playerLayer, video: self.currentVideo!)
+ }
+ } else {
+ self.addPlayerFades(player: self.player!, playerLayer: self.playerLayer, video: self.currentVideo!)
+ }
+
+ // Descriptions on main only for now
+
+ self.addDescriptions(player: self.player!, video: self.currentVideo!)
+ }
}
+ // MARK: - playNextVideo()
func playNextVideo() {
//let timeManagement = TimeManagement.sharedInstance
let notificationCenter = NotificationCenter.default
+ // Clear everything
+ if (timeObserver != nil) {
+ self.player!.removeTimeObserver(timeObserver!)
+ }
+ self.textLayer.removeAllAnimations()
+ self.clockLayer.removeAllAnimations()
+ self.messageLayer.removeAllAnimations()
// remove old entries
notificationCenter.removeObserver(self)
@@ -303,7 +396,7 @@ class AerialView: ScreenSaverView {
AerialView.previewPlayer = player
}
- debugLog("Setting player for all player layers in \(AerialView.sharedViews)")
+ debugLog("\(self.description) Setting player for all player layers in \(AerialView.sharedViews)")
for view in AerialView.sharedViews {
view.playerLayer.player = player
}
@@ -323,7 +416,7 @@ class AerialView: ScreenSaverView {
let randomVideo = ManifestLoader.instance.randomVideo(excluding: currentVideos)
guard let video = randomVideo else {
- NSLog("Aerial: Error grabbing random video!")
+ errorLog("\(self.description) Error grabbing random video!")
return
}
self.currentVideo = video
@@ -333,27 +426,27 @@ class AerialView: ScreenSaverView {
if !video.isAvailableOffline
{
player.replaceCurrentItem(with: item)
- debugLog("streaming video (not fully available offline) : \(video.url)")
+ 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)
player.replaceCurrentItem(with: localitem)
- debugLog("playing video (OFFLINE MODE) : \(localurl)")
+ debugLog("\(self.description) playing video (OFFLINE MODE) : \(localurl)")
}
-
- if player.rate == 0 {
+/*
+ // The first time we start from start animation !
+ if hasStartedPlaying && player.rate == 0 {
player.play()
- //player.rate = 32.0
}
-
+ */
guard let currentItem = player.currentItem else {
- NSLog("Aerial Error: No current item!")
+ errorLog("\(self.description) No current item!")
return
}
- debugLog("observing current item \(currentItem)")
+ debugLog("\(self.description) observing current item \(currentItem)")
// Descriptions and fades are set when we begin playback
if !observerWasSet {
@@ -380,24 +473,8 @@ class AerialView: ScreenSaverView {
player.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none
}
- // Wait for the player to be ready
- internal override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
- if self.playerLayer.isReadyForDisplay {
- // All playerLayers should fade, we only have one shared player
- if AerialView.sharingPlayers {
- for view in AerialView.sharedViews {
- self.addPlayerFades(player: self.player!, playerLayer: view.playerLayer, video: self.currentVideo!)
- }
- } else {
- self.addPlayerFades(player: self.player!, playerLayer: self.playerLayer, video: self.currentVideo!)
- }
-
- // Descriptions on main only for now
+ // MARK: - Extra Animations
- self.addDescriptions(player: self.player!, video: self.currentVideo!)
- }
- }
-
private func addPlayerFades(player: AVPlayer, playerLayer: AVPlayerLayer, video: AerialVideo)
{
// We only fade in/out if we have duration
@@ -423,14 +500,13 @@ class AerialView: ScreenSaverView {
if (preferences.showDescriptions)
{
// Preventively, make sure we have poi as tvOS11/10 videos won't have them
- if video.poi.count > 0 && poiStringProvider.loadedDescriptions
+ if (video.poi.count > 0 && poiStringProvider.loadedDescriptions) || (preferences.useCommunityDescriptions && video.communityPoi.count > 0)
{
// Collect all the timestamps from the JSON
var times = [NSValue]()
+ let keys = poiStringProvider.getPoiKeys(video: video)
- for pkv in video.poi
- {
- //print("time \(pkv.key) \(poiStringProvider.getString(key: video.poi[pkv.key]!))")
+ for pkv in keys {
let timeStamp = Double(pkv.key)!
times.append(NSValue(time: CMTime(seconds: timeStamp, preferredTimescale: 1)))
}
@@ -438,8 +514,8 @@ class AerialView: ScreenSaverView {
times.sort(by: { ($0 as! CMTime).seconds < ($1 as! CMTime).seconds } )
// Animate the very first one on it's own
- let str = poiStringProvider.getString(key: video.poi["0"]!)
-
+ let str = poiStringProvider.getString(key: keys["0"]!, video: video)
+
var fadeAnimation:CAKeyframeAnimation
if (preferences.showDescriptionsMode == Preferences.DescriptionMode.fade10seconds.rawValue)
@@ -466,12 +542,17 @@ class AerialView: ScreenSaverView {
}
self.textLayer.add(fadeAnimation, forKey: "textfade")
- setupTextLayer(string: str, duration: fadeAnimation.duration)
+ if (video.duration > 0) {
+ setupTextLayer(string: str, duration: fadeAnimation.duration, isInitial: true, totalDuration: video.duration - 1)
+ } else {
+ setupTextLayer(string: str, duration: fadeAnimation.duration, isInitial: true, totalDuration: 807)
+ }
let mainQueue = DispatchQueue.main
+
// We then callback for each timestamp
- player.addBoundaryTimeObserver(forTimes: times, queue: mainQueue) {
+ timeObserver = player.addBoundaryTimeObserver(forTimes: times, queue: mainQueue) {
var isLastTimeStamp = true
var intervalUntilNextTimeStamp = 0.0
@@ -521,16 +602,21 @@ class AerialView: ScreenSaverView {
}
// Get the string for the current timestamp
let key = String(format: "%.0f",closestTime)
- let str = poiStringProvider.getString(key: video.poi[key]!)
- self.setupTextLayer(string: str, duration: fadeAnimation.duration)
+ let str = poiStringProvider.getString(key: keys[key]!, video: video)
+ self.setupTextLayer(string: str, duration: fadeAnimation.duration, isInitial: false, totalDuration: video.duration-1)
self.textLayer.add(fadeAnimation, forKey: "textfade")
}
}
else
{
- // We don't have any extended description, using video name (City)
- let str = video.name
+ // We don't have any extended description, using Secondary name (location) or video name (City)
+ let str: String
+ if (video.secondaryName != "") {
+ str = video.secondaryName
+ } else {
+ str = video.name
+ }
var fadeAnimation:CAKeyframeAnimation
if (preferences.showDescriptionsMode == Preferences.DescriptionMode.fade10seconds.rawValue)
@@ -551,25 +637,15 @@ class AerialView: ScreenSaverView {
}
}
self.textLayer.add(fadeAnimation, forKey: "textfade")
- setupTextLayer(string: str, duration : fadeAnimation.duration)
+ setupTextLayer(string: str, duration : fadeAnimation.duration, isInitial: false, totalDuration: video.duration)
}
}
}
- // Create a Fade In/Out animation
- func createFadeInOutAnimation(duration: Double) -> CAKeyframeAnimation {
- let fadeAnimation = CAKeyframeAnimation(keyPath: "opacity")
- fadeAnimation.values = [0, 0, 1, 1, 0] as [NSNumber]
- fadeAnimation.keyTimes = [0, Double( 1/duration ), Double( (1+AerialView.textFadeDuration)/duration ), Double( 1-AerialView.textFadeDuration/duration ), 1] as [NSNumber]
- fadeAnimation.duration = duration
-
- return fadeAnimation
- }
-
- func setupTextLayer(string:String, duration: CFTimeInterval) {
+ func setupTextLayer(string:String, duration: CFTimeInterval, isInitial: Bool, totalDuration: Double) {
// Setup string
self.textLayer.string = string
-
+ self.textLayer.isWrapped = true
let preferences = Preferences.sharedInstance
// We override font size on previews
@@ -587,13 +663,16 @@ class AerialView: ScreenSaverView {
// Make sure we change the layer font/size
self.textLayer.font = font
self.textLayer.fontSize = fontSize
-
+
let attributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.font : font as Any]
// Calculate bounding box
let s = NSAttributedString(string: string, attributes: attributes)
- let rect = s.boundingRect(with: layer!.visibleRect.size, options: NSString.DrawingOptions.usesLineFragmentOrigin)
-
+
+ var rect = s.boundingRect(with: layer!.visibleRect.size, options: [.truncatesLastVisibleLine, .usesLineFragmentOrigin])
+ // Last line won't appear if we don't adjust
+ rect = CGRect(x: rect.origin.x, y: rect.origin.y, width: rect.width, height: rect.height+10)
+
// Rebind frame
self.textLayer.frame = rect
@@ -607,65 +686,78 @@ class AerialView: ScreenSaverView {
lastCorner = corner
repositionTextLayer(position: corner)
- setupAndRepositionExtra(position: corner, duration: duration)
+ setupAndRepositionExtra(position: corner, duration: duration, isInitial: isInitial, totalDuration: totalDuration)
} else {
repositionTextLayer(position: preferences.descriptionCorner!) // Or set position from pref
- setupAndRepositionExtra(position: preferences.descriptionCorner!, duration: duration)
+ setupAndRepositionExtra(position: preferences.descriptionCorner!, duration: duration, isInitial: isInitial, totalDuration: totalDuration)
}
}
- private func setupAndRepositionExtra(position: Int, duration: CFTimeInterval)
+ private func setupAndRepositionExtra(position: Int, duration: CFTimeInterval, isInitial: Bool, totalDuration: Double)
{
let preferences = Preferences.sharedInstance
if (preferences.showClock)
{
- if (clockTimer == nil)
- {
- if #available(OSX 10.12, *) {
- clockTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { (Timer) in
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "j:mm:ss", options: 0, locale: Locale.current)
- let dateString = dateFormatter.string(from: Date())
- self.clockLayer.string = dateString
- })
+ if (isInitial) {
+ if (clockTimer == nil)
+ {
+ if #available(OSX 10.12, *) {
+ clockTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { (Timer) in
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "j:mm:ss", options: 0, locale: Locale.current)
+ let dateString = dateFormatter.string(from: Date())
+ self.clockLayer.string = dateString
+ })
+ }
+
}
- }
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "j:mm:ss", options: 0, locale: Locale.current)
- let dateString = dateFormatter.string(from: Date())
-
- self.clockLayer.string = dateString
-
- let preferences = Preferences.sharedInstance
-
- // We override font size on previews
- var fontSize = CGFloat(preferences.extraFontSize!)
- if (layer!.bounds.height < 200) {
- fontSize = 12
+
+ let dateFormatter = DateFormatter()
+ if (preferences.withSeconds) {
+ dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "j:mm:ss", options: 0, locale: Locale.current)
+ } else {
+ dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "j:mm", options: 0, locale: Locale.current)
+ }
+ let dateString = dateFormatter.string(from: Date())
+
+ self.clockLayer.string = dateString
+
+ // We override font size on previews
+ var fontSize = CGFloat(preferences.extraFontSize!)
+ if (layer!.bounds.height < 200) {
+ fontSize = 12
+ }
+
+ // Get font with a fallback in case
+ var font = NSFont(name: "Monaco", size: 28)
+ if let tryFont = NSFont(name: preferences.extraFontName!,size: fontSize) {
+ font = tryFont
+ }
+
+ // Make sure we change the layer font/size
+ self.clockLayer.font = font
+ self.clockLayer.fontSize = fontSize
+
+ let attributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.font : font as Any]
+
+ // Calculate bounding box
+ let s = NSAttributedString(string: dateString, attributes: attributes)
+ let rect = s.boundingRect(with: layer!.visibleRect.size, options: NSString.DrawingOptions.usesLineFragmentOrigin)
+
+ // Rebind frame
+ self.clockLayer.frame = rect
+ //clockLayer.anchorPoint = CGPoint(x: 0, y:0)
+ //clockLayer.position = CGPoint(x:10 ,y:10+textLayer.visibleRect.height)
+ //clockLayer.opacity = 1.0
}
- // Get font with a fallback in case
- var font = NSFont(name: "Helvetica Neue Medium", size: 28)
- if let tryFont = NSFont(name: preferences.extraFontName!,size: fontSize) {
- font = tryFont
+ if (preferences.descriptionCorner == Preferences.DescriptionCorner.random.rawValue) {
+ clockLayer.add(createFadeInOutAnimation(duration: duration), forKey: "textfade")
+ } else if isInitial && preferences.showDescriptionsMode == Preferences.DescriptionMode.always.rawValue {
+ clockLayer.add(createFadeInOutAnimation(duration: totalDuration), forKey: "textfade")
+ } else if preferences.showDescriptionsMode == Preferences.DescriptionMode.fade10seconds.rawValue {
+ clockLayer.add(createFadeInOutAnimation(duration: duration), forKey: "textfade")
}
-
- // Make sure we change the layer font/size
- self.clockLayer.font = font
- self.clockLayer.fontSize = fontSize
-
- let attributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.font : font as Any]
-
- // Calculate bounding box
- let s = NSAttributedString(string: dateString, attributes: attributes)
- let rect = s.boundingRect(with: layer!.visibleRect.size, options: NSString.DrawingOptions.usesLineFragmentOrigin)
-
- // Rebind frame
- self.clockLayer.frame = rect
- //clockLayer.anchorPoint = CGPoint(x: 0, y:0)
- //clockLayer.position = CGPoint(x:10 ,y:10+textLayer.visibleRect.height)
- //clockLayer.opacity = 1.0
- clockLayer.add(createFadeInOutAnimation(duration: duration), forKey: "textfade")
}
if (preferences.showMessage && preferences.showMessageString != "") {
@@ -698,18 +790,65 @@ class AerialView: ScreenSaverView {
//messageLayer.anchorPoint = CGPoint(x: 0, y:0)
//messageLayer.position = CGPoint(x:10 ,y:10+textLayer.visibleRect.height)
//messageLayer.opacity = 1.0
- self.messageLayer.add(createFadeInOutAnimation(duration: duration), forKey: "textfade")
+ if (preferences.descriptionCorner == Preferences.DescriptionCorner.random.rawValue) {
+ self.messageLayer.add(createFadeInOutAnimation(duration: duration), forKey: "textfade")
+ } else if isInitial && preferences.showDescriptionsMode == Preferences.DescriptionMode.always.rawValue {
+ self.messageLayer.add(createFadeInOutAnimation(duration: totalDuration), forKey: "textfade")
+ } else if preferences.showDescriptionsMode == Preferences.DescriptionMode.fade10seconds.rawValue {
+ self.messageLayer.add(createFadeInOutAnimation(duration: duration), forKey: "textfade")
+ }
+
+
}
- if preferences.extraCorner == Preferences.ExtraCorner.same.rawValue{
- repositionClockAndMessageLayer(position: position, alone: false)
- } else if preferences.extraCorner == Preferences.ExtraCorner.hOpposed.rawValue{
- repositionClockAndMessageLayer(position: (position+2)%4, alone: true)
- } else if preferences.extraCorner == Preferences.ExtraCorner.dOpposed.rawValue{
- repositionClockAndMessageLayer(position: 3-position, alone: true)
+ if (!isInitial && preferences.extraCorner == Preferences.ExtraCorner.same.rawValue && preferences.showDescriptionsMode == Preferences.DescriptionMode.always.rawValue && preferences.descriptionCorner != Preferences.DescriptionCorner.random.rawValue) {
+ animateClockAndMessageLayer(position: position)
+ } else {
+ if preferences.extraCorner == Preferences.ExtraCorner.same.rawValue{
+ repositionClockAndMessageLayer(position: position, alone: false)
+ } else if preferences.extraCorner == Preferences.ExtraCorner.hOpposed.rawValue{
+ repositionClockAndMessageLayer(position: (position+2)%4, alone: true)
+ } else if preferences.extraCorner == Preferences.ExtraCorner.dOpposed.rawValue{
+ repositionClockAndMessageLayer(position: 3-position, alone: true)
+ }
}
}
+ private func animateClockAndMessageLayer(position: Int) {
+ var clockDecal : CGFloat = 0
+ var messageDecal : CGFloat = 0
+ let preferences = Preferences.sharedInstance
+
+ clockDecal += textLayer.visibleRect.height
+ messageDecal += textLayer.visibleRect.height
+
+ if preferences.showMessage {
+ clockDecal += messageLayer.visibleRect.height
+ }
+ let duration = 1 + AerialView.textFadeDuration
+
+ var cto, mto : CGPoint
+ if (position == Preferences.DescriptionCorner.topLeft.rawValue) {
+ cto = CGPoint(x: 10, y: layer!.bounds.height-10-clockDecal)
+ mto = CGPoint(x: 10, y: layer!.bounds.height-10-messageDecal)
+ } else if (position == Preferences.DescriptionCorner.bottomLeft.rawValue) {
+ cto = CGPoint(x: 10, y: 10+clockDecal)
+ mto = CGPoint(x: 10, y: 10+messageDecal)
+ } else if (position == Preferences.DescriptionCorner.topRight.rawValue) {
+ cto = CGPoint(x: layer!.bounds.width-10, y: layer!.bounds.height-10-clockDecal)
+ mto = CGPoint(x: layer!.bounds.width-10, y: layer!.bounds.height-10-messageDecal)
+ } else {
+ cto = CGPoint(x: layer!.bounds.width-10, y: 10+clockDecal)
+ mto = CGPoint(x: layer!.bounds.width-10, y: 10+messageDecal)
+ }
+
+ self.clockLayer.add(createMoveAnimation(layer: clockLayer, to: cto, duration: duration), forKey: "position")
+ self.messageLayer.add(createMoveAnimation(layer: messageLayer, to: mto, duration: duration), forKey: "position")
+ }
+
+
+
+
private func repositionClockAndMessageLayer(position:Int, alone:Bool) {
var clockDecal : CGFloat = 0
var messageDecal : CGFloat = 0
@@ -763,6 +902,25 @@ class AerialView: ScreenSaverView {
}
}
+ // Create a Fade In/Out animation
+ func createFadeInOutAnimation(duration: Double) -> CAKeyframeAnimation {
+ let fadeAnimation = CAKeyframeAnimation(keyPath: "opacity")
+ fadeAnimation.values = [0, 0, 1, 1, 0] as [NSNumber]
+ fadeAnimation.keyTimes = [0, Double( 1/duration ), Double( (1+AerialView.textFadeDuration)/duration ), Double( 1-AerialView.textFadeDuration/duration ), 1] as [NSNumber]
+ fadeAnimation.duration = duration
+
+ return fadeAnimation
+ }
+
+ func createMoveAnimation(layer : CALayer, to: CGPoint, duration: Double) -> CABasicAnimation {
+ let moveAnimation = CABasicAnimation(keyPath: "position")
+ moveAnimation.fromValue = layer.position
+ moveAnimation.toValue = to
+ moveAnimation.duration = duration
+ layer.position = to;
+ return moveAnimation
+ }
+
// MARK: - Preferences
override var hasConfigureSheet: Bool {
diff --git a/Aerial/Source/Views/CheckCellView.swift b/Aerial/Source/Views/CheckCellView.swift
index 4de14c87..1560fdc3 100644
--- a/Aerial/Source/Views/CheckCellView.swift
+++ b/Aerial/Source/Views/CheckCellView.swift
@@ -103,12 +103,21 @@ class CheckCellView: NSTableCellView {
queuedImage.isHidden = true
status = .downloaded
- NSLog("video download finished")
+ debugLog("Video download finished")
video!.updateDuration()
}
+ func markAsNotDownloaded() {
+ addButton.isHidden = false
+ progressIndicator.isHidden = true
+ queuedImage.isHidden = true
+ status = .notAvailable
+
+ debugLog("Video download finished with error/cancel")
+ }
+
func markAsQueued() {
- debugLog("queued \(video!)")
+ debugLog("Queued \(video!)")
status = .queued
addButton.isHidden = true
progressIndicator.isHidden = true