diff --git a/.gitignore b/.gitignore index 8604db7..7d666b7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ Examples/SimpleVideoExport/SimpleVideoExport.xcodeproj/project.xcworkspace/xcuse Examples/SimpleRealtimeFilterPlayback/SimpleRealtimeFilterPlayback.xcodeproj/xcuserdata Examples/SimpleVideoTransition/SimpleVideoTransition.xcodeproj/project.xcworkspace/xcuserdata/renzhumacro.xcuserdatad Examples/SimpleVideoTransition/SimpleVideoTransition.xcodeproj/xcuserdata/renzhumacro.xcuserdatad +Examples/SimpleVideoAnimation/Pods/Pods.xcodeproj/xcuserdata/renzhumacro.xcuserdatad +Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.xcworkspace/xcuserdata +Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/xcuserdata +Examples/SimpleVideoAnimation/SimpleVideoAnimation.xcworkspace/xcuserdata +Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/xcuserdata diff --git a/Examples/SimpleFourSquareVideo/SimpleFourSquareVideo/ViewController.swift b/Examples/SimpleFourSquareVideo/SimpleFourSquareVideo/ViewController.swift index bb0fe70..02719f0 100644 --- a/Examples/SimpleFourSquareVideo/SimpleFourSquareVideo/ViewController.swift +++ b/Examples/SimpleFourSquareVideo/SimpleFourSquareVideo/ViewController.swift @@ -37,13 +37,17 @@ class ViewController: UIViewController { let backgroundFrame = self.renderView.frame let backgroundSize = CGSize(width: backgroundFrame.size.width * 1.5, height: backgroundFrame.size.height * 1.5) - MetalVideoProcessBackground.canvasSize = backgroundSize + MetalVideoProcessBackground.canvasSize = backgroundSize + let transform1 = MetalVideoProcessTransformFilter() let transform2 = MetalVideoProcessTransformFilter() let transform3 = MetalVideoProcessTransformFilter() let transform4 = MetalVideoProcessTransformFilter() - + + + + transform1.stretchType = .aspectToFit transform1.roi = CGRect(x: 0.1, y: 0.1, width: 0.8, @@ -79,37 +83,49 @@ class ViewController: UIViewController { player.addTarget(transform4, trackID: item4.trackID, targetTrackId: item4.trackID) transform1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID) - +// transform2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID) - +// transform3.saveUniformSettings(forTimelineRange: item3.timeRange, trackID: item3.trackID) - +// transform4.saveUniformSettings(forTimelineRange: item4.timeRange, trackID: item4.trackID) let layer1 = MetalVideoProcessBlendFilter() background --> layer1 transform1 --> layer1 + +// layer1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID) let layer2 = MetalVideoProcessBlendFilter() layer1 --> layer2 transform2 --> layer2 - +// +// layer2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID) +// let layer3 = MetalVideoProcessBlendFilter() layer2 --> layer3 transform3 --> layer3 - +// layer3.saveUniformSettings(forTimelineRange: item3.timeRange, trackID: item3.trackID) + let layer4 = MetalVideoProcessBlendFilter() layer3 --> layer4 transform4 --> layer4 - +// layer4.saveUniformSettings(forTimelineRange: item4.timeRange, trackID: item4.trackID) +// + self.progress.minimumValue = 0.0 self.progress.maximumValue = Float(playerItem.duration.seconds) + + layer4 --> renderView +// layer3 --> self.renderView +// transform1 --> self.renderView + self.player = player self.player?.playerDelegate = self diff --git a/Examples/SimpleVideoAnimation/Podfile b/Examples/SimpleVideoAnimation/Podfile new file mode 100644 index 0000000..03c3fe6 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Podfile @@ -0,0 +1,11 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'SimpleVideoAnimation' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for SimpleVideoAnimation + pod 'PryntTrimmerView' + +end diff --git a/Examples/SimpleVideoAnimation/Podfile.lock b/Examples/SimpleVideoAnimation/Podfile.lock new file mode 100644 index 0000000..1721dbc --- /dev/null +++ b/Examples/SimpleVideoAnimation/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - PryntTrimmerView (4.0.0) + +DEPENDENCIES: + - PryntTrimmerView + +SPEC REPOS: + trunk: + - PryntTrimmerView + +SPEC CHECKSUMS: + PryntTrimmerView: 67e67573ffd4c073f1308effa66aefd0cfa667a0 + +PODFILE CHECKSUM: 4473f02a1528566660f9c5ed79ea1d9d7df2096d + +COCOAPODS: 1.8.4 diff --git a/Examples/SimpleVideoAnimation/Pods/Manifest.lock b/Examples/SimpleVideoAnimation/Pods/Manifest.lock new file mode 100644 index 0000000..1721dbc --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Manifest.lock @@ -0,0 +1,16 @@ +PODS: + - PryntTrimmerView (4.0.0) + +DEPENDENCIES: + - PryntTrimmerView + +SPEC REPOS: + trunk: + - PryntTrimmerView + +SPEC CHECKSUMS: + PryntTrimmerView: 67e67573ffd4c073f1308effa66aefd0cfa667a0 + +PODFILE CHECKSUM: 4473f02a1528566660f9c5ed79ea1d9d7df2096d + +COCOAPODS: 1.8.4 diff --git a/Examples/SimpleVideoAnimation/Pods/Pods.xcodeproj/project.pbxproj b/Examples/SimpleVideoAnimation/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ebb715f --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,632 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 032F877127CB819B89EEF1CD59F35190 /* AssetVideoScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4A1BACF84E64074643AF16313BED24 /* AssetVideoScrollView.swift */; }; + 084A42F97C9368ECB9C3C1BD50B83EAC /* VideoScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC439C201F68AF1EFFAC34B79FC8AABD /* VideoScrollView.swift */; }; + 1930B7608D16293CE006BBFFA59D2227 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */; }; + 3128FA0C6EF67C366889FD25B20C26B7 /* PryntTrimmerView-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = D432584627C3896D39BF7C2FE358F7B1 /* PryntTrimmerView-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 425982410476D4F38E665418296EA801 /* PryntTrimmerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F96BD1A00504790CD63CC5AAF94AAD4 /* PryntTrimmerView.swift */; }; + 4B1654EAC59903A3F57D722DD9D63788 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */; }; + 71138F4FB33001A655E478C0D395323E /* Pods-SimpleVideoAnimation-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 010BFB1D34029B68E2B40E8D668E3A8C /* Pods-SimpleVideoAnimation-dummy.m */; }; + 79AC849669741DC41B762F02D976F3B3 /* VideoCropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8625023B3E6DF67C7E31E4320747783 /* VideoCropView.swift */; }; + 8FE524682461A32D247F3280857E8576 /* ThumbSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E3490BF84958387765BD324D9462ADE /* ThumbSelectorView.swift */; }; + A1C503843AC95229CA9962770191D8CB /* AVAssetTimeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = F60A650D855F62F330AA252B6C596BBB /* AVAssetTimeSelector.swift */; }; + B47B377106D48A971F34C95FD6027D4E /* HandlerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DD5E1779CA8E74EA322A3C642F0050 /* HandlerView.swift */; }; + DD7151461459B7C1560131156E029E30 /* Pods-SimpleVideoAnimation-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = BCA28EAFECF92C9585C9095F6F893A77 /* Pods-SimpleVideoAnimation-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E6F4D1AA87BE2E09EFB06D3A7DB61858 /* PryntTrimmerView-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 1275509E869B124B7D2117626286EE7E /* PryntTrimmerView-dummy.m */; }; + EAB22516ACAA6EA3DA06A1BE26196675 /* CropMaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5574049F3812F6B26ABE1805FD9F4C18 /* CropMaskView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 86668A496B743E07BBAD7FF5F1AE251A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 492A3F539D36426FFCED62F4F58F688B; + remoteInfo = PryntTrimmerView; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0038897C32633F5F73BE1AEAC19DE309 /* Pods-SimpleVideoAnimation.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-SimpleVideoAnimation.modulemap"; sourceTree = ""; }; + 010BFB1D34029B68E2B40E8D668E3A8C /* Pods-SimpleVideoAnimation-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-SimpleVideoAnimation-dummy.m"; sourceTree = ""; }; + 017DD82CAC46C38F8A97838282A19238 /* Pods_SimpleVideoAnimation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_SimpleVideoAnimation.framework; path = "Pods-SimpleVideoAnimation.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 0C3506BDF9400573529648DF3D801256 /* Pods-SimpleVideoAnimation.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SimpleVideoAnimation.debug.xcconfig"; sourceTree = ""; }; + 0F96BD1A00504790CD63CC5AAF94AAD4 /* PryntTrimmerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PryntTrimmerView.swift; path = PryntTrimmerView/Classes/Trimmer/PryntTrimmerView.swift; sourceTree = ""; }; + 1275509E869B124B7D2117626286EE7E /* PryntTrimmerView-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "PryntTrimmerView-dummy.m"; sourceTree = ""; }; + 2A4A1BACF84E64074643AF16313BED24 /* AssetVideoScrollView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssetVideoScrollView.swift; path = PryntTrimmerView/Classes/Parents/AssetVideoScrollView.swift; sourceTree = ""; }; + 3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 44D06F46436AACB33E8D2E85504271CE /* Pods-SimpleVideoAnimation-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-SimpleVideoAnimation-acknowledgements.markdown"; sourceTree = ""; }; + 4F75A400052C49C7C0424C6199354326 /* Pods-SimpleVideoAnimation-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-SimpleVideoAnimation-Info.plist"; sourceTree = ""; }; + 5574049F3812F6B26ABE1805FD9F4C18 /* CropMaskView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CropMaskView.swift; path = PryntTrimmerView/Classes/Cropper/CropMaskView.swift; sourceTree = ""; }; + 56395D8727605D379FEE467729B17928 /* PryntTrimmerView.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = PryntTrimmerView.xcconfig; sourceTree = ""; }; + 782DD95E3C1D9DD6570A9B32B5E8C027 /* PryntTrimmerView.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = PryntTrimmerView.modulemap; sourceTree = ""; }; + 9541F8AE153E6A07FF82F8BE4D1DF4CE /* Pods-SimpleVideoAnimation-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SimpleVideoAnimation-frameworks.sh"; sourceTree = ""; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9E3490BF84958387765BD324D9462ADE /* ThumbSelectorView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ThumbSelectorView.swift; path = PryntTrimmerView/Classes/ThumbSelectorView.swift; sourceTree = ""; }; + A21A3E5DAE9D963771CB82663DB77372 /* PryntTrimmerView-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "PryntTrimmerView-prefix.pch"; sourceTree = ""; }; + BCA28EAFECF92C9585C9095F6F893A77 /* Pods-SimpleVideoAnimation-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-SimpleVideoAnimation-umbrella.h"; sourceTree = ""; }; + C45D1BDF6010C3F370037BA1366D39A9 /* Pods-SimpleVideoAnimation.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SimpleVideoAnimation.release.xcconfig"; sourceTree = ""; }; + C63296516747E930E004498D034CBEC8 /* PryntTrimmerView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "PryntTrimmerView-Info.plist"; sourceTree = ""; }; + CB21E0A22EAC99D55621CBFDCE99B6FB /* Pods-SimpleVideoAnimation-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-SimpleVideoAnimation-acknowledgements.plist"; sourceTree = ""; }; + CC439C201F68AF1EFFAC34B79FC8AABD /* VideoScrollView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = VideoScrollView.swift; path = PryntTrimmerView/Classes/Cropper/VideoScrollView.swift; sourceTree = ""; }; + D09DB48D8145BFA029AAEA61818EC68B /* PryntTrimmerView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = PryntTrimmerView.framework; path = PryntTrimmerView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D432584627C3896D39BF7C2FE358F7B1 /* PryntTrimmerView-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "PryntTrimmerView-umbrella.h"; sourceTree = ""; }; + E3DD5E1779CA8E74EA322A3C642F0050 /* HandlerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HandlerView.swift; path = PryntTrimmerView/Classes/Trimmer/HandlerView.swift; sourceTree = ""; }; + F60A650D855F62F330AA252B6C596BBB /* AVAssetTimeSelector.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AVAssetTimeSelector.swift; path = PryntTrimmerView/Classes/Parents/AVAssetTimeSelector.swift; sourceTree = ""; }; + F8625023B3E6DF67C7E31E4320747783 /* VideoCropView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = VideoCropView.swift; path = PryntTrimmerView/Classes/Cropper/VideoCropView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + AF1BE2436FA9C0AFA75A7FA0ADD09D5B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1930B7608D16293CE006BBFFA59D2227 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C96EDBA35CA5052A71FDC4046C1C72B2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B1654EAC59903A3F57D722DD9D63788 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3299D943353D836EC70C7C067763BC0A /* PryntTrimmerView */ = { + isa = PBXGroup; + children = ( + 2A4A1BACF84E64074643AF16313BED24 /* AssetVideoScrollView.swift */, + F60A650D855F62F330AA252B6C596BBB /* AVAssetTimeSelector.swift */, + 5574049F3812F6B26ABE1805FD9F4C18 /* CropMaskView.swift */, + E3DD5E1779CA8E74EA322A3C642F0050 /* HandlerView.swift */, + 0F96BD1A00504790CD63CC5AAF94AAD4 /* PryntTrimmerView.swift */, + 9E3490BF84958387765BD324D9462ADE /* ThumbSelectorView.swift */, + F8625023B3E6DF67C7E31E4320747783 /* VideoCropView.swift */, + CC439C201F68AF1EFFAC34B79FC8AABD /* VideoScrollView.swift */, + 913459968288E6874F51EBA6C6BC4F42 /* Support Files */, + ); + name = PryntTrimmerView; + path = PryntTrimmerView; + sourceTree = ""; + }; + 5F0F96C3DFA71EC2D7EDB6D805C616DF /* Products */ = { + isa = PBXGroup; + children = ( + 017DD82CAC46C38F8A97838282A19238 /* Pods_SimpleVideoAnimation.framework */, + D09DB48D8145BFA029AAEA61818EC68B /* PryntTrimmerView.framework */, + ); + name = Products; + sourceTree = ""; + }; + 657908C41F07AE316229BADE15AE40AA /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 68CC947BC3E006F2F0C7914F735374B2 /* Pods-SimpleVideoAnimation */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + 68CC947BC3E006F2F0C7914F735374B2 /* Pods-SimpleVideoAnimation */ = { + isa = PBXGroup; + children = ( + 0038897C32633F5F73BE1AEAC19DE309 /* Pods-SimpleVideoAnimation.modulemap */, + 44D06F46436AACB33E8D2E85504271CE /* Pods-SimpleVideoAnimation-acknowledgements.markdown */, + CB21E0A22EAC99D55621CBFDCE99B6FB /* Pods-SimpleVideoAnimation-acknowledgements.plist */, + 010BFB1D34029B68E2B40E8D668E3A8C /* Pods-SimpleVideoAnimation-dummy.m */, + 9541F8AE153E6A07FF82F8BE4D1DF4CE /* Pods-SimpleVideoAnimation-frameworks.sh */, + 4F75A400052C49C7C0424C6199354326 /* Pods-SimpleVideoAnimation-Info.plist */, + BCA28EAFECF92C9585C9095F6F893A77 /* Pods-SimpleVideoAnimation-umbrella.h */, + 0C3506BDF9400573529648DF3D801256 /* Pods-SimpleVideoAnimation.debug.xcconfig */, + C45D1BDF6010C3F370037BA1366D39A9 /* Pods-SimpleVideoAnimation.release.xcconfig */, + ); + name = "Pods-SimpleVideoAnimation"; + path = "Target Support Files/Pods-SimpleVideoAnimation"; + sourceTree = ""; + }; + 913459968288E6874F51EBA6C6BC4F42 /* Support Files */ = { + isa = PBXGroup; + children = ( + 782DD95E3C1D9DD6570A9B32B5E8C027 /* PryntTrimmerView.modulemap */, + 56395D8727605D379FEE467729B17928 /* PryntTrimmerView.xcconfig */, + 1275509E869B124B7D2117626286EE7E /* PryntTrimmerView-dummy.m */, + C63296516747E930E004498D034CBEC8 /* PryntTrimmerView-Info.plist */, + A21A3E5DAE9D963771CB82663DB77372 /* PryntTrimmerView-prefix.pch */, + D432584627C3896D39BF7C2FE358F7B1 /* PryntTrimmerView-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/PryntTrimmerView"; + sourceTree = ""; + }; + 9D4B4E6D53A1F0FBAE805F589D033B5A /* Pods */ = { + isa = PBXGroup; + children = ( + 3299D943353D836EC70C7C067763BC0A /* PryntTrimmerView */, + ); + name = Pods; + sourceTree = ""; + }; + C0834CEBB1379A84116EF29F93051C60 /* iOS */ = { + isa = PBXGroup; + children = ( + 3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; + CF1408CF629C7361332E53B88F7BD30C = { + isa = PBXGroup; + children = ( + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, + D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */, + 9D4B4E6D53A1F0FBAE805F589D033B5A /* Pods */, + 5F0F96C3DFA71EC2D7EDB6D805C616DF /* Products */, + 657908C41F07AE316229BADE15AE40AA /* Targets Support Files */, + ); + sourceTree = ""; + }; + D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C0834CEBB1379A84116EF29F93051C60 /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 86A339BA1B223A27BCFB984A3C47E28B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 3128FA0C6EF67C366889FD25B20C26B7 /* PryntTrimmerView-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DFB6EC49F1D54FFAAF97A8DC14107147 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + DD7151461459B7C1560131156E029E30 /* Pods-SimpleVideoAnimation-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 492A3F539D36426FFCED62F4F58F688B /* PryntTrimmerView */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5F256E2E76CAD307A29C8FD48E4430F4 /* Build configuration list for PBXNativeTarget "PryntTrimmerView" */; + buildPhases = ( + 86A339BA1B223A27BCFB984A3C47E28B /* Headers */, + 29AFF14336C5C60CF4D7ECE4FCF71B99 /* Sources */, + AF1BE2436FA9C0AFA75A7FA0ADD09D5B /* Frameworks */, + 90642AE968639B75967B5E9FBE4FB582 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PryntTrimmerView; + productName = PryntTrimmerView; + productReference = D09DB48D8145BFA029AAEA61818EC68B /* PryntTrimmerView.framework */; + productType = "com.apple.product-type.framework"; + }; + A385EDF46896AAD667E7CB6AEBEEA453 /* Pods-SimpleVideoAnimation */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5510412BFB33A41088328A2944E8429E /* Build configuration list for PBXNativeTarget "Pods-SimpleVideoAnimation" */; + buildPhases = ( + DFB6EC49F1D54FFAAF97A8DC14107147 /* Headers */, + 47130975D92528825F7CD9F43190F66E /* Sources */, + C96EDBA35CA5052A71FDC4046C1C72B2 /* Frameworks */, + 90C5D17977835042DD034C1A0D848044 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4F0D8E61E9CE9580D4CB4657DFBBF101 /* PBXTargetDependency */, + ); + name = "Pods-SimpleVideoAnimation"; + productName = "Pods-SimpleVideoAnimation"; + productReference = 017DD82CAC46C38F8A97838282A19238 /* Pods_SimpleVideoAnimation.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BFDFE7DC352907FC980B868725387E98 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1100; + LastUpgradeCheck = 1100; + }; + buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CF1408CF629C7361332E53B88F7BD30C; + productRefGroup = 5F0F96C3DFA71EC2D7EDB6D805C616DF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A385EDF46896AAD667E7CB6AEBEEA453 /* Pods-SimpleVideoAnimation */, + 492A3F539D36426FFCED62F4F58F688B /* PryntTrimmerView */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 90642AE968639B75967B5E9FBE4FB582 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 90C5D17977835042DD034C1A0D848044 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 29AFF14336C5C60CF4D7ECE4FCF71B99 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 032F877127CB819B89EEF1CD59F35190 /* AssetVideoScrollView.swift in Sources */, + A1C503843AC95229CA9962770191D8CB /* AVAssetTimeSelector.swift in Sources */, + EAB22516ACAA6EA3DA06A1BE26196675 /* CropMaskView.swift in Sources */, + B47B377106D48A971F34C95FD6027D4E /* HandlerView.swift in Sources */, + E6F4D1AA87BE2E09EFB06D3A7DB61858 /* PryntTrimmerView-dummy.m in Sources */, + 425982410476D4F38E665418296EA801 /* PryntTrimmerView.swift in Sources */, + 8FE524682461A32D247F3280857E8576 /* ThumbSelectorView.swift in Sources */, + 79AC849669741DC41B762F02D976F3B3 /* VideoCropView.swift in Sources */, + 084A42F97C9368ECB9C3C1BD50B83EAC /* VideoScrollView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 47130975D92528825F7CD9F43190F66E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 71138F4FB33001A655E478C0D395323E /* Pods-SimpleVideoAnimation-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4F0D8E61E9CE9580D4CB4657DFBBF101 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PryntTrimmerView; + target = 492A3F539D36426FFCED62F4F58F688B /* PryntTrimmerView */; + targetProxy = 86668A496B743E07BBAD7FF5F1AE251A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 13967C5C8F4B10B3EE4D6C208A0A7BEB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + 22EFBC31083FBA35442432B8942CCB05 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C45D1BDF6010C3F370037BA1366D39A9 /* Pods-SimpleVideoAnimation.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 4812C7722161295CE314C28F40961E87 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 56395D8727605D379FEE467729B17928 /* PryntTrimmerView.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/PryntTrimmerView/PryntTrimmerView-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/PryntTrimmerView/PryntTrimmerView-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/PryntTrimmerView/PryntTrimmerView.modulemap"; + PRODUCT_MODULE_NAME = PryntTrimmerView; + PRODUCT_NAME = PryntTrimmerView; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 72B8A024B2332A85E8D4B741B60F6372 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0C3506BDF9400573529648DF3D801256 /* Pods-SimpleVideoAnimation.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 871B6E28ABA34DAE69824AB9D22D3B7D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_RELEASE=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Release; + }; + F1B17B29AA8479A18A091848A6D12474 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 56395D8727605D379FEE467729B17928 /* PryntTrimmerView.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/PryntTrimmerView/PryntTrimmerView-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/PryntTrimmerView/PryntTrimmerView-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/PryntTrimmerView/PryntTrimmerView.modulemap"; + PRODUCT_MODULE_NAME = PryntTrimmerView; + PRODUCT_NAME = PryntTrimmerView; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13967C5C8F4B10B3EE4D6C208A0A7BEB /* Debug */, + 871B6E28ABA34DAE69824AB9D22D3B7D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5510412BFB33A41088328A2944E8429E /* Build configuration list for PBXNativeTarget "Pods-SimpleVideoAnimation" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 72B8A024B2332A85E8D4B741B60F6372 /* Debug */, + 22EFBC31083FBA35442432B8942CCB05 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5F256E2E76CAD307A29C8FD48E4430F4 /* Build configuration list for PBXNativeTarget "PryntTrimmerView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F1B17B29AA8479A18A091848A6D12474 /* Debug */, + 4812C7722161295CE314C28F40961E87 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; +} diff --git a/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/LICENSE b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/LICENSE new file mode 100644 index 0000000..0d593ec --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Prynt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Cropper/CropMaskView.swift b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Cropper/CropMaskView.swift new file mode 100644 index 0000000..2c93912 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Cropper/CropMaskView.swift @@ -0,0 +1,114 @@ +// +// CropMaskView.swift +// PryntTrimmerView +// +// Created by Henry on 10/04/2017. +// Copyright © 2017 CocoaPods. All rights reserved. +// + +import UIKit + +class CropMaskView: UIView { + + let cropBoxView = UIView() + let frameView = UIView() + let maskLayer = CAShapeLayer() + let frameLayer = CAShapeLayer() + + private let lineWidth: CGFloat = 4.0 + private var cropFrame: CGRect = CGRect.zero + + override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupSubviews() + } + + private func setupSubviews() { + + maskLayer.fillRule = CAShapeLayerFillRule.evenOdd + maskLayer.fillColor = UIColor.black.cgColor + maskLayer.opacity = 1.0 + + frameLayer.strokeColor = UIColor.white.cgColor + frameLayer.fillColor = UIColor.clear.cgColor + + frameView.layer.addSublayer(frameLayer) + cropBoxView.layer.mask = maskLayer + + cropBoxView.translatesAutoresizingMaskIntoConstraints = false + cropBoxView.backgroundColor = UIColor.white.withAlphaComponent(0.7) + addSubview(cropBoxView) + addSubview(frameView) + + cropBoxView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + cropBoxView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true + cropBoxView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + cropBoxView.topAnchor.constraint(equalTo: topAnchor).isActive = true + } + + override func layoutSubviews() { + super.layoutSubviews() + + let path = UIBezierPath(rect: bounds) + let framePath = UIBezierPath(rect: cropFrame) + path.append(framePath) + path.usesEvenOddFillRule = true + maskLayer.path = path.cgPath + + framePath.lineWidth = lineWidth + frameLayer.path = framePath.cgPath + } + + func setCropFrame(_ frame: CGRect, animated: Bool) { + + cropFrame = frame + guard animated else { + setNeedsLayout() + return + } + + let (path, framePath) = getPaths(with: cropFrame) + + CATransaction.begin() + + let animation = getPathAnimation(with: path) + maskLayer.path = maskLayer.presentation()?.path + frameLayer.path = frameLayer.presentation()?.path + + maskLayer.removeAnimation(forKey: "maskPath") + maskLayer.add(animation, forKey: "maskPath") + + animation.toValue = framePath + frameLayer.removeAnimation(forKey: "framePath") + frameLayer.add(animation, forKey: "framePath") + CATransaction.commit() + } + + private func getPaths(with cropFrame: CGRect) -> (path: CGPath, framePath: CGPath) { + + let path = UIBezierPath(rect: bounds) + let framePath = UIBezierPath(rect: cropFrame) + framePath.lineWidth = lineWidth + path.append(framePath) + path.usesEvenOddFillRule = true + + return (path.cgPath, framePath.cgPath) + } + + private func getPathAnimation(with path: CGPath) -> CABasicAnimation { + + let animation = CABasicAnimation(keyPath: "path") + animation.toValue = path + animation.duration = 0.3 + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) + animation.fillMode = CAMediaTimingFillMode.both + animation.isRemovedOnCompletion = false + + return animation + } +} diff --git a/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Cropper/VideoCropView.swift b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Cropper/VideoCropView.swift new file mode 100644 index 0000000..564eddf --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Cropper/VideoCropView.swift @@ -0,0 +1,123 @@ +// +// VideoCropView.swift +// PryntTrimmerView +// +// Created by Henry on 07/04/2017. +// Copyright © 2017 CocoaPods. All rights reserved. +// + +import UIKit +import AVFoundation + +private let margin: CGFloat = 16 + +/// A view to preview a video inside an `AVPlayer`, with a scroll view to be able to select a specific area of the video. +/// Simply set the `asset` property to load you video inside the view, and use the `getImageCropFrame` method to retrieve the +/// selected frame of the video. +public class VideoCropView: UIView { + + let videoScrollView = VideoScrollView() + let cropMaskView = CropMaskView() + + /// The asset being cropped. Setting the property will load the asset in the `AVPlayer` contained in the scroll view. + public var asset: AVAsset? { + didSet { + if let asset = asset { + videoScrollView.setupVideo(with: asset) + } + } + } + + var cropFrame = CGRect.zero + + /// The current aspect ratio of the crop view. + public private(set) var aspectRatio = CGSize(width: 1, height: 1) + + /// The player used in the scroll view. Use it if you want to seek a specific time of your video. + public var player: AVPlayer? { + return videoScrollView.player + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupSubviews() + } + + private func setupSubviews() { + + videoScrollView.translatesAutoresizingMaskIntoConstraints = false + addSubview(videoScrollView) + + videoScrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + videoScrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true + videoScrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + videoScrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true + + cropMaskView.isUserInteractionEnabled = false + cropMaskView.translatesAutoresizingMaskIntoConstraints = false + + addSubview(cropMaskView) + + cropMaskView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + cropMaskView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true + cropMaskView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + cropMaskView.topAnchor.constraint(equalTo: topAnchor).isActive = true + + setAspectRatio(aspectRatio, animated: false) + } + + /// Set the aspect ratio for the cropping box. + /// + /// [Issue #3]: https://github.com/HHK1/PryntTrimmerView/issues/3 + /// - Parameters: + /// - aspectRatio: The desired aspect ratio. + /// - animated: true if you want to animate the change. TODO: the animation glitches sometimes. See [Issue #3] + public func setAspectRatio(_ aspectRatio: CGSize, animated: Bool) { + + self.aspectRatio = aspectRatio + let ratio = aspectRatio.width / aspectRatio.height + let cropBoxWidth = ratio > 1 ? (bounds.width - 2 * margin) : (bounds.height - 2 * margin) * ratio + let cropBoxHeight = cropBoxWidth / ratio + let origin = CGPoint(x: (bounds.width - cropBoxWidth) / 2, y: (bounds.height - cropBoxHeight) / 2) + cropFrame = CGRect(origin: origin, size: CGSize(width: cropBoxWidth, height: cropBoxHeight)) + + let edgeInsets = UIEdgeInsets(top: origin.y, left: origin.x, bottom: origin.y, right: origin.x) + let duration: TimeInterval = animated ? 0.15 : 0.0 + + cropMaskView.setCropFrame(cropFrame, animated: animated) + UIView.animate(withDuration: duration, delay: 0, options: [.beginFromCurrentState, .curveEaseInOut], animations: { + self.videoScrollView.scrollView.contentInset = edgeInsets + }, completion: nil) + videoScrollView.setZoomScaleAndCenter(animated: animated) + } + + /// Get the currently selected frame of the asset. You can then use it to perform the actual cropping of the video or of an + /// isolated image + public func getImageCropFrame() -> CGRect { + + let imageSize = videoScrollView.assetSize + let contentSize = videoScrollView.scrollView.contentSize + let cropBoxFrame = cropFrame + let contentOffset = videoScrollView.scrollView.contentOffset + let edgeInsets = videoScrollView.scrollView.contentInset + + var frame = CGRect.zero + frame.origin.x = floor((contentOffset.x + edgeInsets.left) * (imageSize.width / contentSize.width)) + frame.origin.x = max(0, frame.origin.x) + + frame.origin.y = floor((contentOffset.y + edgeInsets.top) * (imageSize.height / contentSize.height)) + frame.origin.y = max(0, frame.origin.y) + + frame.size.width = ceil(cropBoxFrame.size.width * (imageSize.width / contentSize.width)) + frame.size.width = min(imageSize.width, frame.size.width) + + frame.size.height = ceil(cropBoxFrame.size.height * (imageSize.height / contentSize.height)) + frame.size.height = min(imageSize.height, frame.size.height) + return frame + } +} diff --git a/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Cropper/VideoScrollView.swift b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Cropper/VideoScrollView.swift new file mode 100644 index 0000000..1affc0f --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Cropper/VideoScrollView.swift @@ -0,0 +1,103 @@ +// +// VideoScrollView.swift +// PryntTrimmerView +// +// Created by Henry on 10/04/2017. +// Copyright © 2017 CocoaPods. All rights reserved. +// + +import UIKit +import AVFoundation + +class VideoScrollView: UIView { + + let scrollView = UIScrollView() + var contentView = UIView() + var assetSize = CGSize.zero + + var playerItem: AVPlayerItem? + var player: AVPlayer? + var playerLayer: AVPlayerLayer? + + override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupSubviews() + } + + private func setupSubviews() { + + scrollView.showsVerticalScrollIndicator = false + scrollView.showsHorizontalScrollIndicator = false + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.addSubview(contentView) + scrollView.delegate = self + addSubview(scrollView) + + scrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + scrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true + scrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + scrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true + } + + func setupVideo(with asset: AVAsset) { + + guard let track = asset.tracks(withMediaType: AVMediaType.video).first else { return } + let trackSize = track.naturalSize.applying(track.preferredTransform) + assetSize = CGSize(width: abs(trackSize.width), height: abs(trackSize.height)) + + scrollView.zoomScale = 1.0 // Reset zoom scale before changing the frame of the content view. + playerItem = AVPlayerItem(asset: asset) + let playerFrame = CGRect(x: 0, y: 0, width: assetSize.width, height: assetSize.height) + addVideoLayer(with: playerFrame) + + scrollView.contentSize = assetSize + setZoomScaleAndCenter(animated: false) + } + + private func addVideoLayer(with playerFrame: CGRect) { + + playerLayer?.removeFromSuperlayer() + player = AVPlayer(playerItem: playerItem) + playerLayer = AVPlayerLayer(player: player) + playerLayer?.frame = playerFrame + playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill + + contentView.frame = playerFrame + contentView.layer.addSublayer(playerLayer!) + } + + func setZoomScaleAndCenter(animated: Bool) { + + guard assetSize != CGSize.zero else { return } + + let scrollWidth = scrollView.bounds.width - scrollView.contentInset.left - scrollView.contentInset.right + let scrollHeight = scrollView.bounds.height - scrollView.contentInset.top - scrollView.contentInset.bottom + let scale = max(scrollWidth / assetSize.width, scrollHeight / assetSize.height) + scrollView.minimumZoomScale = scale + scrollView.maximumZoomScale = 3.0 + + var offset = scrollView.contentOffset + offset.x = -scrollView.contentInset.left - (scrollWidth - assetSize.width * scale) / 2 + offset.y = -scrollView.contentInset.top - (scrollHeight - assetSize.height * scale) / 2 + + scrollView.setZoomScale(scale, animated: animated) + scrollView.setContentOffset(offset, animated: animated) + } +} + +extension VideoScrollView: UIScrollViewDelegate { + + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + return contentView + } + + func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { + let scaledAssetSize = CGSize(width: assetSize.width * scale, height: assetSize.height * scale) + scrollView.contentSize = scaledAssetSize + } +} diff --git a/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Parents/AVAssetTimeSelector.swift b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Parents/AVAssetTimeSelector.swift new file mode 100644 index 0000000..d4673d4 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Parents/AVAssetTimeSelector.swift @@ -0,0 +1,85 @@ +// +// AVAssetTimeSelector.swift +// Pods +// +// Created by Henry on 06/04/2017. +// +// + +import UIKit +import AVFoundation + +/// A generic class to display an asset into a scroll view with thumbnail images, and make the equivalence between a time in +// the asset and a position in the scroll view +public class AVAssetTimeSelector: UIView, UIScrollViewDelegate { + + let assetPreview = AssetVideoScrollView() + + /// The asset to be displayed in the underlying scroll view. Setting a new asset will automatically refresh the thumbnails. + public var asset: AVAsset? { + didSet { + assetDidChange(newAsset: asset) + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupSubviews() + } + + func setupSubviews() { + setupAssetPreview() + constrainAssetPreview() + } + + // MARK: - Asset Preview + + func setupAssetPreview() { + + assetPreview.translatesAutoresizingMaskIntoConstraints = false + assetPreview.delegate = self + addSubview(assetPreview) + } + + func constrainAssetPreview() { + assetPreview.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + assetPreview.rightAnchor.constraint(equalTo: rightAnchor).isActive = true + assetPreview.topAnchor.constraint(equalTo: topAnchor).isActive = true + assetPreview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } + + func assetDidChange(newAsset: AVAsset?) { + if let asset = newAsset { + assetPreview.regenerateThumbnails(for: asset) + } + } + + // MARK: - Time & Position Equivalence + + var durationSize: CGFloat { + return assetPreview.contentSize.width + } + + func getTime(from position: CGFloat) -> CMTime? { + guard let asset = asset else { + return nil + } + let normalizedRatio = max(min(1, position / durationSize), 0) + let positionTimeValue = Double(normalizedRatio) * Double(asset.duration.value) + return CMTime(value: Int64(positionTimeValue), timescale: asset.duration.timescale) + } + + func getPosition(from time: CMTime) -> CGFloat? { + guard let asset = asset else { + return nil + } + let timeRatio = CGFloat(time.value) * CGFloat(asset.duration.timescale) / + (CGFloat(time.timescale) * CGFloat(asset.duration.value)) + return timeRatio * durationSize + } +} diff --git a/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Parents/AssetVideoScrollView.swift b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Parents/AssetVideoScrollView.swift new file mode 100644 index 0000000..5316f69 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Parents/AssetVideoScrollView.swift @@ -0,0 +1,166 @@ +// +// AssetVideoScrollView.swift +// PryntTrimmerView +// +// Created by HHK on 28/03/2017. +// Copyright © 2017 Prynt. All rights reserved. +// + +import AVFoundation +import UIKit + +class AssetVideoScrollView: UIScrollView { + + private var widthConstraint: NSLayoutConstraint? + + let contentView = UIView() + var maxDuration: Double = 15 + private var generator: AVAssetImageGenerator? + + override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupSubviews() + } + + private func setupSubviews() { + + backgroundColor = .clear + showsVerticalScrollIndicator = false + showsHorizontalScrollIndicator = false + clipsToBounds = true + + contentView.backgroundColor = .clear + contentView.translatesAutoresizingMaskIntoConstraints = false + contentView.tag = -1 + addSubview(contentView) + + contentView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + contentView.topAnchor.constraint(equalTo: topAnchor).isActive = true + contentView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + widthConstraint = contentView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0) + widthConstraint?.isActive = true + } + + override func layoutSubviews() { + super.layoutSubviews() + contentSize = contentView.bounds.size + } + + internal func regenerateThumbnails(for asset: AVAsset) { + guard let thumbnailSize = getThumbnailFrameSize(from: asset), thumbnailSize.width != 0 else { + print("Could not calculate the thumbnail size.") + return + } + + generator?.cancelAllCGImageGeneration() + removeFormerThumbnails() + let newContentSize = setContentSize(for: asset) + let visibleThumbnailsCount = Int(ceil(frame.width / thumbnailSize.width)) + let thumbnailCount = Int(ceil(newContentSize.width / thumbnailSize.width)) + addThumbnailViews(thumbnailCount, size: thumbnailSize) + let timesForThumbnail = getThumbnailTimes(for: asset, numberOfThumbnails: thumbnailCount) + generateImages(for: asset, at: timesForThumbnail, with: thumbnailSize, visibleThumnails: visibleThumbnailsCount) + } + + private func getThumbnailFrameSize(from asset: AVAsset) -> CGSize? { + guard let track = asset.tracks(withMediaType: AVMediaType.video).first else { return nil} + + let assetSize = track.naturalSize.applying(track.preferredTransform) + + let height = frame.height + let ratio = assetSize.width / assetSize.height + let width = height * ratio + return CGSize(width: abs(width), height: abs(height)) + } + + private func removeFormerThumbnails() { + contentView.subviews.forEach({ $0.removeFromSuperview() }) + } + + private func setContentSize(for asset: AVAsset) -> CGSize { + + let contentWidthFactor = CGFloat(max(1, asset.duration.seconds / maxDuration)) + widthConstraint?.isActive = false + widthConstraint = contentView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: contentWidthFactor) + widthConstraint?.isActive = true + layoutIfNeeded() + return contentView.bounds.size + } + + private func addThumbnailViews(_ count: Int, size: CGSize) { + + for index in 0.. contentView.frame.width { + thumbnailView.frame.size = CGSize(width: size.width + (contentView.frame.width - viewEndX), height: size.height) + thumbnailView.contentMode = .scaleAspectFill + } else { + thumbnailView.frame.size = size + thumbnailView.contentMode = .scaleAspectFit + } + + thumbnailView.frame.origin = CGPoint(x: CGFloat(index) * size.width, y: 0) + thumbnailView.tag = index + contentView.addSubview(thumbnailView) + } + } + + private func getThumbnailTimes(for asset: AVAsset, numberOfThumbnails: Int) -> [NSValue] { + + let timeIncrement = (asset.duration.seconds * 1000) / Double(numberOfThumbnails) + var timesForThumbnails = [NSValue]() + for index in 0.. Void in + + if count == 0 { + self?.displayFirstImage(cgimage, visibleThumbnails: visibleThumnails) + } + self?.displayImage(cgimage, at: count) + count += 1 + }) + } + } + + generator?.generateCGImagesAsynchronously(forTimes: times, completionHandler: handler) + } + + private func displayFirstImage(_ cgImage: CGImage, visibleThumbnails: Int) { + for i in 0...visibleThumbnails { + displayImage(cgImage, at: i) + } + } + + private func displayImage(_ cgImage: CGImage, at index: Int) { + if let imageView = contentView.viewWithTag(index) as? UIImageView { + let uiimage = UIImage(cgImage: cgImage, scale: 1.0, orientation: UIImage.Orientation.up) + imageView.image = uiimage + } + } +} diff --git a/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/ThumbSelectorView.swift b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/ThumbSelectorView.swift new file mode 100644 index 0000000..592b64a --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/ThumbSelectorView.swift @@ -0,0 +1,178 @@ +// +// ThumbSelectorView.swift +// Pods +// +// Created by Henry on 06/04/2017. +// +// + +import UIKit +import AVFoundation + +/// A delegate to be notified of when the thumb position has changed. Useful to link an instance of the ThumbSelectorView to a +/// video preview like an `AVPlayer`. +public protocol ThumbSelectorViewDelegate: class { + func didChangeThumbPosition(_ imageTime: CMTime) +} + +/// A view to select a specific time of an `AVAsset`. It is composed of an asset preview within a scroll view, and a thumb view +/// to select a precise time of the video. Set the `asset` property to load the video, and use the `selectedTime` property to +// retrieve the exact frame of the asset that was selected. +public class ThumbSelectorView: AVAssetTimeSelector { + + public var thumbBorderColor: UIColor = .white { + didSet { + thumbView.layer.borderColor = thumbBorderColor.cgColor + } + } + + private let thumbView = UIImageView() + private let dimmingView = UIView() + + private var leftThumbConstraint: NSLayoutConstraint? + private var currentThumbConstraint: CGFloat = 0 + + private var generator: AVAssetImageGenerator? + + public weak var delegate: ThumbSelectorViewDelegate? + + // MARK: - View & constraints configurations + + override func setupSubviews() { + super.setupSubviews() + setupDimmingView() + setupThumbView() + } + + private func setupDimmingView() { + + dimmingView.translatesAutoresizingMaskIntoConstraints = false + dimmingView.isUserInteractionEnabled = false + dimmingView.backgroundColor = UIColor.white.withAlphaComponent(0.7) + addSubview(dimmingView) + dimmingView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + dimmingView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true + dimmingView.topAnchor.constraint(equalTo: topAnchor).isActive = true + dimmingView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } + + private func setupThumbView() { + + thumbView.translatesAutoresizingMaskIntoConstraints = false + thumbView.layer.borderWidth = 2.0 + thumbView.layer.borderColor = thumbBorderColor.cgColor + thumbView.isUserInteractionEnabled = true + thumbView.contentMode = .scaleAspectFill + thumbView.clipsToBounds = true + addSubview(thumbView) + + leftThumbConstraint = thumbView.leftAnchor.constraint(equalTo: leftAnchor) + leftThumbConstraint?.isActive = true + thumbView.widthAnchor.constraint(equalTo: thumbView.heightAnchor).isActive = true + thumbView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true + thumbView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + + let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ThumbSelectorView.handlePanGesture(_:))) + thumbView.addGestureRecognizer(panGestureRecognizer) + } + + // MARK: - Gesture handling + + @objc func handlePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) { + guard let superView = gestureRecognizer.view?.superview else { return } + + switch gestureRecognizer.state { + + case .began: + currentThumbConstraint = leftThumbConstraint!.constant + updateSelectedTime() + case .changed: + + let translation = gestureRecognizer.translation(in: superView) + updateThumbConstraint(with: translation) + layoutIfNeeded() + updateSelectedTime() + + case .cancelled, .ended, .failed: + updateSelectedTime() + default: break + } + } + + private func updateThumbConstraint(with translation: CGPoint) { + let maxConstraint = frame.width - thumbView.frame.width + let newConstraint = min(max(0, currentThumbConstraint + translation.x), maxConstraint) + leftThumbConstraint?.constant = newConstraint + } + + // MARK: - Thumbnail Generation + + override func assetDidChange(newAsset: AVAsset?) { + if let asset = newAsset { + setupThumbnailGenerator(with: asset) + leftThumbConstraint?.constant = 0 + updateSelectedTime() + } + super.assetDidChange(newAsset: newAsset) + } + + private func setupThumbnailGenerator(with asset: AVAsset) { + generator = AVAssetImageGenerator(asset: asset) + generator?.appliesPreferredTrackTransform = true + generator?.requestedTimeToleranceAfter = CMTime.zero + generator?.requestedTimeToleranceBefore = CMTime.zero + generator?.maximumSize = getThumbnailFrameSize(from: asset) ?? CGSize.zero + } + + private func getThumbnailFrameSize(from asset: AVAsset) -> CGSize? { + guard let track = asset.tracks(withMediaType: AVMediaType.video).first else { return nil} + + let assetSize = track.naturalSize.applying(track.preferredTransform) + + let maxDimension = max(assetSize.width, assetSize.height) + let minDimension = min(assetSize.width, assetSize.height) + let ratio = maxDimension / minDimension + let side = thumbView.frame.height * ratio * UIScreen.main.scale + return CGSize(width: side, height: side) + } + + private func generateThumbnailImage(for time: CMTime) { + + generator?.generateCGImagesAsynchronously(forTimes: [time as NSValue], + completionHandler: { (_, image, _, _, _) in + guard let image = image else { + return + } + DispatchQueue.main.async { + self.generator?.cancelAllCGImageGeneration() + let uiimage = UIImage(cgImage: image) + self.thumbView.image = uiimage + } + }) + } + + // MARK: - Time & Position Equivalence + + override var durationSize: CGFloat { + return assetPreview.contentSize.width - thumbView.frame.width + } + + /// The currently selected time of the asset. + public var selectedTime: CMTime? { + let thumbPosition = thumbView.center.x + assetPreview.contentOffset.x - (thumbView.frame.width / 2) + return getTime(from: thumbPosition) + } + + private func updateSelectedTime() { + if let selectedTime = selectedTime { + delegate?.didChangeThumbPosition(selectedTime) + generateThumbnailImage(for: selectedTime) + } + } + + // MARK: - UIScrollViewDelegate + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + updateSelectedTime() + } +} diff --git a/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Trimmer/HandlerView.swift b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Trimmer/HandlerView.swift new file mode 100644 index 0000000..ce3b70d --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Trimmer/HandlerView.swift @@ -0,0 +1,22 @@ +// +// HandlerView.swift +// PryntTrimmerView +// +// Created by HHK on 27/03/2017. +// Copyright © 2017 Prynt. All rights reserved. +// + +import Foundation +import UIKit + +class HandlerView: UIView { + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let hitFrame = bounds.insetBy(dx: -20, dy: -20) + return hitFrame.contains(point) ? self : nil + } + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + let hitFrame = bounds.insetBy(dx: -20, dy: -20) + return hitFrame.contains(point) + } +} diff --git a/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Trimmer/PryntTrimmerView.swift b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Trimmer/PryntTrimmerView.swift new file mode 100644 index 0000000..fc26a1a --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/Trimmer/PryntTrimmerView.swift @@ -0,0 +1,345 @@ +// +// PryntTrimmerView.swift +// PryntTrimmerView +// +// Created by HHK on 27/03/2017. +// Copyright © 2017 Prynt. All rights reserved. +// + +import AVFoundation +import UIKit + +public protocol TrimmerViewDelegate: class { + func didChangePositionBar(_ playerTime: CMTime) + func positionBarStoppedMoving(_ playerTime: CMTime) +} + +/// A view to select a specific time range of a video. It consists of an asset preview with thumbnails inside a scroll view, two +/// handles on the side to select the beginning and the end of the range, and a position bar to synchronize the control with a +/// video preview, typically with an `AVPlayer`. +/// Load the video by setting the `asset` property. Access the `startTime` and `endTime` of the view to get the selected time +// range +@IBDesignable public class TrimmerView: AVAssetTimeSelector { + + // MARK: - Properties + + // MARK: Color Customization + + /// The color of the main border of the view + @IBInspectable public var mainColor: UIColor = UIColor.orange { + didSet { + updateMainColor() + } + } + + /// The color of the handles on the side of the view + @IBInspectable public var handleColor: UIColor = UIColor.gray { + didSet { + updateHandleColor() + } + } + + /// The color of the position indicator + @IBInspectable public var positionBarColor: UIColor = UIColor.white { + didSet { + positionBar.backgroundColor = positionBarColor + } + } + + // MARK: Interface + + public weak var delegate: TrimmerViewDelegate? + + // MARK: Subviews + + private let trimView = UIView() + private let leftHandleView = HandlerView() + private let rightHandleView = HandlerView() + private let positionBar = UIView() + private let leftHandleKnob = UIView() + private let rightHandleKnob = UIView() + private let leftMaskView = UIView() + private let rightMaskView = UIView() + + // MARK: Constraints + + private var currentLeftConstraint: CGFloat = 0 + private var currentRightConstraint: CGFloat = 0 + private var leftConstraint: NSLayoutConstraint? + private var rightConstraint: NSLayoutConstraint? + private var positionConstraint: NSLayoutConstraint? + + private let handleWidth: CGFloat = 15 + + /// The maximum duration allowed for the trimming. Change it before setting the asset, as the asset preview + public var maxDuration: Double = 15 { + didSet { + assetPreview.maxDuration = maxDuration + } + } + + /// The minimum duration allowed for the trimming. The handles won't pan further if the minimum duration is attained. + public var minDuration: Double = 3 + + // MARK: - View & constraints configurations + + override func setupSubviews() { + + super.setupSubviews() + backgroundColor = UIColor.clear + layer.zPosition = 1 + setupTrimmerView() + setupHandleView() + setupMaskView() + setupPositionBar() + setupGestures() + updateMainColor() + updateHandleColor() + } + + override func constrainAssetPreview() { + assetPreview.leftAnchor.constraint(equalTo: leftAnchor, constant: handleWidth).isActive = true + assetPreview.rightAnchor.constraint(equalTo: rightAnchor, constant: -handleWidth).isActive = true + assetPreview.topAnchor.constraint(equalTo: topAnchor).isActive = true + assetPreview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } + + private func setupTrimmerView() { + + trimView.layer.borderWidth = 2.0 + trimView.layer.cornerRadius = 2.0 + trimView.translatesAutoresizingMaskIntoConstraints = false + trimView.isUserInteractionEnabled = false + addSubview(trimView) + + trimView.topAnchor.constraint(equalTo: topAnchor).isActive = true + trimView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + leftConstraint = trimView.leftAnchor.constraint(equalTo: leftAnchor) + rightConstraint = trimView.rightAnchor.constraint(equalTo: rightAnchor) + leftConstraint?.isActive = true + rightConstraint?.isActive = true + } + + private func setupHandleView() { + + leftHandleView.isUserInteractionEnabled = true + leftHandleView.layer.cornerRadius = 2.0 + leftHandleView.translatesAutoresizingMaskIntoConstraints = false + addSubview(leftHandleView) + + leftHandleView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true + leftHandleView.widthAnchor.constraint(equalToConstant: handleWidth).isActive = true + leftHandleView.leftAnchor.constraint(equalTo: trimView.leftAnchor).isActive = true + leftHandleView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + + leftHandleKnob.translatesAutoresizingMaskIntoConstraints = false + leftHandleView.addSubview(leftHandleKnob) + + leftHandleKnob.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5).isActive = true + leftHandleKnob.widthAnchor.constraint(equalToConstant: 2).isActive = true + leftHandleKnob.centerYAnchor.constraint(equalTo: leftHandleView.centerYAnchor).isActive = true + leftHandleKnob.centerXAnchor.constraint(equalTo: leftHandleView.centerXAnchor).isActive = true + + rightHandleView.isUserInteractionEnabled = true + rightHandleView.layer.cornerRadius = 2.0 + rightHandleView.translatesAutoresizingMaskIntoConstraints = false + addSubview(rightHandleView) + + rightHandleView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true + rightHandleView.widthAnchor.constraint(equalToConstant: handleWidth).isActive = true + rightHandleView.rightAnchor.constraint(equalTo: trimView.rightAnchor).isActive = true + rightHandleView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + + rightHandleKnob.translatesAutoresizingMaskIntoConstraints = false + rightHandleView.addSubview(rightHandleKnob) + + rightHandleKnob.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5).isActive = true + rightHandleKnob.widthAnchor.constraint(equalToConstant: 2).isActive = true + rightHandleKnob.centerYAnchor.constraint(equalTo: rightHandleView.centerYAnchor).isActive = true + rightHandleKnob.centerXAnchor.constraint(equalTo: rightHandleView.centerXAnchor).isActive = true + } + + private func setupMaskView() { + + leftMaskView.isUserInteractionEnabled = false + leftMaskView.backgroundColor = .white + leftMaskView.alpha = 0.7 + leftMaskView.translatesAutoresizingMaskIntoConstraints = false + insertSubview(leftMaskView, belowSubview: leftHandleView) + + leftMaskView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + leftMaskView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + leftMaskView.topAnchor.constraint(equalTo: topAnchor).isActive = true + leftMaskView.rightAnchor.constraint(equalTo: leftHandleView.centerXAnchor).isActive = true + + rightMaskView.isUserInteractionEnabled = false + rightMaskView.backgroundColor = .white + rightMaskView.alpha = 0.7 + rightMaskView.translatesAutoresizingMaskIntoConstraints = false + insertSubview(rightMaskView, belowSubview: rightHandleView) + + rightMaskView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true + rightMaskView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + rightMaskView.topAnchor.constraint(equalTo: topAnchor).isActive = true + rightMaskView.leftAnchor.constraint(equalTo: rightHandleView.centerXAnchor).isActive = true + } + + private func setupPositionBar() { + + positionBar.frame = CGRect(x: 0, y: 0, width: 3, height: frame.height) + positionBar.backgroundColor = positionBarColor + positionBar.center = CGPoint(x: leftHandleView.frame.maxX, y: center.y) + positionBar.layer.cornerRadius = 1 + positionBar.translatesAutoresizingMaskIntoConstraints = false + positionBar.isUserInteractionEnabled = false + addSubview(positionBar) + + positionBar.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + positionBar.widthAnchor.constraint(equalToConstant: 3).isActive = true + positionBar.heightAnchor.constraint(equalTo: heightAnchor).isActive = true + positionConstraint = positionBar.leftAnchor.constraint(equalTo: leftHandleView.rightAnchor, constant: 0) + positionConstraint?.isActive = true + } + + private func setupGestures() { + + let leftPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(TrimmerView.handlePanGesture)) + leftHandleView.addGestureRecognizer(leftPanGestureRecognizer) + let rightPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(TrimmerView.handlePanGesture)) + rightHandleView.addGestureRecognizer(rightPanGestureRecognizer) + } + + private func updateMainColor() { + trimView.layer.borderColor = mainColor.cgColor + leftHandleView.backgroundColor = mainColor + rightHandleView.backgroundColor = mainColor + } + + private func updateHandleColor() { + leftHandleKnob.backgroundColor = handleColor + rightHandleKnob.backgroundColor = handleColor + } + + // MARK: - Trim Gestures + + @objc func handlePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) { + guard let view = gestureRecognizer.view, let superView = gestureRecognizer.view?.superview else { return } + let isLeftGesture = view == leftHandleView + switch gestureRecognizer.state { + + case .began: + if isLeftGesture { + currentLeftConstraint = leftConstraint!.constant + } else { + currentRightConstraint = rightConstraint!.constant + } + updateSelectedTime(stoppedMoving: false) + case .changed: + let translation = gestureRecognizer.translation(in: superView) + if isLeftGesture { + updateLeftConstraint(with: translation) + } else { + updateRightConstraint(with: translation) + } + layoutIfNeeded() + if let startTime = startTime, isLeftGesture { + seek(to: startTime) + } else if let endTime = endTime { + seek(to: endTime) + } + updateSelectedTime(stoppedMoving: false) + + case .cancelled, .ended, .failed: + updateSelectedTime(stoppedMoving: true) + default: break + } + } + + private func updateLeftConstraint(with translation: CGPoint) { + let maxConstraint = max(rightHandleView.frame.origin.x - handleWidth - minimumDistanceBetweenHandle, 0) + let newConstraint = min(max(0, currentLeftConstraint + translation.x), maxConstraint) + leftConstraint?.constant = newConstraint + } + + private func updateRightConstraint(with translation: CGPoint) { + let maxConstraint = min(2 * handleWidth - frame.width + leftHandleView.frame.origin.x + minimumDistanceBetweenHandle, 0) + let newConstraint = max(min(0, currentRightConstraint + translation.x), maxConstraint) + rightConstraint?.constant = newConstraint + } + + // MARK: - Asset loading + + override func assetDidChange(newAsset: AVAsset?) { + super.assetDidChange(newAsset: newAsset) + resetHandleViewPosition() + } + + private func resetHandleViewPosition() { + leftConstraint?.constant = 0 + rightConstraint?.constant = 0 + layoutIfNeeded() + } + + // MARK: - Time Equivalence + + /// Move the position bar to the given time. + public func seek(to time: CMTime) { + if let newPosition = getPosition(from: time) { + + let offsetPosition = newPosition - assetPreview.contentOffset.x - leftHandleView.frame.origin.x + let maxPosition = rightHandleView.frame.origin.x - (leftHandleView.frame.origin.x + handleWidth) + - positionBar.frame.width + let normalizedPosition = min(max(0, offsetPosition), maxPosition) + positionConstraint?.constant = normalizedPosition + layoutIfNeeded() + } + } + + /// The selected start time for the current asset. + public var startTime: CMTime? { + let startPosition = leftHandleView.frame.origin.x + assetPreview.contentOffset.x + return getTime(from: startPosition) + } + + /// The selected end time for the current asset. + public var endTime: CMTime? { + let endPosition = rightHandleView.frame.origin.x + assetPreview.contentOffset.x - handleWidth + return getTime(from: endPosition) + } + + private func updateSelectedTime(stoppedMoving: Bool) { + guard let playerTime = positionBarTime else { + return + } + if stoppedMoving { + delegate?.positionBarStoppedMoving(playerTime) + } else { + delegate?.didChangePositionBar(playerTime) + } + } + + private var positionBarTime: CMTime? { + let barPosition = positionBar.frame.origin.x + assetPreview.contentOffset.x - handleWidth + return getTime(from: barPosition) + } + + private var minimumDistanceBetweenHandle: CGFloat { + guard let asset = asset else { return 0 } + return CGFloat(minDuration) * assetPreview.contentView.frame.width / CGFloat(asset.duration.seconds) + } + + // MARK: - Scroll View Delegate + + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + updateSelectedTime(stoppedMoving: true) + } + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + updateSelectedTime(stoppedMoving: true) + } + } + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + updateSelectedTime(stoppedMoving: false) + } +} diff --git a/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/README.md b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/README.md new file mode 100644 index 0000000..894e662 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/README.md @@ -0,0 +1,88 @@ +# PryntTrimmerView + +[![codebeat badge](https://codebeat.co/badges/ac008534-7f30-4b04-8434-0c6d69251e4b)](https://codebeat.co/projects/github-com-prynt-prynttrimmerview-master) +[![Platform](https://img.shields.io/cocoapods/p/PryntTrimmerView.svg?style=flat)](http://cocoapods.org/pods/PryntTrimmerView) +[![License](https://img.shields.io/cocoapods/l/PryntTrimmerView.svg?style=flat)](http://cocoapods.org/pods/PryntTrimmerView) +[![Version](https://img.shields.io/cocoapods/v/PryntTrimmerView.svg?style=flat)](http://cocoapods.org/pods/PryntTrimmerView) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) + +A set of tools written in swift to crop and trim videos. + + +## Example + +To run the example project, clone the repo, and run `pod install` from the Example directory first. + +### Trimming + +![](https://media.giphy.com/media/GwZGkLiKxZcTm/giphy.gif) + +### Cropping + +![](https://media.giphy.com/media/10FsDfHS7616XC/giphy.gif) + +## Requirements + +PryntTrimmerView requires iOS9: It uses Layout Anchors to define the constraints. + +## Installation + +#### CocoaPods + +PryntTrimmerView is available through [CocoaPods](http://cocoapods.org). To install +it, simply add the following line to your Podfile: + +```ruby +pod "PryntTrimmerView" +``` + +Then, run `pod install` to download the source and add it to your workspace. + +#### Carthage + +PryntTrimmmerView is available through Carthage. To install +it, simply add the following line to your Cartfile: + +``` +github "HHK1/PryntTrimmerView" +``` + +Run `carthage update` to build the framework and drag the built PryntTrimmerView.framework into your Xcode project. + +#### Swift Version + +- Swift 3 compatibility: use version 1.0.1 or below. +- Swift 4 compatibility: use version 2.x.x. +- Swift 4.2 compatibility: use version 3.x.x + +## Usage + +:warning: _This library does not contain an API to crop or trim your video asset. You can find a possible implementation for this in the example pod, but the library only provides the UI._ + +### Trimming + +Create a `TrimmerView` instance (in interface builder or through code), and add it to your view hierarchy. + +``` +trimmerView.asset = asset +trimmerView.delegate = self +``` + +Access the `startTime` and `endTime` property to know where to trim your asset. You can use the `TrimmerViewDelegate` to link the trimmer with an `AVPlayer` and provide the end user with a preview. See the `VideoTrimmerViewController` inside the project to see an example. + +You can also customize the trimmer view by changing its colors: +``` +trimmerView.handleColor = UIColor.white +trimmerView.mainColor = UIColor.orange +trimmerView.positionBarColor = UIColor.white +``` + +### Cropping + +Create an instance of the `VideoCropView` and add it to your view hierarchy, then load your video into the crop view: `videoCropView.asset = asset`. + +You can set the aspect ratio you want using the `setAspectRatio` method. Once you are satisfied with the portion of the asset you want to crop, call `getImageCropFrame` to retrieve the select frame. See the `VideoCropperViewController` in the example app for an actual example of how to crop the video for export. + +## License + +PryntTrimmerView is available under the MIT license. See the LICENSE file for more info. diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-Info.plist b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-Info.plist new file mode 100644 index 0000000..2243fe6 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-acknowledgements.markdown b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-acknowledgements.markdown new file mode 100644 index 0000000..a83ad01 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-acknowledgements.markdown @@ -0,0 +1,28 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## PryntTrimmerView + +MIT License + +Copyright (c) 2017 Prynt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Generated by CocoaPods - https://cocoapods.org diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-acknowledgements.plist b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-acknowledgements.plist new file mode 100644 index 0000000..638a571 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-acknowledgements.plist @@ -0,0 +1,60 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + MIT License + +Copyright (c) 2017 Prynt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + License + MIT + Title + PryntTrimmerView + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-dummy.m b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-dummy.m new file mode 100644 index 0000000..f198dc6 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_SimpleVideoAnimation : NSObject +@end +@implementation PodsDummy_Pods_SimpleVideoAnimation +@end diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Debug-input-files.xcfilelist b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Debug-input-files.xcfilelist new file mode 100644 index 0000000..172c7e3 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Debug-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh +${BUILT_PRODUCTS_DIR}/PryntTrimmerView/PryntTrimmerView.framework \ No newline at end of file diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Debug-output-files.xcfilelist b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Debug-output-files.xcfilelist new file mode 100644 index 0000000..229ad67 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Debug-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PryntTrimmerView.framework \ No newline at end of file diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Release-input-files.xcfilelist b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Release-input-files.xcfilelist new file mode 100644 index 0000000..172c7e3 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Release-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh +${BUILT_PRODUCTS_DIR}/PryntTrimmerView/PryntTrimmerView.framework \ No newline at end of file diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Release-output-files.xcfilelist b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Release-output-files.xcfilelist new file mode 100644 index 0000000..229ad67 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-Release-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PryntTrimmerView.framework \ No newline at end of file diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh new file mode 100755 index 0000000..9b0e9c1 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh @@ -0,0 +1,171 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then + # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy + # frameworks to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + +# Used as a return value for each invocation of `strip_invalid_archs` function. +STRIP_BINARY_RETVAL=0 + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +# Copies and strips a vendored framework +install_framework() +{ + if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then + local source="${BUILT_PRODUCTS_DIR}/$1" + elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then + local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" + elif [ -r "$1" ]; then + local source="$1" + fi + + local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + + if [ -L "${source}" ]; then + echo "Symlinked..." + source="$(readlink "${source}")" + fi + + # Use filter instead of exclude so missing patterns don't throw errors. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" + + local basename + basename="$(basename -s .framework "$1")" + binary="${destination}/${basename}.framework/${basename}" + + if ! [ -r "$binary" ]; then + binary="${destination}/${basename}" + elif [ -L "${binary}" ]; then + echo "Destination binary is symlinked..." + dirname="$(dirname "${binary}")" + binary="${dirname}/$(readlink "${binary}")" + fi + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then + strip_invalid_archs "$binary" + fi + + # Resign the code if required by the build settings to avoid unstable apps + code_sign_if_enabled "${destination}/$(basename "$1")" + + # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. + if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then + local swift_runtime_libs + swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) + for lib in $swift_runtime_libs; do + echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" + rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" + code_sign_if_enabled "${destination}/${lib}" + done + fi +} + +# Copies and strips a vendored dSYM +install_dsym() { + local source="$1" + if [ -r "$source" ]; then + # Copy the dSYM into a the targets temp dir. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" + + local basename + basename="$(basename -s .framework.dSYM "$source")" + binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then + strip_invalid_archs "$binary" + fi + + if [[ $STRIP_BINARY_RETVAL == 1 ]]; then + # Move the stripped file into its final destination. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" + else + # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. + touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" + fi + fi +} + +# Copies the bcsymbolmap files of a vendored framework +install_bcsymbolmap() { + local bcsymbolmap_path="$1" + local destination="${BUILT_PRODUCTS_DIR}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" +} + +# Signs a framework with the provided identity +code_sign_if_enabled() { + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then + # Use the current code_sign_identity + echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" + + if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + code_sign_cmd="$code_sign_cmd &" + fi + echo "$code_sign_cmd" + eval "$code_sign_cmd" + fi +} + +# Strip invalid architectures +strip_invalid_archs() { + binary="$1" + # Get architectures for current target binary + binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" + # Intersect them with the architectures we are building for + intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" + # If there are no archs supported by this binary then warn the user + if [[ -z "$intersected_archs" ]]; then + echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." + STRIP_BINARY_RETVAL=0 + return + fi + stripped="" + for arch in $binary_archs; do + if ! [[ "${ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + fi + STRIP_BINARY_RETVAL=1 +} + + +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/PryntTrimmerView/PryntTrimmerView.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/PryntTrimmerView/PryntTrimmerView.framework" +fi +if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + wait +fi diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-umbrella.h b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-umbrella.h new file mode 100644 index 0000000..0d08a3e --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double Pods_SimpleVideoAnimationVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_SimpleVideoAnimationVersionString[]; + diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.debug.xcconfig b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.debug.xcconfig new file mode 100644 index 0000000..1262a08 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.debug.xcconfig @@ -0,0 +1,12 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PryntTrimmerView" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PryntTrimmerView/PryntTrimmerView.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -framework "PryntTrimmerView" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.modulemap b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.modulemap new file mode 100644 index 0000000..7b09f62 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.modulemap @@ -0,0 +1,6 @@ +framework module Pods_SimpleVideoAnimation { + umbrella header "Pods-SimpleVideoAnimation-umbrella.h" + + export * + module * { export * } +} diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.release.xcconfig b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.release.xcconfig new file mode 100644 index 0000000..1262a08 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.release.xcconfig @@ -0,0 +1,12 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PryntTrimmerView" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PryntTrimmerView/PryntTrimmerView.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -framework "PryntTrimmerView" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-Info.plist b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-Info.plist new file mode 100644 index 0000000..3424ca6 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 4.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-dummy.m b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-dummy.m new file mode 100644 index 0000000..be8848d --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_PryntTrimmerView : NSObject +@end +@implementation PodsDummy_PryntTrimmerView +@end diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-prefix.pch b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-umbrella.h b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-umbrella.h new file mode 100644 index 0000000..eaf5b4c --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double PryntTrimmerViewVersionNumber; +FOUNDATION_EXPORT const unsigned char PryntTrimmerViewVersionString[]; + diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView.modulemap b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView.modulemap new file mode 100644 index 0000000..a166777 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView.modulemap @@ -0,0 +1,6 @@ +framework module PryntTrimmerView { + umbrella header "PryntTrimmerView-umbrella.h" + + export * + module * { export * } +} diff --git a/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView.xcconfig b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView.xcconfig new file mode 100644 index 0000000..318ff12 --- /dev/null +++ b/Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/PryntTrimmerView +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/PryntTrimmerView +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.pbxproj b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ad6c6a4 --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.pbxproj @@ -0,0 +1,477 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 0194809395268398D3F15306 /* Pods_SimpleVideoAnimation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16B763834C8C3CCC5972A2A2 /* Pods_SimpleVideoAnimation.framework */; }; + 6A54C20024C7DE9200EF216E /* cute.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 6A54C1FE24C7DE9200EF216E /* cute.mp4 */; }; + 6A874BA024C53EF700ADFD8A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A874B9F24C53EF700ADFD8A /* AppDelegate.swift */; }; + 6A874BA224C53EF700ADFD8A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A874BA124C53EF700ADFD8A /* SceneDelegate.swift */; }; + 6A874BA424C53EF700ADFD8A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A874BA324C53EF700ADFD8A /* ViewController.swift */; }; + 6A874BA724C53EF700ADFD8A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6A874BA524C53EF700ADFD8A /* Main.storyboard */; }; + 6A874BA924C53EF800ADFD8A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A874BA824C53EF800ADFD8A /* Assets.xcassets */; }; + 6A874BAC24C53EF800ADFD8A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6A874BAA24C53EF800ADFD8A /* LaunchScreen.storyboard */; }; + 6A874BB924C5462C00ADFD8A /* MetalVideoProcess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A874BB824C545CC00ADFD8A /* MetalVideoProcess.framework */; }; + 6A874BBA24C5462C00ADFD8A /* MetalVideoProcess.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6A874BB824C545CC00ADFD8A /* MetalVideoProcess.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 6A874BDA24C6FDE200ADFD8A /* 853.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 6A874BD824C6FDE200ADFD8A /* 853.mp4 */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 6A874BB724C545CC00ADFD8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6A874BB324C545CC00ADFD8A /* MetalVideoProcess.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 6ADB062424AD885F0010A817; + remoteInfo = MetalVideoProcess; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 6A874BBB24C5462C00ADFD8A /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 6A874BBA24C5462C00ADFD8A /* MetalVideoProcess.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 16B763834C8C3CCC5972A2A2 /* Pods_SimpleVideoAnimation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SimpleVideoAnimation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6A54C1FE24C7DE9200EF216E /* cute.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = cute.mp4; sourceTree = ""; }; + 6A874B9C24C53EF700ADFD8A /* SimpleVideoAnimation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleVideoAnimation.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6A874B9F24C53EF700ADFD8A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 6A874BA124C53EF700ADFD8A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 6A874BA324C53EF700ADFD8A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 6A874BA624C53EF700ADFD8A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 6A874BA824C53EF800ADFD8A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6A874BAB24C53EF800ADFD8A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 6A874BAD24C53EF800ADFD8A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6A874BB324C545CC00ADFD8A /* MetalVideoProcess.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MetalVideoProcess.xcodeproj; path = ../../MetalVideoProcess.xcodeproj; sourceTree = ""; }; + 6A874BD824C6FDE200ADFD8A /* 853.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; name = 853.mp4; path = SimpleVIdeoAnimation/853.mp4; sourceTree = SOURCE_ROOT; }; + A512CD4DD2F5AB414CF01FE6 /* Pods-SimpleVideoAnimation.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimpleVideoAnimation.release.xcconfig"; path = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.release.xcconfig"; sourceTree = ""; }; + D107CF0887B69DDEA376CBEA /* Pods-SimpleVideoAnimation.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimpleVideoAnimation.debug.xcconfig"; path = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6A874B9924C53EF700ADFD8A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6A874BB924C5462C00ADFD8A /* MetalVideoProcess.framework in Frameworks */, + 0194809395268398D3F15306 /* Pods_SimpleVideoAnimation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 09662178662A79DB0CFDDB51 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 16B763834C8C3CCC5972A2A2 /* Pods_SimpleVideoAnimation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 6A874B9324C53EF700ADFD8A = { + isa = PBXGroup; + children = ( + 6A874BB324C545CC00ADFD8A /* MetalVideoProcess.xcodeproj */, + 6A874B9E24C53EF700ADFD8A /* SimpleVideoAnimation */, + 6A874B9D24C53EF700ADFD8A /* Products */, + 99120D7228A6AD7ADD24AEFC /* Pods */, + 09662178662A79DB0CFDDB51 /* Frameworks */, + ); + sourceTree = ""; + }; + 6A874B9D24C53EF700ADFD8A /* Products */ = { + isa = PBXGroup; + children = ( + 6A874B9C24C53EF700ADFD8A /* SimpleVideoAnimation.app */, + ); + name = Products; + sourceTree = ""; + }; + 6A874B9E24C53EF700ADFD8A /* SimpleVideoAnimation */ = { + isa = PBXGroup; + children = ( + 6A874BD824C6FDE200ADFD8A /* 853.mp4 */, + 6A54C1FE24C7DE9200EF216E /* cute.mp4 */, + 6A874B9F24C53EF700ADFD8A /* AppDelegate.swift */, + 6A874BA124C53EF700ADFD8A /* SceneDelegate.swift */, + 6A874BA324C53EF700ADFD8A /* ViewController.swift */, + 6A874BA524C53EF700ADFD8A /* Main.storyboard */, + 6A874BA824C53EF800ADFD8A /* Assets.xcassets */, + 6A874BAA24C53EF800ADFD8A /* LaunchScreen.storyboard */, + 6A874BAD24C53EF800ADFD8A /* Info.plist */, + ); + path = SimpleVideoAnimation; + sourceTree = ""; + }; + 6A874BB424C545CC00ADFD8A /* Products */ = { + isa = PBXGroup; + children = ( + 6A874BB824C545CC00ADFD8A /* MetalVideoProcess.framework */, + ); + name = Products; + sourceTree = ""; + }; + 99120D7228A6AD7ADD24AEFC /* Pods */ = { + isa = PBXGroup; + children = ( + D107CF0887B69DDEA376CBEA /* Pods-SimpleVideoAnimation.debug.xcconfig */, + A512CD4DD2F5AB414CF01FE6 /* Pods-SimpleVideoAnimation.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6A874B9B24C53EF700ADFD8A /* SimpleVideoAnimation */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6A874BB024C53EF800ADFD8A /* Build configuration list for PBXNativeTarget "SimpleVideoAnimation" */; + buildPhases = ( + F6586DA6B62A1C6F6FBAC023 /* [CP] Check Pods Manifest.lock */, + 6A874B9824C53EF700ADFD8A /* Sources */, + 6A874B9924C53EF700ADFD8A /* Frameworks */, + 6A874B9A24C53EF700ADFD8A /* Resources */, + D3C3047B9CCE5998654663D7 /* [CP] Embed Pods Frameworks */, + 6A874BBB24C5462C00ADFD8A /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SimpleVideoAnimation; + productName = SimpleVideoAnimation; + productReference = 6A874B9C24C53EF700ADFD8A /* SimpleVideoAnimation.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6A874B9424C53EF700ADFD8A /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1160; + LastUpgradeCheck = 1160; + ORGANIZATIONNAME = "RenZhu Macro"; + TargetAttributes = { + 6A874B9B24C53EF700ADFD8A = { + CreatedOnToolsVersion = 11.6; + }; + }; + }; + buildConfigurationList = 6A874B9724C53EF700ADFD8A /* Build configuration list for PBXProject "SimpleVideoAnimation" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6A874B9324C53EF700ADFD8A; + productRefGroup = 6A874B9D24C53EF700ADFD8A /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 6A874BB424C545CC00ADFD8A /* Products */; + ProjectRef = 6A874BB324C545CC00ADFD8A /* MetalVideoProcess.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 6A874B9B24C53EF700ADFD8A /* SimpleVideoAnimation */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 6A874BB824C545CC00ADFD8A /* MetalVideoProcess.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MetalVideoProcess.framework; + remoteRef = 6A874BB724C545CC00ADFD8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 6A874B9A24C53EF700ADFD8A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6A874BAC24C53EF800ADFD8A /* LaunchScreen.storyboard in Resources */, + 6A874BA924C53EF800ADFD8A /* Assets.xcassets in Resources */, + 6A874BDA24C6FDE200ADFD8A /* 853.mp4 in Resources */, + 6A54C20024C7DE9200EF216E /* cute.mp4 in Resources */, + 6A874BA724C53EF700ADFD8A /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + D3C3047B9CCE5998654663D7 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F6586DA6B62A1C6F6FBAC023 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SimpleVideoAnimation-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6A874B9824C53EF700ADFD8A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6A874BA424C53EF700ADFD8A /* ViewController.swift in Sources */, + 6A874BA024C53EF700ADFD8A /* AppDelegate.swift in Sources */, + 6A874BA224C53EF700ADFD8A /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 6A874BA524C53EF700ADFD8A /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6A874BA624C53EF700ADFD8A /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 6A874BAA24C53EF800ADFD8A /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6A874BAB24C53EF800ADFD8A /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 6A874BAE24C53EF800ADFD8A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 6A874BAF24C53EF800ADFD8A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6A874BB124C53EF800ADFD8A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D107CF0887B69DDEA376CBEA /* Pods-SimpleVideoAnimation.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = RYRPKMVKDL; + INFOPLIST_FILE = SimpleVideoAnimation/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.wangrenzhu.SimpleVideoAnimation; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6A874BB224C53EF800ADFD8A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A512CD4DD2F5AB414CF01FE6 /* Pods-SimpleVideoAnimation.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = RYRPKMVKDL; + INFOPLIST_FILE = SimpleVideoAnimation/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.wangrenzhu.SimpleVideoAnimation; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6A874B9724C53EF700ADFD8A /* Build configuration list for PBXProject "SimpleVideoAnimation" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6A874BAE24C53EF800ADFD8A /* Debug */, + 6A874BAF24C53EF800ADFD8A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6A874BB024C53EF800ADFD8A /* Build configuration list for PBXNativeTarget "SimpleVideoAnimation" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6A874BB124C53EF800ADFD8A /* Debug */, + 6A874BB224C53EF800ADFD8A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6A874B9424C53EF700ADFD8A /* Project object */; +} diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ab4d44a --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/xcshareddata/xcschemes/SimpleVideoAnimation.xcscheme b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/xcshareddata/xcschemes/SimpleVideoAnimation.xcscheme new file mode 100644 index 0000000..50cac4c --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/xcshareddata/xcschemes/SimpleVideoAnimation.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/853.mp4 b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/853.mp4 new file mode 100644 index 0000000..399578c Binary files /dev/null and b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/853.mp4 differ diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/AppDelegate.swift b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/AppDelegate.swift new file mode 100644 index 0000000..38d063b --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// SimpleVideoAnimation +// +// Created by RenZhu Macro on 2020/7/20. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Assets.xcassets/Contents.json b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Base.lproj/LaunchScreen.storyboard b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Base.lproj/Main.storyboard b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Base.lproj/Main.storyboard new file mode 100644 index 0000000..60b3f00 --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Base.lproj/Main.storyboard @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Info.plist b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Info.plist new file mode 100644 index 0000000..2a3483c --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/Info.plist @@ -0,0 +1,64 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/SceneDelegate.swift b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/SceneDelegate.swift new file mode 100644 index 0000000..dec33dc --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/SceneDelegate.swift @@ -0,0 +1,53 @@ +// +// SceneDelegate.swift +// SimpleVideoAnimation +// +// Created by RenZhu Macro on 2020/7/20. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/ViewController.swift b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/ViewController.swift new file mode 100644 index 0000000..e5f6645 --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/ViewController.swift @@ -0,0 +1,130 @@ +// +// ViewController.swift +// SimpleVideoAnimation +// +// Created by RenZhu Macro on 2020/7/20. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit +import AVFoundation +import MetalVideoProcess +import PryntTrimmerView + + +class ViewController: UIViewController { + + @IBOutlet weak var renderView: MetalVideoProcessRenderView! + + @IBOutlet weak var progressView: TrimmerView! + + var player: MetalVideoProcessPlayer? + var editor: MetalVideoEditor? + + override func viewDidLoad() { + super.viewDidLoad() + let asset1 = AVAsset(url: Bundle.main.url(forResource: "853", withExtension: "mp4")!) + + let item1 = MetalVideoEditorItem(asset: asset1) + + do { + let editor = try MetalVideoEditor(videoItems: [item1]) + self.editor = editor + let playerItem = editor.buildPlayerItem() + + let fadeIn = MetalVideoProcessFadeInMotion() + fadeIn.timingType = .quadraticEaseOut + + let fadeOut = MetalVideoProcessFadeOutMotion() + fadeOut.timingType = .quarticEaseOut + + let fadeInTimeRange = CMTimeRangeMake(start: CMTime(seconds: 2.0), duration: CMTime(seconds: 2.0)) + fadeIn.saveUniformSettings(forTimelineRange: fadeInTimeRange, trackID: item1.trackID) + + let fadeOutTimeRange = CMTimeRange(start: item1.timeRange.end - CMTime(seconds: 4.0), duration: CMTime(seconds: 1.5)) + fadeOut.saveUniformSettings(forTimelineRange: fadeOutTimeRange, trackID: item1.trackID) + + let player = try MetalVideoProcessPlayer(playerItem: playerItem) + player.playerDelegate = self + + let background = MetalVideoProcessBackground(trackID: 0) + background.aspectRatioType = .Ratio9_16 + background.canvasSizeType = .Type720p + background.setBackgroundType(type: .Blur) + + let transform = MetalVideoProcessTransformFilter() + transform.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID) + transform.roi = CGRect(x: 0.25, y: 0.25, width: 0.5, height: 0.5) + + player.addTarget(background, atTargetIndex: 0, trackID: item1.trackID, targetTrackId: 0) + + background --> fadeIn //source 0 + player.addTarget(transform, atTargetIndex: nil, trackID: item1.trackID, targetTrackId: 0) //source 1 + + transform --> fadeIn //source 1 + + background --> fadeOut //source 0 + fadeIn --> fadeOut --> renderView //source 1 + + self.player = player + } catch { + + } + + + } + + override func viewDidAppear(_ animated: Bool) { + guard let asset = self.editor?.playerItem?.asset else { + return + } + self.progressView.asset = asset + self.progressView.delegate = self + } + + @IBAction func play(_ sender: Any) { + self.player?.play() + } + + @IBAction func pause(_ sender: Any) { + self.player?.pause() + } + + @IBAction func progressChanged(_ sender: Any) { + } +} + +extension ViewController: TrimmerViewDelegate { + func didChangePositionBar(_ playerTime: CMTime) { + self.player?.seekTo(time: playerTime.seconds) + } + + func positionBarStoppedMoving(_ playerTime: CMTime) { + + } +} + +extension ViewController: MetalVideoProcessPlayerDelegate { + func playbackFrameTimeChanged(frameTime time: CMTime, player: AVPlayer) { + if player.rate == 0.0 { + return + } + DispatchQueue.main.async { [weak self] in + guard let `self` = self else { return } + self.progressView.seek(to: time) + } + + } + + func playEnded(currentPlayer player: AVPlayer) { + + } + + func finishExport(error: NSError?) { + + } + + func exportProgressChanged(_ progress: Float) { + + } +} diff --git a/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/cute.mp4 b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/cute.mp4 new file mode 100644 index 0000000..62b0b47 Binary files /dev/null and b/Examples/SimpleVideoAnimation/SimpleVIdeoAnimation/cute.mp4 differ diff --git a/Examples/SimpleVideoAnimation/SimpleVideoAnimation.xcworkspace/contents.xcworkspacedata b/Examples/SimpleVideoAnimation/SimpleVideoAnimation.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..d6f7b1c --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVideoAnimation.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Examples/SimpleVideoAnimation/SimpleVideoAnimation.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/SimpleVideoAnimation/SimpleVideoAnimation.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Examples/SimpleVideoAnimation/SimpleVideoAnimation.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Examples/SimpleVideoExport/SimpleVideoExport/ViewController.swift b/Examples/SimpleVideoExport/SimpleVideoExport/ViewController.swift index 7665ab3..7b64000 100644 --- a/Examples/SimpleVideoExport/SimpleVideoExport/ViewController.swift +++ b/Examples/SimpleVideoExport/SimpleVideoExport/ViewController.swift @@ -68,7 +68,10 @@ class ViewController: UIViewController { transform1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID) transform2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID) - + + + + let layer1 = MetalVideoProcessBlendFilter() videoBackground --> layer1 diff --git a/Examples/SimpleVideoTransition/SimpleVideoTransition/Base.lproj/Main.storyboard b/Examples/SimpleVideoTransition/SimpleVideoTransition/Base.lproj/Main.storyboard index 3945b01..5eb92f3 100644 --- a/Examples/SimpleVideoTransition/SimpleVideoTransition/Base.lproj/Main.storyboard +++ b/Examples/SimpleVideoTransition/SimpleVideoTransition/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -10,7 +10,7 @@ - + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/853.mp4 b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/853.mp4 new file mode 100644 index 0000000..19fb927 Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/853.mp4 differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/AppDelegate.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/AppDelegate.swift new file mode 100644 index 0000000..45ba351 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// SimpleVideoTransition +// +// Created by RenZhu Macro on 2020/7/2. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Assets.xcassets/Contents.json b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Base.lproj/LaunchScreen.storyboard b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Base.lproj/Main.storyboard b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Base.lproj/Main.storyboard new file mode 100644 index 0000000..46832fe --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Base.lproj/Main.storyboard @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/Contents.json b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/Contents.json new file mode 100755 index 0000000..da4a164 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/Contents.json b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/Contents.json new file mode 100644 index 0000000..6e7fe30 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "baseline_camera_alt_black_24pt_1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "baseline_camera_alt_black_24pt_2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "baseline_camera_alt_black_24pt_3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/baseline_camera_alt_black_24pt_1x.png b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/baseline_camera_alt_black_24pt_1x.png new file mode 100644 index 0000000..cd0c763 Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/baseline_camera_alt_black_24pt_1x.png differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/baseline_camera_alt_black_24pt_2x.png b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/baseline_camera_alt_black_24pt_2x.png new file mode 100644 index 0000000..a9611c3 Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/baseline_camera_alt_black_24pt_2x.png differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/baseline_camera_alt_black_24pt_3x.png b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/baseline_camera_alt_black_24pt_3x.png new file mode 100644 index 0000000..e197199 Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_camera.imageset/baseline_camera_alt_black_24pt_3x.png differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/Contents.json b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/Contents.json new file mode 100644 index 0000000..d656d80 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "baseline_cloud_black_24pt_1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "baseline_cloud_black_24pt_2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "baseline_cloud_black_24pt_3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/baseline_cloud_black_24pt_1x.png b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/baseline_cloud_black_24pt_1x.png new file mode 100644 index 0000000..d477a54 Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/baseline_cloud_black_24pt_1x.png differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/baseline_cloud_black_24pt_2x.png b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/baseline_cloud_black_24pt_2x.png new file mode 100644 index 0000000..a3fe27f Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/baseline_cloud_black_24pt_2x.png differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/baseline_cloud_black_24pt_3x.png b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/baseline_cloud_black_24pt_3x.png new file mode 100644 index 0000000..9ad6306 Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_cloud.imageset/baseline_cloud_black_24pt_3x.png differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/Contents.json b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/Contents.json new file mode 100644 index 0000000..583b599 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "baseline_photo_library_black_24pt_1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "baseline_photo_library_black_24pt_2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "baseline_photo_library_black_24pt_3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/baseline_photo_library_black_24pt_1x.png b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/baseline_photo_library_black_24pt_1x.png new file mode 100644 index 0000000..8881b85 Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/baseline_photo_library_black_24pt_1x.png differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/baseline_photo_library_black_24pt_2x.png b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/baseline_photo_library_black_24pt_2x.png new file mode 100644 index 0000000..497e8b8 Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/baseline_photo_library_black_24pt_2x.png differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/baseline_photo_library_black_24pt_3x.png b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/baseline_photo_library_black_24pt_3x.png new file mode 100644 index 0000000..a300d5d Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Images.xcassets/gallery_picker_library.imageset/baseline_photo_library_black_24pt_3x.png differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Info.plist b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Info.plist new file mode 100644 index 0000000..2a3483c --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Info.plist @@ -0,0 +1,64 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Model/ResourceItem.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Model/ResourceItem.swift new file mode 100644 index 0000000..22f0f13 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Model/ResourceItem.swift @@ -0,0 +1,38 @@ +// +// ResourceItem.swift +// SimpleVideoEditor +// +// Created by RenZhu Macro on 2020/7/7. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import Foundation +import MetalVideoProcess + +class ResourceItem: MetalVideoEditorItem { + + public var rotate = 0.0 + public var scale = Position(1.0, 1.0) + //translation 为归一化 + public var translation = Position(0.0, 0.0) + + var orientation : UIInterfaceOrientation = .portrait + weak var transformFilter: MetalVideoProcessTransformFilter? + weak var currentLayer: MetalVideoProcessBlendFilter? + var roi: CGRect = CGRect.zero + var fillType: MetalVideoProcessTransformFilter.StretchType = .aspectToFill + + var isSelected: Bool = false + + var startTimeText: NSString { + get { + return NSString(format: "%.2f", self.startTime.seconds) + } + } + + var durationText: NSString { + get { + return NSString(format: "%.2f", self.duration.seconds) + } + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ResourceItemEditView.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ResourceItemEditView.swift new file mode 100644 index 0000000..d433737 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ResourceItemEditView.swift @@ -0,0 +1,194 @@ +// +// ResourceEditorViewController.swift +// SimpleVideoEditor +// +// Created by RenZhu Macro on 2020/7/8. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit +import MetalVideoProcess +import AVFoundation + +class ResourceItemEditView: UIViewController { + + deinit { + + } + + public weak var resourceItem: ResourceItem? + + @IBOutlet weak var timeRangeText: UILabel! + @IBOutlet weak var renderView: MetalVideoProcessRenderView! + + @IBOutlet weak var startTimeSlider: UISlider! + + @IBOutlet weak var durationSlider: UISlider! + + @IBOutlet weak var positionSlider: UISlider! + + var player: MetalVideoProcessPlayer? + var editor: MetalVideoEditor? + var assetDuration: CMTime = .zero + + @IBOutlet weak var isMuteSwitch: UISwitch! + var isPipItem: Bool = false + var isImageResource: Bool = false + + public func loadResourceItem(_ item: ResourceItem, isPipItem: Bool = false) { + self.isPipItem = isPipItem + self.resourceItem = item + self.isMuteSwitch.isOn = item.isMute + + if let imageResource = item.resource as? ImageResource { + self.isImageResource = true + //picture + let mtlTexture = imageResource.sourceTexture(at: .zero)! + let texture = Texture(orientation: .landscapeLeft, texture: mtlTexture, timingStyle: .stillImage) + self.renderView.newTextureAvailable(texture, fromSourceIndex: 0, trackID: 0) + + + } else if let asset = (item.resource as? AVAssetTrackResource)?.asset { + assetDuration = asset.duration + self.editor = try? MetalVideoEditor(videoItems: [item]) + + + guard let playerItem = self.editor?.buildPlayerItem() else { + return + } + + self.player = try? MetalVideoProcessPlayer(playerItem: playerItem) + + let orientation = orientationForTrack(asset: asset) + + let transform = MetalVideoProcessTransformFilter() + + switch orientation { + case .portrait: + break + case .landscapeLeft: + transform.rotate = 90.0 + break + case .landscapeRight: + transform.rotate = -90.0 + break + case .portraitUpsideDown: + transform.rotate = 180.0 + break + default: + break + + } + + self.player?.addTarget(transform, atTargetIndex: nil, trackID: item.trackID, targetTrackId: 0) + transform --> self.renderView + } else { + return //unsupported + } + + + + self.startTimeSlider.minimumValue = 0.0 + + //min value of a item is 0.1 seconds + self.durationSlider.minimumValue = 0.1 + if self.isImageResource { + self.durationSlider.maximumValue = 100.0 + assetDuration = CMTime(seconds: 100.0) + } else { + self.durationSlider.maximumValue = Float(assetDuration.seconds) + } + + self.startTimeSlider.maximumValue = self.durationSlider.maximumValue - 0.1 + + self.startTimeSlider.value = Float(item.resource.selectedTimeRange.start.seconds) + self.durationSlider.value = Float(item.resource.selectedTimeRange.duration.seconds) + self.positionSlider.value = Float(item.startTime.seconds) + + if self.isPipItem { + self.positionSlider.isEnabled = true + self.positionSlider.maximumValue = 100.0 + self.positionSlider.value = Float(item.startTime.seconds) + } + + timeRangeText.text = NSString(format: "s:%.2f d:%.2f p:%.2f", + self.startTimeSlider.value, + self.durationSlider.value, + self.positionSlider.value) as String + + self.player?.seekTo(time: 0.0) + } + + override func viewDidDisappear(_ animated: Bool) { + self.player?.suspend() + self.player?.dispose() + self.player = nil + self.editor = nil + } + + @IBAction func positionValueChanged(_ sender: Any) { + self.resourceItem?.startTime = CMTime(seconds: self.positionSlider.value) + + + timeRangeText.text = NSString(format: "s:%.2f d:%.2f p:%.2f", + self.startTimeSlider.value, + self.durationSlider.value, + self.positionSlider.value) as String + } + + @IBAction func startTimeValueChanged(_ sender: Any) { + let totalValue = self.startTimeSlider.value + self.durationSlider.value + if totalValue > Float(self.assetDuration.seconds) { + self.durationSlider.value = Float(self.assetDuration.seconds) - self.startTimeSlider.value + } + + resourceItem?.resource.selectedTimeRange = CMTimeRange(start: CMTime(seconds: self.startTimeSlider.value), + duration: CMTime(seconds: self.durationSlider.value)) + if self.isPipItem { + self.player?.seekTo(time: Float64(resourceItem?.resource.selectedTimeRange.start.seconds ?? 0.0)) + } else { + self.player?.seekTo(time: Float64(self.startTimeSlider.value)) + } + timeRangeText.text = NSString(format: "s:%.2f d:%.2f p:%.2f", + self.startTimeSlider.value, + self.durationSlider.value, + self.positionSlider.value) as String + + } + + @IBAction func DurationValueChanged(_ sender: Any) { + let totalValue = self.startTimeSlider.value + self.durationSlider.value + if totalValue > Float(self.assetDuration.seconds) { + self.startTimeSlider.value = Float(self.assetDuration.seconds) - self.durationSlider.value + } + resourceItem?.resource.selectedTimeRange = CMTimeRange(start: CMTime(seconds: self.startTimeSlider.value), + duration: CMTime(seconds: self.durationSlider.value)) + timeRangeText.text = NSString(format: "s:%.2f d:%.2f p:%.2f", + self.startTimeSlider.value, + self.durationSlider.value, + self.positionSlider.value) as String + if self.isImageResource { + //No need to render the image with frameTime + return + } + self.player?.seekTo(time: Float64(self.startTimeSlider.value + self.durationSlider.value)) + } + + + @IBAction func isMuteOn(_ sender: UISwitch) { + self.resourceItem?.isMute = sender.isOn + } + + @IBAction func play(_ sender: Any) { + guard let playerItem = self.editor?.buildPlayerItem() else { + return + } + self.player?.updatePlayerItem(playerItem: playerItem) + + self.player?.play() + } + + @IBAction func pause(_ sender: Any) { + self.player?.pause() + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ResourceItemEditView.xib b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ResourceItemEditView.xib new file mode 100644 index 0000000..37b5cfb --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ResourceItemEditView.xib @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ResourceItemTableViewCell.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ResourceItemTableViewCell.swift new file mode 100644 index 0000000..9937df1 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ResourceItemTableViewCell.swift @@ -0,0 +1,25 @@ +// +// ResourceItemTableViewCell.swift +// SimpleVideoEditor +// +// Created by RenZhu Macro on 2020/7/8. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit + +class ResourceItemTableViewCell: UITableViewCell { + @IBOutlet weak var trackIdLabel: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/SceneDelegate.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/SceneDelegate.swift new file mode 100644 index 0000000..247ba9b --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/SceneDelegate.swift @@ -0,0 +1,53 @@ +// +// SceneDelegate.swift +// SimpleVideoTransition +// +// Created by RenZhu Macro on 2020/7/2. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/SimpleVideoEditor-Bridging-Header.h b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/SimpleVideoEditor-Bridging-Header.h new file mode 100644 index 0000000..a4438cc --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/SimpleVideoEditor-Bridging-Header.h @@ -0,0 +1,10 @@ +// +// Header.h +// SimpleVideoExport +// +// Created by RenZhu Macro on 2020/7/7. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +#import "ProgressHUD.h" +#import "MBProgressHUD.h" diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/BlurLayer.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/BlurLayer.swift new file mode 100644 index 0000000..7f19f88 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/BlurLayer.swift @@ -0,0 +1,131 @@ +// +// BlurLayer.swift +// DynamicBlurView +// +// Created by Kyohei Ito on 2017/08/14. +// Copyright © 2017年 kyohei_ito. All rights reserved. +// + +import UIKit + +private extension CGRect { + func rectangle(_ s: CGSize) -> CGRect { + let x = origin.x / s.width + let y = origin.y / s.height + let width = size.width / s.width + let height = size.height / s.height + return CGRect(x: x, y: y, width: width, height: height) + } +} + +class BlurLayer: CALayer { + private static let blurRadiusKey = "blurRadius" + private static let blurLayoutKey = "blurLayout" + @NSManaged var blurRadius: CGFloat + @NSManaged private var blurLayout: CGFloat + + private var fromBlurRadius: CGFloat? + var presentationRadius: CGFloat { + if let radius = fromBlurRadius { + if let layer = presentation() { + return layer.blurRadius + } else { + return radius + } + } else { + return blurRadius + } + } + + override class func needsDisplay(forKey key: String) -> Bool { + if key == blurRadiusKey || key == blurLayoutKey { + return true + } + return super.needsDisplay(forKey: key) + } + + open override func action(forKey event: String) -> CAAction? { + if event == BlurLayer.blurRadiusKey { + fromBlurRadius = nil + + if let action = super.action(forKey: "opacity") as? CABasicAnimation { + fromBlurRadius = (presentation() ?? self).blurRadius + + action.keyPath = event + action.fromValue = fromBlurRadius + return action + } + } + + if event == BlurLayer.blurLayoutKey, let action = super.action(forKey: "opacity") as? CABasicAnimation { + action.keyPath = event + action.fromValue = 0 + action.toValue = 1 + return action + } + + return super.action(forKey: event) + } +} + +extension BlurLayer { + func draw(_ image: UIImage, fixes isFixes: Bool, baseLayer: CALayer?) { + contents = image.cgImage + contentsScale = image.scale + + if isFixes, let blurLayer = presentation() { + contentsRect = blurLayer.convert(blurLayer.bounds, to: baseLayer).rectangle(image.size) + } + } + + func refresh() { + fromBlurRadius = nil + } + + func animate() { + UIView.performWithoutAnimation { + blurLayout = 0 + } + blurLayout = 1 + } + + func render(in context: CGContext, for layer: CALayer) { + let layers = hideOverlappingLayers(layer.sublayers) + layer.render(in: context) + layers.forEach { + $0.isHidden = false + } + } + + private func hideOverlappingLayers(_ layers: [CALayer]?) -> [CALayer] { + var hiddenLayers: [CALayer] = [] + guard let layers = layers else { + return hiddenLayers + } + + for layer in layers.reversed() { + if isHang(to: layer) { + return hiddenLayers + hideOverlappingLayers(layer.sublayers) + } + if layer.isHidden == false { + layer.isHidden = true + hiddenLayers.append(layer) + } + if layer == self { + return hiddenLayers + } + } + return hiddenLayers + } + + private func isHang(to target: CALayer) -> Bool { + var layer = superlayer + while layer != nil { + if layer == target { + return true + } + layer = layer?.superlayer + } + return false + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/CGContext+CGImage.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/CGContext+CGImage.swift new file mode 100644 index 0000000..548586a --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/CGContext+CGImage.swift @@ -0,0 +1,31 @@ +// +// CGContext+CGImage.swift +// DynamicBlurView +// +// Created by Kyohei Ito on 2017/08/17. +// Copyright © 2017年 kyohei_ito. All rights reserved. +// + +extension CGContext { + static func imageContext(with quality: CaptureQuality, rect: CGRect, opaque: Bool) -> CGContext? { + UIGraphicsBeginImageContextWithOptions(rect.size, opaque, quality.imageScale) + guard let context = UIGraphicsGetCurrentContext() else { + return nil + } + + context.translateBy(x: -rect.origin.x, y: -rect.origin.y) + context.interpolationQuality = quality.interpolationQuality + + return context + } + + func makeImage(with blendColor: UIColor?, blendMode: CGBlendMode, size: CGSize) -> CGImage? { + if let color = blendColor { + setFillColor(color.cgColor) + setBlendMode(blendMode) + fill(CGRect(origin: .zero, size: size)) + } + + return makeImage() + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/CGImage+Accelerate.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/CGImage+Accelerate.swift new file mode 100644 index 0000000..7126c71 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/CGImage+Accelerate.swift @@ -0,0 +1,65 @@ +// +// CGImage+Accelerate.swift +// DynamicBlurView +// +// Created by Kyohei Ito on 2017/08/17. +// Copyright © 2017年 kyohei_ito. All rights reserved. +// + +import Accelerate + +extension CGImage { + var area: Int { + return width * height + } + + private var size: CGSize { + return CGSize(width: width, height: height) + } + + private var bytes: Int { + return bytesPerRow * height + } + + private func imageBuffer(from data: UnsafeMutableRawPointer!) -> vImage_Buffer { + return vImage_Buffer(data: data, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow) + } + + func blurred(with boxSize: UInt32, iterations: Int, blendColor: UIColor?, blendMode: CGBlendMode) -> CGImage? { + guard let providerData = dataProvider?.data else { + return nil + } + + let inData = malloc(bytes) + var inBuffer = imageBuffer(from: inData) + + let outData = malloc(bytes) + var outBuffer = imageBuffer(from: outData) + + let tempSize = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, nil, 0, 0, boxSize, boxSize, nil, vImage_Flags(kvImageEdgeExtend + kvImageGetTempBufferSize)) + let tempData = malloc(tempSize) + + defer { + free(inData) + free(outData) + free(tempData) + } + + let source = CFDataGetBytePtr(providerData) + memcpy(inBuffer.data, source, bytes) + + for _ in 0.. + +//! Project version number for DynamicBlurView. +FOUNDATION_EXPORT double DynamicBlurViewVersionNumber; + +//! Project version string for DynamicBlurView. +FOUNDATION_EXPORT const unsigned char DynamicBlurViewVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/DynamicBlurView.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/DynamicBlurView.swift new file mode 100644 index 0000000..5b9fbb8 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/DynamicBlurView.swift @@ -0,0 +1,195 @@ +// +// DynamicBlurView.swift +// DynamicBlurView +// +// Created by Kyohei Ito on 2015/04/08. +// Copyright (c) 2015年 kyohei_ito. All rights reserved. +// + +import UIKit + +open class DynamicBlurView: UIView { + open override class var layerClass : AnyClass { + return BlurLayer.self + } + + private var staticImage: UIImage? + private var displayLink: CADisplayLink? + private var blurLayer: BlurLayer { + return layer as! BlurLayer + } + private let mainQueue = DispatchQueue.main + private let globalQueue: DispatchQueue = { + if #available (iOS 8.0, *) { + return .global(qos: .userInteractive) + } else { + return .global(priority: .high) + } + }() + private var renderingTarget: UIView? { + if isDeepRendering { + return window + } else { + return superview + } + } + + /// When true, it captures displays image and blur it asynchronously. Try to set true if needs more performance. + /// Asynchronous drawing is possibly crash when needs to process on main thread that drawing with animation for example. + open var drawsAsynchronously: Bool = false + /// Radius of blur. + open var blurRadius: CGFloat { + set { blurLayer.blurRadius = newValue } + get { return blurLayer.blurRadius } + } + /// Default is none. + open var trackingMode: TrackingMode = .none { + didSet { + if trackingMode != oldValue { + linkForDisplay() + } + } + } + /// Blend color. + open var blendColor: UIColor? + /// Blend mode. + open var blendMode: CGBlendMode = .plusLighter + /// Default is 3. + open var iterations: Int = 3 + /// If the view want to render beyond the layer, should be true. + open var isDeepRendering: Bool = false + /// When none of tracking mode, it can change the radius of blur with the ratio. Should set from 0 to 1. + open var blurRatio: CGFloat = 1 { + didSet { + if let image = staticImage, oldValue != blurRatio { + draw(image, blurRadius: blurRadius, fixes: false, baseLayer: renderingTarget?.layer) + } + } + } + /// Quality of captured image. + open var quality: CaptureQuality = .medium + + public override init(frame: CGRect) { + super.init(frame: frame) + isUserInteractionEnabled = false + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + isUserInteractionEnabled = false + } + + open override func didMoveToWindow() { + super.didMoveToWindow() + + if let view = renderingTarget, window != nil && trackingMode == .none { + staticImage = snapshotImage(for: view.layer, conversion: !isDeepRendering) + } + } + + open override func didMoveToSuperview() { + super.didMoveToSuperview() + + if superview == nil { + displayLink?.invalidate() + displayLink = nil + } else { + linkForDisplay() + } + } + + private func async(on queue: DispatchQueue, actions: @escaping () -> Void) { + if drawsAsynchronously { + queue.async(execute: actions) + } else { + actions() + } + } + + private func sync(on queue: DispatchQueue, actions: () -> Void) { + if drawsAsynchronously { + queue.sync(execute: actions) + } else { + actions() + } + } + + private func draw(_ image: UIImage, blurRadius radius: CGFloat, fixes isFixes: Bool, baseLayer: CALayer?) { + async(on: globalQueue) { [weak self] in + if let me = self, let blurredImage = image.blurred(radius: radius, iterations: me.iterations, ratio: me.blurRatio, blendColor: me.blendColor, blendMode: me.blendMode) { + me.sync(on: me.mainQueue) { + me.blurLayer.draw(blurredImage, fixes: isFixes, baseLayer: baseLayer) + } + } + } + } + + private func blurLayerRect(to layer: CALayer, conversion: Bool) -> CGRect { + if conversion { + let presentationLayer = blurLayer.presentation() ?? blurLayer + return presentationLayer.convert(presentationLayer.bounds, to: layer) + } else { + return layer.bounds + } + } + + private func snapshotImage(for layer: CALayer, conversion: Bool) -> UIImage? { + let rect = blurLayerRect(to: layer, conversion: conversion) + guard let context = CGContext.imageContext(with: quality, rect: rect, opaque: isOpaque) else { + return nil + } + + blurLayer.render(in: context, for: layer) + + defer { + UIGraphicsEndImageContext() + } + + return UIGraphicsGetImageFromCurrentImageContext() + } +} + +extension DynamicBlurView { + open override func display(_ layer: CALayer) { + let blurRadius = blurLayer.presentationRadius + let isFixes = isDeepRendering && staticImage != nil + if let view = renderingTarget, let image = staticImage ?? snapshotImage(for: view.layer, conversion: !isFixes) { + draw(image, blurRadius: blurRadius, fixes: isFixes, baseLayer: view.layer) + } + } +} + +extension DynamicBlurView { + private func linkForDisplay() { + displayLink?.invalidate() + displayLink = UIScreen.main.displayLink(withTarget: self, selector: #selector(DynamicBlurView.displayDidRefresh(_:))) + displayLink?.add(to: .main, forMode: RunLoop.Mode(rawValue: trackingMode.description)) + } + + @objc private func displayDidRefresh(_ displayLink: CADisplayLink) { + display(layer) + } +} + +extension DynamicBlurView { + /// Remove cache of blur image then get it again. + open func refresh() { + blurLayer.refresh() + staticImage = nil + blurRatio = 1 + display(layer) + } + + /// Remove cache of blur image. + open func remove() { + blurLayer.refresh() + staticImage = nil + blurRatio = 1 + layer.contents = nil + } + + /// Should use when needs to change layout with animation when is set none of tracking mode. + public func animate() { + blurLayer.animate() + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/TrackingMode.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/TrackingMode.swift new file mode 100644 index 0000000..b9e29f0 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/TrackingMode.swift @@ -0,0 +1,25 @@ +// +// TrackingMode.swift +// DynamicBlurView +// +// Created by Kyohei Ito on 2017/08/17. +// Copyright © 2017年 kyohei_ito. All rights reserved. +// + +public enum TrackingMode: CustomStringConvertible { + case tracking + case common + case none + + public var description: String { + switch self { + case .tracking: + return RunLoop.Mode.tracking.rawValue + case .common: + return RunLoop.Mode.common.rawValue + case .none: + return "" + } + } +} + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/UIImage+Blur.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/UIImage+Blur.swift new file mode 100644 index 0000000..de1306b --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/DynamicBlurView/UIImage+Blur.swift @@ -0,0 +1,28 @@ +// +// UIImage+Blur.swift +// DynamicBlurView +// +// Created by Kyohei Ito on 2017/08/11. +// Copyright © 2017年 kyohei_ito. All rights reserved. +// + +public extension UIImage { + func blurred(radius: CGFloat, iterations: Int, ratio: CGFloat, blendColor color: UIColor?, blendMode mode: CGBlendMode) -> UIImage? { + guard let cgImage = cgImage else { + return nil + } + + if cgImage.area <= 0 || radius <= 0 { + return self + } + + var boxSize = UInt32(radius * scale * ratio) + if boxSize % 2 == 0 { + boxSize += 1 + } + + return cgImage.blurred(with: boxSize, iterations: iterations, blendColor: color, blendMode: mode).map { + UIImage(cgImage: $0, scale: scale, orientation: imageOrientation) + } + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/InteractiveTransition.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/InteractiveTransition.swift new file mode 100644 index 0000000..0f9f2f4 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/InteractiveTransition.swift @@ -0,0 +1,86 @@ +// +// PopupDialogInteractiveTransition.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +// Handles interactive transition triggered via pan gesture recognizer on dialog +final internal class InteractiveTransition: UIPercentDrivenInteractiveTransition { + + // If the interactive transition was started + var hasStarted = false + + // If the interactive transition + var shouldFinish = false + + // The view controller containing the views + // with attached gesture recognizers + weak var viewController: UIViewController? + + @objc func handlePan(_ sender: UIPanGestureRecognizer) { + + guard let vc = viewController else { return } + + guard let progress = calculateProgress(sender: sender) else { return } + + switch sender.state { + case .began: + hasStarted = true + vc.dismiss(animated: true, completion: nil) + case .changed: + shouldFinish = progress > 0.3 + update(progress) + case .cancelled: + hasStarted = false + cancel() + case .ended: + hasStarted = false + completionSpeed = 0.55 + shouldFinish ? finish() : cancel() + default: + break + } + } +} + +internal extension InteractiveTransition { + + /*! + Translates the pan gesture recognizer position to the progress percentage + - parameter sender: A UIPanGestureRecognizer + - returns: Progress + */ + func calculateProgress(sender: UIPanGestureRecognizer) -> CGFloat? { + guard let vc = viewController else { return nil } + + // http://www.thorntech.com/2016/02/ios-tutorial-close-modal-dragging/ + let translation = sender.translation(in: vc.view) + let verticalMovement = translation.y / vc.view.bounds.height + let downwardMovement = fmaxf(Float(verticalMovement), 0.0) + let downwardMovementPercent = fminf(downwardMovement, 1.0) + let progress = CGFloat(downwardMovementPercent) + + return progress + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialog+Keyboard.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialog+Keyboard.swift new file mode 100644 index 0000000..38ed6df --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialog+Keyboard.swift @@ -0,0 +1,132 @@ +// +// PopupDialog+Keyboard.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +/// This extension is designed to handle dialog positioning +/// if a keyboard is displayed while the popup is on top +internal extension PopupDialog { + + // MARK: - Keyboard & orientation observers + + /*! Add obserservers for UIKeyboard notifications */ + func addObservers() { + NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), + name: UIDevice.orientationDidChangeNotification, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardWillShow), + name: UIResponder.keyboardWillShowNotification, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardWillHide), + name: UIResponder.keyboardWillHideNotification, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardWillChangeFrame), + name: UIResponder.keyboardWillChangeFrameNotification, + object: nil) + } + + /*! Remove observers */ + func removeObservers() { + NotificationCenter.default.removeObserver(self, + name: UIDevice.orientationDidChangeNotification, + object: nil) + + NotificationCenter.default.removeObserver(self, + name: UIResponder.keyboardWillShowNotification, + object: nil) + + NotificationCenter.default.removeObserver(self, + name: UIResponder.keyboardWillHideNotification, + object: nil) + + NotificationCenter.default.removeObserver(self, + name: UIResponder.keyboardWillChangeFrameNotification, + object: nil) + } + + // MARK: - Actions + + /*! + Keyboard will show notification listener + - parameter notification: NSNotification + */ + @objc fileprivate func keyboardWillShow(_ notification: Notification) { + guard isTopAndVisible else { return } + keyboardShown = true + centerPopup() + } + + /*! + Keyboard will hide notification listener + - parameter notification: NSNotification + */ + @objc fileprivate func keyboardWillHide(_ notification: Notification) { + guard isTopAndVisible else { return } + keyboardShown = false + centerPopup() + } + + /*! + Keyboard will change frame notification listener + - parameter notification: NSNotification + */ + @objc fileprivate func keyboardWillChangeFrame(_ notification: Notification) { + guard let keyboardRect = (notification as NSNotification).userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { + return + } + keyboardHeight = keyboardRect.cgRectValue.height + } + + /*! + Listen to orientation changes + - parameter notification: NSNotification + */ + @objc fileprivate func orientationChanged(_ notification: Notification) { + if keyboardShown { centerPopup() } + } + + fileprivate func centerPopup() { + + // Make sure keyboard should reposition on keayboard notifications + guard keyboardShiftsView else { return } + + // Make sure a valid keyboard height is available + guard let keyboardHeight = keyboardHeight else { return } + + // Calculate new center of shadow background + let popupCenter = keyboardShown ? keyboardHeight / -2 : 0 + + // Reposition and animate + popupContainerView.centerYConstraint?.constant = popupCenter + popupContainerView.pv_layoutIfNeededAnimated() + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialog.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialog.swift new file mode 100644 index 0000000..69e7a00 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialog.swift @@ -0,0 +1,342 @@ +// +// PopupDialog.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +/// Creates a Popup dialog similar to UIAlertController +final public class PopupDialog: UIViewController { + + // MARK: Private / Internal + + /// First init flag + fileprivate var initialized = false + + /// StatusBar display related + fileprivate let hideStatusBar: Bool + fileprivate var statusBarShouldBeHidden: Bool = false + + /// Width for iPad displays + fileprivate let preferredWidth: CGFloat + + /// The completion handler + fileprivate var completion: (() -> Void)? + + /// The custom transition presentation manager + fileprivate var presentationManager: PresentationManager! + + /// Interactor class for pan gesture dismissal + fileprivate lazy var interactor = InteractiveTransition() + + /// Returns the controllers view + internal var popupContainerView: PopupDialogContainerView { + return view as! PopupDialogContainerView // swiftlint:disable:this force_cast + } + + /// The set of buttons + fileprivate var buttons = [PopupDialogButton]() + + /// Whether keyboard has shifted view + internal var keyboardShown = false + + /// Keyboard height + internal var keyboardHeight: CGFloat? + + // MARK: Public + + /// The content view of the popup dialog + public var viewController: UIViewController + + /// Whether or not to shift view for keyboard display + public var keyboardShiftsView = true + + // MARK: - Initializers + + /*! + Creates a standard popup dialog with title, message and image field + + - parameter title: The dialog title + - parameter message: The dialog message + - parameter image: The dialog image + - parameter buttonAlignment: The dialog button alignment + - parameter transitionStyle: The dialog transition style + - parameter preferredWidth: The preferred width for iPad screens + - parameter tapGestureDismissal: Indicates if dialog can be dismissed via tap gesture + - parameter panGestureDismissal: Indicates if dialog can be dismissed via pan gesture + - parameter hideStatusBar: Whether to hide the status bar on PopupDialog presentation + - parameter completion: Completion block invoked when dialog was dismissed + + - returns: Popup dialog default style + */ + @objc public convenience init( + title: String?, + message: String?, + image: UIImage? = nil, + buttonAlignment: NSLayoutConstraint.Axis = .vertical, + transitionStyle: PopupDialogTransitionStyle = .bounceUp, + preferredWidth: CGFloat = 340, + tapGestureDismissal: Bool = true, + panGestureDismissal: Bool = true, + hideStatusBar: Bool = false, + completion: (() -> Void)? = nil) { + + // Create and configure the standard popup dialog view + let viewController = PopupDialogDefaultViewController() + viewController.titleText = title + viewController.messageText = message + viewController.image = image + + // Call designated initializer + self.init(viewController: viewController, + buttonAlignment: buttonAlignment, + transitionStyle: transitionStyle, + preferredWidth: preferredWidth, + tapGestureDismissal: tapGestureDismissal, + panGestureDismissal: panGestureDismissal, + hideStatusBar: hideStatusBar, + completion: completion) + } + + /*! + Creates a popup dialog containing a custom view + + - parameter viewController: A custom view controller to be displayed + - parameter buttonAlignment: The dialog button alignment + - parameter transitionStyle: The dialog transition style + - parameter preferredWidth: The preferred width for iPad screens + - parameter tapGestureDismissal: Indicates if dialog can be dismissed via tap gesture + - parameter panGestureDismissal: Indicates if dialog can be dismissed via pan gesture + - parameter hideStatusBar: Whether to hide the status bar on PopupDialog presentation + - parameter completion: Completion block invoked when dialog was dismissed + + - returns: Popup dialog with a custom view controller + */ + @objc public init( + viewController: UIViewController, + buttonAlignment: NSLayoutConstraint.Axis = .vertical, + transitionStyle: PopupDialogTransitionStyle = .bounceUp, + preferredWidth: CGFloat = 340, + tapGestureDismissal: Bool = true, + panGestureDismissal: Bool = true, + hideStatusBar: Bool = false, + completion: (() -> Void)? = nil) { + + self.viewController = viewController + self.preferredWidth = preferredWidth + self.hideStatusBar = hideStatusBar + self.completion = completion + super.init(nibName: nil, bundle: nil) + + // Init the presentation manager + presentationManager = PresentationManager(transitionStyle: transitionStyle, interactor: interactor) + + // Assign the interactor view controller + interactor.viewController = self + + // Define presentation styles + transitioningDelegate = presentationManager + modalPresentationStyle = .custom + + // StatusBar setup + modalPresentationCapturesStatusBarAppearance = true + + // Add our custom view to the container + addChild(viewController) + popupContainerView.stackView.insertArrangedSubview(viewController.view, at: 0) + popupContainerView.buttonStackView.axis = buttonAlignment + viewController.didMove(toParent: self) + + // Allow for dialog dismissal on background tap + if tapGestureDismissal { + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) + tapRecognizer.cancelsTouchesInView = false + popupContainerView.addGestureRecognizer(tapRecognizer) + } + // Allow for dialog dismissal on dialog pan gesture + if panGestureDismissal { + let panRecognizer = UIPanGestureRecognizer(target: interactor, action: #selector(InteractiveTransition.handlePan)) + panRecognizer.cancelsTouchesInView = false + popupContainerView.stackView.addGestureRecognizer(panRecognizer) + } + } + + // Init with coder not implemented + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View life cycle + + /// Replaces controller view with popup view + public override func loadView() { + view = PopupDialogContainerView(frame: UIScreen.main.bounds, preferredWidth: preferredWidth) + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + addObservers() + + guard !initialized else { return } + appendButtons() + initialized = true + } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + statusBarShouldBeHidden = hideStatusBar + UIView.animate(withDuration: 0.15) { + self.setNeedsStatusBarAppearanceUpdate() + } + } + + public override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + removeObservers() + } + + deinit { + completion?() + completion = nil + } + + // MARK: - Dismissal related + + @objc fileprivate func handleTap(_ sender: UITapGestureRecognizer) { + + // Make sure it's not a tap on the dialog but the background + let point = sender.location(in: popupContainerView.stackView) + guard !popupContainerView.stackView.point(inside: point, with: nil) else { return } + dismiss() + } + + /*! + Dismisses the popup dialog + */ + @objc public func dismiss(_ completion: (() -> Void)? = nil) { + self.dismiss(animated: true) { + completion?() + } + } + + // MARK: - Button related + + /*! + Appends the buttons added to the popup dialog + to the placeholder stack view + */ + fileprivate func appendButtons() { + + // Add action to buttons + let stackView = popupContainerView.stackView + let buttonStackView = popupContainerView.buttonStackView + if buttons.isEmpty { + stackView.removeArrangedSubview(popupContainerView.buttonStackView) + } + + for (index, button) in buttons.enumerated() { + button.needsLeftSeparator = buttonStackView.axis == .horizontal && index > 0 + buttonStackView.addArrangedSubview(button) + button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside) + } + } + + /*! + Adds a single PopupDialogButton to the Popup dialog + - parameter button: A PopupDialogButton instance + */ + @objc public func addButton(_ button: PopupDialogButton) { + buttons.append(button) + } + + /*! + Adds an array of PopupDialogButtons to the Popup dialog + - parameter buttons: A list of PopupDialogButton instances + */ + @objc public func addButtons(_ buttons: [PopupDialogButton]) { + self.buttons += buttons + } + + /// Calls the action closure of the button instance tapped + @objc fileprivate func buttonTapped(_ button: PopupDialogButton) { + if button.dismissOnTap { + dismiss({ button.buttonAction?() }) + } else { + button.buttonAction?() + } + } + + /*! + Simulates a button tap for the given index + Makes testing a breeze + - parameter index: The index of the button to tap + */ + public func tapButtonWithIndex(_ index: Int) { + let button = buttons[index] + button.buttonAction?() + } + + // MARK: - StatusBar display related + + public override var prefersStatusBarHidden: Bool { + return statusBarShouldBeHidden + } + + public override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { + return .slide + } +} + +// MARK: - View proxy values + +extension PopupDialog { + + /// The button alignment of the alert dialog + @objc public var buttonAlignment: NSLayoutConstraint.Axis { + get { + return popupContainerView.buttonStackView.axis + } + set { + popupContainerView.buttonStackView .axis = newValue + popupContainerView.pv_layoutIfNeededAnimated() + } + } + + /// The transition style + @objc public var transitionStyle: PopupDialogTransitionStyle { + get { return presentationManager.transitionStyle } + set { presentationManager.transitionStyle = newValue } + } +} + +// MARK: - Shake + +extension PopupDialog { + + /// Performs a shake animation on the dialog + @objc public func shake() { + popupContainerView.pv_shake() + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogButton.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogButton.swift new file mode 100644 index 0000000..774213b --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogButton.swift @@ -0,0 +1,166 @@ +// +// PopupDialogButton.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +/// Represents the default button for the popup dialog +open class PopupDialogButton: UIButton { + + public typealias PopupDialogButtonAction = () -> Void + + // MARK: Public + + /// The font and size of the button title + @objc open dynamic var titleFont: UIFont? { + get { return titleLabel?.font } + set { titleLabel?.font = newValue } + } + + /// The height of the button + @objc open dynamic var buttonHeight: Int + + /// The title color of the button + @objc open dynamic var titleColor: UIColor? { + get { return self.titleColor(for: UIControl.State()) } + set { setTitleColor(newValue, for: UIControl.State()) } + } + + /// The background color of the button + @objc open dynamic var buttonColor: UIColor? { + get { return backgroundColor } + set { backgroundColor = newValue } + } + + /// The separator color of this button + @objc open dynamic var separatorColor: UIColor? { + get { return separator.backgroundColor } + set { + separator.backgroundColor = newValue + leftSeparator.backgroundColor = newValue + } + } + + /// Default appearance of the button + open var defaultTitleFont = UIFont.systemFont(ofSize: 14) + open var defaultTitleColor = UIColor(red: 0.25, green: 0.53, blue: 0.91, alpha: 1) + open var defaultButtonColor = UIColor.clear + open var defaultSeparatorColor = UIColor(white: 0.9, alpha: 1) + + /// Whether button should dismiss popup when tapped + @objc open var dismissOnTap = true + + /// The action called when the button is tapped + open fileprivate(set) var buttonAction: PopupDialogButtonAction? + + // MARK: Private + + fileprivate lazy var separator: UIView = { + let line = UIView(frame: .zero) + line.translatesAutoresizingMaskIntoConstraints = false + return line + }() + + fileprivate lazy var leftSeparator: UIView = { + let line = UIView(frame: .zero) + line.translatesAutoresizingMaskIntoConstraints = false + line.alpha = 0 + return line + }() + + // MARK: Internal + + internal var needsLeftSeparator: Bool = false { + didSet { + leftSeparator.alpha = needsLeftSeparator ? 1.0 : 0.0 + } + } + + // MARK: Initializers + + /*! + Creates a button that can be added to the popup dialog + + - parameter title: The button title + - parameter dismisssOnTap: Whether a tap automatically dismisses the dialog + - parameter action: The action closure + + - returns: PopupDialogButton + */ + @objc public init(title: String, height: Int = 45, dismissOnTap: Bool = true, action: PopupDialogButtonAction?) { + + // Assign the button height + buttonHeight = height + + // Assign the button action + buttonAction = action + + super.init(frame: .zero) + + // Set the button title + setTitle(title, for: UIControl.State()) + + self.dismissOnTap = dismissOnTap + + // Setup the views + setupView() + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: View setup + + open func setupView() { + + // Default appearance + setTitleColor(defaultTitleColor, for: UIControl.State()) + titleLabel?.font = defaultTitleFont + backgroundColor = defaultButtonColor + separator.backgroundColor = defaultSeparatorColor + leftSeparator.backgroundColor = defaultSeparatorColor + + // Add and layout views + addSubview(separator) + addSubview(leftSeparator) + + let views = ["separator": separator, "leftSeparator": leftSeparator, "button": self] + let metrics = ["buttonHeight": buttonHeight] + var constraints = [NSLayoutConstraint]() + constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:[button(buttonHeight)]", options: [], metrics: metrics, views: views) + constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[separator]|", options: [], metrics: nil, views: views) + constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[separator(1)]", options: [], metrics: nil, views: views) + constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[leftSeparator(1)]", options: [], metrics: nil, views: views) + constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[leftSeparator]|", options: [], metrics: nil, views: views) + NSLayoutConstraint.activate(constraints) + } + + open override var isHighlighted: Bool { + didSet { + isHighlighted ? pv_fade(.out, 0.5) : pv_fade(.in, 1.0) + } + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogContainerView.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogContainerView.swift new file mode 100644 index 0000000..4f36e6c --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogContainerView.swift @@ -0,0 +1,197 @@ +// +// PopupDialogContainerView.swift +// Pods +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +/// The main view of the popup dialog +final public class PopupDialogContainerView: UIView { + + // MARK: - Appearance + + /// The background color of the popup dialog + override public dynamic var backgroundColor: UIColor? { + get { return container.backgroundColor } + set { container.backgroundColor = newValue } + } + + /// The corner radius of the popup view + @objc public dynamic var cornerRadius: Float { + get { return Float(shadowContainer.layer.cornerRadius) } + set { + let radius = CGFloat(newValue) + shadowContainer.layer.cornerRadius = radius + container.layer.cornerRadius = radius + } + } + + // MARK: Shadow related + + /// Enable / disable shadow rendering of the container + @objc public dynamic var shadowEnabled: Bool { + get { return shadowContainer.layer.shadowRadius > 0 } + set { shadowContainer.layer.shadowRadius = newValue ? shadowRadius : 0 } + } + + /// Color of the container shadow + @objc public dynamic var shadowColor: UIColor? { + get { + guard let color = shadowContainer.layer.shadowColor else { + return nil + } + return UIColor(cgColor: color) + } + set { shadowContainer.layer.shadowColor = newValue?.cgColor } + } + + /// Radius of the container shadow + @objc public dynamic var shadowRadius: CGFloat { + get { return shadowContainer.layer.shadowRadius } + set { shadowContainer.layer.shadowRadius = newValue } + } + + /// Opacity of the the container shadow + @objc public dynamic var shadowOpacity: Float { + get { return shadowContainer.layer.shadowOpacity } + set { shadowContainer.layer.shadowOpacity = newValue } + } + + /// Offset of the the container shadow + @objc public dynamic var shadowOffset: CGSize { + get { return shadowContainer.layer.shadowOffset } + set { shadowContainer.layer.shadowOffset = newValue } + } + + /// Path of the the container shadow + @objc public dynamic var shadowPath: CGPath? { + get { return shadowContainer.layer.shadowPath} + set { shadowContainer.layer.shadowPath = newValue } + } + + // MARK: - Views + + /// The shadow container is the basic view of the PopupDialog + /// As it does not clip subviews, a shadow can be applied to it + internal lazy var shadowContainer: UIView = { + let shadowContainer = UIView(frame: .zero) + shadowContainer.translatesAutoresizingMaskIntoConstraints = false + shadowContainer.backgroundColor = UIColor.clear + shadowContainer.layer.shadowColor = UIColor.black.cgColor + shadowContainer.layer.shadowRadius = 5 + shadowContainer.layer.shadowOpacity = 0.4 + shadowContainer.layer.shadowOffset = CGSize(width: 0, height: 0) + shadowContainer.layer.cornerRadius = 4 + return shadowContainer + }() + + /// The container view is a child of shadowContainer and contains + /// all other views. It clips to bounds so cornerRadius can be set + internal lazy var container: UIView = { + let container = UIView(frame: .zero) + container.translatesAutoresizingMaskIntoConstraints = false + container.backgroundColor = UIColor.white + container.clipsToBounds = true + container.layer.cornerRadius = 4 + return container + }() + + // The container stack view for buttons + internal lazy var buttonStackView: UIStackView = { + let buttonStackView = UIStackView() + buttonStackView.translatesAutoresizingMaskIntoConstraints = false + buttonStackView.distribution = .fillEqually + buttonStackView.spacing = 0 + return buttonStackView + }() + + // The main stack view, containing all relevant views + internal lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [self.buttonStackView]) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.spacing = 0 + return stackView + }() + + // The preferred width for iPads + fileprivate let preferredWidth: CGFloat + + // MARK: - Constraints + + /// The center constraint of the shadow container + internal var centerYConstraint: NSLayoutConstraint? + + // MARK: - Initializers + + internal init(frame: CGRect, preferredWidth: CGFloat) { + self.preferredWidth = preferredWidth + super.init(frame: frame) + setupViews() + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View setup + + internal func setupViews() { + + // Add views + addSubview(shadowContainer) + shadowContainer.addSubview(container) + container.addSubview(stackView) + + // Layout views + let views = ["shadowContainer": shadowContainer, "container": container, "stackView": stackView] + var constraints = [NSLayoutConstraint]() + + // Shadow container constraints + if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad { + let metrics = ["preferredWidth": preferredWidth] + constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-(>=40)-[shadowContainer(==preferredWidth@900)]-(>=40)-|", options: [], metrics: metrics, views: views) + } else { + constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-(>=10,==20@900)-[shadowContainer(<=340,>=300)]-(>=10,==20@900)-|", options: [], metrics: nil, views: views) + } + constraints += [NSLayoutConstraint(item: shadowContainer, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)] + centerYConstraint = NSLayoutConstraint(item: shadowContainer, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0) + + if let centerYConstraint = centerYConstraint { + constraints.append(centerYConstraint) + } + + // Container constraints + constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[container]|", options: [], metrics: nil, views: views) + constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[container]|", options: [], metrics: nil, views: views) + + // Main stack view constraints + constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[stackView]|", options: [], metrics: nil, views: views) + constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[stackView]|", options: [], metrics: nil, views: views) + + // Activate constraints + NSLayoutConstraint.activate(constraints) + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogDefaultButtons.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogDefaultButtons.swift new file mode 100644 index 0000000..0bb130f --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogDefaultButtons.swift @@ -0,0 +1,54 @@ +// +// PopupDialogDefaultButtons.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +// MARK: Default button + +/// Represents the default button for the popup dialog +public final class DefaultButton: PopupDialogButton {} + +// MARK: Cancel button + +/// Represents a cancel button for the popup dialog +public final class CancelButton: PopupDialogButton { + + override public func setupView() { + defaultTitleColor = UIColor.lightGray + super.setupView() + } +} + +// MARK: destructive button + +/// Represents a destructive button for the popup dialog +public final class DestructiveButton: PopupDialogButton { + + override public func setupView() { + defaultTitleColor = UIColor.red + super.setupView() + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogDefaultView.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogDefaultView.swift new file mode 100644 index 0000000..7107222 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogDefaultView.swift @@ -0,0 +1,148 @@ +// +// PopupDialogView.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +/// The main view of the popup dialog +final public class PopupDialogDefaultView: UIView { + + // MARK: - Appearance + + /// The font and size of the title label + @objc public dynamic var titleFont: UIFont { + get { return titleLabel.font } + set { titleLabel.font = newValue } + } + + /// The color of the title label + @objc public dynamic var titleColor: UIColor? { + get { return titleLabel.textColor } + set { titleLabel.textColor = newValue } + } + + /// The text alignment of the title label + @objc public dynamic var titleTextAlignment: NSTextAlignment { + get { return titleLabel.textAlignment } + set { titleLabel.textAlignment = newValue } + } + + /// The font and size of the body label + @objc public dynamic var messageFont: UIFont { + get { return messageLabel.font } + set { messageLabel.font = newValue } + } + + /// The color of the message label + @objc public dynamic var messageColor: UIColor? { + get { return messageLabel.textColor } + set { messageLabel.textColor = newValue} + } + + /// The text alignment of the message label + @objc public dynamic var messageTextAlignment: NSTextAlignment { + get { return messageLabel.textAlignment } + set { messageLabel.textAlignment = newValue } + } + + // MARK: - Views + + /// The view that will contain the image, if set + internal lazy var imageView: UIImageView = { + let imageView = UIImageView(frame: .zero) + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + return imageView + }() + + /// The title label of the dialog + internal lazy var titleLabel: UILabel = { + let titleLabel = UILabel(frame: .zero) + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.numberOfLines = 0 + titleLabel.textAlignment = .center + titleLabel.textColor = UIColor(white: 0.4, alpha: 1) + titleLabel.font = .boldSystemFont(ofSize: 14) + return titleLabel + }() + + /// The message label of the dialog + internal lazy var messageLabel: UILabel = { + let messageLabel = UILabel(frame: .zero) + messageLabel.translatesAutoresizingMaskIntoConstraints = false + messageLabel.numberOfLines = 0 + messageLabel.textAlignment = .center + messageLabel.textColor = UIColor(white: 0.6, alpha: 1) + messageLabel.font = .systemFont(ofSize: 14) + return messageLabel + }() + + /// The height constraint of the image view, 0 by default + internal var imageHeightConstraint: NSLayoutConstraint? + + // MARK: - Initializers + + internal override init(frame: CGRect) { + super.init(frame: frame) + setupViews() + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View setup + + internal func setupViews() { + + // Self setup + translatesAutoresizingMaskIntoConstraints = false + + // Add views + addSubview(imageView) + addSubview(titleLabel) + addSubview(messageLabel) + + // Layout views + let views = ["imageView": imageView, "titleLabel": titleLabel, "messageLabel": messageLabel] as [String: Any] + var constraints = [NSLayoutConstraint]() + + constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[imageView]|", options: [], metrics: nil, views: views) + constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-(==20@900)-[titleLabel]-(==20@900)-|", options: [], metrics: nil, views: views) + constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-(==20@900)-[messageLabel]-(==20@900)-|", options: [], metrics: nil, views: views) + constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[imageView]-(==30@900)-[titleLabel]-(==8@900)-[messageLabel]-(==30@900)-|", options: [], metrics: nil, views: views) + + // ImageView height constraint + imageHeightConstraint = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: imageView, attribute: .height, multiplier: 0, constant: 0) + + if let imageHeightConstraint = imageHeightConstraint { + constraints.append(imageHeightConstraint) + } + + // Activate constraints + NSLayoutConstraint.activate(constraints) + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogDefaultViewController.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogDefaultViewController.swift new file mode 100644 index 0000000..cdda411 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogDefaultViewController.swift @@ -0,0 +1,133 @@ +// +// PopupDialogDefaultViewController.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit + +final public class PopupDialogDefaultViewController: UIViewController { + + public var standardView: PopupDialogDefaultView { + return view as! PopupDialogDefaultView // swiftlint:disable:this force_cast + } + + override public func loadView() { + super.loadView() + view = PopupDialogDefaultView(frame: .zero) + } +} + +public extension PopupDialogDefaultViewController { + + // MARK: - Setter / Getter + + // MARK: Content + + /// The dialog image + var image: UIImage? { + get { return standardView.imageView.image } + set { + standardView.imageView.image = newValue + standardView.imageHeightConstraint?.constant = standardView.imageView.pv_heightForImageView() + } + } + + /// The title text of the dialog + var titleText: String? { + get { return standardView.titleLabel.text } + set { + standardView.titleLabel.text = newValue + standardView.pv_layoutIfNeededAnimated() + } + } + + /// The message text of the dialog + var messageText: String? { + get { return standardView.messageLabel.text } + set { + standardView.messageLabel.text = newValue + standardView.pv_layoutIfNeededAnimated() + } + } + + // MARK: Appearance + + /// The font and size of the title label + @objc dynamic var titleFont: UIFont { + get { return standardView.titleFont } + set { + standardView.titleFont = newValue + standardView.pv_layoutIfNeededAnimated() + } + } + + /// The color of the title label + @objc dynamic var titleColor: UIColor? { + get { return standardView.titleLabel.textColor } + set { + standardView.titleColor = newValue + standardView.pv_layoutIfNeededAnimated() + } + } + + /// The text alignment of the title label + @objc dynamic var titleTextAlignment: NSTextAlignment { + get { return standardView.titleTextAlignment } + set { + standardView.titleTextAlignment = newValue + standardView.pv_layoutIfNeededAnimated() + } + } + + /// The font and size of the body label + @objc dynamic var messageFont: UIFont { + get { return standardView.messageFont} + set { + standardView.messageFont = newValue + standardView.pv_layoutIfNeededAnimated() + } + } + + /// The color of the message label + @objc dynamic var messageColor: UIColor? { + get { return standardView.messageColor } + set { + standardView.messageColor = newValue + standardView.pv_layoutIfNeededAnimated() + } + } + + /// The text alignment of the message label + @objc dynamic var messageTextAlignment: NSTextAlignment { + get { return standardView.messageTextAlignment } + set { + standardView.messageTextAlignment = newValue + standardView.pv_layoutIfNeededAnimated() + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + standardView.imageHeightConstraint?.constant = standardView.imageView.pv_heightForImageView() + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogOverlayView.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogOverlayView.swift new file mode 100644 index 0000000..dcb32ce --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PopupDialogOverlayView.swift @@ -0,0 +1,127 @@ +// +// PopupDialogOverlayView.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// The (blurred) overlay view below the popup dialog +final public class PopupDialogOverlayView: UIView { + + // MARK: - Appearance + + /// Turns the blur of the overlay view on or off + @objc public dynamic var blurEnabled: Bool { + get { return !blurView.isHidden } + set { blurView.isHidden = !newValue } + } + + /// The blur radius of the overlay view + @objc public dynamic var blurRadius: CGFloat { + get { return blurView.blurRadius } + set { blurView.blurRadius = newValue } + } + + /// Whether the blur view should allow for + /// live rendering of the background + @objc public dynamic var liveBlurEnabled: Bool { + get { return blurView.trackingMode == .common } + set { + if newValue { + blurView.trackingMode = .common + } else { + blurView.trackingMode = .none + } + } + } + + /// The background color of the overlay view + @objc public dynamic var color: UIColor? { + get { return overlay.backgroundColor } + set { overlay.backgroundColor = newValue } + } + + /// The opacity of the overlay view + @objc public dynamic var opacity: CGFloat { + get { return overlay.alpha } + set { overlay.alpha = newValue } + } + + // MARK: - Views + + internal lazy var blurView: DynamicBlurView = { + let blurView = DynamicBlurView(frame: .zero) + blurView.blurRadius = 8 + blurView.trackingMode = .none + blurView.isDeepRendering = true + blurView.tintColor = .clear + blurView.autoresizingMask = [.flexibleHeight, .flexibleWidth] + return blurView + }() + + internal lazy var overlay: UIView = { + let overlay = UIView(frame: .zero) + overlay.backgroundColor = .black + overlay.alpha = 0.7 + overlay.autoresizingMask = [.flexibleHeight, .flexibleWidth] + return overlay + }() + + // MARK: - Inititalizers + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View setup + + fileprivate func setupView() { + + autoresizingMask = [.flexibleHeight, .flexibleWidth] + backgroundColor = .clear + alpha = 0 + + addSubview(blurView) + addSubview(overlay) + } + +} + +// MARK: - Deprecated + +extension PopupDialogOverlayView { + + /// Whether the blur view should allow for + /// dynamic rendering of the background + @available(*, deprecated, message: "liveBlur has been deprecated and will be removed with future versions of PopupDialog. Please use isLiveBlurEnabled instead.") + @objc public dynamic var liveBlur: Bool { + get { return liveBlurEnabled } + set { liveBlurEnabled = newValue } + } + +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PresentationController.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PresentationController.swift new file mode 100644 index 0000000..ccca56a --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PresentationController.swift @@ -0,0 +1,61 @@ +// +// PopupDialogPresentationController.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +final internal class PresentationController: UIPresentationController { + + private lazy var overlay: PopupDialogOverlayView = { + return PopupDialogOverlayView(frame: .zero) + }() + + override func presentationTransitionWillBegin() { + + guard let containerView = containerView else { return } + + overlay.frame = containerView.bounds + containerView.insertSubview(overlay, at: 0) + + presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in + self?.overlay.alpha = 1.0 + }, completion: nil) + } + + override func dismissalTransitionWillBegin() { + presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in + self?.overlay.alpha = 0.0 + }, completion: nil) + } + + override func containerViewWillLayoutSubviews() { + + guard let presentedView = presentedView else { return } + + presentedView.frame = frameOfPresentedViewInContainerView + overlay.blurView.refresh() + } + +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PresentationManager.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PresentationManager.swift new file mode 100644 index 0000000..fdc1072 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/PresentationManager.swift @@ -0,0 +1,86 @@ +// +// PopupDialogPresentationManager.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +final internal class PresentationManager: NSObject, UIViewControllerTransitioningDelegate { + + var transitionStyle: PopupDialogTransitionStyle + var interactor: InteractiveTransition + + init(transitionStyle: PopupDialogTransitionStyle, interactor: InteractiveTransition) { + self.transitionStyle = transitionStyle + self.interactor = interactor + super.init() + } + + func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { + let presentationController = PresentationController(presentedViewController: presented, presenting: source) + return presentationController + } + + func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + + var transition: TransitionAnimator + switch transitionStyle { + case .bounceUp: + transition = BounceUpTransition(direction: .in) + case .bounceDown: + transition = BounceDownTransition(direction: .in) + case .zoomIn: + transition = ZoomTransition(direction: .in) + case .fadeIn: + transition = FadeTransition(direction: .in) + } + + return transition + } + + func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + + if interactor.hasStarted || interactor.shouldFinish { + return DismissInteractiveTransition() + } + + var transition: TransitionAnimator + switch transitionStyle { + case .bounceUp: + transition = BounceUpTransition(direction: .out) + case .bounceDown: + transition = BounceDownTransition(direction: .out) + case .zoomIn: + transition = ZoomTransition(direction: .out) + case .fadeIn: + transition = FadeTransition(direction: .out) + } + + return transition + } + + func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + return interactor.hasStarted ? interactor : nil + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/TransitionAnimations.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/TransitionAnimations.swift new file mode 100644 index 0000000..e496b0c --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/TransitionAnimations.swift @@ -0,0 +1,186 @@ +// +// PopupDialogTransitionAnimations.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +/*! + Presentation transition styles for the popup dialog + + - BounceUp: Dialog bounces in from bottom and is dismissed to bottom + - BounceDown: Dialog bounces in from top and is dismissed to top + - ZoomIn: Dialog zooms in and is dismissed by zooming out + - FadeIn: Dialog fades in and is dismissed by fading out + */ +@objc public enum PopupDialogTransitionStyle: Int { + case bounceUp + case bounceDown + case zoomIn + case fadeIn +} + +/// Dialog bounces in from bottom and is dismissed to bottom +final internal class BounceUpTransition: TransitionAnimator { + + init(direction: AnimationDirection) { + super.init(inDuration: 0.22, outDuration: 0.2, direction: direction) + } + + override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + super.animateTransition(using: transitionContext) + + switch direction { + case .in: + to.view.bounds.origin = CGPoint(x: 0, y: -from.view.bounds.size.height) + UIView.animate(withDuration: 0.6, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: [.curveEaseOut], animations: { [weak self] in + guard let self = self else { return } + self.to.view.bounds = self.from.view.bounds + }, completion: { _ in + transitionContext.completeTransition(true) + }) + case .out: + UIView.animate(withDuration: outDuration, delay: 0.0, options: [.curveEaseIn], animations: { [weak self] in + guard let self = self else { return } + self.from.view.bounds.origin = CGPoint(x: 0, y: -self.from.view.bounds.size.height) + self.from.view.alpha = 0.0 + }, completion: { _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + }) + } + } +} + + +/// Dialog bounces in from top and is dismissed to top +final internal class BounceDownTransition: TransitionAnimator { + + init(direction: AnimationDirection) { + super.init(inDuration: 0.22, outDuration: 0.2, direction: direction) + } + + override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + super.animateTransition(using: transitionContext) + + switch direction { + case .in: + to.view.bounds.origin = CGPoint(x: 0, y: from.view.bounds.size.height) + UIView.animate(withDuration: 0.6, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: [.curveEaseOut], animations: { [weak self] in + guard let self = self else { return } + self.to.view.bounds = self.from.view.bounds + }, completion: { _ in + transitionContext.completeTransition(true) + }) + case .out: + UIView.animate(withDuration: outDuration, delay: 0.0, options: [.curveEaseIn], animations: { [weak self] in + guard let self = self else { return } + self.from.view.bounds.origin = CGPoint(x: 0, y: self.from.view.bounds.size.height) + self.from.view.alpha = 0.0 + }, completion: { _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + }) + } + } +} + +/// Dialog zooms in and is dismissed by zooming out +final internal class ZoomTransition: TransitionAnimator { + + init(direction: AnimationDirection) { + super.init(inDuration: 0.22, outDuration: 0.2, direction: direction) + } + + override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + super.animateTransition(using: transitionContext) + + switch direction { + case .in: + to.view.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) + UIView.animate(withDuration: 0.6, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: [.curveEaseOut], animations: { [weak self] in + guard let self = self else { return } + self.to.view.transform = CGAffineTransform(scaleX: 1, y: 1) + }, completion: { _ in + transitionContext.completeTransition(true) + }) + case .out: + UIView.animate(withDuration: outDuration, delay: 0.0, options: [.curveEaseIn], animations: { [weak self] in + guard let self = self else { return } + self.from.view.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) + self.from.view.alpha = 0.0 + }, completion: { _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + }) + } + } +} + +/// Dialog fades in and is dismissed by fading out +final internal class FadeTransition: TransitionAnimator { + + init(direction: AnimationDirection) { + super.init(inDuration: 0.22, outDuration: 0.2, direction: direction) + } + + override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + super.animateTransition(using: transitionContext) + + switch direction { + case .in: + to.view.alpha = 0 + UIView.animate(withDuration: 0.6, delay: 0.0, options: [.curveEaseOut], + animations: { [weak self] in + guard let self = self else { return } + self.to.view.alpha = 1 + }, completion: { _ in + transitionContext.completeTransition(true) + }) + case .out: + UIView.animate(withDuration: outDuration, delay: 0.0, options: [.curveEaseIn], animations: { [weak self] in + guard let self = self else { return } + self.from.view.alpha = 0.0 + }, completion: { _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + }) + } + } +} + +/// Used for the always drop out animation with pan gesture dismissal +final internal class DismissInteractiveTransition: TransitionAnimator { + + init() { + super.init(inDuration: 0.22, outDuration: 0.32, direction: .out) + } + + override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + super.animateTransition(using: transitionContext) + UIView.animate(withDuration: outDuration, delay: 0.0, options: [.beginFromCurrentState], animations: { [weak self] in + guard let self = self else { return } + self.from.view.bounds.origin = CGPoint(x: 0, y: -self.from.view.bounds.size.height) + self.from.view.alpha = 0.0 + }, completion: { _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + }) + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/TransitionAnimator.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/TransitionAnimator.swift new file mode 100644 index 0000000..fd996fa --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/TransitionAnimator.swift @@ -0,0 +1,68 @@ +// +// PopupDialogTransitionAnimator.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +/// Base class for custom transition animations +internal class TransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning { + + var to: UIViewController! + var from: UIViewController! + let inDuration: TimeInterval + let outDuration: TimeInterval + let direction: AnimationDirection + + init(inDuration: TimeInterval, outDuration: TimeInterval, direction: AnimationDirection) { + self.inDuration = inDuration + self.outDuration = outDuration + self.direction = direction + super.init() + } + + internal func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return direction == .in ? inDuration : outDuration + } + + internal func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + switch direction { + case .in: + guard let to = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to), + let from = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { return } + + self.to = to + self.from = from + + let container = transitionContext.containerView + container.addSubview(to.view) + case .out: + guard let to = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to), + let from = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { return } + + self.to = to + self.from = from + } + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/UIImageView+Calculations.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/UIImageView+Calculations.swift new file mode 100755 index 0000000..2ad9121 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/UIImageView+Calculations.swift @@ -0,0 +1,44 @@ +// +// UIImageView+Calculations.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +internal extension UIImageView { + + /*! + Calculates the height of the the UIImageView has to + have so the image is displayed correctly + - returns: Height to set on the imageView + */ + func pv_heightForImageView() -> CGFloat { + guard let image = image, image.size.height > 0 else { + return 0.0 + } + let width = bounds.size.width + let ratio = image.size.height / image.size.width + return width * ratio + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/UIView+Animations.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/UIView+Animations.swift new file mode 100644 index 0000000..68adc29 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/UIView+Animations.swift @@ -0,0 +1,82 @@ +// +// UIView+Animations.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +/*! + The intended direction of the animation + - in: Animate in + - out: Animate out + */ +internal enum AnimationDirection { + case `in` // swiftlint:disable:this identifier_name + case out +} + +internal extension UIView { + + /// The key for the fade animation + var fadeKey: String { return "FadeAnimation" } + var shakeKey: String { return "ShakeAnimation" } + + func pv_fade(_ direction: AnimationDirection, _ value: Float, duration: CFTimeInterval = 0.08) { + layer.removeAnimation(forKey: fadeKey) + let animation = CABasicAnimation(keyPath: "opacity") + animation.duration = duration + animation.fromValue = layer.presentation()?.opacity + layer.opacity = value + animation.fillMode = CAMediaTimingFillMode.forwards + layer.add(animation, forKey: fadeKey) + } + + func pv_layoutIfNeededAnimated(duration: CFTimeInterval = 0.08) { + UIView.animate(withDuration: duration, delay: 0, options: UIView.AnimationOptions(), animations: { + self.layoutIfNeeded() + }, completion: nil) + } + + // As found at https://gist.github.com/mourad-brahim/cf0bfe9bec5f33a6ea66#file-uiview-animations-swift-L9 + // Slightly modified + func pv_shake() { + layer.removeAnimation(forKey: shakeKey) + let vals: [Double] = [-2, 2, -2, 2, 0] + + let translation = CAKeyframeAnimation(keyPath: "transform.translation.x") + translation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + translation.values = vals + + let rotation = CAKeyframeAnimation(keyPath: "transform.rotation.z") + rotation.values = vals.map { (degrees: Double) in + let radians: Double = (Double.pi * degrees) / 180.0 + return radians + } + + let shakeGroup: CAAnimationGroup = CAAnimationGroup() + shakeGroup.animations = [translation, rotation] + shakeGroup.duration = 0.3 + self.layer.add(shakeGroup, forKey: shakeKey) + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/UIViewController+Visibility.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/UIViewController+Visibility.swift new file mode 100644 index 0000000..079b1ac --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/PopupDialog/UIViewController+Visibility.swift @@ -0,0 +1,52 @@ +// +// UIViewController+Visibility.swift +// +// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk) +// Author - Martin Wildfeuer (http://www.mwfire.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import UIKit + +// http://stackoverflow.com/questions/2777438/how-to-tell-if-uiviewcontrollers-view-is-visible +internal extension UIViewController { + + var isTopAndVisible: Bool { + return isVisible && isTopViewController + } + + var isVisible: Bool { + if isViewLoaded { + return view.window != nil + } + return false + } + + var isTopViewController: Bool { + if self.navigationController != nil { + return self.navigationController?.visibleViewController === self + } else if self.tabBarController != nil { + return self.tabBarController?.selectedViewController == self && self.presentedViewController == nil + } else { + return self.presentedViewController == nil && self.isVisible + } + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/ScrollRects.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/ScrollRects.swift new file mode 100644 index 0000000..b70a7c6 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/ScrollRects.swift @@ -0,0 +1,41 @@ +// +// ScrollRects.swift +// TableViewDragger +// +// Created by Kyohei Ito on 2017/12/08. +// Copyright © 2017年 kyohei_ito. All rights reserved. +// + +import Foundation +import CoreImage + +struct ScrollRects { + private let maxDistance: CGFloat = 10 + private let size: CGSize + private let scrollRange: CGFloat + + let topRect: CGRect + let bottomRect: CGRect + + init(size: CGSize) { + self.size = size + scrollRange = size.height / 2.5 + + let scrollSize = CGSize(width: size.width, height: scrollRange) + topRect = CGRect(origin: .zero, size: scrollSize) + bottomRect = CGRect(origin: CGPoint(x: 0, y: size.height - scrollRange), size: scrollSize) + } + + func distance(at point: CGPoint) -> CGFloat { + let ratio: CGFloat + if topRect.contains(point) { + ratio = -(scrollRange - point.y) + } else if bottomRect.contains(point) { + ratio = point.y - (size.height - scrollRange) + } else { + ratio = 0 + } + + return max(min(ratio / 30, maxDistance), -maxDistance) + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/TableViewDragger.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/TableViewDragger.swift new file mode 100644 index 0000000..efc05e1 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/TableViewDragger.swift @@ -0,0 +1,371 @@ +// +// TableViewDragger.swift +// TableViewDragger +// +// Created by Kyohei Ito on 2015/09/24. +// Copyright © 2015年 kyohei_ito. All rights reserved. +// + +import UIKit + +@objc public protocol TableViewDraggerDelegate: class { + /// If allow movement of cell, please return `true`. require a call to `moveRowAt:toIndexPath:` of UITableView and rearranged of data. + func dragger(_ dragger: TableViewDragger, moveDraggingAt indexPath: IndexPath, newIndexPath: IndexPath) -> Bool + + /// If allow dragging of cell, prease return `true`. + @objc optional func dragger(_ dragger: TableViewDragger, shouldDragAt indexPath: IndexPath) -> Bool + @objc optional func dragger(_ dragger: TableViewDragger, willBeginDraggingAt indexPath: IndexPath) + @objc optional func dragger(_ dragger: TableViewDragger, didBeginDraggingAt indexPath: IndexPath) + @objc optional func dragger(_ dragger: TableViewDragger, willEndDraggingAt indexPath: IndexPath) + @objc optional func dragger(_ dragger: TableViewDragger, didEndDraggingAt indexPath: IndexPath) +} + +@objc public protocol TableViewDraggerDataSource: class { + /// Return any cell if want to change the cell in drag. + @objc optional func dragger(_ dragger: TableViewDragger, cellForRowAt indexPath: IndexPath) -> UIView? + /// Return the indexPath if want to change the indexPath to start drag. + @objc optional func dragger(_ dragger: TableViewDragger, indexPathForDragAt indexPath: IndexPath) -> IndexPath +} + +open class TableViewDragger: NSObject { + let longPressGesture = UILongPressGestureRecognizer() + let panGesture = UIPanGestureRecognizer() + var draggingCell: TableViewDraggerCell? + var displayLink: CADisplayLink? + var targetClipsToBounds = true + weak var targetTableView: UITableView? + private var draggingDirection: UIScrollView.DraggingDirection? + + /// It will be `true` if want to hide the original cell. + open var isHiddenOriginCell: Bool = true + /// Zoom scale of cell in drag. + open var zoomScaleForCell: CGFloat = 1 + /// Alpha of cell in drag. + open var alphaForCell: CGFloat = 1 + /// Opacity of cell shadow in drag. + open var opacityForShadowOfCell: Float = 0.4 + /// Velocity of auto scroll in drag. + open var scrollVelocity: CGFloat = 1 + open weak var delegate: TableViewDraggerDelegate? + open weak var dataSource: TableViewDraggerDataSource? + // + open var availableHorizontalScroll : Bool = true + open var tableView: UITableView? { + return targetTableView + } + + /// `UITableView` want to drag. + public init(tableView: UITableView, _ minimumPressDuration: CFTimeInterval = 0.5) { + super.init() + + self.targetTableView = tableView + tableView.addGestureRecognizer(longPressGesture) + tableView.addGestureRecognizer(panGesture) + + longPressGesture.addTarget(self, action: #selector(TableViewDragger.longPressGestureAction(_:))) + longPressGesture.delegate = self + longPressGesture.allowableMovement = 5.0 + longPressGesture.minimumPressDuration = minimumPressDuration + + panGesture.addTarget(self, action: #selector(TableViewDragger.panGestureAction(_:))) + panGesture.delegate = self + panGesture.maximumNumberOfTouches = 1 + } + + deinit { + targetTableView?.removeGestureRecognizer(longPressGesture) + targetTableView?.removeGestureRecognizer(panGesture) + } + + func targetIndexPath(_ tableView: UITableView, draggingCell: TableViewDraggerCell) -> IndexPath { + let location = draggingCell.location + let offsetY = (draggingCell.viewHeight / 2) + 2 + let offsetX = tableView.center.x + let topPoint = CGPoint(x: offsetX, y: location.y - offsetY) + let bottomPoint = CGPoint(x: offsetX, y: location.y + offsetY) + let point = draggingDirection == .up ? topPoint : bottomPoint + + if let targetIndexPath = tableView.indexPathForRow(at: point) { + if tableView.cellForRow(at: targetIndexPath) == nil { + return draggingCell.dropIndexPath + } + + let targetRect = tableView.rectForRow(at: targetIndexPath) + let targetCenterY = targetRect.origin.y + (targetRect.height / 2) + + guard let direction = draggingDirection else { + return draggingCell.dropIndexPath + } + + switch direction { + case .up: + if (targetCenterY > point.y && draggingCell.dropIndexPath > targetIndexPath) { + return targetIndexPath + } + case .down: + if (targetCenterY < point.y && draggingCell.dropIndexPath < targetIndexPath) { + return targetIndexPath + } + } + } else { + let section = (0.. Bool in + tableView.rect(forSection: section).contains(point) + }.first + + if let section = section, tableView.numberOfRows(inSection: section) == 0 { + return IndexPath(row: 0, section: section) + } + } + + return draggingCell.dropIndexPath + } + + func dragCell(_ tableView: UITableView, draggingCell: TableViewDraggerCell) { + let indexPath = targetIndexPath(tableView, draggingCell: draggingCell) + if draggingCell.dropIndexPath.compare(indexPath) == .orderedSame { + return + } + + if let cell = tableView.cellForRow(at: draggingCell.dropIndexPath) { + cell.isHidden = isHiddenOriginCell + } + if delegate?.dragger(self, moveDraggingAt: draggingCell.dropIndexPath, newIndexPath: indexPath) == true { + draggingCell.dropIndexPath = indexPath + } + } + + func copiedCell(at indexPath: IndexPath) -> UIView? { + if let view = dataSource?.dragger?(self, cellForRowAt: indexPath) { + return view + } + + if let cell = targetTableView?.cellForRow(at: indexPath) { + if let view = cell.snapshotView(afterScreenUpdates: false) { + return view + } else if let view = cell.captured() { + view.frame = cell.bounds + return view + } + } + + return nil + } + + func draggedCell(_ tableView: UITableView, indexPath: IndexPath) -> TableViewDraggerCell? { + guard let copiedCell = copiedCell(at: indexPath) else { + return nil + } + + let cellRect = tableView.rectForRow(at: indexPath) + copiedCell.frame.size = cellRect.size + + if let height = tableView.delegate?.tableView?(tableView, heightForRowAt: indexPath) { + copiedCell.frame.size.height = height + } + + let cell = TableViewDraggerCell(cell: copiedCell) + cell.dragScale = zoomScaleForCell + cell.dragAlpha = alphaForCell + cell.dragShadowOpacity = opacityForShadowOfCell + cell.dropIndexPath = indexPath + + return cell + } +} + +// MARK: - Dragging Cell +extension TableViewDragger { + private func draggingDidBegin(_ gesture: UIGestureRecognizer, indexPath: IndexPath) { + displayLink?.invalidate() + displayLink = UIScreen.main.displayLink(withTarget: self, selector: #selector(TableViewDragger.displayDidRefresh(_:))) + displayLink?.add(to: .main, forMode: .default) + displayLink?.isPaused = true + + let dragIndexPath = dataSource?.dragger?(self, indexPathForDragAt: indexPath) ?? indexPath + delegate?.dragger?(self, willBeginDraggingAt: dragIndexPath) + + if let tableView = targetTableView { + let actualCell = tableView.cellForRow(at: dragIndexPath) + + if let draggedCell = draggedCell(tableView, indexPath: dragIndexPath) { + let point = gesture.location(in: actualCell) + + if availableHorizontalScroll == true{ + + draggedCell.offset = point + draggedCell.transformToPoint(point) + draggedCell.location = gesture.location(in: tableView) + + } else { + + draggedCell.offset = CGPoint(x: (draggedCell.frame.size.width)/2, y: point.y) + draggedCell.transformToPoint(CGPoint(x: (draggedCell.frame.size.width)/2, y: point.y)) + draggedCell.location = CGPoint(x: (draggedCell.frame.size.width)/2, y: gesture.location(in: tableView).y) + + } + + tableView.addSubview(draggedCell) + draggingCell = draggedCell + } + + actualCell?.isHidden = isHiddenOriginCell + targetClipsToBounds = tableView.clipsToBounds + tableView.clipsToBounds = false + } + + delegate?.dragger?(self, didBeginDraggingAt: indexPath) + } + + private func draggingDidChange(_ gesture: UIGestureRecognizer, direction: UIScrollView.DraggingDirection?) { + guard let tableView = targetTableView, let draggingCell = draggingCell else { + return + } + + if availableHorizontalScroll == true{ + + draggingCell.location = gesture.location(in: tableView) + + } else { + + draggingCell.location = CGPoint(x: (draggingCell.frame.size.width)/2, y: gesture.location(in: tableView).y) + } + + + if let adjustedDirection = tableView.draggingDirection(at: draggingCell.adjustedCenter(on: tableView)) { + displayLink?.isPaused = false + draggingDirection = adjustedDirection + } else { + draggingDirection = direction + } + + + dragCell(tableView, draggingCell: draggingCell) + } + + private func draggingDidEnd(_ gesture: UIGestureRecognizer) { + displayLink?.invalidate() + displayLink = nil + + guard let tableView = targetTableView, let draggingCell = draggingCell else { + return + } + + delegate?.dragger?(self, willEndDraggingAt: draggingCell.dropIndexPath) + + let targetRect = tableView.rectForRow(at: draggingCell.dropIndexPath) + let center = CGPoint(x: targetRect.width / 2, y: targetRect.origin.y + (targetRect.height / 2)) + + draggingCell.drop(center) { + self.delegate?.dragger?(self, didEndDraggingAt: draggingCell.dropIndexPath) + + if let cell = tableView.cellForRow(at: draggingCell.dropIndexPath) { + cell.isHidden = false + } + + tableView.clipsToBounds = self.targetClipsToBounds + + self.draggingCell = nil + } + } +} + +// MARK: - Action Methods +private extension TableViewDragger { + @objc func displayDidRefresh(_ displayLink: CADisplayLink) { + guard let tableView = targetTableView, let draggingCell = draggingCell else { + return + } + + let center = draggingCell.adjustedCenter(on: tableView) + + if let direction = tableView.draggingDirection(at: center) { + draggingDirection = direction + } else { + displayLink.isPaused = true + } + + tableView.contentOffset = tableView.preferredContentOffset(at: center, velocity: scrollVelocity) + + dragCell(tableView, draggingCell: draggingCell) + + if availableHorizontalScroll == true{ + + draggingCell.location = panGesture.location(in: tableView) + } else { + + draggingCell.location = CGPoint(x: draggingCell.frame.size.width/2, y: panGesture.location(in: tableView).y) + } + + } + + @objc func longPressGestureAction(_ gesture: UILongPressGestureRecognizer) { + switch gesture.state { + case .began: + targetTableView?.isScrollEnabled = false + + let point = gesture.location(in: targetTableView) + if let path = targetTableView?.indexPathForRow(at: point) { + draggingDidBegin(gesture, indexPath: path) + } + case .ended, .cancelled: + draggingDidEnd(gesture) + + targetTableView?.isScrollEnabled = true + + case .changed, .failed, .possible: + break + @unknown default: + return + } + } + + @objc func panGestureAction(_ gesture: UIPanGestureRecognizer) { + guard targetTableView?.isScrollEnabled == false && gesture.state == .changed else { + return + } + + let offsetY = gesture.translation(in: targetTableView).y + if offsetY < 0 { + draggingDidChange(gesture, direction: .up) + } else if offsetY > 0 { + draggingDidChange(gesture, direction: .down) + } else { + draggingDidChange(gesture, direction: nil) + } + + gesture.setTranslation(.zero, in: targetTableView) + } +} + +// MARK: - UIGestureRecognizerDelegate Methods +extension TableViewDragger: UIGestureRecognizerDelegate { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + if gestureRecognizer == longPressGesture { + let point = touch.location(in: targetTableView) + + if let indexPath = targetTableView?.indexPathForRow(at: point) { + if let ret = delegate?.dragger?(self, shouldDragAt: indexPath) { + return ret + } + } else { + return false + } + } + + return true + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return gestureRecognizer == panGesture || otherGestureRecognizer == panGesture || gestureRecognizer == longPressGesture || otherGestureRecognizer == longPressGesture + } +} + +extension UIView { + fileprivate func captured() -> UIView? { + guard let data = try? NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false) else { + return nil + } + + return try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [UIView.self], from: data) as? UIView + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/TableViewDraggerCell.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/TableViewDraggerCell.swift new file mode 100644 index 0000000..ba19863 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/TableViewDraggerCell.swift @@ -0,0 +1,136 @@ +// +// TableViewDraggerCell.swift +// TableViewDragger +// +// Created by Kyohei Ito on 2015/09/24. +// Copyright © 2015年 kyohei_ito. All rights reserved. +// + +import UIKit + +class TableViewDraggerCell: UIScrollView { + private let zoomingView: UIView! + + var dragAlpha: CGFloat = 1 + var dragScale: CGFloat = 1 + var dragShadowOpacity: Float = 0.4 + + var dropIndexPath: IndexPath = IndexPath(index: 0) + var offset: CGPoint = CGPoint.zero { + didSet { + offset.x -= (bounds.width / 2) + offset.y -= (bounds.height / 2) + center = adjustCenter(location) + } + } + var location: CGPoint = CGPoint.zero { + didSet { + center = adjustCenter(location) + } + } + var viewHeight: CGFloat { + return zoomingView.bounds.height * zoomScale + } + + private func adjustCenter(_ center: CGPoint) -> CGPoint { + var center = center + center.x -= offset.x + center.y -= offset.y + return center + } + + required init?(coder aDecoder: NSCoder) { + zoomingView = UIView(frame: .zero) + super.init(coder: aDecoder) + } + + init(cell: UIView) { + zoomingView = UIView(frame: cell.bounds) + zoomingView.addSubview(cell) + + super.init(frame: cell.bounds) + + delegate = self + clipsToBounds = false + + layer.shadowColor = UIColor.black.cgColor + layer.shadowOpacity = 0 + layer.shadowRadius = 5 + layer.shadowOffset = .zero + + addSubview(zoomingView) + } + + func transformToPoint(_ point: CGPoint) { + if dragScale > 1 { + maximumZoomScale = dragScale + } else { + minimumZoomScale = dragScale + } + + var center = zoomingView.center + center.x -= (center.x * dragScale) - point.x + center.y -= (center.y * dragScale) - point.y + + UIView.animate(withDuration: 0.25, delay: 0.1, options: .curveEaseInOut, animations: { + self.zoomingView.center = center + self.zoomScale = self.dragScale + self.alpha = self.dragAlpha + }, completion: nil) + + CATransaction.begin() + let anim = CABasicAnimation(keyPath: "shadowOpacity") + anim.fromValue = 0 + anim.toValue = dragShadowOpacity + anim.duration = 0.1 + anim.isRemovedOnCompletion = false + anim.fillMode = .forwards + layer.add(anim, forKey: "cellDragAnimation") + CATransaction.commit() + } + + func adjustedCenter(on scrollView: UIScrollView) -> CGPoint { + var center = location + center.y -= scrollView.contentOffset.y + center.x = scrollView.center.x + return center + } + + func drop(_ center: CGPoint, completion: (() -> Void)? = nil) { + UIView.animate(withDuration: 0.25) { + self.zoomingView.adjustCenterAtRect(self.zoomingView.frame) + self.center = center + self.zoomScale = 1.0 + self.alpha = 1.0 + } + + CATransaction.begin() + let anim = CABasicAnimation(keyPath: "shadowOpacity") + anim.fromValue = dragShadowOpacity + anim.toValue = 0 + anim.duration = 0.15 + anim.beginTime = CACurrentMediaTime() + 0.15 + anim.isRemovedOnCompletion = false + anim.fillMode = .forwards + CATransaction.setCompletionBlock { + self.removeFromSuperview() + + completion?() + } + layer.add(anim, forKey: "cellDropAnimation") + CATransaction.commit() + } +} + +extension TableViewDraggerCell: UIScrollViewDelegate { + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + return zoomingView + } +} + +private extension UIView { + func adjustCenterAtRect(_ rect: CGRect) { + let center = CGPoint(x: rect.size.width / 2, y: rect.size.height / 2) + self.center = center + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/UIScrollViewExtension.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/UIScrollViewExtension.swift new file mode 100644 index 0000000..428a7d5 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/Vender/TableViewDragger/UIScrollViewExtension.swift @@ -0,0 +1,58 @@ +// +// UIScrollViewExtension.swift +// Pods +// +// Created by Kyohei Ito on 2015/09/29. +// +// + +import UIKit + +extension UIScrollView { + enum DraggingDirection { + case up + case down + } + + func preferredContentOffset(at point: CGPoint, velocity: CGFloat) -> CGPoint { + let distance = ScrollRects(size: bounds.size).distance(at: point) / velocity + var offset = contentOffset + offset.y += distance + + let topOffset = -contentInset.top + let bottomOffset = contentInset.bottom + let height = floor(contentSize.height) - bounds.size.height + + if offset.y > height + bottomOffset { + offset.y = height + bottomOffset + } else if offset.y < topOffset { + offset.y = topOffset + } + + return offset + } + + func draggingDirection(at point: @autoclosure () -> CGPoint) -> DraggingDirection? { + let contentHeight = floor(contentSize.height) + if bounds.size.height >= contentHeight { + return nil + } + + let rects = ScrollRects(size: bounds.size) + let point = point() + + if rects.topRect.contains(point) { + let topOffset = -contentInset.top + if contentOffset.y > topOffset { + return .up + } + } else if rects.bottomRect.contains(point) { + let bottomOffset = contentHeight + contentInset.bottom - bounds.size.height + if contentOffset.y < bottomOffset { + return .down + } + } + + return nil + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+MetalVideoProcessPlayerDelegate.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+MetalVideoProcessPlayerDelegate.swift new file mode 100644 index 0000000..6d8c65f --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+MetalVideoProcessPlayerDelegate.swift @@ -0,0 +1,143 @@ +// +// ViewController+MetalVideoProcessPlayerDelegate.swift +// SimpleVideoEditor +// +// Created by RenZhu Macro on 2020/7/9. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import Foundation +import MetalVideoProcess +import AVFoundation +import AVKit + +extension ViewController: MetalVideoProcessPlayerDelegate { + func playbackFrameTimeChanged(frameTime time: CMTime, player: AVPlayer) { + DispatchQueue.main.async { + self.progress.value = Float(time.seconds) + } + } + + func playEnded(currentPlayer player: AVPlayer) { + + } + + func finishExport(error: NSError?) { + DispatchQueue.main.async { [weak self] in + guard let `self` = self else { return } + if error != nil { + MBProgressHUD.hide(for: self.view, animated: true) + guard let source = self.movieWriter?.sources.sources[0] else { + return + } + source.removeAllTargets() + source --> self.renderView + self.view.isUserInteractionEnabled = true + let alertVC = UIAlertController(title: "", message: "导出失败, 请重新尝试", preferredStyle: .alert) + + let alertBtn = UIAlertAction(title: "确认", style: .cancel) { (_) in + alertVC.dismiss(animated: true, completion: nil) + } + alertVC.addAction(alertBtn) + self.present(alertVC, animated: true) { + + } + return + } + } + if error != nil { + self.view.isUserInteractionEnabled = true + return + } + //这里准备调用writer结束 + self.movieWriter?.finishRecording({ + //这里去预览界面播放编辑后的视频 + DispatchQueue.main.async { + MBProgressHUD.hide(for: self.view, animated: true) + guard let source = self.movieWriter?.sources.sources[0] else { + return + } + source.removeAllTargets() + source --> self.renderView + + let playerVC = AVPlayerViewController() + playerVC.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Info", style: .done, target: self, action: #selector(self.showInfo)) + playerVC.player = AVPlayer.init(url: self.movieWriter!.fileURL) + self.present(playerVC, animated: true, completion: nil) + self.view.isUserInteractionEnabled = true + } + + }) + } + + func exportProgressChanged(_ progress: Float) { + DispatchQueue.main.async { + self.progressHUD?.progress = progress + } + } + + @objc func showInfo() { + let data = NSData(contentsOf: self.movieWriter!.fileURL) + guard let size = data?.length else { + return + } + + let mSize = Double(size) / (1024 * 1024) + let message = NSString(format: "%..2f MB", mSize) as String + let alertVC = UIAlertController(title: "Video file size", message: message, preferredStyle: .alert) + + let alertBtn = UIAlertAction(title: "OK", style: .cancel) { (_) in + alertVC.dismiss(animated: true, completion: nil) + } + alertVC.addAction(alertBtn) + self.present(alertVC, animated: true) { + + } + } + + func initWriter() { + self.movieWriter?.removeSourceAtIndex(0) + self.movieWriter = nil + //导出相关 + do { + let documentsDir = try FileManager.default.url(for:.documentDirectory, in:.userDomainMask, appropriateFor:nil, create:true) + let fileURL = URL(string:"test.mp4", relativeTo:documentsDir)! + do { + try FileManager.default.removeItem(at:fileURL) + } catch { + } + + + let cgSize = MetalVideoProcessBackground.canvasSize + self.movieWriter = try MetalVideoProcessMovieWriter(URL: fileURL, + size: Size(width: Float(cgSize.width), + height: Float(cgSize.height)), + liveVideo: false) + self.player?.audioEncodingTarget = nil + self.player?.audioEncodingTarget = self.movieWriter + //最后一个输出节点指向writer + + + + } catch { + + } + } + + @IBAction func export(_ sender: Any) { + self.progressHUD = MBProgressHUD.showHub(with: .loading("exporting..."), self.view, isUserInteractionEnabled: false) + + + guard let source = self.renderView?.sources.sources[0] else { + return + } + initWriter() + source.removeAllTargets() + source --> self.movieWriter! + self.view.isUserInteractionEnabled = false + + try? self.player?.startExport() + self.movieWriter?.activateAudioTrack() + self.movieWriter?.startRecording() + } +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+UIGestureRecognizerDelegate.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+UIGestureRecognizerDelegate.swift new file mode 100644 index 0000000..7bea3ac --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+UIGestureRecognizerDelegate.swift @@ -0,0 +1,71 @@ +// +// ViewController+UIGestureRecognizerDelegate.swift +// SimpleVideoEditor +// +// Created by RenZhu Macro on 2020/7/9. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import Foundation +import MetalVideoProcess + +extension ViewController: UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true; + } + + @IBAction func rotateAction(ges: UIRotationGestureRecognizer) { + guard let item = (self.mainSelectedItem != nil ? self.mainSelectedItem : self.subSelectedItem) else { + return + } + guard let transformFilter = item.transformFilter else { + return + } + let rotDeg = transformFilter.rotate + Double(-ges.velocity) + transformFilter.rotate = rotDeg + item.rotate = rotDeg + } + + @IBAction func pinchAction(ges: UIPinchGestureRecognizer) { + guard let item = (self.mainSelectedItem != nil ? self.mainSelectedItem : self.subSelectedItem) else { + return + } + guard let transformFilter = item.transformFilter else { + return + } + transformFilter.scale = Position((transformFilter.scale.x) + Float(ges.velocity * 0.01), (transformFilter.scale.y) + Float(ges.velocity * 0.01)) + var x: Float = (transformFilter.scale.x) + var y: Float = (transformFilter.scale.y) + if ((transformFilter.scale.x) < 0.01) { + x = 0.01 + } + if ((transformFilter.scale.y) < 0.01) { + y = 0.01 + } + transformFilter.scale = Position(x, y) + item.scale = transformFilter.scale + } + + @IBAction func panAction(ges: UIPanGestureRecognizer) { + guard let item = (self.mainSelectedItem != nil ? self.mainSelectedItem : self.subSelectedItem) else { + return + } + guard let transformFilter = item.transformFilter else { + return + } + + if ges.state == .changed { + let tanl = ges.translation(in: ges.view) + + // have to content the view scaleFactor and the vertex to texture scale. + transformFilter.translation = Position(currentPostion.x + Float(tanl.x * self.renderView.contentScaleFactor * 2.0) / Float(self.renderView.drawableSize.width), currentPostion.y - Float(tanl.y * self.renderView.contentScaleFactor * 2.0) / Float(self.renderView.drawableSize.height)) + + } else if ges.state == .ended { + currentPostion = (transformFilter.translation) + } + + item.translation = transformFilter.translation + } + + +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+UIImagePickerControllerDelegate.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+UIImagePickerControllerDelegate.swift new file mode 100644 index 0000000..4bbfba2 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+UIImagePickerControllerDelegate.swift @@ -0,0 +1,104 @@ +// +// ViewController+UIImagePickerControllerDelegate.swift +// SimpleVideoEditor +// +// Created by RenZhu Macro on 2020/7/9. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import Foundation +import AVFoundation +import MobileCoreServices + +func orientationForTrack(asset: AVAsset) -> UIInterfaceOrientation { + let track = asset.tracks(withMediaType: .video).first + guard let txf = track?.preferredTransform else { + return .portrait + } + if txf.a == 0 && txf.b == 1.0 && txf.c == -1.0 && txf.d == 0 { + return .landscapeRight + } else if txf.a == 0 && txf.b == -1.0 && txf.c == 1.0 && txf.d == 0 { + return .landscapeLeft + } else if txf.a == 1.0 && txf.b == 0 && txf.c == 0 && txf.d == 1.0 { + return .portrait + } else if txf.a == -1.0 && txf.b == 0 && txf.c == 0 && txf.d == -1.0 { + return .portraitUpsideDown + } else { + return .unknown + } +} + + +extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + debugPrint("info:", info) + picker.dismiss(animated: true, completion: nil) + guard let mediaType = info[.mediaType] else { + return + } + + if mediaType as! CFString == kUTTypeMovie { + debugPrint("video") + guard let url = info[.mediaURL] as? URL else { + return + } + + let asset = AVAsset(url: url) + let orientation = orientationForTrack(asset: asset) + + debugPrint("asset:", asset) + let item = ResourceItem(asset: asset) + + item.fillType = .aspectToFill + item.orientation = orientation + switch item.orientation { + case .portrait: + break + case .landscapeLeft: + item.rotate = 90.0 + break + case .landscapeRight: + item.rotate = -90.0 + break + case .portraitUpsideDown: + item.rotate = 180.0 + break + default: + break + } + + if picker == self.subPicker { + try? self.videoEditor?.insertOverlayItem(overlayItem: item) + item.roi = CGRect(x: 0.4, y: 0.4, width: 0.8, height: 0.8) + self.subResources.append(item) + } else { + + try? self.videoEditor?.insertItem(videoItem: item) + item.roi = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0) + self.mainResources.append(item) + } + self.rebuildPipeline() + self.subTableView.reloadData() + self.mainTableView.reloadData() + + } else { + debugPrint("unsupported") + return + } + + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + + } + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + + } + + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + + } + + +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+UITableViewDelegate.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+UITableViewDelegate.swift new file mode 100644 index 0000000..85212b8 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController+UITableViewDelegate.swift @@ -0,0 +1,108 @@ +// +// ViewController+UITableViewDelegate.swift +// SimpleVideoEditor +// +// Created by RenZhu Macro on 2020/7/9. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import Foundation +import MetalVideoProcess + +extension ViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + //selected + if tableView == self.mainTableView { + let item = self.mainResources[indexPath.row] + self.mainSelectedItem = item + currentPostion = item.transformFilter?.translation ?? Position(0.0, 0.0) + self.mainEditButton.isEnabled = true + self.mainDeleteButton.isEnabled = true + self.subEditButton.isEnabled = false + self.subDeleteButton.isEnabled = false + self.subSelectedItem = nil + guard let subIndex = self.subTableView.indexPathForSelectedRow else { + return + } + self.subTableView.deselectRow(at: subIndex, animated: true) + + } else { + let item = self.subResources[indexPath.row] + self.subSelectedItem = item + currentPostion = item.transformFilter?.translation ?? Position(0.0, 0.0) + self.mainEditButton.isEnabled = false + self.mainDeleteButton.isEnabled = false + self.subEditButton.isEnabled = true + self.subDeleteButton.isEnabled = true + self.mainSelectedItem = nil + guard let mainIndex = self.mainTableView.indexPathForSelectedRow else { + return + } + self.mainTableView.deselectRow(at: mainIndex, animated: true) + } + } +} + +extension ViewController: TableViewDraggerDelegate { + func dragger(_ dragger: TableViewDragger, moveDraggingAt indexPath: IndexPath, newIndexPath: IndexPath) -> Bool { + if dragger == self.mainDragger! { + let item = self.mainResources[indexPath.row] + self.mainResources.remove(at: indexPath.row) + self.mainResources.insert(item, at: newIndexPath.row) + mainTableView.moveRow(at: indexPath, to: newIndexPath) + } else { + let item = self.subResources[indexPath.row] + self.subResources.remove(at: indexPath.row) + self.subResources.insert(item, at: newIndexPath.row) + subTableView.moveRow(at: indexPath, to: newIndexPath) + } + + + + return true + } + + func dragger(_ dragger: TableViewDragger, didEndDraggingAt indexPath: IndexPath) { + dragger.tableView?.reloadData() + } + + func dragger(_ dragger: TableViewDragger, willEndDraggingAt indexPath: IndexPath) { + self.rebuildPipeline() + } +} + +extension ViewController: TableViewDraggerDataSource { + +} + +extension ViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if self.mainTableView == tableView { + return self.mainResources.count + } else { + return self.subResources.count + } + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if self.mainTableView == tableView { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "mainCell", for: indexPath) as? ResourceItemTableViewCell else { + return UITableViewCell() + } + let item = self.mainResources[indexPath.row] + cell.textLabel?.text = NSString(format: "t:%d s:%@ d:%@", item.trackID, item.startTimeText, item.durationText) as String + return cell + } else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "pipCell", for: indexPath) as? ResourceItemTableViewCell else { + return UITableViewCell() + } + let item = self.subResources[indexPath.row] + cell.textLabel?.text = NSString(format: "t:%d s:%@ d:%@", item.trackID, item.startTimeText, item.durationText) as String + return cell + } + } + + +} + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController.swift new file mode 100644 index 0000000..a97f243 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController.swift @@ -0,0 +1,261 @@ +// +// ViewController.swift +// SimpleVideoTransition +// +// Created by RenZhu Macro on 2020/7/2. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit +import MetalVideoProcess +import AVFoundation + +class ViewController: UIViewController { + + @IBOutlet weak var renderView: MetalVideoProcessRenderView! + @IBOutlet weak var progress: UISlider! + var player: MetalVideoProcessPlayer? + var beauty1: MetalVideoProcessBeautyFilter? + var beauty2: MetalVideoProcessBeautyFilter? + + var blur1: MetalVideoProcessGaussianBlurFilter? + var blur2: MetalVideoProcessGaussianBlurFilter? + + var grayFilter: MetalVideoProcessLuminance? + + var transition: MetalVideoProcessTransition? { + didSet { + if let targets = oldValue?.targets { + + // 因为双向链,需要去除target的sourse, 需调用removeSourceAtIndex, 不建议用removeAll函数 + for target in targets { + for (index, _) in target.0.sources.sources.enumerated() { + target.0.removeSourceAtIndex(UInt(index)) + } + } + + for target in targets { + transition!.addTarget(target.0, trackID: 0) + } + } + + if let sourses = oldValue?.sources { + + for sourse in sourses.sources { + sourse.value --> transition! + } + + for (i, _) in sourses.sources.enumerated() { + oldValue?.removeSourceAtIndex(UInt(i)) + } + } + } + } + + var item1: MetalVideoEditorItem? + var item2: MetalVideoEditorItem? + var transitionTimeRange = CMTimeRange.init() + override func viewDidLoad() { + super.viewDidLoad() + let asset1 = AVAsset(url: Bundle.main.url(forResource: "853", withExtension: "mp4")!) + let asset2 = AVAsset(url: Bundle.main.url(forResource: "cute", withExtension: "mp4")!) + self.item1 = MetalVideoEditorItem(asset: asset1) + self.item2 = MetalVideoEditorItem(asset: asset2) + + guard let item1 = self.item1, let item2 = self.item2 else { + return + } + //set transition before build editor + let transitionDuration = CMTime.init(seconds: 2.0, preferredTimescale: 600) + + item1.videoTransition = TransitionDuration(duration: transitionDuration) + item1.audioTransition = FadeInOutAudioTransition(duration: transitionDuration) + do { + let editor = try MetalVideoEditor(videoItems: [item1, item2], + customVideoCompositorClass: MetalVideoProcessCompositor.self) + + let playerItem = editor.buildPlayerItem() + self.progress.maximumValue = Float(playerItem.duration.seconds) + let player = try MetalVideoProcessPlayer(playerItem: playerItem) + + let beautyFilter1 = MetalVideoProcessBeautyFilter() + beautyFilter1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID) + beautyFilter1.isEnable = false + + let beautyFilter2 = MetalVideoProcessBeautyFilter() + beautyFilter2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID) + beautyFilter2.isEnable = false + + let blurFilter1 = MetalVideoProcessGaussianBlurFilter() + blurFilter1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID) + blurFilter1.isEnable = false + + let blurFilter2 = MetalVideoProcessGaussianBlurFilter() + blurFilter2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID) + blurFilter2.isEnable = false + + let gray = MetalVideoProcessLuminance() + gray.isEnable = false + self.grayFilter = gray + + self.beauty1 = beautyFilter1 + self.beauty2 = beautyFilter2 + + self.blur1 = blurFilter1 + self.blur2 = blurFilter2 + + self.transitionTimeRange = item1.timeRange.intersection(item2.timeRange) + self.transition = MetalVideoProcessFadeTransition() + guard let transition = self.transition else { + return + } + + //注意顺序 第一个视频在前 第二视频在后 + transition.mainTrackIDs.append(item1.trackID) + transition.mainTrackIDs.append(item2.trackID) + + //告知转场的时间 通过item1和item2的intersection计算 + transition.saveUniformSettings(forTimelineRange: transitionTimeRange, trackID: 0) + item1.transitoin = transition + + //Begin build pipeline + player.addTarget(beautyFilter1, atTargetIndex: nil, trackID: item1.trackID, targetTrackId: 0) + player.addTarget(beautyFilter2, atTargetIndex: nil, trackID: item2.trackID, targetTrackId: item2.trackID) + //mapping trackId on mainTrack 0 + + beautyFilter1 --> blurFilter1 --> transition + beautyFilter2 --> blurFilter2 --> transition --> gray --> renderView + //Done + + self.player = player + self.player?.playerDelegate = self + + } catch { + debugPrint("init error") + } + + let segment = UISegmentedControl(frame:CGRect(x: self.view.bounds.width * 0.1, y: 50, width: self.view.bounds.width * 0.8, height: 40)) + + self.view.addSubview(segment) + + segment.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.3) + segment.insertSegment(withTitle: "叠化", at: 0, animated: true) + segment.insertSegment(withTitle: "闪白", at: 1, animated: true) + segment.insertSegment(withTitle: "向下擦除", at: 2, animated: true) + segment.insertSegment(withTitle: "镜像翻转", at: 3, animated: true) + segment.insertSegment(withTitle: "倒影", at: 4, animated: true) + segment.selectedSegmentIndex = 0 + segment.addTarget(self, action: #selector(self.segmentValueChange(_:)), for: UIControl.Event.valueChanged) + } + + + private func resetTransInTrack() { + guard let transition = self.transition, let item1 = self.item1, let item2 = self.item2 else { + return + } + + //注意顺序 第一个视频在前 第二视频在后 + transition.mainTrackIDs.append(item1.trackID) + transition.mainTrackIDs.append(item2.trackID) + + //告知转场的时间 通过item1和item2的intersection计算 + transition.saveUniformSettings(forTimelineRange: transitionTimeRange, trackID:0) + item1.transitoin = transition + } + + @IBAction func segmentValueChange(_ sender: UISegmentedControl) { + guard let transition = self.transition else { + return + } + switch sender.selectedSegmentIndex { + case 0: + self.transition = MetalVideoProcessFadeTransition() + resetTransInTrack() + + break + case 1: + + self.transition = MetalVideoProcessShanBai() + resetTransInTrack() + break + case 2: + + self.transition = MetalVideoProcessEraseDownTransition() + resetTransInTrack() + break + case 3: + + self.transition = MetalVideoProcessMirrorRotateTransition() + resetTransInTrack() + case 4: + + self.transition = MetalVideoProcessReflectTransition() + resetTransInTrack() + break + default: + break + } + } + + @IBAction func play(_ sender: Any) { + self.player?.play() + } + + @IBAction func pause(_ sender: Any) { + self.player?.pause() + } + + @IBAction func progressChanged(_ sender: UISlider) { + let value = sender.value + self.player?.seekTo(time: Float64(value)) + } + + @IBAction func filterOn(_ sender: UISwitch) { + var operation: MetalVideoProcessOperation? + switch sender.tag { + case 0: + operation = self.beauty1 + break + case 1: + operation = self.blur1 + + break + case 2: + operation = self.beauty2 + break + case 3: + operation = self.blur2 + break + case 4: + operation = self.grayFilter + break + default: + break + } + operation?.isEnable = sender.isOn + } +} + +extension ViewController: MetalVideoProcessPlayerDelegate { + func playbackFrameTimeChanged(frameTime time: CMTime, player: AVPlayer) { + DispatchQueue.main.async { + self.progress.value = Float(time.seconds) + } + } + + func playEnded(currentPlayer player: AVPlayer) { + + } + + func finishExport(error: NSError?) { + + } + + func exportProgressChanged(_ progress: Float) { + + } + + +} + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_BACKUP_17810.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_BACKUP_17810.swift new file mode 100644 index 0000000..677877a --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_BACKUP_17810.swift @@ -0,0 +1,506 @@ +// +// ViewController.swift +// SimpleVideoEditor +// +// Created by RenZhu Macro on 2020/7/2. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit +<<<<<<< Updated upstream +import MetalVideoProcess +import AVFoundation + +class ViewController: UIViewController { + + @IBOutlet weak var renderView: MetalVideoProcessRenderView! + @IBOutlet weak var progress: UISlider! + var player: MetalVideoProcessPlayer? + var beauty1: MetalVideoProcessBeautyFilter? + var beauty2: MetalVideoProcessBeautyFilter? + + var blur1: MetalVideoProcessGaussianBlurFilter? + var blur2: MetalVideoProcessGaussianBlurFilter? + + var grayFilter: MetalVideoProcessLuminance? + + override func viewDidLoad() { + super.viewDidLoad() + let asset1 = AVAsset(url: Bundle.main.url(forResource: "853", withExtension: "mp4")!) + let asset2 = AVAsset(url: Bundle.main.url(forResource: "cute", withExtension: "mp4")!) + let item1 = MetalVideoEditorItem(asset: asset1) + let item2 = MetalVideoEditorItem(asset: asset2) + + //set transition before build editor + let transitionDuration = CMTime.init(seconds: 2.0, preferredTimescale: 600) + item1.videoTransition = TransitionDuration(duration: transitionDuration) + item1.audioTransition = FadeInOutAudioTransition(duration: transitionDuration) + do { + let editor = try MetalVideoEditor(videoItems: [item1, item2], + customVideoCompositorClass: MetalVideoProcessCompositor.self) + + let playerItem = editor.buildPlayerItem() + self.progress.maximumValue = Float(playerItem.duration.seconds) + let player = try MetalVideoProcessPlayer(playerItem: playerItem) + + let beautyFilter1 = MetalVideoProcessBeautyFilter() + beautyFilter1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID) + beautyFilter1.isEnable = false + + let beautyFilter2 = MetalVideoProcessBeautyFilter() + beautyFilter2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID) + beautyFilter2.isEnable = false + + let blurFilter1 = MetalVideoProcessGaussianBlurFilter() + blurFilter1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID) + blurFilter1.isEnable = false + + let blurFilter2 = MetalVideoProcessGaussianBlurFilter() + blurFilter2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID) + blurFilter2.isEnable = false + + let gray = MetalVideoProcessLuminance() + gray.isEnable = false + self.grayFilter = gray + + self.beauty1 = beautyFilter1 + self.beauty2 = beautyFilter2 + + self.blur1 = blurFilter1 + self.blur2 = blurFilter2 + + let transitionTimeRange = item1.timeRange.intersection(item2.timeRange) + let fadeTransition = MetalVideoProcessFadeTransition() + + //注意顺序 第一个视频在前 第二视频在后 + fadeTransition.mainTrackIDs.append(item1.trackID) + fadeTransition.mainTrackIDs.append(item2.trackID) + + //告知转场的时间 通过item1和item2的intersection计算 + fadeTransition.saveUniformSettings(forTimelineRange: transitionTimeRange, trackID: 0) + item1.transitoin = fadeTransition + + //Begin build pipeline + player.addTarget(beautyFilter1, atTargetIndex: nil, trackID: item1.trackID, targetTrackId: 0) + player.addTarget(beautyFilter2, atTargetIndex: nil, trackID: item2.trackID, targetTrackId: item2.trackID) + //mapping trackId on mainTrack 0 + + beautyFilter1 --> blurFilter1 --> fadeTransition + beautyFilter2 --> blurFilter2 --> fadeTransition --> gray --> renderView + //Done + + self.player = player + self.player?.playerDelegate = self + + } catch { + debugPrint("init error") + } + } + + @IBAction func play(_ sender: Any) { + self.player?.play() + } + + @IBAction func pause(_ sender: Any) { + self.player?.pause() + } + +======= +import AVFoundation +import MobileCoreServices +import MetalVideoProcess + +class ViewController: UIViewController { + + @IBOutlet weak var renderView: MetalVideoProcessRenderView! + @IBOutlet weak var mainEditButton: UIButton! + @IBOutlet weak var mainDeleteButton: UIButton! + + @IBOutlet weak var subEditButton: UIButton! + @IBOutlet weak var subDeleteButton: UIButton! + + var mainPicker: UIImagePickerController? + var subPicker: UIImagePickerController? + + var mainLayer: MetalVideoProcessBlendFilter? + var player: MetalVideoProcessPlayer? + var movieWriter: MetalVideoProcessMovieWriter? + var videoBackground: MetalVideoProcessBackground = MetalVideoProcessBackground(trackID: 0) + var pop: PopupDialog? + var progressHUD: MBProgressHUD? + + var mainResources: [ResourceItem] = [] + var lastMainLayerItem: ResourceItem? + + @IBOutlet weak var mainTableView: UITableView! + var mainDragger: TableViewDragger? + var videoEditor: MetalVideoEditor? + var mainSelectedItem: ResourceItem? + + var subResources: [ResourceItem] = [] + @IBOutlet weak var subTableView: UITableView! + var subDragger: TableViewDragger? + var subSelectedItem: ResourceItem? + + @IBOutlet weak var progress: UISlider! + + private var rotGes: UIRotationGestureRecognizer? + private var pinchGes: UIPinchGestureRecognizer? + var currentPostion: Position = Position(0.0, 0.0) + + override func viewDidLoad() { + super.viewDidLoad() + self.navigationController?.setNavigationBarHidden(true, animated: false) + + + self.mainTableView.register(ResourceItemTableViewCell.self, forCellReuseIdentifier: "mainCell") + self.mainTableView.allowsSelection = true + self.mainTableView.dataSource = self + self.mainTableView.delegate = self + + self.subTableView.register(ResourceItemTableViewCell.self, forCellReuseIdentifier: "pipCell") + self.subTableView.allowsSelection = true + self.subTableView.dataSource = self + self.subTableView.delegate = self + + self.mainDragger = TableViewDragger(tableView: self.mainTableView) + self.mainDragger?.delegate = self + self.subDragger = TableViewDragger(tableView: self.subTableView) + self.subDragger?.delegate = self + + let asset1 = AVAsset(url: Bundle.main.url(forResource: "cute", withExtension: "mp4")!) + let asset2 = AVAsset(url: Bundle.main.url(forResource: "movie", withExtension: "mov")!) + let sampleMainItem = ResourceItem(asset: asset1) + let sampleSubItem = ResourceItem(asset: asset2) + self.lastMainLayerItem = sampleMainItem + + + let backgroundFrame = self.renderView.frame + let backgroundSize = CGSize(width: backgroundFrame.size.width * 2.0, + height: backgroundFrame.size.height * 2.0) + MetalVideoProcessBackground.canvasSize = backgroundSize + + videoBackground.canvasSizeType = .Type720p + videoBackground.aspectRatioType = .Ratio16_9 + videoBackground.setBackgroundType(type: .Blur) + + self.mainResources.append(sampleMainItem) + self.subResources.append(sampleSubItem) + + + + self.progress.minimumValue = 0.0 + + sampleMainItem.fillType = .aspectToFit + sampleMainItem.roi = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0) + sampleSubItem.fillType = .aspectToFit + sampleSubItem.roi = CGRect(x: 0.5, y: 0.5, width: 0.5, height: 0.5) + self.rebuildPipeline() + self.mainTableView.reloadData() + self.subTableView.reloadData() + + self.renderView.isUserInteractionEnabled = true + + rotGes = UIRotationGestureRecognizer(target: self, + action: #selector(self.rotateAction)) + rotGes!.delegate = self; + pinchGes = UIPinchGestureRecognizer(target: self, action: #selector(self.pinchAction)) + pinchGes!.delegate = self; + let panGes = UIPanGestureRecognizer(target: self, action: #selector(self.panAction)) + panGes.delegate = self + self.renderView.addGestureRecognizer(rotGes!) + self.renderView.addGestureRecognizer(pinchGes!) + self.renderView.addGestureRecognizer(panGes) + } + + @IBAction func changeFillType(_ sender: UIButton) { + let tag = sender.tag + switch tag { + case 0: + self.videoBackground.aspectRatioType = .Ratio16_9 + break + case 1: + self.videoBackground.aspectRatioType = .Ratio9_16 + case 2: + self.videoBackground.aspectRatioType = .Ratio1_1 + case 3: + self.videoBackground.aspectRatioType = .Ratio4_3 + case 4: + self.videoBackground.aspectRatioType = .Ratio3_4 + default: + break + } + } + + @IBAction func play(_ sender: Any) { + self.player?.play() + } + + @IBAction func pause(_ sender: Any) { + self.player?.pause() + } + + + @IBAction func importMain(_ sender: Any) { + let picker = UIImagePickerController() + + picker.delegate = self + picker.sourceType = .photoLibrary + + var pickerTypes: [String] = [] + + pickerTypes.append(kUTTypeMovie as String) + picker.mediaTypes = pickerTypes + + picker.allowsEditing = true + self.navigationController?.present(picker, animated: true, completion: nil) + self.mainPicker = picker + } + + @IBAction func importPIP(_ sender: Any) { + let picker = UIImagePickerController() + + picker.delegate = self + picker.sourceType = .photoLibrary + + var pickerTypes: [String] = [] + + pickerTypes.append(kUTTypeMovie as String) + picker.mediaTypes = pickerTypes + + picker.allowsEditing = true + self.navigationController?.present(picker, animated: true, completion: nil) + self.subPicker = picker + } + +>>>>>>> Stashed changes + @IBAction func progressChanged(_ sender: UISlider) { + let value = sender.value + self.player?.seekTo(time: Float64(value)) + } + +<<<<<<< Updated upstream + @IBAction func filterOn(_ sender: UISwitch) { + var operation: MetalVideoProcessOperation? + switch sender.tag { + case 0: + operation = self.beauty1 + break + case 1: + operation = self.blur1 + + break + case 2: + operation = self.beauty2 + break + case 3: + operation = self.blur2 + break + case 4: + operation = self.grayFilter + break + default: + break + } + operation?.isEnable = sender.isOn + } +} + +extension ViewController: MetalVideoProcessPlayerDelegate { + func playbackFrameTimeChanged(frameTime time: CMTime, player: AVPlayer) { + DispatchQueue.main.async { + self.progress.value = Float(time.seconds) + } + } + + func playEnded(currentPlayer player: AVPlayer) { + + } + + func finishExport(error: NSError?) { + + } + + func exportProgressChanged(_ progress: Float) { + + } + + +======= + @IBAction func editItem(_ sender: UIButton) { + + let resourceEditView = ResourceItemEditView(nibName: "ResourceItemEditView", bundle: nil) + self.pop = PopupDialog(viewController: resourceEditView, buttonAlignment: .horizontal, transitionStyle: .bounceDown, tapGestureDismissal: true, panGestureDismissal: false) + + let btnCancel = CancelButton(title: "Cancel", height: 60) { + self.pop?.viewController.removeFromParent() + self.pop?.removeFromParent() + self.pop = nil + return + } + + let btnOK = DefaultButton(title: "OK", height: 60) { + self.rebuildPipeline() + self.mainTableView.reloadData() + self.subTableView.reloadData() + self.pop?.viewController.removeFromParent() + self.pop?.removeFromParent() + self.pop = nil + return + } + + pop?.addButtons([btnOK, btnCancel]) + if sender == self.mainEditButton { + guard let item = self.mainSelectedItem else { + return + } + self.present(pop! + , animated: true) { + resourceEditView.loadResourceItem(item) + } + } else { + guard let item = self.subSelectedItem else { + return + } + self.present(pop! + , animated: true) { + resourceEditView.loadResourceItem(item, isPipItem: true) + } + } + + } + + @IBAction func deleteItem(_ sender: UIButton) { + + if sender == self.mainDeleteButton { + self.mainResources.removeAll { $0.trackID == self.mainSelectedItem?.trackID ?? 0 } + } else { + self.subResources.removeAll { $0.trackID == self.subSelectedItem?.trackID ?? 0 } + } + self.rebuildPipeline() + self.mainTableView.reloadData() + self.subTableView.reloadData() + self.subEditButton.isEnabled = self.subResources.count > 0 + self.subDeleteButton.isEnabled = self.subResources.count > 0 + + self.mainEditButton.isEnabled = self.mainResources.count > 0 + self.mainDeleteButton.isEnabled = self.mainResources.count > 0 + } + + func rebuildPipeline() { + + self.videoEditor = nil + + //rebuild player + self.player?.suspend() + self.player?.dispose() + self.player?.removeAllTargets() + self.player = nil + + //clear all sources + renderView.sources.sources.removeAll() + + do { + let currentEditor = try MetalVideoEditor(videoItems: self.mainResources, overlayItems: self.subResources) + self.videoEditor = currentEditor + try currentEditor.updateChannel() + print("before build PlayerItem mainItems:", currentEditor.editorItems) + print("before build PlayerItem overlayItems:", currentEditor.overlayItems) + let playerItem = currentEditor.buildPlayerItem() + print("after build PlayerItem mainItems:", currentEditor.editorItems) + print("after build PlayerItem overlayItems:", currentEditor.overlayItems) + + + + self.progress.maximumValue = Float(playerItem.duration.seconds) + let player = try MetalVideoProcessPlayer(playerItem: playerItem) + + let mainLayer = MetalVideoProcessBlendFilter() + mainLayer.debugName = "mainLayer" + var lastTransform: MetalVideoProcessTransformFilter? = nil + for mainItem in self.mainResources { + let transform = MetalVideoProcessTransformFilter() + transform.saveUniformSettings(forTimelineRange: mainItem.timeRange, trackID: mainItem.trackID) + transform.roi = mainItem.roi + transform.debugName = "main transform trackId:\(mainItem.trackID)" + //render blur background with every main track + player.addTarget(videoBackground, atTargetIndex: nil, trackID: mainItem.trackID, targetTrackId: 0) + + mainItem.transformFilter = transform + transform.rotate = mainItem.rotate + transform.translation = mainItem.translation + transform.scale = mainItem.scale + + + mainItem.currentLayer = mainLayer + if let lt = lastTransform { + //link every main transform in mainLayer + lt --> transform + } + lastTransform = transform + } + + // make sure mapping every item‘s trackID on main track to main trackContainer with trackID 0, + // if you don't do this, player can't render every item's texutre to item's target + if let firstTransform = self.mainResources.first?.transformFilter { + self.mainResources.forEach { (item) in + player.addTarget(firstTransform, atTargetIndex: nil, trackID: item.trackID, targetTrackId: 0) + } + } + + + guard let lt = lastTransform else { + // zero items + self.player?.suspend() + self.player?.dispose() + self.player?.removeAllTargets() + self.player = nil + return + } + + videoBackground --> mainLayer + // place lt on videoBackground, secondary source to mainLayer + lt --> mainLayer + + var lastLayer: MetalVideoProcessBlendFilter = mainLayer + + for subItem in self.subResources { + let pipLayer = MetalVideoProcessBlendFilter() + + // render pipLayer in subItem.timeRange + pipLayer.saveUniformSettings(forTimelineRange: subItem.timeRange, trackID: subItem.trackID) + + let transform = MetalVideoProcessTransformFilter() + // transform subItem's texture in subItem.timeRange + transform.saveUniformSettings(forTimelineRange: subItem.timeRange, trackID: subItem.trackID) + transform.roi = subItem.roi + + // create a new targetContainer for subItem & pipLayer + player.addTarget(transform, atTargetIndex: nil, trackID: subItem.trackID, targetTrackId: subItem.trackID) + + subItem.currentLayer = pipLayer + subItem.transformFilter = transform + transform.rotate = subItem.rotate + transform.translation = subItem.translation + transform.scale = subItem.scale + //compositing two layer (warning!!! place transform to secondary source to pipLayer) + lastLayer --> pipLayer + transform --> pipLayer + + + lastLayer = pipLayer + } + + //render the final layer + lastLayer --> renderView + player.playerDelegate = self + self.player = player + + } catch { + + } + } +>>>>>>> Stashed changes +} + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_BASE_17810.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_BASE_17810.swift new file mode 100644 index 0000000..b77c90f --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_BASE_17810.swift @@ -0,0 +1,20 @@ +// +// ViewController.swift +// SimpleVideoTransition +// +// Created by RenZhu Macro on 2020/7/2. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + +} + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_LOCAL_17810.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_LOCAL_17810.swift new file mode 100644 index 0000000..61c2c84 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_LOCAL_17810.swift @@ -0,0 +1,160 @@ +// +// ViewController.swift +// SimpleVideoTransition +// +// Created by RenZhu Macro on 2020/7/2. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit +import MetalVideoProcess +import AVFoundation + +class ViewController: UIViewController { + + @IBOutlet weak var renderView: MetalVideoProcessRenderView! + @IBOutlet weak var progress: UISlider! + var player: MetalVideoProcessPlayer? + var beauty1: MetalVideoProcessBeautyFilter? + var beauty2: MetalVideoProcessBeautyFilter? + + var blur1: MetalVideoProcessGaussianBlurFilter? + var blur2: MetalVideoProcessGaussianBlurFilter? + + var grayFilter: MetalVideoProcessLuminance? + + override func viewDidLoad() { + super.viewDidLoad() + let asset1 = AVAsset(url: Bundle.main.url(forResource: "853", withExtension: "mp4")!) + let asset2 = AVAsset(url: Bundle.main.url(forResource: "cute", withExtension: "mp4")!) + let item1 = MetalVideoEditorItem(asset: asset1) + let item2 = MetalVideoEditorItem(asset: asset2) + + //set transition before build editor + let transitionDuration = CMTime.init(seconds: 2.0, preferredTimescale: 600) + item1.videoTransition = TransitionDuration(duration: transitionDuration) + item1.audioTransition = FadeInOutAudioTransition(duration: transitionDuration) + do { + let editor = try MetalVideoEditor(videoItems: [item1, item2], + customVideoCompositorClass: MetalVideoProcessCompositor.self) + + let playerItem = editor.buildPlayerItem() + self.progress.maximumValue = Float(playerItem.duration.seconds) + let player = try MetalVideoProcessPlayer(playerItem: playerItem) + + let beautyFilter1 = MetalVideoProcessBeautyFilter() + beautyFilter1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID) + beautyFilter1.isEnable = false + + let beautyFilter2 = MetalVideoProcessBeautyFilter() + beautyFilter2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID) + beautyFilter2.isEnable = false + + let blurFilter1 = MetalVideoProcessGaussianBlurFilter() + blurFilter1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID) + blurFilter1.isEnable = false + + let blurFilter2 = MetalVideoProcessGaussianBlurFilter() + blurFilter2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID) + blurFilter2.isEnable = false + + let gray = MetalVideoProcessLuminance() + gray.isEnable = false + self.grayFilter = gray + + self.beauty1 = beautyFilter1 + self.beauty2 = beautyFilter2 + + self.blur1 = blurFilter1 + self.blur2 = blurFilter2 + + let transitionTimeRange = item1.timeRange.intersection(item2.timeRange) + let fadeTransition = MetalVideoProcessFadeTransition() + + //注意顺序 第一个视频在前 第二视频在后 + fadeTransition.mainTrackIDs.append(item1.trackID) + fadeTransition.mainTrackIDs.append(item2.trackID) + + //告知转场的时间 通过item1和item2的intersection计算 + fadeTransition.saveUniformSettings(forTimelineRange: transitionTimeRange, trackID: 0) + item1.transitoin = fadeTransition + + //Begin build pipeline + player.addTarget(beautyFilter1, atTargetIndex: nil, trackID: item1.trackID, targetTrackId: 0) + player.addTarget(beautyFilter2, atTargetIndex: nil, trackID: item2.trackID, targetTrackId: item2.trackID) + //mapping trackId on mainTrack 0 + + beautyFilter1 --> blurFilter1 --> fadeTransition + beautyFilter2 --> blurFilter2 --> fadeTransition --> gray --> renderView + //Done + + self.player = player + self.player?.playerDelegate = self + + } catch { + debugPrint("init error") + } + } + + @IBAction func play(_ sender: Any) { + self.player?.play() + } + + @IBAction func pause(_ sender: Any) { + self.player?.pause() + } + + @IBAction func progressChanged(_ sender: UISlider) { + let value = sender.value + self.player?.seekTo(time: Float64(value)) + } + + @IBAction func filterOn(_ sender: UISwitch) { + var operation: MetalVideoProcessOperation? + switch sender.tag { + case 0: + operation = self.beauty1 + break + case 1: + operation = self.blur1 + + break + case 2: + operation = self.beauty2 + break + case 3: + operation = self.blur2 + break + case 4: + operation = self.grayFilter + break + default: + break + } + operation?.isEnable = sender.isOn + } +} + +extension ViewController: MetalVideoProcessPlayerDelegate { + func playbackFrameTimeChanged(frameTime time: CMTime, player: AVPlayer) { + DispatchQueue.main.async { + self.progress.value = Float(time.seconds) + } + } + + func playEnded(currentPlayer player: AVPlayer) { + + } + + func finishExport(error: NSError?) { + + } + + func exportProgressChanged(_ progress: Float) { + + } + + +} + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_REMOTE_17810.swift b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_REMOTE_17810.swift new file mode 100644 index 0000000..b9f205d --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/ViewController_REMOTE_17810.swift @@ -0,0 +1,356 @@ +// +// ViewController.swift +// SimpleVideoEditor +// +// Created by RenZhu Macro on 2020/7/2. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit +import AVFoundation +import MobileCoreServices +import MetalVideoProcess + +class ViewController: UIViewController { + + @IBOutlet weak var renderView: MetalVideoProcessRenderView! + @IBOutlet weak var mainEditButton: UIButton! + @IBOutlet weak var mainDeleteButton: UIButton! + + @IBOutlet weak var subEditButton: UIButton! + @IBOutlet weak var subDeleteButton: UIButton! + + var mainPicker: UIImagePickerController? + var subPicker: UIImagePickerController? + + var mainLayer: MetalVideoProcessBlendFilter? + var player: MetalVideoProcessPlayer? + var movieWriter: MetalVideoProcessMovieWriter? + var videoBackground: MetalVideoProcessBackground = MetalVideoProcessBackground(trackID: 0) + var pop: PopupDialog? + var progressHUD: MBProgressHUD? + + var mainResources: [ResourceItem] = [] + var lastMainLayerItem: ResourceItem? + + @IBOutlet weak var mainTableView: UITableView! + var mainDragger: TableViewDragger? + var videoEditor: MetalVideoEditor? + var mainSelectedItem: ResourceItem? + + var subResources: [ResourceItem] = [] + @IBOutlet weak var subTableView: UITableView! + var subDragger: TableViewDragger? + var subSelectedItem: ResourceItem? + + @IBOutlet weak var progress: UISlider! + + private var rotGes: UIRotationGestureRecognizer? + private var pinchGes: UIPinchGestureRecognizer? + var currentPostion: Position = Position(0.0, 0.0) + + override func viewDidLoad() { + super.viewDidLoad() + self.navigationController?.setNavigationBarHidden(true, animated: false) + + + self.mainTableView.register(ResourceItemTableViewCell.self, forCellReuseIdentifier: "mainCell") + self.mainTableView.allowsSelection = true + self.mainTableView.dataSource = self + self.mainTableView.delegate = self + + self.subTableView.register(ResourceItemTableViewCell.self, forCellReuseIdentifier: "pipCell") + self.subTableView.allowsSelection = true + self.subTableView.dataSource = self + self.subTableView.delegate = self + + self.mainDragger = TableViewDragger(tableView: self.mainTableView) + self.mainDragger?.delegate = self + self.subDragger = TableViewDragger(tableView: self.subTableView) + self.subDragger?.delegate = self + + let asset1 = AVAsset(url: Bundle.main.url(forResource: "cute", withExtension: "mp4")!) + let asset2 = AVAsset(url: Bundle.main.url(forResource: "movie", withExtension: "mov")!) + let sampleMainItem = ResourceItem(asset: asset1) + let sampleSubItem = ResourceItem(asset: asset2) + self.lastMainLayerItem = sampleMainItem + + + let backgroundFrame = self.renderView.frame + let backgroundSize = CGSize(width: backgroundFrame.size.width * 2.0, + height: backgroundFrame.size.height * 2.0) + MetalVideoProcessBackground.canvasSize = backgroundSize + + videoBackground.canvasSizeType = .Type720p + videoBackground.aspectRatioType = .Ratio16_9 + videoBackground.setBackgroundType(type: .Blur) + + self.mainResources.append(sampleMainItem) + self.subResources.append(sampleSubItem) + + + + self.progress.minimumValue = 0.0 + + sampleMainItem.fillType = .aspectToFit + sampleMainItem.roi = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0) + sampleSubItem.fillType = .aspectToFit + sampleSubItem.roi = CGRect(x: 0.5, y: 0.5, width: 0.5, height: 0.5) + self.rebuildPipeline() + self.mainTableView.reloadData() + self.subTableView.reloadData() + + self.renderView.isUserInteractionEnabled = true + + rotGes = UIRotationGestureRecognizer(target: self, + action: #selector(self.rotateAction)) + rotGes!.delegate = self; + pinchGes = UIPinchGestureRecognizer(target: self, action: #selector(self.pinchAction)) + pinchGes!.delegate = self; + let panGes = UIPanGestureRecognizer(target: self, action: #selector(self.panAction)) + panGes.delegate = self + self.renderView.addGestureRecognizer(rotGes!) + self.renderView.addGestureRecognizer(pinchGes!) + self.renderView.addGestureRecognizer(panGes) + } + + @IBAction func changeFillType(_ sender: UIButton) { + let tag = sender.tag + switch tag { + case 0: + self.videoBackground.aspectRatioType = .Ratio16_9 + break + case 1: + self.videoBackground.aspectRatioType = .Ratio9_16 + case 2: + self.videoBackground.aspectRatioType = .Ratio1_1 + case 3: + self.videoBackground.aspectRatioType = .Ratio4_3 + case 4: + self.videoBackground.aspectRatioType = .Ratio3_4 + default: + break + } + } + + @IBAction func play(_ sender: Any) { + self.player?.play() + } + + @IBAction func pause(_ sender: Any) { + self.player?.pause() + } + + + @IBAction func importMain(_ sender: Any) { + let picker = UIImagePickerController() + + picker.delegate = self + picker.sourceType = .photoLibrary + + var pickerTypes: [String] = [] + + pickerTypes.append(kUTTypeMovie as String) + picker.mediaTypes = pickerTypes + + picker.allowsEditing = true + self.navigationController?.present(picker, animated: true, completion: nil) + self.mainPicker = picker + } + + @IBAction func importPIP(_ sender: Any) { + let picker = UIImagePickerController() + + picker.delegate = self + picker.sourceType = .photoLibrary + + var pickerTypes: [String] = [] + + pickerTypes.append(kUTTypeMovie as String) + picker.mediaTypes = pickerTypes + + picker.allowsEditing = true + self.navigationController?.present(picker, animated: true, completion: nil) + self.subPicker = picker + } + + @IBAction func progressChanged(_ sender: UISlider) { + let value = sender.value + self.player?.seekTo(time: Float64(value)) + } + + @IBAction func editItem(_ sender: UIButton) { + + let resourceEditView = ResourceItemEditView(nibName: "ResourceItemEditView", bundle: nil) + self.pop = PopupDialog(viewController: resourceEditView, buttonAlignment: .horizontal, transitionStyle: .bounceDown, tapGestureDismissal: true, panGestureDismissal: false) + + let btnCancel = CancelButton(title: "Cancel", height: 60) { + self.pop?.viewController.removeFromParent() + self.pop?.removeFromParent() + self.pop = nil + return + } + + let btnOK = DefaultButton(title: "OK", height: 60) { + self.rebuildPipeline() + self.mainTableView.reloadData() + self.subTableView.reloadData() + self.pop?.viewController.removeFromParent() + self.pop?.removeFromParent() + self.pop = nil + return + } + + pop?.addButtons([btnOK, btnCancel]) + if sender == self.mainEditButton { + guard let item = self.mainSelectedItem else { + return + } + self.present(pop! + , animated: true) { + resourceEditView.loadResourceItem(item) + } + } else { + guard let item = self.subSelectedItem else { + return + } + self.present(pop! + , animated: true) { + resourceEditView.loadResourceItem(item, isPipItem: true) + } + } + + } + + @IBAction func deleteItem(_ sender: UIButton) { + + if sender == self.mainDeleteButton { + self.mainResources.removeAll { $0.trackID == self.mainSelectedItem?.trackID ?? 0 } + } else { + self.subResources.removeAll { $0.trackID == self.subSelectedItem?.trackID ?? 0 } + } + self.rebuildPipeline() + self.mainTableView.reloadData() + self.subTableView.reloadData() + self.subEditButton.isEnabled = self.subResources.count > 0 + self.subDeleteButton.isEnabled = self.subResources.count > 0 + + self.mainEditButton.isEnabled = self.mainResources.count > 0 + self.mainDeleteButton.isEnabled = self.mainResources.count > 0 + } + + func rebuildPipeline() { + + self.videoEditor = nil + + //rebuild player + self.player?.suspend() + self.player?.dispose() + self.player?.removeAllTargets() + self.player = nil + + //clear all sources + renderView.sources.sources.removeAll() + + do { + let currentEditor = try MetalVideoEditor(videoItems: self.mainResources, overlayItems: self.subResources) + self.videoEditor = currentEditor + try currentEditor.updateChannel() + print("before build PlayerItem mainItems:", currentEditor.editorItems) + print("before build PlayerItem overlayItems:", currentEditor.overlayItems) + let playerItem = currentEditor.buildPlayerItem() + print("after build PlayerItem mainItems:", currentEditor.editorItems) + print("after build PlayerItem overlayItems:", currentEditor.overlayItems) + + + + self.progress.maximumValue = Float(playerItem.duration.seconds) + let player = try MetalVideoProcessPlayer(playerItem: playerItem) + + let mainLayer = MetalVideoProcessBlendFilter() + mainLayer.debugName = "mainLayer" + var lastTransform: MetalVideoProcessTransformFilter? = nil + for mainItem in self.mainResources { + let transform = MetalVideoProcessTransformFilter() + transform.saveUniformSettings(forTimelineRange: mainItem.timeRange, trackID: mainItem.trackID) + transform.roi = mainItem.roi + transform.debugName = "main transform trackId:\(mainItem.trackID)" + //render blur background with every main track + player.addTarget(videoBackground, atTargetIndex: nil, trackID: mainItem.trackID, targetTrackId: 0) + + mainItem.transformFilter = transform + transform.rotate = mainItem.rotate + transform.translation = mainItem.translation + transform.scale = mainItem.scale + + + mainItem.currentLayer = mainLayer + if let lt = lastTransform { + //link every main transform in mainLayer + lt --> transform + } + lastTransform = transform + } + + // make sure mapping every item‘s trackID on main track to main trackContainer with trackID 0, + // if you don't do this, player can't render every item's texutre to item's target + if let firstTransform = self.mainResources.first?.transformFilter { + self.mainResources.forEach { (item) in + player.addTarget(firstTransform, atTargetIndex: nil, trackID: item.trackID, targetTrackId: 0) + } + } + + + guard let lt = lastTransform else { + // zero items + self.player?.suspend() + self.player?.dispose() + self.player?.removeAllTargets() + self.player = nil + return + } + + videoBackground --> mainLayer + // place lt on videoBackground, secondary source to mainLayer + lt --> mainLayer + + var lastLayer: MetalVideoProcessBlendFilter = mainLayer + + for subItem in self.subResources { + let pipLayer = MetalVideoProcessBlendFilter() + + // render pipLayer in subItem.timeRange + pipLayer.saveUniformSettings(forTimelineRange: subItem.timeRange, trackID: subItem.trackID) + + let transform = MetalVideoProcessTransformFilter() + // transform subItem's texture in subItem.timeRange + transform.saveUniformSettings(forTimelineRange: subItem.timeRange, trackID: subItem.trackID) + transform.roi = subItem.roi + + // create a new targetContainer for subItem & pipLayer + player.addTarget(transform, atTargetIndex: nil, trackID: subItem.trackID, targetTrackId: subItem.trackID) + + subItem.currentLayer = pipLayer + subItem.transformFilter = transform + transform.rotate = subItem.rotate + transform.translation = subItem.translation + transform.scale = subItem.scale + //compositing two layer (warning!!! place transform to secondary source to pipLayer) + lastLayer --> pipLayer + transform --> pipLayer + + + lastLayer = pipLayer + } + + //render the final layer + lastLayer --> renderView + player.playerDelegate = self + self.player = player + + } catch { + + } + } +} + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/cute.mp4 b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/cute.mp4 new file mode 100644 index 0000000..8f5f51e Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/cute.mp4 differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/movie.mov b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/movie.mov new file mode 100644 index 0000000..e252a23 Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransition/movie.mov differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.pbxproj b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.pbxproj new file mode 100644 index 0000000..9ac4918 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.pbxproj @@ -0,0 +1,415 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 6A3E708F24C067C3009A0AC9 /* 853.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 6A3E708C24C067C3009A0AC9 /* 853.mp4 */; }; + 6A3E709024C067C3009A0AC9 /* cute.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 6A3E708E24C067C3009A0AC9 /* cute.mp4 */; }; + 6A3E709224C067DC009A0AC9 /* MetalVideoProcess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A3E705724BD5274009A0AC9 /* MetalVideoProcess.framework */; }; + 6A3E709324C067DC009A0AC9 /* MetalVideoProcess.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6A3E705724BD5274009A0AC9 /* MetalVideoProcess.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 6ADB07A224ADDE450010A817 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADB07A124ADDE450010A817 /* AppDelegate.swift */; }; + 6ADB07A424ADDE450010A817 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADB07A324ADDE450010A817 /* SceneDelegate.swift */; }; + 6ADB07A624ADDE450010A817 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADB07A524ADDE450010A817 /* ViewController.swift */; }; + 6ADB07A924ADDE450010A817 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6ADB07A724ADDE450010A817 /* Main.storyboard */; }; + 6ADB07AB24ADDE460010A817 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6ADB07AA24ADDE460010A817 /* Assets.xcassets */; }; + 6ADB07AE24ADDE460010A817 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6ADB07AC24ADDE460010A817 /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 6A3E705624BD5274009A0AC9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6A3E705224BD5274009A0AC9 /* MetalVideoProcess.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 6ADB062424AD885F0010A817; + remoteInfo = MetalVideoProcess; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 6A3E709424C067DC009A0AC9 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 6A3E709324C067DC009A0AC9 /* MetalVideoProcess.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 6A3E705224BD5274009A0AC9 /* MetalVideoProcess.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MetalVideoProcess.xcodeproj; path = ../../MetalVideoProcess.xcodeproj; sourceTree = ""; }; + 6A3E708C24C067C3009A0AC9 /* 853.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = 853.mp4; sourceTree = ""; }; + 6A3E708E24C067C3009A0AC9 /* cute.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = cute.mp4; sourceTree = ""; }; + 6ADB079E24ADDE450010A817 /* SimpleVideoTransitionSwitch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleVideoTransitionSwitch.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6ADB07A124ADDE450010A817 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 6ADB07A324ADDE450010A817 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 6ADB07A524ADDE450010A817 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 6ADB07A824ADDE450010A817 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 6ADB07AA24ADDE460010A817 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6ADB07AD24ADDE460010A817 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 6ADB07AF24ADDE460010A817 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6ADB079B24ADDE450010A817 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6A3E709224C067DC009A0AC9 /* MetalVideoProcess.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 6A3E705324BD5274009A0AC9 /* Products */ = { + isa = PBXGroup; + children = ( + 6A3E705724BD5274009A0AC9 /* MetalVideoProcess.framework */, + ); + name = Products; + sourceTree = ""; + }; + 6A3E709124C067DC009A0AC9 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 6ADB079524ADDE450010A817 = { + isa = PBXGroup; + children = ( + 6A3E705224BD5274009A0AC9 /* MetalVideoProcess.xcodeproj */, + 6ADB07A024ADDE450010A817 /* SimpleVideoTransition */, + 6ADB079F24ADDE450010A817 /* Products */, + 6A3E709124C067DC009A0AC9 /* Frameworks */, + ); + sourceTree = ""; + }; + 6ADB079F24ADDE450010A817 /* Products */ = { + isa = PBXGroup; + children = ( + 6ADB079E24ADDE450010A817 /* SimpleVideoTransitionSwitch.app */, + ); + name = Products; + sourceTree = ""; + }; + 6ADB07A024ADDE450010A817 /* SimpleVideoTransition */ = { + isa = PBXGroup; + children = ( + 6A3E708C24C067C3009A0AC9 /* 853.mp4 */, + 6A3E708E24C067C3009A0AC9 /* cute.mp4 */, + 6ADB07A124ADDE450010A817 /* AppDelegate.swift */, + 6ADB07A324ADDE450010A817 /* SceneDelegate.swift */, + 6ADB07A524ADDE450010A817 /* ViewController.swift */, + 6ADB07A724ADDE450010A817 /* Main.storyboard */, + 6ADB07AA24ADDE460010A817 /* Assets.xcassets */, + 6ADB07AC24ADDE460010A817 /* LaunchScreen.storyboard */, + 6ADB07AF24ADDE460010A817 /* Info.plist */, + ); + path = SimpleVideoTransition; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6ADB079D24ADDE450010A817 /* SimpleVideoTransitionSwitch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6ADB07B224ADDE460010A817 /* Build configuration list for PBXNativeTarget "SimpleVideoTransitionSwitch" */; + buildPhases = ( + 6ADB079A24ADDE450010A817 /* Sources */, + 6ADB079B24ADDE450010A817 /* Frameworks */, + 6ADB079C24ADDE450010A817 /* Resources */, + 6A3E709424C067DC009A0AC9 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SimpleVideoTransitionSwitch; + productName = SimpleVideoTransition; + productReference = 6ADB079E24ADDE450010A817 /* SimpleVideoTransitionSwitch.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6ADB079624ADDE450010A817 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = "RenZhu Macro"; + TargetAttributes = { + 6ADB079D24ADDE450010A817 = { + CreatedOnToolsVersion = 11.5; + }; + }; + }; + buildConfigurationList = 6ADB079924ADDE450010A817 /* Build configuration list for PBXProject "SimpleVideoTransitionSwitch" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6ADB079524ADDE450010A817; + productRefGroup = 6ADB079F24ADDE450010A817 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 6A3E705324BD5274009A0AC9 /* Products */; + ProjectRef = 6A3E705224BD5274009A0AC9 /* MetalVideoProcess.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 6ADB079D24ADDE450010A817 /* SimpleVideoTransitionSwitch */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 6A3E705724BD5274009A0AC9 /* MetalVideoProcess.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MetalVideoProcess.framework; + remoteRef = 6A3E705624BD5274009A0AC9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 6ADB079C24ADDE450010A817 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6ADB07AE24ADDE460010A817 /* LaunchScreen.storyboard in Resources */, + 6ADB07AB24ADDE460010A817 /* Assets.xcassets in Resources */, + 6A3E708F24C067C3009A0AC9 /* 853.mp4 in Resources */, + 6A3E709024C067C3009A0AC9 /* cute.mp4 in Resources */, + 6ADB07A924ADDE450010A817 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6ADB079A24ADDE450010A817 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6ADB07A624ADDE450010A817 /* ViewController.swift in Sources */, + 6ADB07A224ADDE450010A817 /* AppDelegate.swift in Sources */, + 6ADB07A424ADDE450010A817 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 6ADB07A724ADDE450010A817 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6ADB07A824ADDE450010A817 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 6ADB07AC24ADDE460010A817 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6ADB07AD24ADDE460010A817 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 6ADB07B024ADDE460010A817 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 6ADB07B124ADDE460010A817 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6ADB07B324ADDE460010A817 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = RYRPKMVKDL; + INFOPLIST_FILE = SimpleVideoTransition/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.wangrenzhu.SimpleVideoTransition; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6ADB07B424ADDE460010A817 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = RYRPKMVKDL; + INFOPLIST_FILE = SimpleVideoTransition/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.wangrenzhu.SimpleVideoTransition; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6ADB079924ADDE450010A817 /* Build configuration list for PBXProject "SimpleVideoTransitionSwitch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6ADB07B024ADDE460010A817 /* Debug */, + 6ADB07B124ADDE460010A817 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6ADB07B224ADDE460010A817 /* Build configuration list for PBXNativeTarget "SimpleVideoTransitionSwitch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6ADB07B324ADDE460010A817 /* Debug */, + 6ADB07B424ADDE460010A817 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6ADB079624ADDE450010A817 /* Project object */; +} diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..830898d --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/xcuserdata/renzhumacro.xcuserdatad/UserInterfaceState.xcuserstate b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/xcuserdata/renzhumacro.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..6440c8f Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/xcuserdata/renzhumacro.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/xcuserdata/ruanshengqiang.xcuserdatad/UserInterfaceState.xcuserstate b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/xcuserdata/ruanshengqiang.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..f500116 Binary files /dev/null and b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/project.xcworkspace/xcuserdata/ruanshengqiang.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/xcshareddata/xcschemes/SimpleVideoTransition.xcscheme b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/xcshareddata/xcschemes/SimpleVideoTransition.xcscheme new file mode 100644 index 0000000..e30420a --- /dev/null +++ b/Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/xcshareddata/xcschemes/SimpleVideoTransition.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MetalVideoProcess.xcodeproj/project.pbxproj b/MetalVideoProcess.xcodeproj/project.pbxproj index efd2d9b..be2e880 100644 --- a/MetalVideoProcess.xcodeproj/project.pbxproj +++ b/MetalVideoProcess.xcodeproj/project.pbxproj @@ -10,6 +10,10 @@ 6A3E705924BD9099009A0AC9 /* MetalVideoProcessTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3E705824BD9099009A0AC9 /* MetalVideoProcessTransition.swift */; }; 6A3E708824C0448B009A0AC9 /* MetalVideoProcessFadeTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3E708724C0448B009A0AC9 /* MetalVideoProcessFadeTransition.swift */; }; 6A3E708A24C044A0009A0AC9 /* MetalVideoProcessFadeTransition.metal in Sources */ = {isa = PBXBuildFile; fileRef = 6A3E708924C044A0009A0AC9 /* MetalVideoProcessFadeTransition.metal */; }; + 6A874BC224C594EC00ADFD8A /* MetalVideoProcessMotion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A874BC124C594EC00ADFD8A /* MetalVideoProcessMotion.swift */; }; + 6A874BD024C6E1C200ADFD8A /* MetalVideoProcessFadeInMotion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A874BCF24C6E1C200ADFD8A /* MetalVideoProcessFadeInMotion.swift */; }; + 6A874BD424C6F98100ADFD8A /* MetalVideoProcessFadeMotion.metal in Sources */ = {isa = PBXBuildFile; fileRef = 6A874BD324C6F98100ADFD8A /* MetalVideoProcessFadeMotion.metal */; }; + 6A874BD724C6FD5100ADFD8A /* MetalVideoProcessFadeOutMotion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A874BD624C6FD5100ADFD8A /* MetalVideoProcessFadeOutMotion.swift */; }; 6AA561D324BC57F3004894F7 /* MetalVideoProcessLuminance.metal in Sources */ = {isa = PBXBuildFile; fileRef = 6AA561D124BC57F3004894F7 /* MetalVideoProcessLuminance.metal */; }; 6AA561D424BC57F3004894F7 /* MetalVideoProcessLuminance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AA561D224BC57F3004894F7 /* MetalVideoProcessLuminance.swift */; }; 6ADB062924AD885F0010A817 /* MetalVideoProcess.h in Headers */ = {isa = PBXBuildFile; fileRef = 6ADB062724AD885F0010A817 /* MetalVideoProcess.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -83,6 +87,12 @@ 6ADB072A24ADCC250010A817 /* MetalVideoProcessColorFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADB072724ADCC250010A817 /* MetalVideoProcessColorFilter.swift */; }; 6ADB072B24ADCC250010A817 /* MetalVideoProcessColorFilter.metal in Sources */ = {isa = PBXBuildFile; fileRef = 6ADB072824ADCC250010A817 /* MetalVideoProcessColorFilter.metal */; }; 6ADB07E124AEDC9B0010A817 /* default.metal in Sources */ = {isa = PBXBuildFile; fileRef = 6ADB07E024AEDC9B0010A817 /* default.metal */; }; + AB17C21024C17D770023C974 /* MetalVideoProcessEraseDownTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB17C20E24C17D770023C974 /* MetalVideoProcessEraseDownTransition.swift */; }; + AB17C21124C17D770023C974 /* MetalVideoProcessEraseDownTransition.metal in Sources */ = {isa = PBXBuildFile; fileRef = AB17C20F24C17D770023C974 /* MetalVideoProcessEraseDownTransition.metal */; }; + AB17C21424C182780023C974 /* MetalVideoProcessMirrorRotateTransition.metal in Sources */ = {isa = PBXBuildFile; fileRef = AB17C21224C182780023C974 /* MetalVideoProcessMirrorRotateTransition.metal */; }; + AB17C21524C182780023C974 /* MetalVideoProcessMirrorRotateTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB17C21324C182780023C974 /* MetalVideoProcessMirrorRotateTransition.swift */; }; + AB17C21824C5737B0023C974 /* MetalVideoProcessReflectTransition.metal in Sources */ = {isa = PBXBuildFile; fileRef = AB17C21624C5737B0023C974 /* MetalVideoProcessReflectTransition.metal */; }; + AB17C21924C5737B0023C974 /* MetalVideoProcessReflectTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB17C21724C5737B0023C974 /* MetalVideoProcessReflectTransition.swift */; }; AB64836224C03E6200A0266B /* MetalVideoProcessShanBaiTrans.metal in Sources */ = {isa = PBXBuildFile; fileRef = AB64836024C03E6200A0266B /* MetalVideoProcessShanBaiTrans.metal */; }; AB64836324C03E6200A0266B /* MetalVideoProcessShanBaiTrans.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB64836124C03E6200A0266B /* MetalVideoProcessShanBaiTrans.swift */; }; /* End PBXBuildFile section */ @@ -91,6 +101,10 @@ 6A3E705824BD9099009A0AC9 /* MetalVideoProcessTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalVideoProcessTransition.swift; sourceTree = ""; }; 6A3E708724C0448B009A0AC9 /* MetalVideoProcessFadeTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalVideoProcessFadeTransition.swift; sourceTree = ""; }; 6A3E708924C044A0009A0AC9 /* MetalVideoProcessFadeTransition.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = MetalVideoProcessFadeTransition.metal; sourceTree = ""; }; + 6A874BC124C594EC00ADFD8A /* MetalVideoProcessMotion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalVideoProcessMotion.swift; sourceTree = ""; }; + 6A874BCF24C6E1C200ADFD8A /* MetalVideoProcessFadeInMotion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalVideoProcessFadeInMotion.swift; sourceTree = ""; }; + 6A874BD324C6F98100ADFD8A /* MetalVideoProcessFadeMotion.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = MetalVideoProcessFadeMotion.metal; sourceTree = ""; }; + 6A874BD624C6FD5100ADFD8A /* MetalVideoProcessFadeOutMotion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalVideoProcessFadeOutMotion.swift; sourceTree = ""; }; 6AA561D124BC57F3004894F7 /* MetalVideoProcessLuminance.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = MetalVideoProcessLuminance.metal; sourceTree = ""; }; 6AA561D224BC57F3004894F7 /* MetalVideoProcessLuminance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalVideoProcessLuminance.swift; sourceTree = ""; }; 6ADB062424AD885F0010A817 /* MetalVideoProcess.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MetalVideoProcess.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -167,6 +181,12 @@ 6ADB072824ADCC250010A817 /* MetalVideoProcessColorFilter.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = MetalVideoProcessColorFilter.metal; sourceTree = ""; }; 6ADB07D524AED1260010A817 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 6ADB07E024AEDC9B0010A817 /* default.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = default.metal; sourceTree = ""; }; + AB17C20E24C17D770023C974 /* MetalVideoProcessEraseDownTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalVideoProcessEraseDownTransition.swift; sourceTree = ""; }; + AB17C20F24C17D770023C974 /* MetalVideoProcessEraseDownTransition.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = MetalVideoProcessEraseDownTransition.metal; sourceTree = ""; }; + AB17C21224C182780023C974 /* MetalVideoProcessMirrorRotateTransition.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = MetalVideoProcessMirrorRotateTransition.metal; sourceTree = ""; }; + AB17C21324C182780023C974 /* MetalVideoProcessMirrorRotateTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalVideoProcessMirrorRotateTransition.swift; sourceTree = ""; }; + AB17C21624C5737B0023C974 /* MetalVideoProcessReflectTransition.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = MetalVideoProcessReflectTransition.metal; sourceTree = ""; }; + AB17C21724C5737B0023C974 /* MetalVideoProcessReflectTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalVideoProcessReflectTransition.swift; sourceTree = ""; }; AB64836024C03E6200A0266B /* MetalVideoProcessShanBaiTrans.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = MetalVideoProcessShanBaiTrans.metal; sourceTree = ""; }; AB64836124C03E6200A0266B /* MetalVideoProcessShanBaiTrans.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalVideoProcessShanBaiTrans.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -182,6 +202,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 6A874BCE24C6E10500ADFD8A /* Motions */ = { + isa = PBXGroup; + children = ( + 6A874BCF24C6E1C200ADFD8A /* MetalVideoProcessFadeInMotion.swift */, + 6A874BD324C6F98100ADFD8A /* MetalVideoProcessFadeMotion.metal */, + 6A874BD624C6FD5100ADFD8A /* MetalVideoProcessFadeOutMotion.swift */, + ); + path = Motions; + sourceTree = ""; + }; 6ADB061A24AD885F0010A817 = { isa = PBXGroup; children = ( @@ -374,8 +404,9 @@ 6ADB06E124ADBB1C0010A817 /* VideoProcess */ = { isa = PBXGroup; children = ( - AB64835724C03CEA00A0266B /* Transition */, 6ADB071E24ADC93D0010A817 /* Base */, + 6A874BCE24C6E10500ADFD8A /* Motions */, + AB64835724C03CEA00A0266B /* Transitions */, 6ADB071B24ADC4FB0010A817 /* Filters */, 6ADB070B24ADC4DC0010A817 /* MetalVideoProcessPlayer.swift */, 6ADB070C24ADC4DC0010A817 /* MetalVideoProcessPlayer+CompositorDelegate.swift */, @@ -422,19 +453,26 @@ 6ADB071C24ADC8750010A817 /* MetalVideoProcessCompositor.swift */, 6ADB070E24ADC4DC0010A817 /* MetalVideoProcessCompositionRequestCache.swift */, 6A3E705824BD9099009A0AC9 /* MetalVideoProcessTransition.swift */, + 6A874BC124C594EC00ADFD8A /* MetalVideoProcessMotion.swift */, ); path = Base; sourceTree = ""; }; - AB64835724C03CEA00A0266B /* Transition */ = { + AB64835724C03CEA00A0266B /* Transitions */ = { isa = PBXGroup; children = ( + AB17C21624C5737B0023C974 /* MetalVideoProcessReflectTransition.metal */, + AB17C21724C5737B0023C974 /* MetalVideoProcessReflectTransition.swift */, + AB17C21224C182780023C974 /* MetalVideoProcessMirrorRotateTransition.metal */, + AB17C21324C182780023C974 /* MetalVideoProcessMirrorRotateTransition.swift */, + AB17C20F24C17D770023C974 /* MetalVideoProcessEraseDownTransition.metal */, + AB17C20E24C17D770023C974 /* MetalVideoProcessEraseDownTransition.swift */, AB64836024C03E6200A0266B /* MetalVideoProcessShanBaiTrans.metal */, AB64836124C03E6200A0266B /* MetalVideoProcessShanBaiTrans.swift */, 6A3E708724C0448B009A0AC9 /* MetalVideoProcessFadeTransition.swift */, 6A3E708924C044A0009A0AC9 /* MetalVideoProcessFadeTransition.metal */, ); - path = Transition; + path = Transitions; sourceTree = ""; }; /* End PBXGroup section */ @@ -521,16 +559,21 @@ files = ( 6ADB072A24ADCC250010A817 /* MetalVideoProcessColorFilter.swift in Sources */, 6ADB071124ADC4DC0010A817 /* MetalVideoProcessMovieWriter.swift in Sources */, + AB17C21824C5737B0023C974 /* MetalVideoProcessReflectTransition.metal in Sources */, 6ADB06A224ADB3B70010A817 /* CompositionProvider.swift in Sources */, 6ADB06F924ADBE1D0010A817 /* YUVToRGBConversion.metal in Sources */, 6A3E708824C0448B009A0AC9 /* MetalVideoProcessFadeTransition.swift in Sources */, + AB17C21024C17D770023C974 /* MetalVideoProcessEraseDownTransition.swift in Sources */, 6ADB065E24AD8B7E0010A817 /* MetalRendering.swift in Sources */, 6ADB06B524ADB3B70010A817 /* CMTimeExtensition.swift in Sources */, 6ADB06B024ADB3B70010A817 /* AVAssetReverseImageResource.swift in Sources */, 6ADB070524ADC4100010A817 /* PictureOutput.swift in Sources */, + 6A874BD424C6F98100ADFD8A /* MetalVideoProcessFadeMotion.metal in Sources */, 6ADB070F24ADC4DC0010A817 /* MetalVideoProcessPlayer.swift in Sources */, 6ADB06AE24ADB3B70010A817 /* Resource.swift in Sources */, 6ADB06B624ADB3B70010A817 /* ImageGeneratorExtension.swift in Sources */, + 6A874BD724C6FD5100ADFD8A /* MetalVideoProcessFadeOutMotion.swift in Sources */, + 6A874BD024C6E1C200ADFD8A /* MetalVideoProcessFadeInMotion.swift in Sources */, AB64836324C03E6200A0266B /* MetalVideoProcessShanBaiTrans.swift in Sources */, 6ADB069B24ADB3B70010A817 /* VideoCompositor.swift in Sources */, 6ADB066C24AD8B7E0010A817 /* ImageOrientation.swift in Sources */, @@ -542,10 +585,13 @@ 6ADB06EF24ADBBDA0010A817 /* SolidColorGenerator.swift in Sources */, 6ADB06AD24ADB3B70010A817 /* AVAssetTrackResource.swift in Sources */, 6ADB066B24AD8B7E0010A817 /* MetalRenderingDevice.swift in Sources */, + AB17C21524C182780023C974 /* MetalVideoProcessMirrorRotateTransition.swift in Sources */, 6ADB06A124ADB3B70010A817 /* Timeline.swift in Sources */, 6ADB06B324ADB3B70010A817 /* Log.swift in Sources */, + AB17C21424C182780023C974 /* MetalVideoProcessMirrorRotateTransition.metal in Sources */, 6ADB065D24AD8B7E0010A817 /* Size.swift in Sources */, 6ADB070024ADC1930010A817 /* MetalVideoProcessBlendFilter.metal in Sources */, + 6A874BC224C594EC00ADFD8A /* MetalVideoProcessMotion.swift in Sources */, 6ADB06B124ADB3B70010A817 /* CoreImageExtension.swift in Sources */, 6ADB06E024ADB8B80010A817 /* MetalVideoProcessOperation.swift in Sources */, 6ADB071A24ADC4F80010A817 /* MetalVideoProcessBeautyFilter.metal in Sources */, @@ -570,6 +616,8 @@ 6ADB065A24AD8B7E0010A817 /* Matrix.swift in Sources */, 6ADB069F24ADB3B70010A817 /* AudioProcessingChain.swift in Sources */, 6ADB06A424ADB3B70010A817 /* ImageOverlayItem.swift in Sources */, + AB17C21924C5737B0023C974 /* MetalVideoProcessReflectTransition.swift in Sources */, + AB17C21124C17D770023C974 /* MetalVideoProcessEraseDownTransition.metal in Sources */, 6ADB071924ADC4F80010A817 /* MetalVideoProcessTransformFilter.swift in Sources */, 6A3E708A24C044A0009A0AC9 /* MetalVideoProcessFadeTransition.metal in Sources */, 6ADB06FC24ADBF8F0010A817 /* MetalVideoProcessRenderView.swift in Sources */, diff --git a/MetalVideoProcess/Vender/Editor/Uitl/TimingFunctionFactory.swift b/MetalVideoProcess/Vender/Editor/Uitl/TimingFunctionFactory.swift index 0c1f337..3ec2153 100644 --- a/MetalVideoProcess/Vender/Editor/Uitl/TimingFunctionFactory.swift +++ b/MetalVideoProcess/Vender/Editor/Uitl/TimingFunctionFactory.swift @@ -8,6 +8,94 @@ import Foundation +public enum TimingFunctionType { + case linearInterpolation + case quadraticEaseIn + case quadraticEaseOut + case quadraticEaseInOut + case cubicEaseIn + case cubicEaseOut + case cubicEaseInOut + case quarticEaseIn + case quarticEaseOut + case quarticEaseInOut + case SineEaseIn + case SineEaseOut + case circularEaseIn + case circularEaseOut + case exponentialEaseIn + case exponentialEaseOut + case elasticEaseIn + case elasticEaseOut + case elasticEaseInOut + case backEaseIn + case backEaseOut + case backEaseInOut + case bounceEaseIn + case bounceEaseOut + case bounceEaseInOut + case bounceTwice + + func timingValue(p: Float) -> Float { + switch self { + case .linearInterpolation: + return TimingFunctionFactory.linearInterpolation(p: p) + case .quadraticEaseIn: + return TimingFunctionFactory.quadraticEaseIn(p: p) + case .quadraticEaseOut: + return TimingFunctionFactory.quadraticEaseOut(p: p) + case .quadraticEaseInOut: + return TimingFunctionFactory.quadraticEaseInOut(p: p) + case .cubicEaseIn: + return TimingFunctionFactory.cubicEaseIn(p: p) + case .cubicEaseOut: + return TimingFunctionFactory.cubicEaseOut(p: p) + case .cubicEaseInOut: + return TimingFunctionFactory.cubicEaseInOut(p: p) + case .quarticEaseIn: + return TimingFunctionFactory.quarticEaseIn(p: p) + case .quarticEaseOut: + return TimingFunctionFactory.quarticEaseOut(p: p) + case .quarticEaseInOut: + return TimingFunctionFactory.quarticEaseInOut(p: p) + case .SineEaseIn: + return TimingFunctionFactory.SineEaseIn(p: p) + case .SineEaseOut: + return TimingFunctionFactory.SineEaseOut(p: p) + case .circularEaseIn: + return TimingFunctionFactory.circularEaseIn(p: p) + case .circularEaseOut: + return TimingFunctionFactory.circularEaseOut(p: p) + case .exponentialEaseIn: + return TimingFunctionFactory.exponentialEaseIn(p: p) + case .exponentialEaseOut: + return TimingFunctionFactory.exponentialEaseOut(p: p) + case .elasticEaseIn: + return TimingFunctionFactory.elasticEaseIn(p: p) + case .elasticEaseOut: + return TimingFunctionFactory.elasticEaseOut(p: p) + case .elasticEaseInOut: + return TimingFunctionFactory.elasticEaseInOut(p: p) + case .backEaseIn: + return TimingFunctionFactory.backEaseIn(p: p) + case .backEaseOut: + return TimingFunctionFactory.backEaseOut(p: p) + case .backEaseInOut: + return TimingFunctionFactory.backEaseInOut(p: p) + case .bounceEaseIn: + return TimingFunctionFactory.bounceEaseIn(p: p) + case .bounceEaseOut: + return TimingFunctionFactory.bounceEaseOut(p: p) + case .bounceEaseInOut: + return TimingFunctionFactory.bounceEaseInOut(p: p) + case .bounceTwice: + return TimingFunctionFactory.bounceTwice(p: p) + } + } + + +} + public class TimingFunctionFactory { // Modeled after the line y = x static public func linearInterpolation(p: Float) -> Float { diff --git a/MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessFadeTransition.swift b/MetalVideoProcess/VideoProcess/Base/MetalVideoProcessMotion.swift similarity index 53% rename from MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessFadeTransition.swift rename to MetalVideoProcess/VideoProcess/Base/MetalVideoProcessMotion.swift index 11ca31e..6a310df 100644 --- a/MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessFadeTransition.swift +++ b/MetalVideoProcess/VideoProcess/Base/MetalVideoProcessMotion.swift @@ -1,25 +1,26 @@ // -// MetalVideoProcessFadeTransition.swift +// MetalVideoProcessMotion.swift // MetalVideoProcess // -// Created by RenZhu Macro on 2020/7/16. +// Created by RenZhu Macro on 2020/7/20. // Copyright © 2020 RenZhu Macro. All rights reserved. // import AVFoundation -public class MetalVideoProcessFadeTransition: MetalVideoProcessTransition { +public class MetalVideoProcessMotion: MetalVideoProcessOperation { - public init() { - super.init(fragmentFunctionName: "fadeTransition", numberOfInputs: 2, device: sharedMetalRenderingDevice) - } + public var factor: Float = 1.0 { didSet { uniformSettings["factor"] = factor } } + + public var timingType: TimingFunctionType = .linearInterpolation public override func newTextureAvailable(_ texture: Texture, fromSourceIndex: UInt, trackID: Int32) { if self.timelineRange.containsTime(texture.timingStyle.timestamp?.asCMTime ?? CMTime.invalid) { let distance = CMTime(seconds: texture.frameTime, preferredTimescale: 1000) - self.timelineRange.start - let progress = distance.seconds / self.timelineRange.duration.seconds - self.tweenFactor = TimingFunctionFactory.quadraticEaseOut(p: Float(progress)) + let progress = Float(distance.seconds / self.timelineRange.duration.seconds) + self.factor = self.timingType.timingValue(p: progress) } + super.newTextureAvailable(texture, fromSourceIndex: fromSourceIndex, trackID: trackID) } } diff --git a/MetalVideoProcess/VideoProcess/Base/MetalVideoProcessOperation.swift b/MetalVideoProcess/VideoProcess/Base/MetalVideoProcessOperation.swift index 82cdd06..23d3cf6 100644 --- a/MetalVideoProcess/VideoProcess/Base/MetalVideoProcessOperation.swift +++ b/MetalVideoProcess/VideoProcess/Base/MetalVideoProcessOperation.swift @@ -116,11 +116,7 @@ open class MetalVideoProcessOperation: ImageProcessingOperation { if rs == false { updateTargetsWithTexture(texture, trackID: trackID) return - } else { - let distance = CMTime(seconds: texture.frameTime, preferredTimescale: 1000) - self.timelineRange.start - let progress = distance.seconds / self.timelineRange.duration.seconds - self.uniformSettings.progress = Float(progress) - } + } self.uniformSettings.iGlobalTime = Float(texture.frameTime) diff --git a/MetalVideoProcess/VideoProcess/Base/MetalVideoProcessTransition.swift b/MetalVideoProcess/VideoProcess/Base/MetalVideoProcessTransition.swift index d587aee..631c1ad 100644 --- a/MetalVideoProcess/VideoProcess/Base/MetalVideoProcessTransition.swift +++ b/MetalVideoProcess/VideoProcess/Base/MetalVideoProcessTransition.swift @@ -7,26 +7,29 @@ // import Metal -import CoreImage +import AVFoundation public class MetalVideoProcessTransition: MetalVideoProcessOperation { //Transition factor public var tweenFactor: Float = 1.0 { didSet { uniformSettings["tweenFactor"] = tweenFactor } } -// + + public var timingType: TimingFunctionType = .linearInterpolation + open var mainTrackIDs: [Int32] = [] public override func newTextureAvailable(_ texture: Texture, fromSourceIndex: UInt, trackID: Int32) { guard let sourceIndex = mainTrackIDs.firstIndex(of: trackID) else { - super.newTextureAvailable(texture, fromSourceIndex: fromSourceIndex, trackID: trackID) + updateTargetsWithTexture(texture, trackID: trackID) return } - let testImage = CIImage(mtlTexture: texture.texture, options: nil) - if (testImage?.cgImage == nil) { - + + if self.timelineRange.containsTime(texture.timingStyle.timestamp?.asCMTime ?? CMTime.invalid) { + let distance = CMTime(seconds: texture.frameTime, preferredTimescale: 1000) - self.timelineRange.start + let progress = Float(distance.seconds / self.timelineRange.duration.seconds) + self.tweenFactor = self.timingType.timingValue(p: progress) + super.newTextureAvailable(texture, fromSourceIndex: UInt(sourceIndex), trackID: trackID) + } else { + updateTargetsWithTexture(texture, trackID: trackID) } - - super.newTextureAvailable(texture, fromSourceIndex: UInt(sourceIndex), trackID: trackID) - - } } diff --git a/MetalVideoProcess/VideoProcess/Motions/MetalVideoProcessFadeInMotion.swift b/MetalVideoProcess/VideoProcess/Motions/MetalVideoProcessFadeInMotion.swift new file mode 100644 index 0000000..9497875 --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Motions/MetalVideoProcessFadeInMotion.swift @@ -0,0 +1,36 @@ +// +// MetalVideoProcessFadeInMotion.swift +// MetalVideoProcess +// +// Created by RenZhu Macro on 2020/7/21. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit + +public class MetalVideoProcessFadeInMotion: MetalVideoProcessMotion { + + public init() { + super.init(fragmentFunctionName: "fadeInMotion", numberOfInputs: 2, device: sharedMetalRenderingDevice) + self.timingType = .quadraticEaseOut + self.factor = 0.0 + } + + public override func newTextureAvailable(_ texture: Texture, fromSourceIndex: UInt, trackID: Int32) { + if let time = texture.timingStyle.timestamp?.asCMTime { + if time < timelineRange.start { + factor = 0.0 + } + debugPrint("fadein:", factor, " frameTime:", texture.frameTime) + super.newTextureAvailable(texture, fromSourceIndex: fromSourceIndex, trackID: trackID) + } + } + + + /// before fade in, we need the texture alpha keep to zero + /// - Parameter texture: texture + /// - Returns: result + public override func checkTimelineRange(with texture: Texture) -> (Bool) { + return true + } +} diff --git a/MetalVideoProcess/VideoProcess/Motions/MetalVideoProcessFadeMotion.metal b/MetalVideoProcess/VideoProcess/Motions/MetalVideoProcessFadeMotion.metal new file mode 100644 index 0000000..6bf2c59 --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Motions/MetalVideoProcessFadeMotion.metal @@ -0,0 +1,46 @@ +// +// File.metal +// MetalVideoProcess +// +// Created by RenZhu Macro on 2020/7/21. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +#include +using namespace metal; +#include "../../Vender/Render/Base/OperationShaderTypes.h" + +typedef struct +{ + float factor; +} MotionUniform; + +fragment half4 fadeInMotion(TwoInputVertexIO fragmentInput [[stage_in]], + texture2d inputTexture [[texture(0)]], + texture2d inputTexture2 [[texture(1)]], + constant MotionUniform& uniform [[ buffer(1) ]]) +{ + + constexpr sampler quadSampler; + half4 textureColor = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate); + constexpr sampler quadSampler2; + half4 textureColor2 = inputTexture2.sample(quadSampler, fragmentInput.textureCoordinate2); + + + return half4(mix(textureColor.rgb, textureColor2.rgb, textureColor2.a * half(uniform.factor)), textureColor.a); +} + +fragment half4 fadeOutMotion(TwoInputVertexIO fragmentInput [[stage_in]], + texture2d inputTexture [[texture(0)]], + texture2d inputTexture2 [[texture(1)]], + constant MotionUniform& uniform [[ buffer(1) ]]) +{ + + constexpr sampler quadSampler; + half4 textureColor = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate); + constexpr sampler quadSampler2; + half4 textureColor2 = inputTexture2.sample(quadSampler, fragmentInput.textureCoordinate2); + + + return half4(mix(textureColor.rgb, textureColor2.rgb, textureColor2.a * half(1.0 - uniform.factor)), textureColor.a); +} diff --git a/MetalVideoProcess/VideoProcess/Motions/MetalVideoProcessFadeOutMotion.swift b/MetalVideoProcess/VideoProcess/Motions/MetalVideoProcessFadeOutMotion.swift new file mode 100644 index 0000000..f7a6ddd --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Motions/MetalVideoProcessFadeOutMotion.swift @@ -0,0 +1,37 @@ +// +// MetalVideoProcessFadeOutMotion.swift +// MetalVideoProcess +// +// Created by RenZhu Macro on 2020/7/21. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import UIKit + +public class MetalVideoProcessFadeOutMotion: MetalVideoProcessMotion { + + public init() { + super.init(fragmentFunctionName: "fadeOutMotion", numberOfInputs: 2, device: sharedMetalRenderingDevice) + self.timingType = .quadraticEaseOut + } + + public override func newTextureAvailable(_ texture: Texture, fromSourceIndex: UInt, trackID: Int32) { + if let time = texture.timingStyle.timestamp?.asCMTime { + if time > timelineRange.end { + factor = 1.0 + } else if time < timelineRange.start { + self.updateTargetsWithTexture(texture, trackID: trackID) + return + } + debugPrint("fadeout:", factor, " frameTime:", texture.frameTime) + super.newTextureAvailable(texture, fromSourceIndex: fromSourceIndex, trackID: trackID) + } + } + + /// after fade out, we need the texture alpha keep to zero + /// - Parameter texture: texture + /// - Returns: result + public override func checkTimelineRange(with texture: Texture) -> (Bool) { + return true + } +} diff --git a/MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessShanBaiTrans.swift b/MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessShanBaiTrans.swift deleted file mode 100755 index 11f970a..0000000 --- a/MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessShanBaiTrans.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// MetalVideoProcessShanBai.swift -// MetalVideoProcessShanBai -// Created by Ruanshengqiang Macro on 2020/5/15. -// Copyright © 2020 Ruanshengqiang Macro. All rights reserved. -// -import AVFoundation -/// 闪白转场 -public class MetalVideoProcessShanBai: MetalVideoProcessOperation { - public var renderSize: CGSize? = nil - public init() { - super.init(fragmentFunctionName: "shanBai", numberOfInputs: 2, device: sharedMetalRenderingDevice) - } - - public override func newTextureAvailable(_ inputTexture: Texture, fromSourceIndex: UInt, trackID: Int32) { - self.renderSize = CGSize(width: CGFloat(inputTexture.texture.width), height: CGFloat(inputTexture.texture.height)) - - super.newTextureWithSize(inputTexture, fromSourceIndex: fromSourceIndex, renderSize: renderSize!, trackID: trackID) - - } -} diff --git a/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessEraseDownTransition.metal b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessEraseDownTransition.metal new file mode 100644 index 0000000..ecf9960 --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessEraseDownTransition.metal @@ -0,0 +1,35 @@ +// +// MetalVideoProcessFadeTransition.metal +// MetalVideoProcess +// +// Created by Ruanshengqiang Macro on 2020/7/16. +// Copyright © 2020 Ruanshengqiang Macro. All rights reserved. +// + +#include +using namespace metal; +#include "../../Vender/Render/Base/OperationShaderTypes.h" + +typedef struct +{ + float tweenFactor; +} FadeTransitionUniform; + +fragment half4 eraseDownTransition(TwoInputVertexIO fragmentInput [[stage_in]], + texture2d inputTexture [[texture(0)]], + texture2d inputTexture2 [[texture(1)]], + constant FadeTransitionUniform& uniform [[ buffer(1) ]]) +{ + + constexpr sampler quadSampler(mag_filter:: linear, + min_filter:: linear, + address:: clamp_to_zero); + float2 uv = fragmentInput.textureCoordinate; + half4 input0 = inputTexture.sample(quadSampler, uv); + half4 input1 = inputTexture2.sample(quadSampler, uv); + float process = uniform.tweenFactor; + +// float curve0 = process * process; +// float curve1 = 3. * curve0 - 2. * curve0 * process; + return mix(input0, input1, step(uv.y, process)); +} diff --git a/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessEraseDownTransition.swift b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessEraseDownTransition.swift new file mode 100644 index 0000000..0c3de95 --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessEraseDownTransition.swift @@ -0,0 +1,18 @@ +// +// MetalVideoProcessEraseDownTransition.swift +// MetalVideoProcess +// +// Created by Ruanshengqiang Macro on 2020/7/16. +// Copyright © 2020 Ruanshengqiang Macro. All rights reserved. +// + +import AVFoundation +///向下擦除转场 +public class MetalVideoProcessEraseDownTransition: MetalVideoProcessTransition { + + public init() { + super.init(fragmentFunctionName: "eraseDownTransition", numberOfInputs: 2, device: sharedMetalRenderingDevice) + self.timingType = .quadraticEaseOut + } + +} diff --git a/MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessFadeTransition.metal b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessFadeTransition.metal similarity index 100% rename from MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessFadeTransition.metal rename to MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessFadeTransition.metal diff --git a/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessFadeTransition.swift b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessFadeTransition.swift new file mode 100644 index 0000000..d9032e2 --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessFadeTransition.swift @@ -0,0 +1,17 @@ +// +// MetalVideoProcessFadeTransition.swift +// MetalVideoProcess +// +// Created by RenZhu Macro on 2020/7/16. +// Copyright © 2020 RenZhu Macro. All rights reserved. +// + +import AVFoundation + +public class MetalVideoProcessFadeTransition: MetalVideoProcessTransition { + + public init() { + super.init(fragmentFunctionName: "fadeTransition", numberOfInputs: 2, device: sharedMetalRenderingDevice) + self.timingType = .quadraticEaseOut + } +} diff --git a/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessMirrorRotateTransition.metal b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessMirrorRotateTransition.metal new file mode 100644 index 0000000..8f6afba --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessMirrorRotateTransition.metal @@ -0,0 +1,45 @@ +// +// MetalVideoProcessFadeTransition.metal +// MetalVideoProcess +// +// Created by Ruanshengqiang Macro on 2020/7/16. +// Copyright © 2020 Ruanshengqiang Macro. All rights reserved. +// + +#include +using namespace metal; +#include "../../Vender/Render/Base/OperationShaderTypes.h" + +typedef struct +{ + float tweenFactor; +} FadeTransitionUniform; + +fragment half4 mirrorRotateTransition(TwoInputVertexIO fragmentInput [[stage_in]], + texture2d inputTexture [[texture(0)]], + texture2d inputTexture2 [[texture(1)]], + constant FadeTransitionUniform& uniform [[ buffer(1) ]]) +{ + + constexpr sampler quadSampler(mag_filter:: linear, + min_filter:: linear, + address:: clamp_to_zero); + float2 uv = fragmentInput.textureCoordinate; + + float process = uniform.tweenFactor; + + float curve0 = process * process; + float curve1 = 3. * curve0 - 2. * curve0 * process; + float2 tempUv = uv; + + half4 outCol = half4(0.0); + if (curve1 < 0.5) { + tempUv = float2((tempUv.x - 0.5) * (1. + curve1 * 100.0) + 0.5, uv.y); + outCol = inputTexture.sample(quadSampler, tempUv.xy); + } else { + tempUv = float2((tempUv.x - 0.5) * (1. + (1.0 - curve1) * 100.0) + 0.5, uv.y); + outCol = inputTexture2.sample(quadSampler, tempUv.xy); + } + + return outCol; +} diff --git a/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessMirrorRotateTransition.swift b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessMirrorRotateTransition.swift new file mode 100644 index 0000000..d273c91 --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessMirrorRotateTransition.swift @@ -0,0 +1,17 @@ +// +// MetalVideoProcessMirrorRotateTransition.swift +// MetalVideoProcess +// +// Created by Ruanshengqiang Macro on 2020/7/16. +// Copyright © 2020 Ruanshengqiang Macro. All rights reserved. +// + +import AVFoundation +///镜像翻转转场 +public class MetalVideoProcessMirrorRotateTransition: MetalVideoProcessTransition { + + public init() { + super.init(fragmentFunctionName: "mirrorRotateTransition", numberOfInputs: 2, device: sharedMetalRenderingDevice) + self.timingType = .quadraticEaseOut + } +} diff --git a/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessReflectTransition.metal b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessReflectTransition.metal new file mode 100644 index 0000000..706731b --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessReflectTransition.metal @@ -0,0 +1,106 @@ +// +// MetalVideoProcessReflectTransition.metal +// MetalVideoProcess +// +// Created by Ruanshengqiang Macro on 2020/7/16. +// Copyright © 2020 Ruanshengqiang Macro. All rights reserved. +// + +#include +using namespace metal; +#include "../../Vender/Render/Base/OperationShaderTypes.h" + +typedef struct +{ + float tweenFactor; +} FadeTransitionUniform; + + +constant float reflection = .3; +constant float perspective = .1; +constant float depth = 3.; + +constant half4 black = half4(0.0, 0.0, 0.0, 1.0); +constant float2 boundMin = float2(0.0, 0.0); +constant float2 boundMax = float2(1.0, 1.0); + +bool inBounds (float2 p) { + + float2 compare0 = step(boundMin, p); + float2 compare1 = step(p, boundMax); + if (compare0.x * compare0.y * compare1.x * compare1.y > 0.5) { + return true; + } else { + return false; + } +} + +float2 project (float2 p) { + return p * float2(1.0, -1.2) + float2(0.0, -0.02); +} + +half4 bgColor (float2 p, float2 pfr, float2 pto, texture2d from, texture2d to, sampler samp) { + half4 c = black; + pfr = project(pfr); + if (inBounds(pfr)) { + c += mix(black, from.sample(samp, pfr), reflection * mix(1.0, 0.0, pfr.y)); + } + + pto = project(pto); + if (inBounds(pto)) { + c += mix(black, to.sample(samp, pto), reflection * mix(1.0, 0.0, pto.y)); + } + return c; +} + +fragment half4 reflectTransition(TwoInputVertexIO fragmentInput [[stage_in]], + texture2d from [[texture(0)]], + texture2d to [[texture(1)]], + constant FadeTransitionUniform& uniform [[buffer(1)]]) +{ + constexpr sampler quadSampler(mag_filter:: linear, + min_filter:: linear, + address:: clamp_to_zero); + float2 uv = fragmentInput.textureCoordinate; + float iTime = uniform.tweenFactor; + float progress = iTime; + float2 pfr = float2(-1.); + float2 pto = float2(-1.); + + float size = mix(1.0, depth, progress); + float persp = perspective * progress; + float2 p = float2(uv.x, 1.0 - uv.y); + pfr = (p + float2(-0.0, -0.5)) * float2(size / (1.0 - perspective * progress), size / (1.0 - size * persp * p.x)) + float2(0.0, 0.5); + + size = mix(1.0, depth, 1. - progress); + persp = perspective * (1. - progress); + pto = (p + float2(-1.0, -0.5)) * float2(size / (1.0 - perspective * (1.0 - progress)), size / (1.0 - size * persp * (0.5 - p.x))) + float2(1.0, 0.5); + + bool fromOver = progress < 0.5; + half4 fragColor = half4(0.0); + if (fromOver) { + if (inBounds(pfr)) { + fragColor = from.sample(quadSampler, float2(pfr.x, 1. - pfr.y)); + } + else if (inBounds(pto)) { + fragColor = to.sample(quadSampler, float2(pto.x, 1. - pto.y)); + } + else { + fragColor = bgColor(p, pfr, pto, from, to, quadSampler); + } + } + else { + if (inBounds(pto)) { + + fragColor = to.sample(quadSampler, float2(pto.x, 1. - pto.y)); + } + else if (inBounds(pfr)) { + + fragColor = from.sample(quadSampler, float2(pfr.x, 1. - pfr.y)); + } + else { + fragColor = bgColor(p, pfr, pto, from, to, quadSampler); + } + } + return fragColor; +} diff --git a/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessReflectTransition.swift b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessReflectTransition.swift new file mode 100644 index 0000000..b99f7bd --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessReflectTransition.swift @@ -0,0 +1,17 @@ +// +// MetalVideoProcessEraseDownTransition.swift +// MetalVideoProcess +// +// Created by Ruanshengqiang Macro on 2020/7/16. +// Copyright © 2020 Ruanshengqiang Macro. All rights reserved. +// + +import AVFoundation +///向下擦除转场 +public class MetalVideoProcessReflectTransition: MetalVideoProcessTransition { + + public init() { + super.init(fragmentFunctionName: "reflectTransition", numberOfInputs: 2, device: sharedMetalRenderingDevice) + self.timingType = .quadraticEaseOut + } +} diff --git a/MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessShanBaiTrans.metal b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessShanBaiTrans.metal similarity index 77% rename from MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessShanBaiTrans.metal rename to MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessShanBaiTrans.metal index 27b94e8..bf3fa6e 100755 --- a/MetalVideoProcess/VideoProcess/Transition/MetalVideoProcessShanBaiTrans.metal +++ b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessShanBaiTrans.metal @@ -2,10 +2,15 @@ using namespace metal; #include "../../Vender/Render/Base/OperationShaderTypes.h" +typedef struct +{ + float tweenFactor; +} FadeTransitionUniform; + fragment half4 shanBai(TwoInputVertexIO fragmentInput [[stage_in]], texture2d inputTexture [[texture(0)]], texture2d inputTexture2 [[texture(1)]], - constant float& process[[buffer(3)]]) + constant FadeTransitionUniform& uniform [[ buffer(1) ]]) { constexpr sampler quadSampler(mag_filter:: linear, @@ -17,11 +22,13 @@ fragment half4 shanBai(TwoInputVertexIO fragmentInput [[stage_in]], half4 moveOutTex = inputTexture2.sample(quadSampler, fragmentInput.textureCoordinate2); float curve = 0.0; half4 outputTex = moveInText; - if (process < 0.5) { + + float process = uniform.tweenFactor * 2.0; + if (process < 1.0) { curve = process; outputTex = half4(moveInText.rgb + curve, moveInText.a); } else { - curve = 1.0 - process; + curve = 2.0 - process; outputTex = half4(moveOutTex.rgb + curve, moveOutTex.a); } return outputTex; diff --git a/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessShanBaiTrans.swift b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessShanBaiTrans.swift new file mode 100755 index 0000000..c62b37a --- /dev/null +++ b/MetalVideoProcess/VideoProcess/Transitions/MetalVideoProcessShanBaiTrans.swift @@ -0,0 +1,15 @@ +// +// MetalVideoProcessShanBai.swift +// MetalVideoProcessShanBai +// Created by Ruanshengqiang Macro on 2020/5/15. +// Copyright © 2020 Ruanshengqiang Macro. All rights reserved. +// +import AVFoundation +/// 闪白转场 +public class MetalVideoProcessShanBai: MetalVideoProcessTransition { + public var renderSize: CGSize? = nil + public init() { + super.init(fragmentFunctionName: "shanBai", numberOfInputs: 2, device: sharedMetalRenderingDevice) + self.timingType = .quadraticEaseOut + } +} diff --git a/README.md b/README.md index beb32c9..8b8f02b 100644 --- a/README.md +++ b/README.md @@ -43,15 +43,14 @@ let asset1 = AVAsset(url: Bundle.main.url(forResource: "test", withExtension: "m let item1 = MetalVideoEditorItem(asset: asset1) do { - let editor = try MetalVideoEditor(videoItems: [item1]) - let playerItem = editor.buildPlayerItem() - let player = try MetalVideoProcessPlayer(playerItem: playerItem) - - let beautyFilter1 = MetalVideoProcessBeautyFilter() - player -- > beautyFilter1 --> renderView - self.player = player - self.player?.playerDelegate = self - + let editor = try MetalVideoEditor(videoItems: [item1]) + let playerItem = editor.buildPlayerItem() + let player = try MetalVideoProcessPlayer(playerItem: playerItem) + + let beautyFilter1 = MetalVideoProcessBeautyFilter() + player -- > beautyFilter1 --> renderView + self.player = player + self.player?.playerDelegate = self } catch { debugPrint("init error") }