├── .gitignore ├── Design └── Keyframes Player Icon.sketch ├── Gemfile ├── Gemfile.lock ├── Keyframes Player.xcodeproj └── project.pbxproj ├── Keyframes Player ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-512.png │ │ ├── Icon-512@2x.png │ │ ├── KeyframesPlayerIcon-128.png │ │ ├── KeyframesPlayerIcon-128@2x.png │ │ ├── KeyframesPlayerIcon-16.png │ │ ├── KeyframesPlayerIcon-16@2x.png │ │ ├── KeyframesPlayerIcon-256.png │ │ ├── KeyframesPlayerIcon-256@2x.png │ │ ├── KeyframesPlayerIcon-32.png │ │ └── KeyframesPlayerIcon-32@2x.png ├── Base.lproj │ └── Main.storyboard ├── BridgingHeader.h ├── CAExporter.h ├── CAExporter.m ├── CanvasView.swift ├── CanvasViewController.swift ├── Document.swift ├── Info.plist ├── PlaybackViewController.swift └── UsesVector.swift ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── releases └── appcast.xml └── screenshot.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | __MACOSX 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | *.xcworkspace 12 | !default.xcworkspace 13 | xcuserdata 14 | profile 15 | *.moved-aside 16 | DerivedData 17 | .idea/ 18 | Crashlytics.sh 19 | generatechangelog.sh 20 | Pods/ -------------------------------------------------------------------------------- /Design/Keyframes Player Icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Design/Keyframes Player Icon.sketch -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '1.2.0' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (2.3.5) 5 | activesupport (4.2.8) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | claide (1.0.1) 11 | cocoapods (1.2.0) 12 | activesupport (>= 4.0.2, < 5) 13 | claide (>= 1.0.1, < 2.0) 14 | cocoapods-core (= 1.2.0) 15 | cocoapods-deintegrate (>= 1.0.1, < 2.0) 16 | cocoapods-downloader (>= 1.1.3, < 2.0) 17 | cocoapods-plugins (>= 1.0.0, < 2.0) 18 | cocoapods-search (>= 1.0.0, < 2.0) 19 | cocoapods-stats (>= 1.0.0, < 2.0) 20 | cocoapods-trunk (>= 1.1.2, < 2.0) 21 | cocoapods-try (>= 1.1.0, < 2.0) 22 | colored (~> 1.2) 23 | escape (~> 0.0.4) 24 | fourflusher (~> 2.0.1) 25 | gh_inspector (~> 1.0) 26 | molinillo (~> 0.5.5) 27 | nap (~> 1.0) 28 | ruby-macho (~> 0.2.5) 29 | xcodeproj (>= 1.4.1, < 2.0) 30 | cocoapods-core (1.2.0) 31 | activesupport (>= 4.0.2, < 5) 32 | fuzzy_match (~> 2.0.4) 33 | nap (~> 1.0) 34 | cocoapods-deintegrate (1.0.1) 35 | cocoapods-downloader (1.1.3) 36 | cocoapods-plugins (1.0.0) 37 | nap 38 | cocoapods-search (1.0.0) 39 | cocoapods-stats (1.0.0) 40 | cocoapods-trunk (1.1.2) 41 | nap (>= 0.8, < 2.0) 42 | netrc (= 0.7.8) 43 | cocoapods-try (1.1.0) 44 | colored (1.2) 45 | escape (0.0.4) 46 | fourflusher (2.0.1) 47 | fuzzy_match (2.0.4) 48 | gh_inspector (1.0.3) 49 | i18n (0.8.1) 50 | minitest (5.10.1) 51 | molinillo (0.5.6) 52 | nanaimo (0.2.3) 53 | nap (1.1.0) 54 | netrc (0.7.8) 55 | ruby-macho (0.2.6) 56 | thread_safe (0.3.6) 57 | tzinfo (1.2.2) 58 | thread_safe (~> 0.1) 59 | xcodeproj (1.4.2) 60 | CFPropertyList (~> 2.3.3) 61 | activesupport (>= 3) 62 | claide (>= 1.0.1, < 2.0) 63 | colored (~> 1.2) 64 | nanaimo (~> 0.2.3) 65 | 66 | PLATFORMS 67 | ruby 68 | 69 | DEPENDENCIES 70 | cocoapods (= 1.2.0) 71 | 72 | BUNDLED WITH 73 | 1.13.6 74 | -------------------------------------------------------------------------------- /Keyframes Player.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7E557622FA3122E35CD9B3BE /* Pods_Keyframes_Player.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81BC1BE66CDBE9E7B60C1C5C /* Pods_Keyframes_Player.framework */; }; 11 | DD0557E11F8F169900A5F12C /* CAExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0557E01F8F169900A5F12C /* CAExporter.m */; }; 12 | DD96A3921E00546F004E1209 /* PlaybackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD96A3911E00546F004E1209 /* PlaybackViewController.swift */; }; 13 | DDA2DE611DFF275A006155E3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2DE601DFF275A006155E3 /* AppDelegate.swift */; }; 14 | DDA2DE631DFF275A006155E3 /* CanvasViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2DE621DFF275A006155E3 /* CanvasViewController.swift */; }; 15 | DDA2DE651DFF275A006155E3 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2DE641DFF275A006155E3 /* Document.swift */; }; 16 | DDA2DE671DFF275A006155E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDA2DE661DFF275A006155E3 /* Assets.xcassets */; }; 17 | DDA2DE6A1DFF275A006155E3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DDA2DE681DFF275A006155E3 /* Main.storyboard */; }; 18 | DDA2DE761DFF2874006155E3 /* UsesVector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2DE751DFF2874006155E3 /* UsesVector.swift */; }; 19 | DDA2DE7A1DFF2AF5006155E3 /* CanvasView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2DE791DFF2AF5006155E3 /* CanvasView.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 0F975873624B0406502B3325 /* Pods-Keyframes Player.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Keyframes Player.release.xcconfig"; path = "Pods/Target Support Files/Pods-Keyframes Player/Pods-Keyframes Player.release.xcconfig"; sourceTree = ""; }; 24 | 3B6884811F3C2D29BCA6B808 /* Pods-Keyframes Player.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Keyframes Player.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Keyframes Player/Pods-Keyframes Player.debug.xcconfig"; sourceTree = ""; }; 25 | 81BC1BE66CDBE9E7B60C1C5C /* Pods_Keyframes_Player.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Keyframes_Player.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | DD0557DF1F8F169900A5F12C /* CAExporter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CAExporter.h; sourceTree = ""; }; 27 | DD0557E01F8F169900A5F12C /* CAExporter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CAExporter.m; sourceTree = ""; }; 28 | DD750F181F8EDE850036C6DB /* BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = ""; }; 29 | DD96A3911E00546F004E1209 /* PlaybackViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaybackViewController.swift; sourceTree = ""; }; 30 | DDA2DE5D1DFF275A006155E3 /* Keyframes Player.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Keyframes Player.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | DDA2DE601DFF275A006155E3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 32 | DDA2DE621DFF275A006155E3 /* CanvasViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanvasViewController.swift; sourceTree = ""; }; 33 | DDA2DE641DFF275A006155E3 /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = ""; }; 34 | DDA2DE661DFF275A006155E3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 35 | DDA2DE691DFF275A006155E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 36 | DDA2DE6B1DFF275A006155E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | DDA2DE751DFF2874006155E3 /* UsesVector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsesVector.swift; sourceTree = ""; }; 38 | DDA2DE791DFF2AF5006155E3 /* CanvasView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CanvasView.swift; sourceTree = ""; }; 39 | /* End PBXFileReference section */ 40 | 41 | /* Begin PBXFrameworksBuildPhase section */ 42 | DDA2DE5A1DFF275A006155E3 /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | 7E557622FA3122E35CD9B3BE /* Pods_Keyframes_Player.framework in Frameworks */, 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXFrameworksBuildPhase section */ 51 | 52 | /* Begin PBXGroup section */ 53 | 9F6B03428E732990DCF9F8FC /* Pods */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | 3B6884811F3C2D29BCA6B808 /* Pods-Keyframes Player.debug.xcconfig */, 57 | 0F975873624B0406502B3325 /* Pods-Keyframes Player.release.xcconfig */, 58 | ); 59 | name = Pods; 60 | sourceTree = ""; 61 | }; 62 | BF0499BF98EFF434CFBD81E3 /* Frameworks */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 81BC1BE66CDBE9E7B60C1C5C /* Pods_Keyframes_Player.framework */, 66 | ); 67 | name = Frameworks; 68 | sourceTree = ""; 69 | }; 70 | DDA2DE541DFF275A006155E3 = { 71 | isa = PBXGroup; 72 | children = ( 73 | DDA2DE5F1DFF275A006155E3 /* Keyframes Player */, 74 | DDA2DE5E1DFF275A006155E3 /* Products */, 75 | 9F6B03428E732990DCF9F8FC /* Pods */, 76 | BF0499BF98EFF434CFBD81E3 /* Frameworks */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | DDA2DE5E1DFF275A006155E3 /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | DDA2DE5D1DFF275A006155E3 /* Keyframes Player.app */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | DDA2DE5F1DFF275A006155E3 /* Keyframes Player */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | DDA2DE741DFF2816006155E3 /* Resources */, 92 | DDA2DE781DFF2AE4006155E3 /* Views */, 93 | DDA2DE721DFF27FD006155E3 /* Bootstrap */, 94 | DDA2DE711DFF27F6006155E3 /* Controllers */, 95 | ); 96 | path = "Keyframes Player"; 97 | sourceTree = ""; 98 | }; 99 | DDA2DE711DFF27F6006155E3 /* Controllers */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | DDA2DE641DFF275A006155E3 /* Document.swift */, 103 | DDA2DE621DFF275A006155E3 /* CanvasViewController.swift */, 104 | DD96A3911E00546F004E1209 /* PlaybackViewController.swift */, 105 | DDA2DE751DFF2874006155E3 /* UsesVector.swift */, 106 | ); 107 | name = Controllers; 108 | sourceTree = ""; 109 | }; 110 | DDA2DE721DFF27FD006155E3 /* Bootstrap */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | DD750F181F8EDE850036C6DB /* BridgingHeader.h */, 114 | DD0557DF1F8F169900A5F12C /* CAExporter.h */, 115 | DD0557E01F8F169900A5F12C /* CAExporter.m */, 116 | DDA2DE601DFF275A006155E3 /* AppDelegate.swift */, 117 | ); 118 | name = Bootstrap; 119 | sourceTree = ""; 120 | }; 121 | DDA2DE741DFF2816006155E3 /* Resources */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | DDA2DE661DFF275A006155E3 /* Assets.xcassets */, 125 | DDA2DE681DFF275A006155E3 /* Main.storyboard */, 126 | DDA2DE6B1DFF275A006155E3 /* Info.plist */, 127 | ); 128 | name = Resources; 129 | sourceTree = ""; 130 | }; 131 | DDA2DE781DFF2AE4006155E3 /* Views */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | DDA2DE791DFF2AF5006155E3 /* CanvasView.swift */, 135 | ); 136 | name = Views; 137 | sourceTree = ""; 138 | }; 139 | /* End PBXGroup section */ 140 | 141 | /* Begin PBXNativeTarget section */ 142 | DDA2DE5C1DFF275A006155E3 /* Keyframes Player */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = DDA2DE6E1DFF275A006155E3 /* Build configuration list for PBXNativeTarget "Keyframes Player" */; 145 | buildPhases = ( 146 | 52B26F33E135E2AE4B517CC4 /* [CP] Check Pods Manifest.lock */, 147 | DDA2DE591DFF275A006155E3 /* Sources */, 148 | DDA2DE5A1DFF275A006155E3 /* Frameworks */, 149 | DDA2DE5B1DFF275A006155E3 /* Resources */, 150 | 3F6E3C45AF196DD7AC697783 /* [CP] Embed Pods Frameworks */, 151 | 67932BA35D860810403F991C /* [CP] Copy Pods Resources */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = "Keyframes Player"; 158 | productName = "Keyframes Player"; 159 | productReference = DDA2DE5D1DFF275A006155E3 /* Keyframes Player.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | DDA2DE551DFF275A006155E3 /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | LastSwiftUpdateCheck = 0810; 169 | LastUpgradeCheck = 0900; 170 | ORGANIZATIONNAME = "Guilherme Rambo"; 171 | TargetAttributes = { 172 | DDA2DE5C1DFF275A006155E3 = { 173 | CreatedOnToolsVersion = 8.1; 174 | DevelopmentTeam = 8C7439RJLG; 175 | LastSwiftMigration = 0900; 176 | ProvisioningStyle = Automatic; 177 | }; 178 | }; 179 | }; 180 | buildConfigurationList = DDA2DE581DFF275A006155E3 /* Build configuration list for PBXProject "Keyframes Player" */; 181 | compatibilityVersion = "Xcode 3.2"; 182 | developmentRegion = English; 183 | hasScannedForEncodings = 0; 184 | knownRegions = ( 185 | en, 186 | Base, 187 | ); 188 | mainGroup = DDA2DE541DFF275A006155E3; 189 | productRefGroup = DDA2DE5E1DFF275A006155E3 /* Products */; 190 | projectDirPath = ""; 191 | projectRoot = ""; 192 | targets = ( 193 | DDA2DE5C1DFF275A006155E3 /* Keyframes Player */, 194 | ); 195 | }; 196 | /* End PBXProject section */ 197 | 198 | /* Begin PBXResourcesBuildPhase section */ 199 | DDA2DE5B1DFF275A006155E3 /* Resources */ = { 200 | isa = PBXResourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | DDA2DE671DFF275A006155E3 /* Assets.xcassets in Resources */, 204 | DDA2DE6A1DFF275A006155E3 /* Main.storyboard in Resources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | /* End PBXResourcesBuildPhase section */ 209 | 210 | /* Begin PBXShellScriptBuildPhase section */ 211 | 3F6E3C45AF196DD7AC697783 /* [CP] Embed Pods Frameworks */ = { 212 | isa = PBXShellScriptBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | ); 216 | inputPaths = ( 217 | "${SRCROOT}/Pods/Target Support Files/Pods-Keyframes Player/Pods-Keyframes Player-frameworks.sh", 218 | "${BUILT_PRODUCTS_DIR}/KVOController/KVOController.framework", 219 | "${BUILT_PRODUCTS_DIR}/Keyframes/Keyframes.framework", 220 | "${PODS_ROOT}/Sparkle/Sparkle.framework", 221 | "${PODS_ROOT}/Sparkle/Sparkle.framework.dSYM", 222 | ); 223 | name = "[CP] Embed Pods Frameworks"; 224 | outputPaths = ( 225 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KVOController.framework", 226 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Keyframes.framework", 227 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sparkle.framework", 228 | "${DWARF_DSYM_FOLDER_PATH}/Sparkle.framework.dSYM", 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | shellPath = /bin/sh; 232 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Keyframes Player/Pods-Keyframes Player-frameworks.sh\"\n"; 233 | showEnvVarsInLog = 0; 234 | }; 235 | 52B26F33E135E2AE4B517CC4 /* [CP] Check Pods Manifest.lock */ = { 236 | isa = PBXShellScriptBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | ); 240 | inputPaths = ( 241 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 242 | "${PODS_ROOT}/Manifest.lock", 243 | ); 244 | name = "[CP] Check Pods Manifest.lock"; 245 | outputPaths = ( 246 | "$(DERIVED_FILE_DIR)/Pods-Keyframes Player-checkManifestLockResult.txt", 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | shellPath = /bin/sh; 250 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 251 | showEnvVarsInLog = 0; 252 | }; 253 | 67932BA35D860810403F991C /* [CP] Copy Pods Resources */ = { 254 | isa = PBXShellScriptBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | ); 258 | inputPaths = ( 259 | ); 260 | name = "[CP] Copy Pods Resources"; 261 | outputPaths = ( 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | shellPath = /bin/sh; 265 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Keyframes Player/Pods-Keyframes Player-resources.sh\"\n"; 266 | showEnvVarsInLog = 0; 267 | }; 268 | /* End PBXShellScriptBuildPhase section */ 269 | 270 | /* Begin PBXSourcesBuildPhase section */ 271 | DDA2DE591DFF275A006155E3 /* Sources */ = { 272 | isa = PBXSourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | DDA2DE631DFF275A006155E3 /* CanvasViewController.swift in Sources */, 276 | DDA2DE611DFF275A006155E3 /* AppDelegate.swift in Sources */, 277 | DDA2DE7A1DFF2AF5006155E3 /* CanvasView.swift in Sources */, 278 | DD96A3921E00546F004E1209 /* PlaybackViewController.swift in Sources */, 279 | DDA2DE761DFF2874006155E3 /* UsesVector.swift in Sources */, 280 | DDA2DE651DFF275A006155E3 /* Document.swift in Sources */, 281 | DD0557E11F8F169900A5F12C /* CAExporter.m in Sources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | /* End PBXSourcesBuildPhase section */ 286 | 287 | /* Begin PBXVariantGroup section */ 288 | DDA2DE681DFF275A006155E3 /* Main.storyboard */ = { 289 | isa = PBXVariantGroup; 290 | children = ( 291 | DDA2DE691DFF275A006155E3 /* Base */, 292 | ); 293 | name = Main.storyboard; 294 | sourceTree = ""; 295 | }; 296 | /* End PBXVariantGroup section */ 297 | 298 | /* Begin XCBuildConfiguration section */ 299 | DDA2DE6C1DFF275A006155E3 /* Debug */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | ALWAYS_SEARCH_USER_PATHS = NO; 303 | CLANG_ANALYZER_NONNULL = YES; 304 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 305 | CLANG_CXX_LIBRARY = "libc++"; 306 | CLANG_ENABLE_MODULES = YES; 307 | CLANG_ENABLE_OBJC_ARC = YES; 308 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 309 | CLANG_WARN_BOOL_CONVERSION = YES; 310 | CLANG_WARN_COMMA = YES; 311 | CLANG_WARN_CONSTANT_CONVERSION = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 314 | CLANG_WARN_EMPTY_BODY = YES; 315 | CLANG_WARN_ENUM_CONVERSION = YES; 316 | CLANG_WARN_INFINITE_RECURSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 319 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 321 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 322 | CLANG_WARN_STRICT_PROTOTYPES = YES; 323 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 324 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 325 | CLANG_WARN_UNREACHABLE_CODE = YES; 326 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 327 | CODE_SIGN_IDENTITY = "-"; 328 | COPY_PHASE_STRIP = NO; 329 | DEBUG_INFORMATION_FORMAT = dwarf; 330 | ENABLE_STRICT_OBJC_MSGSEND = YES; 331 | ENABLE_TESTABILITY = YES; 332 | GCC_C_LANGUAGE_STANDARD = gnu99; 333 | GCC_DYNAMIC_NO_PIC = NO; 334 | GCC_NO_COMMON_BLOCKS = YES; 335 | GCC_OPTIMIZATION_LEVEL = 0; 336 | GCC_PREPROCESSOR_DEFINITIONS = ( 337 | "DEBUG=1", 338 | "$(inherited)", 339 | ); 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | MACOSX_DEPLOYMENT_TARGET = 10.12; 347 | MTL_ENABLE_DEBUG_INFO = YES; 348 | ONLY_ACTIVE_ARCH = YES; 349 | SDKROOT = macosx; 350 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 351 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 352 | }; 353 | name = Debug; 354 | }; 355 | DDA2DE6D1DFF275A006155E3 /* Release */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ALWAYS_SEARCH_USER_PATHS = NO; 359 | CLANG_ANALYZER_NONNULL = YES; 360 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 361 | CLANG_CXX_LIBRARY = "libc++"; 362 | CLANG_ENABLE_MODULES = YES; 363 | CLANG_ENABLE_OBJC_ARC = YES; 364 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 365 | CLANG_WARN_BOOL_CONVERSION = YES; 366 | CLANG_WARN_COMMA = YES; 367 | CLANG_WARN_CONSTANT_CONVERSION = YES; 368 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 369 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 370 | CLANG_WARN_EMPTY_BODY = YES; 371 | CLANG_WARN_ENUM_CONVERSION = YES; 372 | CLANG_WARN_INFINITE_RECURSION = YES; 373 | CLANG_WARN_INT_CONVERSION = YES; 374 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 375 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 376 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 377 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 378 | CLANG_WARN_STRICT_PROTOTYPES = YES; 379 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 380 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 381 | CLANG_WARN_UNREACHABLE_CODE = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | CODE_SIGN_IDENTITY = "-"; 384 | COPY_PHASE_STRIP = NO; 385 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 386 | ENABLE_NS_ASSERTIONS = NO; 387 | ENABLE_STRICT_OBJC_MSGSEND = YES; 388 | GCC_C_LANGUAGE_STANDARD = gnu99; 389 | GCC_NO_COMMON_BLOCKS = YES; 390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 392 | GCC_WARN_UNDECLARED_SELECTOR = YES; 393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 394 | GCC_WARN_UNUSED_FUNCTION = YES; 395 | GCC_WARN_UNUSED_VARIABLE = YES; 396 | MACOSX_DEPLOYMENT_TARGET = 10.12; 397 | MTL_ENABLE_DEBUG_INFO = NO; 398 | SDKROOT = macosx; 399 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 400 | }; 401 | name = Release; 402 | }; 403 | DDA2DE6F1DFF275A006155E3 /* Debug */ = { 404 | isa = XCBuildConfiguration; 405 | baseConfigurationReference = 3B6884811F3C2D29BCA6B808 /* Pods-Keyframes Player.debug.xcconfig */; 406 | buildSettings = { 407 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 408 | COMBINE_HIDPI_IMAGES = YES; 409 | DEVELOPMENT_TEAM = 8C7439RJLG; 410 | INFOPLIST_FILE = "Keyframes Player/Info.plist"; 411 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 412 | PRODUCT_BUNDLE_IDENTIFIER = "br.com.guilhermerambo.Keyframes-Player"; 413 | PRODUCT_NAME = "$(TARGET_NAME)"; 414 | SWIFT_OBJC_BRIDGING_HEADER = "Keyframes Player/BridgingHeader.h"; 415 | SWIFT_VERSION = 4.0; 416 | }; 417 | name = Debug; 418 | }; 419 | DDA2DE701DFF275A006155E3 /* Release */ = { 420 | isa = XCBuildConfiguration; 421 | baseConfigurationReference = 0F975873624B0406502B3325 /* Pods-Keyframes Player.release.xcconfig */; 422 | buildSettings = { 423 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 424 | COMBINE_HIDPI_IMAGES = YES; 425 | DEVELOPMENT_TEAM = 8C7439RJLG; 426 | INFOPLIST_FILE = "Keyframes Player/Info.plist"; 427 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 428 | PRODUCT_BUNDLE_IDENTIFIER = "br.com.guilhermerambo.Keyframes-Player"; 429 | PRODUCT_NAME = "$(TARGET_NAME)"; 430 | SWIFT_OBJC_BRIDGING_HEADER = "Keyframes Player/BridgingHeader.h"; 431 | SWIFT_VERSION = 4.0; 432 | }; 433 | name = Release; 434 | }; 435 | /* End XCBuildConfiguration section */ 436 | 437 | /* Begin XCConfigurationList section */ 438 | DDA2DE581DFF275A006155E3 /* Build configuration list for PBXProject "Keyframes Player" */ = { 439 | isa = XCConfigurationList; 440 | buildConfigurations = ( 441 | DDA2DE6C1DFF275A006155E3 /* Debug */, 442 | DDA2DE6D1DFF275A006155E3 /* Release */, 443 | ); 444 | defaultConfigurationIsVisible = 0; 445 | defaultConfigurationName = Release; 446 | }; 447 | DDA2DE6E1DFF275A006155E3 /* Build configuration list for PBXNativeTarget "Keyframes Player" */ = { 448 | isa = XCConfigurationList; 449 | buildConfigurations = ( 450 | DDA2DE6F1DFF275A006155E3 /* Debug */, 451 | DDA2DE701DFF275A006155E3 /* Release */, 452 | ); 453 | defaultConfigurationIsVisible = 0; 454 | defaultConfigurationName = Release; 455 | }; 456 | /* End XCConfigurationList section */ 457 | }; 458 | rootObject = DDA2DE551DFF275A006155E3 /* Project object */; 459 | } 460 | -------------------------------------------------------------------------------- /Keyframes Player/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Keyframes Player 4 | // 5 | // Created by Guilherme Rambo on 12/12/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "KeyframesPlayerIcon-16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "KeyframesPlayerIcon-16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "KeyframesPlayerIcon-32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "KeyframesPlayerIcon-32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "KeyframesPlayerIcon-128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "KeyframesPlayerIcon-128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "KeyframesPlayerIcon-256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "KeyframesPlayerIcon-256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "Icon-512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "Icon-512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Keyframes Player/Assets.xcassets/AppIcon.appiconset/Icon-512.png -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/Icon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Keyframes Player/Assets.xcassets/AppIcon.appiconset/Icon-512@2x.png -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-128.png -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-128@2x.png -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-16.png -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-16@2x.png -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-256.png -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-256@2x.png -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-32.png -------------------------------------------------------------------------------- /Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/Keyframes Player/Assets.xcassets/AppIcon.appiconset/KeyframesPlayerIcon-32@2x.png -------------------------------------------------------------------------------- /Keyframes Player/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | Default 523 | 524 | 525 | 526 | 527 | 528 | 529 | Left to Right 530 | 531 | 532 | 533 | 534 | 535 | 536 | Right to Left 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | Default 548 | 549 | 550 | 551 | 552 | 553 | 554 | Left to Right 555 | 556 | 557 | 558 | 559 | 560 | 561 | Right to Left 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | -------------------------------------------------------------------------------- /Keyframes Player/BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // CAEncodeLayerTree.h 3 | // caarexport 4 | // 5 | // Created by Guilherme Rambo on 14/09/17. 6 | // Copyright © 2017 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | @import QuartzCore; 10 | 11 | #import "CAExporter.h" 12 | -------------------------------------------------------------------------------- /Keyframes Player/CAExporter.h: -------------------------------------------------------------------------------- 1 | // 2 | // CAExporter.h 3 | // Keyframes Player 4 | // 5 | // Created by Guilherme Rambo on 12/10/17. 6 | // Copyright © 2017 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | @import Cocoa; 10 | 11 | @interface CAExporter : NSObject 12 | 13 | - (instancetype __nonnull)initWithRootLayer:(__kindof CALayer *__nonnull)rootLayer; 14 | 15 | - (BOOL)exportToFileAtURL:(NSURL *__nonnull)url error:(NSError **)outError; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Keyframes Player/CAExporter.m: -------------------------------------------------------------------------------- 1 | // 2 | // CAExporter.m 3 | // Keyframes Player 4 | // 5 | // Created by Guilherme Rambo on 12/10/17. 6 | // Copyright © 2017 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | #import "CAExporter.h" 10 | 11 | extern NSData *__nonnull CAEncodeLayerTreeWithInfo(CALayer *__nonnull rootLayer, id __nullable info); 12 | extern BOOL CAMLEncodeLayerTreeToPathWithInfo(CALayer *__nonnull rootLayer, NSString *__nonnull path, id __nullable info); 13 | 14 | NSString * const kCAExporterErrorDomain = @"br.com.guilhermerambo.CAExporter"; 15 | 16 | @implementation CAExporter 17 | { 18 | __kindof CALayer *_rootLayer; 19 | } 20 | 21 | - (instancetype)initWithRootLayer:(__kindof CALayer *)rootLayer 22 | { 23 | self = [super init]; 24 | 25 | _rootLayer = rootLayer; 26 | 27 | return self; 28 | } 29 | 30 | - (BOOL)exportToFileAtURL:(NSURL *)url error:(NSError *__autoreleasing *)outError 31 | { 32 | if ([url.pathExtension isEqualToString:@"caar"]) { 33 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:@{@"rootLayer": _rootLayer}]; 34 | 35 | if (!data) { 36 | *outError = [NSError errorWithDomain:kCAExporterErrorDomain 37 | code:1 38 | userInfo:@{NSLocalizedDescriptionKey: @"Error encoding layer tree."}]; 39 | 40 | return NO; 41 | } 42 | 43 | return [data writeToURL:url options:NSDataWritingAtomic error:outError]; 44 | } else if ([url.pathExtension isEqualToString:@"ca"]) { 45 | BOOL r = CAMLEncodeLayerTreeToPathWithInfo(_rootLayer, url.path, nil); 46 | 47 | if (!r) { 48 | *outError = [NSError errorWithDomain:kCAExporterErrorDomain 49 | code:2 50 | userInfo:@{NSLocalizedDescriptionKey: @"Error writing CAML."}]; 51 | return NO; 52 | } else { 53 | return YES; 54 | } 55 | } else { 56 | return NO; 57 | } 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Keyframes Player/CanvasView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CanvasView.swift 3 | // Keyframes Player 4 | // 5 | // Created by Guilherme Rambo on 12/12/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class CanvasView: NSView { 12 | 13 | @objc var target: Any? 14 | @objc var action: Selector? 15 | 16 | override var isFlipped: Bool { 17 | return true 18 | } 19 | 20 | override var acceptsFirstResponder: Bool { 21 | return true 22 | } 23 | 24 | override func mouseUp(with event: NSEvent) { 25 | if event.clickCount == 1 { 26 | if let action = self.action { 27 | NSApp.sendAction(action, to: target, from: self) 28 | } 29 | } else { 30 | super.mouseUp(with: event) 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Keyframes Player/CanvasViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CanvasViewController.swift 3 | // Keyframes Player 4 | // 5 | // Created by Guilherme Rambo on 12/12/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Keyframes 11 | import KVOController 12 | 13 | class CanvasViewController: NSViewController, UsesVector { 14 | 15 | private var vectorLayer: KFVectorLayer! 16 | fileprivate var playbackController: PlaybackViewController! 17 | 18 | @objc var vector: KFVector? { 19 | didSet { 20 | updateUI() 21 | } 22 | } 23 | 24 | @objc var canvas: CanvasView? { 25 | return view as? CanvasView 26 | } 27 | 28 | @objc var isPlaying = false 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | canvas?.target = self 34 | canvas?.action = #selector(togglePlayback(_:)) 35 | 36 | view.wantsLayer = true 37 | view.layer?.backgroundColor = NSColor.white.cgColor 38 | 39 | playbackController = PlaybackViewController() 40 | addChildViewController(playbackController) 41 | 42 | view.addSubview(playbackController.view) 43 | playbackController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 44 | playbackController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 45 | playbackController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 46 | 47 | updateUI() 48 | 49 | kvoController.observe(playbackController, keyPath: #keyPath(PlaybackViewController.progress), options: [.initial, .new], action: #selector(seek)) 50 | } 51 | 52 | private func updateUI() { 53 | guard let vector = vector, isViewLoaded else { return } 54 | 55 | if vectorLayer != nil { 56 | vectorLayer.removeFromSuperlayer() 57 | } 58 | 59 | vectorLayer = KFVectorLayer() 60 | 61 | resizeLayer() 62 | vectorLayer.setFaceModel(vector) 63 | vectorLayer.startAnimation() 64 | 65 | view.layer?.addSublayer(vectorLayer) 66 | } 67 | 68 | @IBAction func seek(_ sender: Any? = nil) { 69 | guard vectorLayer != nil && playbackController != nil else { return } 70 | 71 | vectorLayer.seek(toProgress: CGFloat(playbackController.progress)) 72 | } 73 | 74 | @IBAction func play(_ sender: Any? = nil) { 75 | guard vectorLayer != nil else { return } 76 | 77 | vectorLayer.resumeAnimation() 78 | isPlaying = true 79 | } 80 | 81 | @IBAction func pause(_ sender: Any? = nil) { 82 | guard vectorLayer != nil else { return } 83 | 84 | vectorLayer.pauseAnimation() 85 | isPlaying = false 86 | } 87 | 88 | @IBAction func togglePlayback(_ sender: Any? = nil) { 89 | guard vectorLayer != nil else { return } 90 | 91 | if isPlaying { 92 | pause() 93 | } else { 94 | play() 95 | } 96 | } 97 | 98 | private func resizeLayer() { 99 | guard let vector = vector, vectorLayer != nil && isViewLoaded else { return } 100 | 101 | CATransaction.begin() 102 | CATransaction.setDisableActions(true) 103 | CATransaction.setAnimationDuration(0) 104 | 105 | let size = calculateAspectFillSize(originalSize: vector.canvasSize, cropSize: view.bounds.size) 106 | vectorLayer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) 107 | vectorLayer.position = CGPoint(x: view.bounds.midX, y: view.bounds.midY) 108 | 109 | CATransaction.commit() 110 | } 111 | 112 | private func calculateAspectFillSize(originalSize: CGSize, cropSize: CGSize) -> CGSize { 113 | let cropRatio = cropSize.width / cropSize.height; 114 | let originalRatio = originalSize.width / originalSize.height; 115 | 116 | if (cropRatio > originalRatio) { 117 | return CGSize(width: originalSize.width * cropSize.height / originalSize.height, height: cropSize.height) 118 | } else { 119 | return CGSize(width: cropSize.width, height: originalSize.height * cropSize.width / originalSize.width) 120 | } 121 | } 122 | 123 | override func viewDidLayout() { 124 | super.viewDidLayout() 125 | 126 | resizeLayer() 127 | } 128 | 129 | } 130 | 131 | -------------------------------------------------------------------------------- /Keyframes Player/Document.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Document.swift 3 | // Keyframes Player 4 | // 5 | // Created by Guilherme Rambo on 12/12/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Keyframes 11 | 12 | class Document: NSDocument { 13 | 14 | enum DocumentError: Error { 15 | case load 16 | } 17 | 18 | override class var autosavesInPlace: Bool { 19 | return true 20 | } 21 | 22 | override func makeWindowControllers() { 23 | let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) 24 | let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "Document Window Controller")) as! NSWindowController 25 | self.addWindowController(windowController) 26 | } 27 | 28 | private func propagate(_ vector: KFVector) { 29 | windowControllers.forEach { 30 | if var vc = $0.contentViewController as? UsesVector { 31 | vc.vector = vector 32 | } 33 | } 34 | } 35 | 36 | override func read(from url: URL, ofType typeName: String) throws { 37 | try load(from: url) 38 | } 39 | 40 | private var lastModificationDate = Date.distantPast 41 | 42 | private var vector: KFVector? 43 | 44 | private func load(from url: URL) throws { 45 | let data = try Data(contentsOf: url) 46 | 47 | let obj = try JSONSerialization.jsonObject(with: data, options: []) 48 | 49 | guard let face = obj as? [AnyHashable: Any] else { 50 | throw DocumentError.load 51 | } 52 | 53 | guard let vector = KFVectorFromDictionary(face) else { 54 | throw DocumentError.load 55 | } 56 | 57 | self.vector = vector 58 | 59 | DispatchQueue.main.async { 60 | self.propagate(vector) 61 | } 62 | 63 | self.lastModificationDate = fetchLastModificationDate() 64 | } 65 | 66 | private func fetchLastModificationDate() -> Date { 67 | guard let url = presentedItemURL else { 68 | return Date.distantPast 69 | } 70 | 71 | guard let attributes = try? FileManager.default.attributesOfItem(atPath: url.path) else { 72 | return Date.distantPast 73 | } 74 | 75 | return attributes[.modificationDate] as? Date ?? Date.distantPast 76 | } 77 | 78 | override func presentedItemDidChange() { 79 | super.presentedItemDidChange() 80 | 81 | guard let url = presentedItemURL else { return } 82 | 83 | let date = fetchLastModificationDate() 84 | 85 | guard lastModificationDate != date else { return } 86 | 87 | do { 88 | try load(from: url) 89 | } catch { 90 | NSLog("Error reading updated document from path: \(url.path)") 91 | } 92 | } 93 | 94 | @IBAction func exportCoreAnimationArchive(_ sender: Any?) { 95 | guard let vector = self.vector else { return } 96 | 97 | let vectorLayer = KFVectorLayer() 98 | vectorLayer.frame = CGRect(x: 0, y: 0, width: 500, height: 500) 99 | vectorLayer.setFaceModel(vector) 100 | vectorLayer.startAnimation() 101 | 102 | let panel = NSSavePanel() 103 | panel.allowedFileTypes = ["caar", "ca"] 104 | panel.runModal() 105 | 106 | guard let url = panel.url else { return } 107 | 108 | let exporter = CAExporter(rootLayer: vectorLayer) 109 | 110 | do { 111 | try exporter.exportToFile(at: url) 112 | } catch { 113 | NSApp.presentError(error) 114 | } 115 | } 116 | 117 | } 118 | 119 | -------------------------------------------------------------------------------- /Keyframes Player/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | json 13 | 14 | CFBundleTypeIconFile 15 | 16 | CFBundleTypeName 17 | DocumentType 18 | CFBundleTypeOSTypes 19 | 20 | ???? 21 | 22 | CFBundleTypeRole 23 | Viewer 24 | LSTypeIsPackage 25 | 0 26 | NSDocumentClass 27 | $(PRODUCT_MODULE_NAME).Document 28 | 29 | 30 | CFBundleExecutable 31 | $(EXECUTABLE_NAME) 32 | CFBundleIconFile 33 | 34 | CFBundleIdentifier 35 | $(PRODUCT_BUNDLE_IDENTIFIER) 36 | CFBundleInfoDictionaryVersion 37 | 6.0 38 | CFBundleName 39 | $(PRODUCT_NAME) 40 | CFBundlePackageType 41 | APPL 42 | CFBundleShortVersionString 43 | 1.2 44 | CFBundleVersion 45 | 12 46 | LSMinimumSystemVersion 47 | $(MACOSX_DEPLOYMENT_TARGET) 48 | NSHumanReadableCopyright 49 | Copyright © 2016 Guilherme Rambo. All rights reserved. 50 | NSMainStoryboardFile 51 | Main 52 | NSPrincipalClass 53 | NSApplication 54 | SUEnableAutomaticChecks 55 | 56 | SUFeedURL 57 | https://github.com/insidegui/KeyframesPlayer/raw/master/releases/appcast.xml 58 | SUScheduledCheckInterval 59 | 86400 60 | 61 | 62 | -------------------------------------------------------------------------------- /Keyframes Player/PlaybackViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaybackViewController.swift 3 | // Keyframes Player 4 | // 5 | // Created by Guilherme Rambo on 13/12/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class PlaybackViewController: NSViewController { 12 | 13 | @objc var progress = 0.0 { 14 | willSet { 15 | willChangeValue(forKey: "progress") 16 | } 17 | didSet { 18 | didChangeValue(forKey: "progress") 19 | } 20 | } 21 | 22 | init() { 23 | super.init(nibName: nil, bundle: nil) 24 | } 25 | 26 | required init?(coder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | fileprivate var backdropView: NSVisualEffectView? { 31 | return self.view as? NSVisualEffectView 32 | } 33 | 34 | fileprivate lazy var slider: NSSlider = { 35 | let s = NSSlider(value: 0, minValue: 0, maxValue: 1, target: self, action: #selector(sliderDidChange(sender:))) 36 | 37 | s.isContinuous = true 38 | s.translatesAutoresizingMaskIntoConstraints = false 39 | s.sizeToFit() 40 | 41 | return s 42 | }() 43 | 44 | override func loadView() { 45 | let vfxView = NSVisualEffectView(frame: .zero) 46 | vfxView.material = .mediumLight 47 | vfxView.appearance = NSAppearance(named: NSAppearance.Name.vibrantLight) 48 | vfxView.blendingMode = .withinWindow 49 | vfxView.wantsLayer = true 50 | 51 | let separatorView = NSView(frame: NSRect(x: 0, y: -1, width: 0, height: 1)) 52 | separatorView.wantsLayer = true 53 | separatorView.layer = CALayer() 54 | separatorView.layer?.backgroundColor = NSColor.lightGray.cgColor 55 | separatorView.layer?.opacity = 0.3 56 | separatorView.autoresizingMask = [NSView.AutoresizingMask.width, NSView.AutoresizingMask.minYMargin] 57 | self.view = vfxView 58 | 59 | view.translatesAutoresizingMaskIntoConstraints = false 60 | view.heightAnchor.constraint(equalToConstant: 28).isActive = true 61 | view.layer?.zPosition = 9999 62 | 63 | vfxView.addSubview(separatorView) 64 | 65 | view.addSubview(slider) 66 | slider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true 67 | slider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true 68 | slider.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 69 | } 70 | 71 | @objc private func sliderDidChange(sender: NSSlider) { 72 | progress = sender.doubleValue 73 | } 74 | 75 | override func viewDidLoad() { 76 | super.viewDidLoad() 77 | // Do view setup here. 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Keyframes Player/UsesVector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UsesVector.swift 3 | // Keyframes Player 4 | // 5 | // Created by Guilherme Rambo on 12/12/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Keyframes 11 | 12 | protocol UsesVector { 13 | 14 | var vector: KFVector? { get set } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Guilherme Rambo 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | - Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'Keyframes Player' do 5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for Keyframes Player 9 | pod 'Keyframes', :git => 'https://github.com/facebookincubator/Keyframes.git', :branch => 'master' 10 | pod 'KVOController' 11 | pod 'Sparkle' 12 | end 13 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Keyframes (1.0.0) 3 | - KVOController (1.2.0) 4 | - Sparkle (1.16.0) 5 | 6 | DEPENDENCIES: 7 | - Keyframes (from `https://github.com/facebookincubator/Keyframes.git`, branch `master`) 8 | - KVOController 9 | - Sparkle 10 | 11 | EXTERNAL SOURCES: 12 | Keyframes: 13 | :branch: master 14 | :git: https://github.com/facebookincubator/Keyframes.git 15 | 16 | CHECKOUT OPTIONS: 17 | Keyframes: 18 | :commit: 91fb999958d7962f06861475628e792032a836da 19 | :git: https://github.com/facebookincubator/Keyframes.git 20 | 21 | SPEC CHECKSUMS: 22 | Keyframes: 2048d8626d14ef726d1c5cffa0fa5c00f3d8f681 23 | KVOController: d72ace34afea42468329623b3379ab3cd1d286b6 24 | Sparkle: 57650c525b8922b1ef72021f9d92287fc7d5d6f0 25 | 26 | PODFILE CHECKSUM: 3c261889752726827446b0799a3afc113df8bcdc 27 | 28 | COCOAPODS: 1.3.1 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Keyframes Player 2 | 3 | A simple app to preview animations created with [Facebook's keyframes framework](https://github.com/facebookincubator/Keyframes) and export them to Core Animation archives. 4 | 5 | [⬇️ DOWNLOAD LATEST RELEASE ⬇️](https://github.com/insidegui/KeyframesPlayer/releases/latest) 6 | 7 | ### Using the export option 8 | #### Render After Effects animations in your app without external dependencies 9 | 10 | CAAR, or Core Animation archives, are files that hold trees of `CALayer`s encoded using `NSKeyedArchiver`. Since Keyframes uses Core Animation to reconstruct animations created in After Effects, it's possible to export the animations as files that can be read in AppKit or UIKit apps later, without the need to include the Keyframes framework in the shipping app. 11 | 12 | After exporting the .caar file, add it to your app's resources and use [this code snippet (ObjC)](https://gist.github.com/insidegui/59a7264a99d794d41236008fcf1e32d3) or [this one (Swift)](https://gist.github.com/insidegui/f63dad60fd9a99cc23f976d0a1283286) to read the animation in your app. To check if the export worked, you can use [CAAR Player](https://github.com/insidegui/CAARPlayer) on macOS to open the archive file. Not all features will work with this method, but I've found this to work with the most common shape and transform animations. 13 | 14 | ![screenshot](./screenshot.gif) -------------------------------------------------------------------------------- /releases/appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Keyframes Player Changelog 5 | https://github.com/insidegui/KeyframesPlayer/raw/master/releases/appcast.xml 6 | Most recent changes with links to updates. 7 | en 8 | 9 | 10 | Version 1.2 11 | 13 |
  • Adds an export to CAAR option which allows you to use your animations without importing Keyframes (learn more)
  • 14 | 15 | ]]>
    16 | Thu, 12 Oct 2017 18:32:00 -0300 17 | 18 | 10.11 19 |
    20 | 21 | 22 | Version 1.1.1 23 | 25 |
  • Keyframes library updated
  • 26 |
  • Canvas resizing fits animation aspect ratio
  • 27 |
  • Animation continues to play after scrubbing
  • 28 | 29 | ]]>
    30 | Fri, 24 Feb 2017 21:00:00 -0200 31 | 32 | 10.11 33 |
    34 | 35 | 36 | Version 1.1 37 | 39 |
  • Added auto-update (thanks, Sparkle)
  • 40 |
  • The app will now reload an opened document when it is changed externally
  • 41 | 42 | ]]>
    43 | Wed, 11 Jan 2017 21:15:00 -0200 44 | 45 | 10.11 46 |
    47 |
    48 |
    49 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/KeyframesPlayer/7c53c303b80795c34aaa8c6b4ff4c48cbaaac339/screenshot.gif --------------------------------------------------------------------------------