├── .gitignore ├── .travis.yml ├── VideoEditDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── VideoEditDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json └── LaunchImage.launchimage │ ├── Contents.json │ ├── 启动页iphone5.png │ ├── 启动页iphone6.png │ └── 启动页iphone6plus.png ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── VideoEditTool ├── DragEditView.h ├── DragEditView.m ├── VideoEditVC.h ├── VideoEditVC.m └── drag.jpg ├── ViewController.h ├── ViewController.m └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 26 | # Carthage/Checkouts 27 | 28 | Carthage/Build 29 | 30 | # We recommend against adding the Pods directory to your .gitignore. However 31 | # you should judge for yourself, the pros and cons are mentioned at: 32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 33 | # 34 | # Note: if you ignore the Pods directory, make sure to uncomment 35 | # `pod install` in .travis.yml 36 | # 37 | # Pods/ 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/ZWHomeModule.xcworkspace -scheme ZWHomeModule-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /VideoEditDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1B6206711F456F28003425E7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B6206701F456F28003425E7 /* main.m */; }; 11 | 1B6206741F456F28003425E7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B6206731F456F28003425E7 /* AppDelegate.m */; }; 12 | 1B6206771F456F28003425E7 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B6206761F456F28003425E7 /* ViewController.m */; }; 13 | 1B62067A1F456F28003425E7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1B6206781F456F28003425E7 /* Main.storyboard */; }; 14 | 1B62067C1F456F28003425E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1B62067B1F456F28003425E7 /* Assets.xcassets */; }; 15 | 1B62067F1F456F28003425E7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1B62067D1F456F28003425E7 /* LaunchScreen.storyboard */; }; 16 | 1B62068B1F457092003425E7 /* VideoEditVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B62068A1F457092003425E7 /* VideoEditVC.m */; }; 17 | 1B62068E1F458388003425E7 /* DragEditView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B62068D1F458388003425E7 /* DragEditView.m */; }; 18 | 1B6206941F458AAC003425E7 /* drag.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1B6206931F458AAC003425E7 /* drag.jpg */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 1B62066C1F456F28003425E7 /* VideoEditDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VideoEditDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 1B6206701F456F28003425E7 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 24 | 1B6206721F456F28003425E7 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 25 | 1B6206731F456F28003425E7 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 26 | 1B6206751F456F28003425E7 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 27 | 1B6206761F456F28003425E7 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 28 | 1B6206791F456F28003425E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 29 | 1B62067B1F456F28003425E7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | 1B62067E1F456F28003425E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | 1B6206801F456F28003425E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | 1B6206891F457092003425E7 /* VideoEditVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoEditVC.h; sourceTree = ""; }; 33 | 1B62068A1F457092003425E7 /* VideoEditVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoEditVC.m; sourceTree = ""; }; 34 | 1B62068C1F458388003425E7 /* DragEditView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DragEditView.h; sourceTree = ""; }; 35 | 1B62068D1F458388003425E7 /* DragEditView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DragEditView.m; sourceTree = ""; }; 36 | 1B6206931F458AAC003425E7 /* drag.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = drag.jpg; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 1B6206691F456F28003425E7 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 1B6206631F456F28003425E7 = { 51 | isa = PBXGroup; 52 | children = ( 53 | 1B62066E1F456F28003425E7 /* VideoEditDemo */, 54 | 1B62066D1F456F28003425E7 /* Products */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | 1B62066D1F456F28003425E7 /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 1B62066C1F456F28003425E7 /* VideoEditDemo.app */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | 1B62066E1F456F28003425E7 /* VideoEditDemo */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 1B6206881F45707F003425E7 /* VideoEditTool */, 70 | 1B6206721F456F28003425E7 /* AppDelegate.h */, 71 | 1B6206731F456F28003425E7 /* AppDelegate.m */, 72 | 1B6206751F456F28003425E7 /* ViewController.h */, 73 | 1B6206761F456F28003425E7 /* ViewController.m */, 74 | 1B6206781F456F28003425E7 /* Main.storyboard */, 75 | 1B62067B1F456F28003425E7 /* Assets.xcassets */, 76 | 1B62067D1F456F28003425E7 /* LaunchScreen.storyboard */, 77 | 1B6206801F456F28003425E7 /* Info.plist */, 78 | 1B62066F1F456F28003425E7 /* Supporting Files */, 79 | ); 80 | path = VideoEditDemo; 81 | sourceTree = ""; 82 | }; 83 | 1B62066F1F456F28003425E7 /* Supporting Files */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 1B6206701F456F28003425E7 /* main.m */, 87 | ); 88 | name = "Supporting Files"; 89 | sourceTree = ""; 90 | }; 91 | 1B6206881F45707F003425E7 /* VideoEditTool */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 1B6206931F458AAC003425E7 /* drag.jpg */, 95 | 1B6206891F457092003425E7 /* VideoEditVC.h */, 96 | 1B62068A1F457092003425E7 /* VideoEditVC.m */, 97 | 1B62068C1F458388003425E7 /* DragEditView.h */, 98 | 1B62068D1F458388003425E7 /* DragEditView.m */, 99 | ); 100 | path = VideoEditTool; 101 | sourceTree = ""; 102 | }; 103 | /* End PBXGroup section */ 104 | 105 | /* Begin PBXNativeTarget section */ 106 | 1B62066B1F456F28003425E7 /* VideoEditDemo */ = { 107 | isa = PBXNativeTarget; 108 | buildConfigurationList = 1B6206831F456F28003425E7 /* Build configuration list for PBXNativeTarget "VideoEditDemo" */; 109 | buildPhases = ( 110 | 1B6206681F456F28003425E7 /* Sources */, 111 | 1B6206691F456F28003425E7 /* Frameworks */, 112 | 1B62066A1F456F28003425E7 /* Resources */, 113 | ); 114 | buildRules = ( 115 | ); 116 | dependencies = ( 117 | ); 118 | name = VideoEditDemo; 119 | productName = VideoEditDemo; 120 | productReference = 1B62066C1F456F28003425E7 /* VideoEditDemo.app */; 121 | productType = "com.apple.product-type.application"; 122 | }; 123 | /* End PBXNativeTarget section */ 124 | 125 | /* Begin PBXProject section */ 126 | 1B6206641F456F28003425E7 /* Project object */ = { 127 | isa = PBXProject; 128 | attributes = { 129 | LastUpgradeCheck = 0820; 130 | ORGANIZATIONNAME = "刘志伟"; 131 | TargetAttributes = { 132 | 1B62066B1F456F28003425E7 = { 133 | CreatedOnToolsVersion = 8.2; 134 | DevelopmentTeam = 4FZEH78SM7; 135 | ProvisioningStyle = Manual; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 1B6206671F456F28003425E7 /* Build configuration list for PBXProject "VideoEditDemo" */; 140 | compatibilityVersion = "Xcode 3.2"; 141 | developmentRegion = English; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 1B6206631F456F28003425E7; 148 | productRefGroup = 1B62066D1F456F28003425E7 /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 1B62066B1F456F28003425E7 /* VideoEditDemo */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 1B62066A1F456F28003425E7 /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 1B62067F1F456F28003425E7 /* LaunchScreen.storyboard in Resources */, 163 | 1B6206941F458AAC003425E7 /* drag.jpg in Resources */, 164 | 1B62067C1F456F28003425E7 /* Assets.xcassets in Resources */, 165 | 1B62067A1F456F28003425E7 /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXSourcesBuildPhase section */ 172 | 1B6206681F456F28003425E7 /* Sources */ = { 173 | isa = PBXSourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | 1B6206771F456F28003425E7 /* ViewController.m in Sources */, 177 | 1B6206741F456F28003425E7 /* AppDelegate.m in Sources */, 178 | 1B62068B1F457092003425E7 /* VideoEditVC.m in Sources */, 179 | 1B6206711F456F28003425E7 /* main.m in Sources */, 180 | 1B62068E1F458388003425E7 /* DragEditView.m in Sources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXSourcesBuildPhase section */ 185 | 186 | /* Begin PBXVariantGroup section */ 187 | 1B6206781F456F28003425E7 /* Main.storyboard */ = { 188 | isa = PBXVariantGroup; 189 | children = ( 190 | 1B6206791F456F28003425E7 /* Base */, 191 | ); 192 | name = Main.storyboard; 193 | sourceTree = ""; 194 | }; 195 | 1B62067D1F456F28003425E7 /* LaunchScreen.storyboard */ = { 196 | isa = PBXVariantGroup; 197 | children = ( 198 | 1B62067E1F456F28003425E7 /* Base */, 199 | ); 200 | name = LaunchScreen.storyboard; 201 | sourceTree = ""; 202 | }; 203 | /* End PBXVariantGroup section */ 204 | 205 | /* Begin XCBuildConfiguration section */ 206 | 1B6206811F456F28003425E7 /* Debug */ = { 207 | isa = XCBuildConfiguration; 208 | buildSettings = { 209 | ALWAYS_SEARCH_USER_PATHS = NO; 210 | CLANG_ANALYZER_NONNULL = YES; 211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 212 | CLANG_CXX_LIBRARY = "libc++"; 213 | CLANG_ENABLE_MODULES = YES; 214 | CLANG_ENABLE_OBJC_ARC = YES; 215 | CLANG_WARN_BOOL_CONVERSION = YES; 216 | CLANG_WARN_CONSTANT_CONVERSION = YES; 217 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 218 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 219 | CLANG_WARN_EMPTY_BODY = YES; 220 | CLANG_WARN_ENUM_CONVERSION = YES; 221 | CLANG_WARN_INFINITE_RECURSION = YES; 222 | CLANG_WARN_INT_CONVERSION = YES; 223 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 224 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 225 | CLANG_WARN_UNREACHABLE_CODE = YES; 226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 227 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 228 | COPY_PHASE_STRIP = NO; 229 | DEBUG_INFORMATION_FORMAT = dwarf; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | ENABLE_TESTABILITY = YES; 232 | GCC_C_LANGUAGE_STANDARD = gnu99; 233 | GCC_DYNAMIC_NO_PIC = NO; 234 | GCC_NO_COMMON_BLOCKS = YES; 235 | GCC_OPTIMIZATION_LEVEL = 0; 236 | GCC_PREPROCESSOR_DEFINITIONS = ( 237 | "DEBUG=1", 238 | "$(inherited)", 239 | ); 240 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 241 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 242 | GCC_WARN_UNDECLARED_SELECTOR = YES; 243 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 244 | GCC_WARN_UNUSED_FUNCTION = YES; 245 | GCC_WARN_UNUSED_VARIABLE = YES; 246 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 247 | MTL_ENABLE_DEBUG_INFO = YES; 248 | ONLY_ACTIVE_ARCH = YES; 249 | SDKROOT = iphoneos; 250 | TARGETED_DEVICE_FAMILY = "1,2"; 251 | }; 252 | name = Debug; 253 | }; 254 | 1B6206821F456F28003425E7 /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_ANALYZER_NONNULL = YES; 259 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 260 | CLANG_CXX_LIBRARY = "libc++"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 267 | CLANG_WARN_EMPTY_BODY = YES; 268 | CLANG_WARN_ENUM_CONVERSION = YES; 269 | CLANG_WARN_INFINITE_RECURSION = YES; 270 | CLANG_WARN_INT_CONVERSION = YES; 271 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 272 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 273 | CLANG_WARN_UNREACHABLE_CODE = YES; 274 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 275 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 276 | COPY_PHASE_STRIP = NO; 277 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 278 | ENABLE_NS_ASSERTIONS = NO; 279 | ENABLE_STRICT_OBJC_MSGSEND = YES; 280 | GCC_C_LANGUAGE_STANDARD = gnu99; 281 | GCC_NO_COMMON_BLOCKS = YES; 282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 284 | GCC_WARN_UNDECLARED_SELECTOR = YES; 285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 286 | GCC_WARN_UNUSED_FUNCTION = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 289 | MTL_ENABLE_DEBUG_INFO = NO; 290 | SDKROOT = iphoneos; 291 | TARGETED_DEVICE_FAMILY = "1,2"; 292 | VALIDATE_PRODUCT = YES; 293 | }; 294 | name = Release; 295 | }; 296 | 1B6206841F456F28003425E7 /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 300 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 301 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Li Biao (G7VM55KCF3)"; 302 | DEVELOPMENT_TEAM = 4FZEH78SM7; 303 | INFOPLIST_FILE = VideoEditDemo/Info.plist; 304 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 305 | PRODUCT_BUNDLE_IDENTIFIER = ios.test.ideoEditDemo; 306 | PRODUCT_NAME = "$(TARGET_NAME)"; 307 | PROVISIONING_PROFILE = "56993b80-d22e-4821-b6f6-641404407dac"; 308 | PROVISIONING_PROFILE_SPECIFIER = mau_ios_test; 309 | }; 310 | name = Debug; 311 | }; 312 | 1B6206851F456F28003425E7 /* Release */ = { 313 | isa = XCBuildConfiguration; 314 | buildSettings = { 315 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 316 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 317 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Li Biao (G7VM55KCF3)"; 318 | DEVELOPMENT_TEAM = 4FZEH78SM7; 319 | INFOPLIST_FILE = VideoEditDemo/Info.plist; 320 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 321 | PRODUCT_BUNDLE_IDENTIFIER = ios.test.ideoEditDemo; 322 | PRODUCT_NAME = "$(TARGET_NAME)"; 323 | PROVISIONING_PROFILE = "56993b80-d22e-4821-b6f6-641404407dac"; 324 | PROVISIONING_PROFILE_SPECIFIER = mau_ios_test; 325 | }; 326 | name = Release; 327 | }; 328 | /* End XCBuildConfiguration section */ 329 | 330 | /* Begin XCConfigurationList section */ 331 | 1B6206671F456F28003425E7 /* Build configuration list for PBXProject "VideoEditDemo" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | 1B6206811F456F28003425E7 /* Debug */, 335 | 1B6206821F456F28003425E7 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | 1B6206831F456F28003425E7 /* Build configuration list for PBXNativeTarget "VideoEditDemo" */ = { 341 | isa = XCConfigurationList; 342 | buildConfigurations = ( 343 | 1B6206841F456F28003425E7 /* Debug */, 344 | 1B6206851F456F28003425E7 /* Release */, 345 | ); 346 | defaultConfigurationIsVisible = 0; 347 | defaultConfigurationName = Release; 348 | }; 349 | /* End XCConfigurationList section */ 350 | }; 351 | rootObject = 1B6206641F456F28003425E7 /* Project object */; 352 | } 353 | -------------------------------------------------------------------------------- /VideoEditDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VideoEditDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VideoEditDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // VideoEditDemo 4 | // 5 | // Created by 刘志伟 on 2017/8/17. 6 | // Copyright © 2017年 刘志伟. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /VideoEditDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // VideoEditDemo 4 | // 5 | // Created by 刘志伟 on 2017/8/17. 6 | // Copyright © 2017年 刘志伟. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /VideoEditDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /VideoEditDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /VideoEditDemo/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "11.0", 8 | "subtype" : "2436h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "extent" : "full-screen", 13 | "idiom" : "iphone", 14 | "subtype" : "736h", 15 | "filename" : "启动页iphone6plus.png", 16 | "minimum-system-version" : "8.0", 17 | "orientation" : "portrait", 18 | "scale" : "3x" 19 | }, 20 | { 21 | "extent" : "full-screen", 22 | "idiom" : "iphone", 23 | "subtype" : "667h", 24 | "filename" : "启动页iphone6.png", 25 | "minimum-system-version" : "8.0", 26 | "orientation" : "portrait", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "orientation" : "portrait", 31 | "idiom" : "iphone", 32 | "extent" : "full-screen", 33 | "minimum-system-version" : "7.0", 34 | "scale" : "2x" 35 | }, 36 | { 37 | "extent" : "full-screen", 38 | "idiom" : "iphone", 39 | "subtype" : "retina4", 40 | "filename" : "启动页iphone5.png", 41 | "minimum-system-version" : "7.0", 42 | "orientation" : "portrait", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "orientation" : "portrait", 47 | "idiom" : "iphone", 48 | "minimum-system-version" : "7.0", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "orientation" : "portrait", 53 | "idiom" : "iphone", 54 | "minimum-system-version" : "7.0", 55 | "subtype" : "retina4", 56 | "scale" : "2x" 57 | } 58 | ], 59 | "info" : { 60 | "version" : 1, 61 | "author" : "xcode" 62 | } 63 | } -------------------------------------------------------------------------------- /VideoEditDemo/Assets.xcassets/LaunchImage.launchimage/启动页iphone5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterZhiWei/VideoEditDemo/3d184b46030ecda3134bc9202df418cf8c6f03ed/VideoEditDemo/Assets.xcassets/LaunchImage.launchimage/启动页iphone5.png -------------------------------------------------------------------------------- /VideoEditDemo/Assets.xcassets/LaunchImage.launchimage/启动页iphone6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterZhiWei/VideoEditDemo/3d184b46030ecda3134bc9202df418cf8c6f03ed/VideoEditDemo/Assets.xcassets/LaunchImage.launchimage/启动页iphone6.png -------------------------------------------------------------------------------- /VideoEditDemo/Assets.xcassets/LaunchImage.launchimage/启动页iphone6plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterZhiWei/VideoEditDemo/3d184b46030ecda3134bc9202df418cf8c6f03ed/VideoEditDemo/Assets.xcassets/LaunchImage.launchimage/启动页iphone6plus.png -------------------------------------------------------------------------------- /VideoEditDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /VideoEditDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /VideoEditDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSPhotoLibraryUsageDescription 24 | video 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /VideoEditDemo/VideoEditTool/DragEditView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DragEditView.h 3 | // VideoEditDemo 4 | // 5 | // Created by 刘志伟 on 2017/8/17. 6 | // Copyright © 2017年 刘志伟. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DragEditView : UIView 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame Left:(BOOL)left; 14 | 15 | @property (assign, nonatomic) UIEdgeInsets hitTestEdgeInsets; 16 | 17 | - (BOOL)pointInsideSelf:(CGPoint)point; 18 | 19 | - (BOOL)pointInsideImgView:(CGPoint)point; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /VideoEditDemo/VideoEditTool/DragEditView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DragEditView.m 3 | // VideoEditDemo 4 | // 5 | // Created by 刘志伟 on 2017/8/17. 6 | // Copyright © 2017年 刘志伟. All rights reserved. 7 | // 8 | 9 | #import "DragEditView.h" 10 | 11 | @interface DragEditView(){ 12 | UIImageView *imgView; 13 | } 14 | 15 | @property (nonatomic ,assign) BOOL isLeft; 16 | 17 | @end 18 | 19 | @implementation DragEditView 20 | 21 | - (instancetype)initWithFrame:(CGRect)frame Left:(BOOL)left{ 22 | self = [[DragEditView alloc] initWithFrame:frame]; 23 | self.backgroundColor = [UIColor clearColor]; 24 | UIView *backView = [[UIView alloc] initWithFrame:self.bounds]; 25 | backView.backgroundColor = [UIColor blackColor]; 26 | backView.alpha = 0.6; 27 | [self addSubview:backView]; 28 | self.isLeft = left; 29 | [self initSubViews]; 30 | 31 | return self; 32 | } 33 | 34 | - (void)initSubViews{ 35 | CGFloat width = self.frame.size.width; 36 | CGFloat height = self.frame.size.height; 37 | 38 | CGRect imgFrame; 39 | if (self.isLeft) { 40 | imgFrame = CGRectMake(width-10, 0, 10, height); 41 | } 42 | else { 43 | imgFrame = CGRectMake(0, 0, 10, height); 44 | } 45 | 46 | imgView = [[UIImageView alloc] initWithFrame:imgFrame]; 47 | imgView.image = [UIImage imageNamed:@"drag.jpg"]; 48 | [self addSubview:imgView]; 49 | } 50 | 51 | - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{ 52 | return [self pointInsideSelf:point]; 53 | } 54 | 55 | - (BOOL)pointInsideSelf:(CGPoint)point{ 56 | CGRect relativeFrame = self.bounds; 57 | CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, _hitTestEdgeInsets); 58 | return CGRectContainsPoint(hitFrame, point); 59 | } 60 | 61 | - (BOOL)pointInsideImgView:(CGPoint)point{ 62 | CGRect relativeFrame = imgView.frame; 63 | CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, _hitTestEdgeInsets); 64 | return CGRectContainsPoint(hitFrame, point); 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /VideoEditDemo/VideoEditTool/VideoEditVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoEditVC.h 3 | // VideoEditDemo 4 | // 5 | // Created by 刘志伟 on 2017/8/17. 6 | // Copyright © 2017年 刘志伟. All rights reserved. 7 | // 8 | 9 | /* 10 | *根据实际需求,可以判断如果总时长小于10秒或其他时长可以不用编辑 11 | 12 | */ 13 | 14 | #import 15 | 16 | @interface VideoEditVC : UIViewController 17 | 18 | /* 19 | 待编辑视频的URL 20 | */ 21 | @property (nonatomic, strong) NSURL *videoUrl; 22 | 23 | /** 24 | * 默认为YES NO:不显示视频帧并不可编辑剪切视频 25 | */ 26 | @property (nonatomic, assign) BOOL isEdit; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /VideoEditDemo/VideoEditTool/VideoEditVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoEditVC.m 3 | // VideoEditDemo 4 | // 5 | // Created by 刘志伟 on 2017/8/17. 6 | // Copyright © 2017年 刘志伟. All rights reserved. 7 | // 8 | #define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width 9 | #define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height 10 | #define EDGE_EXTENSION_FOR_THUMB 20 11 | 12 | #import "VideoEditVC.h" 13 | #import "DragEditView.h" 14 | #import 15 | #import 16 | 17 | @interface VideoEditVC (){ 18 | UIScrollView *editScrollView; 19 | UIView *bottomView; 20 | DragEditView *leftDragView; 21 | DragEditView *rightDragView; 22 | UIView *line; 23 | UIView *topBorder; 24 | UIView *bottomBorder; 25 | } 26 | 27 | @property (nonatomic, strong) AVPlayerItem *playItem; 28 | @property (nonatomic, strong) AVPlayerLayer *playerLayer; 29 | @property (nonatomic, strong) AVPlayer *player; 30 | @property (nonatomic, strong) NSTimer *repeatTimer; // 循环播放计时器 31 | @property (nonatomic, strong) NSTimer *lineMoveTimer; // 播放条移动计时器 32 | @property (nonatomic, strong) NSMutableArray *framesArray; // 视频帧数组 33 | 34 | @property (nonatomic, strong) NSString *tempVideoPath; 35 | @property (nonatomic, assign) CGPoint leftStartPoint; 36 | @property (nonatomic, assign) CGPoint rightStartPoint; 37 | @property (nonatomic, assign) BOOL isDraggingRightOverlayView; 38 | @property (nonatomic, assign) BOOL isDraggingLeftOverlayView; 39 | @property (nonatomic, assign) CGFloat startTime; // 编辑框内视频开始时间秒 40 | @property (nonatomic, assign) CGFloat endTime; // 编辑框内视频结束时间秒 41 | @property (nonatomic, assign) CGFloat startPointX; // 编辑框起始点 42 | @property (nonatomic, assign) CGFloat endPointX; // 编辑框结束点 43 | @property (nonatomic, assign) CGFloat IMG_Width; // 视频帧宽度 44 | @property (nonatomic, assign) CGFloat linePositionX; // 播放条的位置 45 | @property (nonatomic, assign) CGFloat boderX; // 编辑框边线X 46 | @property (nonatomic, assign) CGFloat boderWidth; // 编辑框边线长度 47 | @property (nonatomic, assign) CGFloat touchPointX; // 编辑视图区域外触点 48 | @property (nonatomic, assign) BOOL isEdited; // YES:编辑完成 49 | 50 | @end 51 | 52 | @implementation VideoEditVC 53 | 54 | #pragma mark lifeCycle 55 | - (void)viewDidLoad { 56 | [super viewDidLoad]; 57 | 58 | self.view.backgroundColor = [UIColor blackColor]; 59 | [self initFunctions]; 60 | } 61 | 62 | - (void)didReceiveMemoryWarning { 63 | [super didReceiveMemoryWarning]; 64 | // Dispose of any resources that can be recreated. 65 | } 66 | 67 | - (void)viewWillDisappear:(BOOL)animated{ 68 | [super viewWillDisappear:animated]; 69 | 70 | if (self.player) { 71 | [self invalidatePlayer]; 72 | } 73 | } 74 | 75 | #pragma mark 释放引用 76 | - (void)invalidatePlayer{ 77 | [self stopTimer]; 78 | [self.player removeObserver:self forKeyPath:@"timeControlStatus"]; 79 | [self.player pause]; 80 | [self.playItem removeObserver:self forKeyPath:@"status"]; 81 | } 82 | 83 | #pragma mark 自定义方法 84 | - (void)initFunctions{ 85 | // 手机静音时可播放声音 86 | AVAudioSession *session = [AVAudioSession sharedInstance]; 87 | [session setActive:YES error:nil]; 88 | [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; 89 | [session setCategory:AVAudioSessionCategoryPlayback error:nil]; 90 | 91 | bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, SCREEN_HEIGHT-80, SCREEN_WIDTH, 80)]; 92 | bottomView.backgroundColor = [UIColor blackColor]; 93 | [self.view addSubview:bottomView]; 94 | editScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, 50)]; 95 | editScrollView.showsHorizontalScrollIndicator = NO; 96 | editScrollView.bounces = NO; 97 | [bottomView addSubview:editScrollView]; 98 | editScrollView.delegate = self; 99 | 100 | // 添加编辑框上下边线 101 | self.boderX = 45; 102 | self.boderWidth = SCREEN_WIDTH-90; 103 | topBorder = [[UIView alloc] initWithFrame:CGRectMake(self.boderX, 0, self.boderWidth, 2)]; 104 | topBorder.backgroundColor = [UIColor whiteColor]; 105 | [bottomView addSubview:topBorder]; 106 | 107 | bottomBorder = [[UIView alloc] initWithFrame:CGRectMake(self.boderX, 50-2, self.boderWidth, 2)]; 108 | bottomBorder.backgroundColor = [UIColor whiteColor]; 109 | [bottomView addSubview:bottomBorder]; 110 | 111 | // 添加左右编辑框拖动条 112 | leftDragView = [[DragEditView alloc] initWithFrame:CGRectMake(-(SCREEN_WIDTH-50), 0, SCREEN_WIDTH, 50) Left:YES]; 113 | leftDragView.hitTestEdgeInsets = UIEdgeInsetsMake(0, -(EDGE_EXTENSION_FOR_THUMB), 0, -(EDGE_EXTENSION_FOR_THUMB)); 114 | [bottomView addSubview:leftDragView]; 115 | 116 | rightDragView = [[DragEditView alloc] initWithFrame:CGRectMake((SCREEN_WIDTH-50), 0, SCREEN_WIDTH, 50) Left:NO]; 117 | rightDragView.hitTestEdgeInsets = UIEdgeInsetsMake(0, -(EDGE_EXTENSION_FOR_THUMB), 0, -(EDGE_EXTENSION_FOR_THUMB)); 118 | [bottomView addSubview:rightDragView]; 119 | 120 | UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveOverlayView:)]; 121 | [bottomView addGestureRecognizer:panGestureRecognizer]; 122 | 123 | // 播放条 124 | line = [[UIView alloc] initWithFrame:CGRectMake(10, 0, 3, 50)]; 125 | line.backgroundColor = [UIColor colorWithRed:214/255.0 green:230/255.0 blue:247/255.0 alpha:1.0]; 126 | [bottomView addSubview:line]; 127 | line.hidden = YES; 128 | 129 | UIButton *doneBtn = [[UIButton alloc] initWithFrame:CGRectMake(SCREEN_WIDTH-60, 50, 60, 30)]; 130 | [doneBtn setTitle:@"完成" forState:UIControlStateNormal]; 131 | [doneBtn setTitleColor:[UIColor colorWithRed:14/255.0 green:178/255.0 blue:10/255.0 alpha:1.0] forState:UIControlStateNormal]; 132 | [doneBtn addTarget:self action:@selector(notifyDelegateOfDidChange) forControlEvents:UIControlEventTouchUpInside]; 133 | [bottomView addSubview:doneBtn]; 134 | 135 | // 默认startTime 0秒 endTime 10秒 136 | self.startTime = 0; 137 | self.endTime = 10; 138 | self.startPointX = 50; 139 | self.endPointX = SCREEN_WIDTH-50; 140 | self.IMG_Width = (SCREEN_WIDTH-100)/10; 141 | } 142 | 143 | #pragma mark 编辑区域手势拖动 144 | - (void)moveOverlayView:(UIPanGestureRecognizer *)gesture{ 145 | switch (gesture.state) { 146 | case UIGestureRecognizerStateBegan: 147 | { 148 | [self stopTimer]; 149 | BOOL isRight = [rightDragView pointInsideImgView:[gesture locationInView:rightDragView]]; 150 | BOOL isLeft = [leftDragView pointInsideImgView:[gesture locationInView:leftDragView]]; 151 | _isDraggingRightOverlayView = NO; 152 | _isDraggingLeftOverlayView = NO; 153 | 154 | self.touchPointX = [gesture locationInView:bottomView].x; 155 | if (isRight){ 156 | self.rightStartPoint = [gesture locationInView:bottomView]; 157 | _isDraggingRightOverlayView = YES; 158 | _isDraggingLeftOverlayView = NO; 159 | } 160 | else if (isLeft){ 161 | self.leftStartPoint = [gesture locationInView:bottomView]; 162 | _isDraggingRightOverlayView = NO; 163 | _isDraggingLeftOverlayView = YES; 164 | 165 | } 166 | } 167 | break; 168 | 169 | case UIGestureRecognizerStateChanged: 170 | { 171 | CGPoint point = [gesture locationInView:bottomView]; 172 | 173 | // Left 174 | if (_isDraggingLeftOverlayView){ 175 | CGFloat deltaX = point.x - self.leftStartPoint.x; 176 | CGPoint center = leftDragView.center; 177 | center.x += deltaX; 178 | CGFloat durationTime = (SCREEN_WIDTH-100)*2/10; // 最小范围2秒 179 | BOOL flag = (self.endPointX-point.x)>durationTime; 180 | 181 | if (center.x >= (50-SCREEN_WIDTH/2) && flag) { 182 | leftDragView.center = center; 183 | self.leftStartPoint = point; 184 | self.startTime = (point.x+editScrollView.contentOffset.x)/self.IMG_Width; 185 | topBorder.frame = CGRectMake(self.boderX+=deltaX/2, 0, self.boderWidth-=deltaX/2, 2); 186 | bottomBorder.frame = CGRectMake(self.boderX+=deltaX/2, 50-2, self.boderWidth-=deltaX/2, 2); 187 | self.startPointX = point.x; 188 | } 189 | CMTime startTime = CMTimeMakeWithSeconds((point.x+editScrollView.contentOffset.x)/self.IMG_Width, self.player.currentTime.timescale); 190 | 191 | // 只有视频播放的时候才能够快进和快退1秒以内 192 | [self.player seekToTime:startTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; 193 | } 194 | else if (_isDraggingRightOverlayView){ // Right 195 | CGFloat deltaX = point.x - self.rightStartPoint.x; 196 | CGPoint center = rightDragView.center; 197 | center.x += deltaX; 198 | CGFloat durationTime = (SCREEN_WIDTH-100)*2/10; // 最小范围2秒 199 | BOOL flag = (point.x-self.startPointX)>durationTime; 200 | if (center.x <= (SCREEN_WIDTH-50+SCREEN_WIDTH/2) && flag) { 201 | rightDragView.center = center; 202 | self.rightStartPoint = point; 203 | self.endTime = (point.x+editScrollView.contentOffset.x)/self.IMG_Width; 204 | topBorder.frame = CGRectMake(self.boderX, 0, self.boderWidth+=deltaX/2, 2); 205 | bottomBorder.frame = CGRectMake(self.boderX, 50-2, self.boderWidth+=deltaX/2, 2); 206 | self.endPointX = point.x; 207 | } 208 | CMTime startTime = CMTimeMakeWithSeconds((point.x+editScrollView.contentOffset.x)/self.IMG_Width, self.player.currentTime.timescale); 209 | 210 | // 只有视频播放的时候才能够快进和快退1秒以内 211 | [self.player seekToTime:startTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; 212 | } 213 | else { // 移动scrollView 214 | CGFloat deltaX = point.x - self.touchPointX; 215 | CGFloat newOffset = editScrollView.contentOffset.x-deltaX; 216 | CGPoint currentOffSet = CGPointMake(newOffset, 0); 217 | 218 | if (currentOffSet.x >= 0 && currentOffSet.x <= (editScrollView.contentSize.width-SCREEN_WIDTH)) { 219 | editScrollView.contentOffset = CGPointMake(newOffset, 0); 220 | self.touchPointX = point.x; 221 | } 222 | } 223 | 224 | } 225 | break; 226 | 227 | case UIGestureRecognizerStateEnded: 228 | { 229 | [self startTimer]; 230 | } 231 | break; 232 | 233 | default: 234 | break; 235 | } 236 | 237 | } 238 | 239 | #pragma mark 视频裁剪 240 | - (void)notifyDelegateOfDidChange{ 241 | self.tempVideoPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmpMov.mov"]; 242 | 243 | [self deleteTempFile]; 244 | 245 | AVAsset *asset = [AVAsset assetWithURL:self.videoUrl]; 246 | AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] 247 | initWithAsset:asset presetName:AVAssetExportPresetPassthrough]; 248 | 249 | NSURL *furl = [NSURL fileURLWithPath:self.tempVideoPath]; 250 | exportSession.outputURL = furl; 251 | exportSession.outputFileType = AVFileTypeQuickTimeMovie; 252 | 253 | CMTime start = CMTimeMakeWithSeconds(self.startTime, self.player.currentTime.timescale); 254 | CMTime duration = CMTimeMakeWithSeconds(self.endTime - self.startTime, self.player.currentTime.timescale);; 255 | CMTimeRange range = CMTimeRangeMake(start, duration); 256 | exportSession.timeRange = range; 257 | 258 | [exportSession exportAsynchronouslyWithCompletionHandler:^{ 259 | switch ([exportSession status]) { 260 | case AVAssetExportSessionStatusFailed: 261 | NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]); 262 | break; 263 | 264 | case AVAssetExportSessionStatusCancelled: 265 | NSLog(@"Export canceled"); 266 | break; 267 | 268 | case AVAssetExportSessionStatusCompleted:{ 269 | NSLog(@"Export completed"); 270 | __weak typeof(self) weakSelf = self; 271 | dispatch_async(dispatch_get_main_queue(), ^{ 272 | UISaveVideoAtPathToSavedPhotosAlbum([furl relativePath], self,@selector(video:didFinishSavingWithError:contextInfo:), nil); 273 | NSLog(@"编辑后的视频路径: %@",weakSelf.tempVideoPath); 274 | 275 | weakSelf.isEdited = YES; 276 | [weakSelf invalidatePlayer]; 277 | [weakSelf initPlayerWithVideoUrl:furl]; 278 | bottomView.hidden = YES; 279 | }); 280 | } 281 | break; 282 | 283 | default: 284 | NSLog(@"Export other"); 285 | 286 | break; 287 | } 288 | }]; 289 | } 290 | 291 | - (void)video:(NSString*)videoPath didFinishSavingWithError:(NSError*)error contextInfo:(void*)contextInfo { 292 | if (error) { 293 | NSLog(@"保存到相册失败"); 294 | } 295 | else { 296 | NSLog(@"保存到相册成功"); 297 | } 298 | } 299 | 300 | - (void)deleteTempFile{ 301 | NSURL *url = [NSURL fileURLWithPath:self.tempVideoPath]; 302 | NSFileManager *fm = [NSFileManager defaultManager]; 303 | BOOL exist = [fm fileExistsAtPath:url.path]; 304 | NSError *err; 305 | if (exist) { 306 | [fm removeItemAtURL:url error:&err]; 307 | NSLog(@"file deleted"); 308 | if (err) { 309 | NSLog(@"file remove error, %@", err.localizedDescription ); 310 | } 311 | } 312 | else { 313 | NSLog(@"no file by that name"); 314 | } 315 | } 316 | 317 | #pragma mark - 初始化player 318 | - (void)initPlayerWithVideoUrl:(NSURL *)videlUrl{ 319 | self.playItem = [[AVPlayerItem alloc] initWithURL:videlUrl]; 320 | [self.playItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; 321 | 322 | self.player = [AVPlayer playerWithPlayerItem:self.playItem]; 323 | [self.player addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil]; 324 | 325 | self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; 326 | self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect; 327 | self.playerLayer.contentsScale = [UIScreen mainScreen].scale; 328 | self.playerLayer.frame = CGRectMake(0, 80, self.view.bounds.size.width, SCREEN_HEIGHT-160); 329 | [self.view.layer addSublayer:self.playerLayer]; 330 | } 331 | 332 | #pragma mark - KVO属性播放属性监听 333 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 334 | 335 | if ([keyPath isEqualToString:@"status"]) { 336 | switch (self.playItem.status) { 337 | case AVPlayerStatusUnknown: 338 | NSLog(@"KVO:未知状态,此时不能播放"); 339 | break; 340 | case AVPlayerStatusReadyToPlay: 341 | if (!_player.timeControlStatus || _player.timeControlStatus != AVPlayerTimeControlStatusPaused) { 342 | [_player play]; 343 | if (!self.isEdited) { 344 | line.hidden = NO; 345 | [self startTimer]; 346 | } 347 | } 348 | NSLog(@"KVO:准备完毕,可以播放"); 349 | break; 350 | case AVPlayerStatusFailed: 351 | NSLog(@"KVO:加载失败,网络或者服务器出现问题"); 352 | break; 353 | default: 354 | break; 355 | } 356 | } 357 | 358 | if ([keyPath isEqualToString:@"timeControlStatus"]) { 359 | // 剪切完视频后自动循环播放 360 | if (self.player.timeControlStatus == AVPlayerTimeControlStatusPaused) { 361 | [self.player seekToTime:CMTimeMake(0, 1)]; 362 | [self.player play]; 363 | } 364 | } 365 | } 366 | 367 | #pragma mark - 开启计时器 368 | - (void)startTimer{ 369 | double duarationTime = (self.endPointX-self.startPointX-20)/SCREEN_WIDTH*10; 370 | line.hidden = NO; 371 | self.linePositionX = self.startPointX+10; 372 | self.lineMoveTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(lineMove) userInfo:nil repeats:YES]; 373 | 374 | // 开启循环播放 375 | self.repeatTimer = [NSTimer scheduledTimerWithTimeInterval:duarationTime target:self selector:@selector(repeatPlay) userInfo:nil repeats:YES]; 376 | [self.repeatTimer fire]; 377 | } 378 | 379 | #pragma mark - 编辑区域循环播放 380 | - (void)repeatPlay{ 381 | [self.player play]; 382 | CMTime start = CMTimeMakeWithSeconds(self.startTime, self.player.currentTime.timescale); 383 | [self.player seekToTime:start toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; 384 | } 385 | 386 | #pragma mark - 播放条移动 387 | - (void)lineMove{ 388 | double duarationTime = (self.endPointX-self.startPointX-20)/SCREEN_WIDTH*10; 389 | self.linePositionX += 0.01*(self.endPointX - self.startPointX-20)/duarationTime; 390 | 391 | if (self.linePositionX >= CGRectGetMinX(rightDragView.frame)-3) { 392 | self.linePositionX = CGRectGetMaxX(leftDragView.frame)+3; 393 | } 394 | 395 | line.frame = CGRectMake(self.linePositionX, 0, 3, 50); 396 | } 397 | 398 | #pragma mark - 关闭计时器 399 | - (void)stopTimer{ 400 | [self.repeatTimer invalidate]; 401 | [self.lineMoveTimer invalidate]; 402 | line.hidden = YES; 403 | } 404 | 405 | #pragma mark - 读取解析视频帧 406 | - (void)analysisVideoFrames{ 407 | // 初始化asset对象 408 | AVURLAsset *videoAsset = [[AVURLAsset alloc]initWithURL:self.videoUrl options:nil]; 409 | // 获取总视频的长度 = 总帧数 / 每秒的帧数 410 | long videoSumTime = videoAsset.duration.value / videoAsset.duration.timescale; 411 | 412 | // 创建AVAssetImageGenerator对象 413 | AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc]initWithAsset:videoAsset]; 414 | generator.maximumSize = bottomView.frame.size; 415 | generator.appliesPreferredTrackTransform = YES; 416 | generator.requestedTimeToleranceBefore = kCMTimeZero; 417 | generator.requestedTimeToleranceAfter = kCMTimeZero; 418 | 419 | // 添加需要帧数的时间集合 420 | self.framesArray = [NSMutableArray array]; 421 | for (int i = 0; i < videoSumTime; i++) { 422 | CMTime time = CMTimeMake(i *videoAsset.duration.timescale , videoAsset.duration.timescale); 423 | NSValue *value = [NSValue valueWithCMTime:time]; 424 | [self.framesArray addObject:value]; 425 | } 426 | 427 | __block long count = 0; 428 | __weak typeof(self) weakSelf = self; 429 | [generator generateCGImagesAsynchronouslyForTimes:self.framesArray completionHandler:^(CMTime requestedTime, CGImageRef img, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error){ 430 | 431 | if (result == AVAssetImageGeneratorSucceeded) { 432 | NSLog(@"%ld",count); 433 | UIImageView *thumImgView = [[UIImageView alloc] initWithFrame:CGRectMake(50+count*weakSelf.IMG_Width, 0, weakSelf.IMG_Width, 70)]; 434 | thumImgView.image = [UIImage imageWithCGImage:img]; 435 | dispatch_async(dispatch_get_main_queue(), ^{ 436 | [editScrollView addSubview:thumImgView]; 437 | editScrollView.contentSize = CGSizeMake(100+count*weakSelf.IMG_Width, 0); 438 | }); 439 | count++; 440 | } 441 | 442 | if (result == AVAssetImageGeneratorFailed) { 443 | NSLog(@"Failed with error: %@", [error localizedDescription]); 444 | } 445 | 446 | if (result == AVAssetImageGeneratorCancelled) { 447 | NSLog(@"AVAssetImageGeneratorCancelled"); 448 | } 449 | }]; 450 | } 451 | 452 | #pragma mark - UIScrollViewDelegate 453 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{ 454 | [self stopTimer]; 455 | } 456 | 457 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ 458 | [self startTimer]; 459 | } 460 | 461 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView{ 462 | [self letScrollViewScrollAndResetPlayerStartTime]; 463 | // 视频暂停时可通过 AVPlayerItem 的API - (void)stepByCount:(NSInteger)stepCount; 滑动,目前未找到step的具体大小 官方文档说的不清楚 464 | // NSInteger step = offsetX/(50.0*self.framesArray.count)*72; 465 | // NSLog(@"移动步数:%ld",step); 466 | // if ([self.playItem canStepForward] && step > 0) { 467 | // [self.playItem stepByCount:step]; 468 | // } 469 | // 470 | // if ([self.playItem canStepBackward] && step < 0) { 471 | // [self.playItem stepByCount:step]; 472 | // } 473 | } 474 | 475 | #pragma mark - scrollView滑动时设置 476 | -(void)letScrollViewScrollAndResetPlayerStartTime{ 477 | CGFloat offsetX = editScrollView.contentOffset.x; 478 | CMTime startTime; 479 | 480 | if (offsetX>=0) { 481 | startTime = CMTimeMakeWithSeconds((offsetX+self.startPointX)/self.IMG_Width, self.player.currentTime.timescale); 482 | CGFloat duration = self.endTime-self.startTime; 483 | self.startTime = (offsetX+self.startPointX)/self.IMG_Width; 484 | self.endTime = self.startTime+duration; 485 | } 486 | else { 487 | startTime = CMTimeMakeWithSeconds(self.startPointX, self.player.currentTime.timescale); 488 | } 489 | 490 | // 只有视频播放的时候才能够快进和快退1秒以内 491 | [self.player seekToTime:startTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; 492 | } 493 | 494 | #pragma mark - setMethod 495 | - (void)setVideoUrl:(NSURL *)videoUrl{ 496 | _videoUrl = videoUrl; 497 | if (!self.isEdit) { 498 | [self analysisVideoFrames]; 499 | } 500 | else { 501 | leftDragView.hidden = YES; 502 | rightDragView.hidden = YES; 503 | } 504 | 505 | [self initPlayerWithVideoUrl:videoUrl]; 506 | 507 | UIButton *backBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 20, 60, 50)]; 508 | [backBtn setTitle:@"返回" forState:UIControlStateNormal]; 509 | [backBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 510 | [backBtn addTarget:self action:@selector(dismissSelfVC) forControlEvents:UIControlEventTouchUpInside]; 511 | [self.view addSubview:backBtn]; 512 | } 513 | 514 | - (void)dismissSelfVC{ 515 | [self dismissViewControllerAnimated:YES completion:^{ }]; 516 | } 517 | 518 | #pragma mark - getMethod 519 | - (NSMutableArray *)framesArray{ 520 | if (!_framesArray) { 521 | _framesArray = [NSMutableArray array]; 522 | } 523 | return _framesArray; 524 | } 525 | 526 | @end 527 | -------------------------------------------------------------------------------- /VideoEditDemo/VideoEditTool/drag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterZhiWei/VideoEditDemo/3d184b46030ecda3134bc9202df418cf8c6f03ed/VideoEditDemo/VideoEditTool/drag.jpg -------------------------------------------------------------------------------- /VideoEditDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // VideoEditDemo 4 | // 5 | // Created by 刘志伟 on 2017/8/17. 6 | // Copyright © 2017年 刘志伟. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /VideoEditDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // VideoEditDemo 4 | // 5 | // Created by 刘志伟 on 2017/8/17. 6 | // Copyright © 2017年 刘志伟. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import 11 | #import "VideoEditVC.h" 12 | #import 13 | #import 14 | 15 | @interface ViewController () 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | // Do any additional setup after loading the view, typically from a nib. 24 | [self loadLaunchImageView]; 25 | 26 | UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(50, 50, 120, 50)]; 27 | [button setTitle:@"选取编辑视频" forState:UIControlStateNormal]; 28 | [button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal]; 29 | [self.view addSubview:button]; 30 | self.view.backgroundColor = [UIColor whiteColor]; 31 | [button addTarget:self action:@selector(selectVideoAsset) forControlEvents:UIControlEventTouchUpInside]; 32 | } 33 | 34 | - (void)loadLaunchImageView{ 35 | CGSize viewSize = self.view.bounds.size; 36 | NSString *viewOrientation = @"Portrait"; //横屏请设置成 @"Landscape" 37 | NSString *launchImage = nil; 38 | 39 | NSArray* imagesDict = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"UILaunchImages"]; 40 | for (NSDictionary* dict in imagesDict) 41 | { 42 | CGSize imageSize = CGSizeFromString(dict[@"UILaunchImageSize"]); 43 | 44 | if (CGSizeEqualToSize(imageSize, viewSize) && [viewOrientation isEqualToString:dict[@"UILaunchImageOrientation"]]) 45 | { 46 | launchImage = dict[@"UILaunchImageName"]; 47 | } 48 | } 49 | 50 | UIImageView *launchView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:launchImage]]; 51 | launchView.frame = self.view.bounds; 52 | launchView.contentMode = UIViewContentModeScaleAspectFill; 53 | [self.view addSubview:launchView]; 54 | 55 | [UIView animateWithDuration:2.0f 56 | delay:0.0f 57 | options:UIViewAnimationOptionBeginFromCurrentState 58 | animations:^{ 59 | 60 | launchView.alpha = 0.0f; 61 | launchView.layer.transform = CATransform3DScale(CATransform3DIdentity, 1.2, 1.2, 1); 62 | 63 | } 64 | completion:^(BOOL finished) { 65 | 66 | [launchView removeFromSuperview]; 67 | 68 | }]; 69 | } 70 | 71 | - (void)selectVideoAsset{ 72 | UIImagePickerController *myImagePickerController = [[UIImagePickerController alloc] init]; 73 | myImagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 74 | myImagePickerController.mediaTypes = 75 | [[NSArray alloc] initWithObjects: (NSString *) kUTTypeMovie, nil]; 76 | myImagePickerController.delegate = self; 77 | myImagePickerController.editing = NO; 78 | [self presentViewController:myImagePickerController animated:YES completion:nil]; 79 | } 80 | 81 | #pragma mark - UIImagePickerControllerDelegate 82 | 83 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ 84 | [picker dismissViewControllerAnimated:YES completion:nil]; 85 | NSURL *url = [info objectForKey:UIImagePickerControllerMediaURL]; 86 | VideoEditVC *videoEditVC = [[VideoEditVC alloc] init]; 87 | videoEditVC.videoUrl = url; 88 | 89 | [self presentViewController:videoEditVC animated:YES completion:^{ }]; 90 | } 91 | 92 | - (void)didReceiveMemoryWarning { 93 | [super didReceiveMemoryWarning]; 94 | // Dispose of any resources that can be recreated. 95 | } 96 | 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /VideoEditDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // VideoEditDemo 4 | // 5 | // Created by 刘志伟 on 2017/8/17. 6 | // Copyright © 2017年 刘志伟. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | --------------------------------------------------------------------------------