├── CustomVideoCompositor.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── CustomVideoCompositor.xcscmblueprint │ └── xcuserdata │ │ └── claygarrett.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── claygarrett.xcuserdatad │ └── xcschemes │ └── CustomVideoCompositor.xcscheme ├── CustomVideoCompositor ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── Media │ ├── panda.png │ └── sorry.mov ├── UI │ └── Main.storyboard ├── Video Processing │ ├── CustomVideoCompositor.swift │ ├── VideoExporter.swift │ └── WatermarkCompositionInstruction.swift └── ViewController.swift └── README.md /CustomVideoCompositor.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E9C1CF571DDCF9B400E9B92E /* CustomVideoCompositor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C1CF561DDCF9B400E9B92E /* CustomVideoCompositor.swift */; }; 11 | E9C1CF5E1DDD018100E9B92E /* sorry.mov in Resources */ = {isa = PBXBuildFile; fileRef = E9C1CF5C1DDD018100E9B92E /* sorry.mov */; }; 12 | E9C1CF611DDD01C300E9B92E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E9C1CF601DDD01C300E9B92E /* Main.storyboard */; }; 13 | E9C1CF631DDD057D00E9B92E /* panda.png in Resources */ = {isa = PBXBuildFile; fileRef = E9C1CF621DDD057D00E9B92E /* panda.png */; }; 14 | E9F8EAD41DDD0F44006EE33B /* VideoExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F8EAD11DDD0F44006EE33B /* VideoExporter.swift */; }; 15 | E9F8EAD51DDD0F44006EE33B /* WatermarkCompositionInstruction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F8EAD21DDD0F44006EE33B /* WatermarkCompositionInstruction.swift */; }; 16 | E9FEAE1F1DDCF8C90065CD7F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FEAE1E1DDCF8C90065CD7F /* AppDelegate.swift */; }; 17 | E9FEAE211DDCF8C90065CD7F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FEAE201DDCF8C90065CD7F /* ViewController.swift */; }; 18 | E9FEAE261DDCF8C90065CD7F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E9FEAE251DDCF8C90065CD7F /* Assets.xcassets */; }; 19 | E9FEAE291DDCF8C90065CD7F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E9FEAE271DDCF8C90065CD7F /* LaunchScreen.storyboard */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | E9C1CF561DDCF9B400E9B92E /* CustomVideoCompositor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CustomVideoCompositor.swift; path = "Video Processing/CustomVideoCompositor.swift"; sourceTree = ""; }; 24 | E9C1CF5C1DDD018100E9B92E /* sorry.mov */ = {isa = PBXFileReference; lastKnownFileType = video.quicktime; name = sorry.mov; path = Media/sorry.mov; sourceTree = ""; }; 25 | E9C1CF601DDD01C300E9B92E /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = UI/Main.storyboard; sourceTree = ""; }; 26 | E9C1CF621DDD057D00E9B92E /* panda.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = panda.png; path = Media/panda.png; sourceTree = ""; }; 27 | E9F8EAD11DDD0F44006EE33B /* VideoExporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VideoExporter.swift; path = "Video Processing/VideoExporter.swift"; sourceTree = ""; }; 28 | E9F8EAD21DDD0F44006EE33B /* WatermarkCompositionInstruction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WatermarkCompositionInstruction.swift; path = "Video Processing/WatermarkCompositionInstruction.swift"; sourceTree = ""; }; 29 | E9FEAE1B1DDCF8C90065CD7F /* CustomVideoCompositor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CustomVideoCompositor.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | E9FEAE1E1DDCF8C90065CD7F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 31 | E9FEAE201DDCF8C90065CD7F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 32 | E9FEAE251DDCF8C90065CD7F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 33 | E9FEAE281DDCF8C90065CD7F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 34 | E9FEAE2A1DDCF8C90065CD7F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | E9FEAE181DDCF8C90065CD7F /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | E9C1CF641DDD08AC00E9B92E /* UI */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | E9C1CF601DDD01C300E9B92E /* Main.storyboard */, 52 | ); 53 | name = UI; 54 | sourceTree = ""; 55 | }; 56 | E9C1CF651DDD08B300E9B92E /* Video Processing */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | E9F8EAD11DDD0F44006EE33B /* VideoExporter.swift */, 60 | E9F8EAD21DDD0F44006EE33B /* WatermarkCompositionInstruction.swift */, 61 | E9C1CF561DDCF9B400E9B92E /* CustomVideoCompositor.swift */, 62 | ); 63 | name = "Video Processing"; 64 | sourceTree = ""; 65 | }; 66 | E9C1CF661DDD0A0700E9B92E /* Media */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | E9C1CF621DDD057D00E9B92E /* panda.png */, 70 | E9C1CF5C1DDD018100E9B92E /* sorry.mov */, 71 | ); 72 | name = Media; 73 | sourceTree = ""; 74 | }; 75 | E9FEAE121DDCF8C90065CD7F = { 76 | isa = PBXGroup; 77 | children = ( 78 | E9FEAE1D1DDCF8C90065CD7F /* CustomVideoCompositor */, 79 | E9FEAE1C1DDCF8C90065CD7F /* Products */, 80 | ); 81 | sourceTree = ""; 82 | }; 83 | E9FEAE1C1DDCF8C90065CD7F /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | E9FEAE1B1DDCF8C90065CD7F /* CustomVideoCompositor.app */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | E9FEAE1D1DDCF8C90065CD7F /* CustomVideoCompositor */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | E9C1CF661DDD0A0700E9B92E /* Media */, 95 | E9C1CF651DDD08B300E9B92E /* Video Processing */, 96 | E9C1CF641DDD08AC00E9B92E /* UI */, 97 | E9FEAE1E1DDCF8C90065CD7F /* AppDelegate.swift */, 98 | E9FEAE201DDCF8C90065CD7F /* ViewController.swift */, 99 | E9FEAE251DDCF8C90065CD7F /* Assets.xcassets */, 100 | E9FEAE271DDCF8C90065CD7F /* LaunchScreen.storyboard */, 101 | E9FEAE2A1DDCF8C90065CD7F /* Info.plist */, 102 | ); 103 | path = CustomVideoCompositor; 104 | sourceTree = ""; 105 | }; 106 | /* End PBXGroup section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | E9FEAE1A1DDCF8C90065CD7F /* CustomVideoCompositor */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = E9FEAE2D1DDCF8C90065CD7F /* Build configuration list for PBXNativeTarget "CustomVideoCompositor" */; 112 | buildPhases = ( 113 | E9FEAE171DDCF8C90065CD7F /* Sources */, 114 | E9FEAE181DDCF8C90065CD7F /* Frameworks */, 115 | E9FEAE191DDCF8C90065CD7F /* Resources */, 116 | ); 117 | buildRules = ( 118 | ); 119 | dependencies = ( 120 | ); 121 | name = CustomVideoCompositor; 122 | productName = CustomVideoCompositor; 123 | productReference = E9FEAE1B1DDCF8C90065CD7F /* CustomVideoCompositor.app */; 124 | productType = "com.apple.product-type.application"; 125 | }; 126 | /* End PBXNativeTarget section */ 127 | 128 | /* Begin PBXProject section */ 129 | E9FEAE131DDCF8C90065CD7F /* Project object */ = { 130 | isa = PBXProject; 131 | attributes = { 132 | LastSwiftUpdateCheck = 0810; 133 | LastUpgradeCheck = 0810; 134 | ORGANIZATIONNAME = "Clay Garrett"; 135 | TargetAttributes = { 136 | E9FEAE1A1DDCF8C90065CD7F = { 137 | CreatedOnToolsVersion = 8.1; 138 | DevelopmentTeam = Y76S6B6ZNR; 139 | ProvisioningStyle = Automatic; 140 | }; 141 | }; 142 | }; 143 | buildConfigurationList = E9FEAE161DDCF8C90065CD7F /* Build configuration list for PBXProject "CustomVideoCompositor" */; 144 | compatibilityVersion = "Xcode 3.2"; 145 | developmentRegion = English; 146 | hasScannedForEncodings = 0; 147 | knownRegions = ( 148 | en, 149 | Base, 150 | ); 151 | mainGroup = E9FEAE121DDCF8C90065CD7F; 152 | productRefGroup = E9FEAE1C1DDCF8C90065CD7F /* Products */; 153 | projectDirPath = ""; 154 | projectRoot = ""; 155 | targets = ( 156 | E9FEAE1A1DDCF8C90065CD7F /* CustomVideoCompositor */, 157 | ); 158 | }; 159 | /* End PBXProject section */ 160 | 161 | /* Begin PBXResourcesBuildPhase section */ 162 | E9FEAE191DDCF8C90065CD7F /* Resources */ = { 163 | isa = PBXResourcesBuildPhase; 164 | buildActionMask = 2147483647; 165 | files = ( 166 | E9FEAE291DDCF8C90065CD7F /* LaunchScreen.storyboard in Resources */, 167 | E9C1CF611DDD01C300E9B92E /* Main.storyboard in Resources */, 168 | E9C1CF631DDD057D00E9B92E /* panda.png in Resources */, 169 | E9C1CF5E1DDD018100E9B92E /* sorry.mov in Resources */, 170 | E9FEAE261DDCF8C90065CD7F /* Assets.xcassets in Resources */, 171 | ); 172 | runOnlyForDeploymentPostprocessing = 0; 173 | }; 174 | /* End PBXResourcesBuildPhase section */ 175 | 176 | /* Begin PBXSourcesBuildPhase section */ 177 | E9FEAE171DDCF8C90065CD7F /* Sources */ = { 178 | isa = PBXSourcesBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | E9F8EAD41DDD0F44006EE33B /* VideoExporter.swift in Sources */, 182 | E9FEAE211DDCF8C90065CD7F /* ViewController.swift in Sources */, 183 | E9FEAE1F1DDCF8C90065CD7F /* AppDelegate.swift in Sources */, 184 | E9F8EAD51DDD0F44006EE33B /* WatermarkCompositionInstruction.swift in Sources */, 185 | E9C1CF571DDCF9B400E9B92E /* CustomVideoCompositor.swift in Sources */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | /* End PBXSourcesBuildPhase section */ 190 | 191 | /* Begin PBXVariantGroup section */ 192 | E9FEAE271DDCF8C90065CD7F /* LaunchScreen.storyboard */ = { 193 | isa = PBXVariantGroup; 194 | children = ( 195 | E9FEAE281DDCF8C90065CD7F /* Base */, 196 | ); 197 | name = LaunchScreen.storyboard; 198 | sourceTree = ""; 199 | }; 200 | /* End PBXVariantGroup section */ 201 | 202 | /* Begin XCBuildConfiguration section */ 203 | E9FEAE2B1DDCF8C90065CD7F /* Debug */ = { 204 | isa = XCBuildConfiguration; 205 | buildSettings = { 206 | ALWAYS_SEARCH_USER_PATHS = NO; 207 | CLANG_ANALYZER_NONNULL = YES; 208 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 209 | CLANG_CXX_LIBRARY = "libc++"; 210 | CLANG_ENABLE_MODULES = YES; 211 | CLANG_ENABLE_OBJC_ARC = YES; 212 | CLANG_WARN_BOOL_CONVERSION = YES; 213 | CLANG_WARN_CONSTANT_CONVERSION = YES; 214 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 215 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 216 | CLANG_WARN_EMPTY_BODY = YES; 217 | CLANG_WARN_ENUM_CONVERSION = YES; 218 | CLANG_WARN_INFINITE_RECURSION = YES; 219 | CLANG_WARN_INT_CONVERSION = YES; 220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 221 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 222 | CLANG_WARN_UNREACHABLE_CODE = YES; 223 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 224 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 225 | COPY_PHASE_STRIP = NO; 226 | DEBUG_INFORMATION_FORMAT = dwarf; 227 | ENABLE_STRICT_OBJC_MSGSEND = YES; 228 | ENABLE_TESTABILITY = YES; 229 | GCC_C_LANGUAGE_STANDARD = gnu99; 230 | GCC_DYNAMIC_NO_PIC = NO; 231 | GCC_NO_COMMON_BLOCKS = YES; 232 | GCC_OPTIMIZATION_LEVEL = 0; 233 | GCC_PREPROCESSOR_DEFINITIONS = ( 234 | "DEBUG=1", 235 | "$(inherited)", 236 | ); 237 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 238 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 239 | GCC_WARN_UNDECLARED_SELECTOR = YES; 240 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 241 | GCC_WARN_UNUSED_FUNCTION = YES; 242 | GCC_WARN_UNUSED_VARIABLE = YES; 243 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 244 | MTL_ENABLE_DEBUG_INFO = YES; 245 | ONLY_ACTIVE_ARCH = YES; 246 | SDKROOT = iphoneos; 247 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 248 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 249 | TARGETED_DEVICE_FAMILY = "1,2"; 250 | }; 251 | name = Debug; 252 | }; 253 | E9FEAE2C1DDCF8C90065CD7F /* Release */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | CLANG_ANALYZER_NONNULL = YES; 258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 259 | CLANG_CXX_LIBRARY = "libc++"; 260 | CLANG_ENABLE_MODULES = YES; 261 | CLANG_ENABLE_OBJC_ARC = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_CONSTANT_CONVERSION = YES; 264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 265 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INFINITE_RECURSION = YES; 269 | CLANG_WARN_INT_CONVERSION = YES; 270 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 271 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 272 | CLANG_WARN_UNREACHABLE_CODE = YES; 273 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 274 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 275 | COPY_PHASE_STRIP = NO; 276 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 277 | ENABLE_NS_ASSERTIONS = NO; 278 | ENABLE_STRICT_OBJC_MSGSEND = YES; 279 | GCC_C_LANGUAGE_STANDARD = gnu99; 280 | GCC_NO_COMMON_BLOCKS = YES; 281 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 282 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 283 | GCC_WARN_UNDECLARED_SELECTOR = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 285 | GCC_WARN_UNUSED_FUNCTION = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 288 | MTL_ENABLE_DEBUG_INFO = NO; 289 | SDKROOT = iphoneos; 290 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 291 | TARGETED_DEVICE_FAMILY = "1,2"; 292 | VALIDATE_PRODUCT = YES; 293 | }; 294 | name = Release; 295 | }; 296 | E9FEAE2E1DDCF8C90065CD7F /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 300 | DEVELOPMENT_TEAM = Y76S6B6ZNR; 301 | INFOPLIST_FILE = CustomVideoCompositor/Info.plist; 302 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 303 | PRODUCT_BUNDLE_IDENTIFIER = com.claygarrett.CustomVideoCompositor.CustomVideoCompositor; 304 | PRODUCT_NAME = "$(TARGET_NAME)"; 305 | SWIFT_VERSION = 3.0; 306 | }; 307 | name = Debug; 308 | }; 309 | E9FEAE2F1DDCF8C90065CD7F /* Release */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 313 | DEVELOPMENT_TEAM = Y76S6B6ZNR; 314 | INFOPLIST_FILE = CustomVideoCompositor/Info.plist; 315 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 316 | PRODUCT_BUNDLE_IDENTIFIER = com.claygarrett.CustomVideoCompositor.CustomVideoCompositor; 317 | PRODUCT_NAME = "$(TARGET_NAME)"; 318 | SWIFT_VERSION = 3.0; 319 | }; 320 | name = Release; 321 | }; 322 | /* End XCBuildConfiguration section */ 323 | 324 | /* Begin XCConfigurationList section */ 325 | E9FEAE161DDCF8C90065CD7F /* Build configuration list for PBXProject "CustomVideoCompositor" */ = { 326 | isa = XCConfigurationList; 327 | buildConfigurations = ( 328 | E9FEAE2B1DDCF8C90065CD7F /* Debug */, 329 | E9FEAE2C1DDCF8C90065CD7F /* Release */, 330 | ); 331 | defaultConfigurationIsVisible = 0; 332 | defaultConfigurationName = Release; 333 | }; 334 | E9FEAE2D1DDCF8C90065CD7F /* Build configuration list for PBXNativeTarget "CustomVideoCompositor" */ = { 335 | isa = XCConfigurationList; 336 | buildConfigurations = ( 337 | E9FEAE2E1DDCF8C90065CD7F /* Debug */, 338 | E9FEAE2F1DDCF8C90065CD7F /* Release */, 339 | ); 340 | defaultConfigurationIsVisible = 0; 341 | defaultConfigurationName = Release; 342 | }; 343 | /* End XCConfigurationList section */ 344 | }; 345 | rootObject = E9FEAE131DDCF8C90065CD7F /* Project object */; 346 | } 347 | -------------------------------------------------------------------------------- /CustomVideoCompositor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CustomVideoCompositor.xcodeproj/project.xcworkspace/xcshareddata/CustomVideoCompositor.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "361A0ADA6F1BA177F69C28659796EE14701AA284", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "480ECCA782BC03EA3F5ED5344256ADD11A32897E" : 9223372036854775807, 8 | "361A0ADA6F1BA177F69C28659796EE14701AA284" : 9223372036854775807 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "E256B047-DE84-43C9-AD61-A0212DDF28AA", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "480ECCA782BC03EA3F5ED5344256ADD11A32897E" : "avplayer-bug\/", 13 | "361A0ADA6F1BA177F69C28659796EE14701AA284" : "CustomVideoCompositor\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "CustomVideoCompositor", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CustomVideoCompositor.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:claygarrett\/CustomVideoCompositor.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "361A0ADA6F1BA177F69C28659796EE14701AA284" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:claygarrett\/avplayer-bug.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "480ECCA782BC03EA3F5ED5344256ADD11A32897E" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /CustomVideoCompositor.xcodeproj/project.xcworkspace/xcuserdata/claygarrett.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claygarrett/CustomVideoCompositor/03b3247bafc22d747654359a20002f06cecca70c/CustomVideoCompositor.xcodeproj/project.xcworkspace/xcuserdata/claygarrett.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CustomVideoCompositor.xcodeproj/xcuserdata/claygarrett.xcuserdatad/xcschemes/CustomVideoCompositor.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CustomVideoCompositor/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CustomVideoCompositor 4 | // 5 | // Created by Clay Garrett on 11/16/16. 6 | // Copyright © 2016 Clay Garrett. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /CustomVideoCompositor/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /CustomVideoCompositor/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /CustomVideoCompositor/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | UIInterfaceOrientationPortraitUpsideDown 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationPortraitUpsideDown 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /CustomVideoCompositor/Media/panda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claygarrett/CustomVideoCompositor/03b3247bafc22d747654359a20002f06cecca70c/CustomVideoCompositor/Media/panda.png -------------------------------------------------------------------------------- /CustomVideoCompositor/Media/sorry.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claygarrett/CustomVideoCompositor/03b3247bafc22d747654359a20002f06cecca70c/CustomVideoCompositor/Media/sorry.mov -------------------------------------------------------------------------------- /CustomVideoCompositor/UI/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 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /CustomVideoCompositor/Video Processing/CustomVideoCompositor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomVideoCompositor.swift 3 | // CustomVideoCompositor 4 | // 5 | // Created by Clay Garrett on 11/16/16. 6 | // Copyright © 2016 Clay Garrett. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class CustomVideoCompositor: NSObject, AVVideoCompositing { 13 | 14 | var duration: CMTime? 15 | 16 | var sourcePixelBufferAttributes: [String : Any]? { 17 | get { 18 | return ["\(kCVPixelBufferPixelFormatTypeKey)": kCVPixelFormatType_32BGRA] 19 | } 20 | } 21 | 22 | var requiredPixelBufferAttributesForRenderContext: [String : Any] { 23 | get { 24 | return ["\(kCVPixelBufferPixelFormatTypeKey)": kCVPixelFormatType_32BGRA] 25 | } 26 | } 27 | 28 | func renderContextChanged(_ newRenderContext: AVVideoCompositionRenderContext) { 29 | // do anything in here you need to before you start writing frames 30 | } 31 | 32 | func startRequest(_ request: AVAsynchronousVideoCompositionRequest) { 33 | // called for every frame 34 | // assuming there's a single video track. account for more complex scenarios as you need to 35 | let buffer = request.sourceFrame(byTrackID: request.sourceTrackIDs[0].int32Value) 36 | let instruction = request.videoCompositionInstruction 37 | 38 | // if we have our expected instructions 39 | if let inst = instruction as? WatermarkCompositionInstruction, let image = inst.watermarkImage, let frame = inst.watermarkFrame { 40 | // lock the buffer, create a new context and draw the watermark image 41 | CVPixelBufferLockBaseAddress(buffer!, CVPixelBufferLockFlags.readOnly) 42 | let newContext = CGContext.init(data: CVPixelBufferGetBaseAddress(buffer!), width: CVPixelBufferGetWidth(buffer!), height: CVPixelBufferGetHeight(buffer!), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(buffer!), space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) 43 | newContext?.draw(image, in: frame) 44 | CVPixelBufferUnlockBaseAddress(buffer!, CVPixelBufferLockFlags.readOnly) 45 | } 46 | request.finish(withComposedVideoFrame: buffer!) 47 | } 48 | 49 | func cancelAllPendingVideoCompositionRequests() { 50 | // anything you want to do when the compositing is canceled 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CustomVideoCompositor/Video Processing/VideoExporter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoExporter.swift 3 | // AVPlayerLayerBug 4 | // 5 | // Created by Clay Garrett on 10/28/16. 6 | // Copyright © 2016 Clay Garrett. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class VideoExporter: NSObject { 13 | 14 | var parentLayer: CALayer? 15 | var imageLayer: CALayer? 16 | let videoUrl: URL = URL(fileURLWithPath: Bundle.main.path(forResource: "sorry", ofType: "mov")!) 17 | let image = UIImage(named: "panda.png")!.cgImage 18 | 19 | func export() { 20 | // remove existing export file if it exists 21 | let baseDirectory = URL(fileURLWithPath: NSTemporaryDirectory()) 22 | let exportUrl = (baseDirectory.appendingPathComponent("export.mov", isDirectory: false) as NSURL).filePathURL! 23 | deleteExistingFile(url: exportUrl) 24 | 25 | // init variables 26 | let videoAsset: AVAsset = AVAsset(url: videoUrl) as AVAsset 27 | let tracks = videoAsset.tracks(withMediaType: AVMediaTypeVideo) 28 | let videoAssetTrack = tracks.first! 29 | let exportSize: CGFloat = 320 30 | 31 | // build video composition 32 | let videoComposition = AVMutableVideoComposition() 33 | videoComposition.customVideoCompositorClass = CustomVideoCompositor.self 34 | videoComposition.renderSize = CGSize(width: exportSize, height: exportSize) 35 | videoComposition.frameDuration = CMTimeMake(1, 30) 36 | 37 | // build instructions 38 | let instructionTimeRange = CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration) 39 | // we're overlaying this on our source video. here, our source video is 1080 x 1080 40 | // so even though our final export is 320 x 320, if we want full coverage of the video with our watermark, 41 | // then we need to make our watermark frame 1080 x 1080 42 | let watermarkFrame = CGRect(x: 0, y: 0, width: 1080, height: 1080) 43 | let instruction = WatermarkCompositionInstruction(timeRange: instructionTimeRange, watermarkImage: image!, watermarkFrame: watermarkFrame) 44 | 45 | videoComposition.instructions = [instruction] 46 | 47 | // create exporter and export 48 | let exporter = AVAssetExportSession(asset: videoAsset, presetName: AVAssetExportPresetHighestQuality) 49 | exporter!.videoComposition = videoComposition 50 | exporter!.outputURL = exportUrl 51 | exporter!.outputFileType = AVFileTypeQuickTimeMovie 52 | exporter!.shouldOptimizeForNetworkUse = true 53 | exporter!.exportAsynchronously(completionHandler: { () -> Void in 54 | switch exporter!.status { 55 | case .completed: 56 | print("Done!") 57 | break 58 | case .failed: 59 | print("Failed! \(exporter!.error)") 60 | default: 61 | break 62 | } 63 | }) 64 | } 65 | 66 | func deleteExistingFile(url: URL) { 67 | let fileManager = FileManager.default 68 | do { 69 | try fileManager.removeItem(at: url) 70 | } 71 | catch _ as NSError { 72 | 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CustomVideoCompositor/Video Processing/WatermarkCompositionInstruction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WatermarkCompositionInstruction.swift 3 | // CustomVideoCompositor 4 | // 5 | // Created by Clay Garrett on 11/16/16. 6 | // Copyright © 2016 Clay Garrett. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | class WatermarkCompositionInstruction: NSObject, AVVideoCompositionInstructionProtocol { 12 | 13 | // AVVideoCompositionInstructionProtocol allows you to pass along any type of instruction that the compositor might need to 14 | // correctly render frames for a given time range. in our case, we need a watermark image and a transform 15 | // but any type of property could go here and then be drawn in your custom compositor 16 | 17 | var watermarkImage: CGImage? 18 | var watermarkFrame: CGRect? 19 | 20 | /// The following 5 items are required for the protocol 21 | /// See information on them here: 22 | /// https://developer.apple.com/reference/avfoundation/avvideocompositioninstructionprotocol 23 | /// set the correct values for your specific use case 24 | 25 | /* Indicates the timeRange during which the instruction is effective. Note requirements for the timeRanges of instructions described in connection with AVVideoComposition's instructions key above. */ 26 | var timeRange: CMTimeRange 27 | 28 | /* If NO, indicates that post-processing should be skipped for the duration of this instruction. 29 | See +[AVVideoCompositionCoreAnimationTool videoCompositionToolWithPostProcessingAsVideoLayer:inLayer:].*/ 30 | var enablePostProcessing: Bool = true 31 | 32 | /* If YES, rendering a frame from the same source buffers and the same composition instruction at 2 different 33 | compositionTime may yield different output frames. If NO, 2 such compositions would yield the 34 | same frame. The media pipeline may me able to avoid some duplicate processing when containsTweening is NO */ 35 | var containsTweening: Bool = true 36 | 37 | /* List of video track IDs required to compose frames for this instruction. If the value of this property is nil, all source tracks will be considered required for composition */ 38 | var requiredSourceTrackIDs: [NSValue]? 39 | 40 | /* If for the duration of the instruction, the video composition result is one of the source frames, this property should 41 | return the corresponding track ID. The compositor won't be run for the duration of the instruction and the proper source 42 | frame will be used instead. The dimensions, clean aperture and pixel aspect ratio of the source buffer will be 43 | matched to the required values automatically */ 44 | var passthroughTrackID: CMPersistentTrackID = kCMPersistentTrackID_Invalid // if not a passthrough instruction 45 | 46 | init(timeRange: CMTimeRange, watermarkImage: CGImage, watermarkFrame: CGRect) { 47 | self.watermarkImage = watermarkImage 48 | self.watermarkFrame = watermarkFrame 49 | self.timeRange = timeRange 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CustomVideoCompositor/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AVPlayerLayerBug 4 | // 5 | // Created by Clay Garrett on 11/16/16. 6 | // Copyright © 2016 Clay Garrett. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var videoView: UIView! 15 | 16 | private var playerLayer: AVPlayerLayer? 17 | private var player: AVPlayer? 18 | private var playerItem: AVPlayerItem? 19 | 20 | override func viewDidLoad() { 21 | exportVideo() 22 | } 23 | 24 | func exportVideo() { 25 | let exporter = VideoExporter() 26 | exporter.export() 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CustomVideoCompositor 2 | A project to help users workaround a bug in iOS 10.0-10.1 related to AVAssetExportSession and AVVideoCompositionCoreAnimationTool 3 | 4 | # Sample Output 5 | ![Sample Output](http://i.imgur.com/ovzm4QU.gif "Logo Title Text 1") 6 | 7 | # Background 8 | In iOS 10, a bug in AVFoundation was introduced that causes `AVPlayer` instances to sometimes stop showing video (while continuing to play audio). This occurs after an instance of `AVAssetExportSession` runs if it utilizes `AVVideoCompositionCoreAnimationTool`, which is commonly used to do various types compositing of images/video. One common scenario is adding a watermark on top of a video while exporting. 9 | 10 | A workaround for this is to create your own custom video compositor that implements `AVVideoCompositing` protocol and custom compositing instructions that implement the `AVVideoCompositionInstructionProtocol` protocol. This projects demonstrates implementation of those protocols and the flow of information from the class that implements the AVAssetExportSession down to the method that renders each video frame. It is in no way meant to be a complete solution (though it should work for very simple use cases), but to help those facing this bug understand how to start solving it. 11 | 12 | # Files of Interest 13 | 14 | ## VideoExporter.swift 15 | Does the basic setup for getting a video export. You should already have code that does most of this. Some important lines to note here. 16 | 17 | Set your customVideoCompositorClass to the class you created that implements `AVVideoCompositing`: 18 | 19 | ```swift 20 | videoComposition.customVideoCompositorClass = CustomVideoCompositor.self 21 | ``` 22 | Set up your custom instructions so your compositor knows what do do with each frame: 23 | ```swift 24 | // build instructions 25 | let instructionTimeRange = CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration) 26 | // we're overlaying this on our source video. here, our source video is 1080 x 1080 27 | // so even though our final export is 320 x 320, if we want full coverage of the video with our watermark, 28 | // then we need to make our watermark frame 1080 x 1080 29 | let watermarkFrame = CGRect(x: 0, y: 0, width: 1080, height: 1080) 30 | let instruction = WatermarkCompositionInstruction(timeRange: instructionTimeRange, watermarkImage: image!, watermarkFrame: watermarkFrame) 31 | videoComposition.instructions = [instruction] 32 | ``` 33 | 34 | Note: make sure you cover the entire duration of the length of your video between all your instructions' Time Range. In our example, we use only one watermark image and our single instruction covers the entire duration of the video. But we could just as easily do one image for the first half and one for the second half by creating 2 separate instructions with differing time ranges. 35 | 36 | ## WatermarkCompositionInstruction.swift 37 | 38 | This is a simple class that stores data about the necessary information to render video for a given time range. In our case, it holds a watermark image and a frame for positioning/sizing that image. 39 | 40 | ## CustomVideoCompositor.swift 41 | 42 | This file actually does the rendering. All of the rendering logic exists here: 43 | 44 | ```swift 45 | func startRequest(_ request: AVAsynchronousVideoCompositionRequest) { 46 | // called for every frame 47 | // assuming there's a single video track. account for more complex scenarios as you need to 48 | let buffer = request.sourceFrame(byTrackID: request.sourceTrackIDs[0].int32Value) 49 | let instruction = request.videoCompositionInstruction 50 | 51 | // if we have our expected instructions 52 | if let inst = instruction as? WatermarkCompositionInstruction, let image = inst.watermarkImage, let frame = inst.watermarkFrame { 53 | // lock the buffer, create a new context and draw the watermark image 54 | CVPixelBufferLockBaseAddress(buffer!, CVPixelBufferLockFlags.readOnly) 55 | let newContext = CGContext.init(data: CVPixelBufferGetBaseAddress(buffer!), width: CVPixelBufferGetWidth(buffer!), height: CVPixelBufferGetHeight(buffer!), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(buffer!), space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) 56 | newContext?.draw(image, in: frame) 57 | CVPixelBufferUnlockBaseAddress(buffer!, CVPixelBufferLockFlags.readOnly) 58 | } 59 | request.finish(withComposedVideoFrame: buffer!) 60 | } 61 | ``` 62 | 63 | # Other potential solutions 64 | 65 | Ethan on Stack Overflow has another pretty great solution on how to do this using CIFilters: http://stackoverflow.com/a/39786820/1120513 Would be a great solution if you're just trying to add a watermark or do something that CIFilters can handle on their own. 66 | 67 | # Bug references: 68 | 69 | http://stackoverflow.com/questions/39760147/ios-10-avplayerlayer-doesnt-show-video-after-using-avvideocompositioncoreanima/39780044?noredirect=1#comment68493623_39780044 70 | 71 | https://forums.developer.apple.com/thread/62521 72 | --------------------------------------------------------------------------------