├── .gitignore ├── LICENSE ├── README.md ├── VideoPlayDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── VideoPlayDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json └── LaunchImage.launchimage │ └── Contents.json ├── Base.lproj └── LaunchScreen.storyboard ├── IMG_0950.jpg ├── Info.plist ├── SJVideoCrop ├── ExportVideoUtil.h ├── ExportVideoUtil.m ├── SJCommon.h ├── SJCropCollectionCell.h ├── SJCropCollectionCell.m ├── SJCropViewController.h ├── SJCropViewController.m ├── SJMediaInfoConfig.h ├── SJMediaInfoConfig.m ├── SJVideoCropView.h ├── SJVideoCropView.m ├── SJVideoPlayView.h ├── SJVideoPlayView.m ├── UIButton+SJExtension.h ├── UIButton+SJExtension.m ├── UIView+SJExtension.h └── UIView+SJExtension.m ├── eg.gif ├── main.m └── video.mp4 /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 张盛杰 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VideoSliderCrop 2 | 仿抖音 相册视频选择后 滑动裁剪界面 3 | 支持裁剪 4 | 5 | ## 效果图 6 | ![图片展示](https://raw.githubusercontent.com/1401788197/VideoSliderCrop/master/VideoPlayDemo/eg.gif) 7 | 8 | -------------------------------------------------------------------------------- /VideoPlayDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2B2F291E2248863200D35301 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B2F291D2248863200D35301 /* AppDelegate.m */; }; 11 | 2B2F29262248863300D35301 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B2F29252248863300D35301 /* Assets.xcassets */; }; 12 | 2B2F29292248863300D35301 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2B2F29272248863300D35301 /* LaunchScreen.storyboard */; }; 13 | 2B2F292C2248863300D35301 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B2F292B2248863300D35301 /* main.m */; }; 14 | 2B2F29342248868100D35301 /* SJVideoPlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B2F29332248868100D35301 /* SJVideoPlayView.m */; }; 15 | 2B2F29362248A9ED00D35301 /* video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 2B2F29352248A9ED00D35301 /* video.mp4 */; }; 16 | 2B392A572248F690002CF7F6 /* SJMediaInfoConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B392A562248F68F002CF7F6 /* SJMediaInfoConfig.m */; }; 17 | 2B5A25D0225C3CB800512C8F /* UIButton+SJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B5A25CF225C3CB800512C8F /* UIButton+SJExtension.m */; }; 18 | 2B5A25D5225C89D700512C8F /* SJCropViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B5A25D4225C89D700512C8F /* SJCropViewController.m */; }; 19 | 2BCABA48224B7B40002E4BF7 /* SJVideoCropView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2BCABA47224B7B40002E4BF7 /* SJVideoCropView.m */; }; 20 | 2BCABA4B224B8035002E4BF7 /* UIView+SJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 2BCABA4A224B8035002E4BF7 /* UIView+SJExtension.m */; }; 21 | 2BCABA4F224B8629002E4BF7 /* SJCropCollectionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2BCABA4E224B8629002E4BF7 /* SJCropCollectionCell.m */; }; 22 | F073A39127E4240100716D10 /* ExportVideoUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = F073A39027E4240100716D10 /* ExportVideoUtil.m */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 2B2F29192248863200D35301 /* VideoPlayDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VideoPlayDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 2B2F291C2248863200D35301 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 28 | 2B2F291D2248863200D35301 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 29 | 2B2F29252248863300D35301 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | 2B2F29282248863300D35301 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | 2B2F292A2248863300D35301 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | 2B2F292B2248863300D35301 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 33 | 2B2F29322248868100D35301 /* SJVideoPlayView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SJVideoPlayView.h; sourceTree = ""; }; 34 | 2B2F29332248868100D35301 /* SJVideoPlayView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SJVideoPlayView.m; sourceTree = ""; }; 35 | 2B2F29352248A9ED00D35301 /* video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = video.mp4; sourceTree = ""; }; 36 | 2B392A552248F68F002CF7F6 /* SJMediaInfoConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SJMediaInfoConfig.h; sourceTree = ""; }; 37 | 2B392A562248F68F002CF7F6 /* SJMediaInfoConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SJMediaInfoConfig.m; sourceTree = ""; }; 38 | 2B5A25CE225C3CB800512C8F /* UIButton+SJExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIButton+SJExtension.h"; sourceTree = ""; }; 39 | 2B5A25CF225C3CB800512C8F /* UIButton+SJExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIButton+SJExtension.m"; sourceTree = ""; }; 40 | 2B5A25D3225C89D700512C8F /* SJCropViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SJCropViewController.h; sourceTree = ""; }; 41 | 2B5A25D4225C89D700512C8F /* SJCropViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SJCropViewController.m; sourceTree = ""; }; 42 | 2BCABA46224B7B40002E4BF7 /* SJVideoCropView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SJVideoCropView.h; sourceTree = ""; }; 43 | 2BCABA47224B7B40002E4BF7 /* SJVideoCropView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SJVideoCropView.m; sourceTree = ""; }; 44 | 2BCABA49224B8035002E4BF7 /* UIView+SJExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SJExtension.h"; sourceTree = ""; }; 45 | 2BCABA4A224B8035002E4BF7 /* UIView+SJExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SJExtension.m"; sourceTree = ""; }; 46 | 2BCABA4C224B80C4002E4BF7 /* SJCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SJCommon.h; sourceTree = ""; }; 47 | 2BCABA4D224B8629002E4BF7 /* SJCropCollectionCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SJCropCollectionCell.h; sourceTree = ""; }; 48 | 2BCABA4E224B8629002E4BF7 /* SJCropCollectionCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SJCropCollectionCell.m; sourceTree = ""; }; 49 | F073A38F27E4240100716D10 /* ExportVideoUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExportVideoUtil.h; sourceTree = ""; }; 50 | F073A39027E4240100716D10 /* ExportVideoUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExportVideoUtil.m; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 2B2F29162248863200D35301 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 2B2F29102248863200D35301 = { 65 | isa = PBXGroup; 66 | children = ( 67 | 2B2F291B2248863200D35301 /* VideoPlayDemo */, 68 | 2B2F291A2248863200D35301 /* Products */, 69 | ); 70 | sourceTree = ""; 71 | }; 72 | 2B2F291A2248863200D35301 /* Products */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 2B2F29192248863200D35301 /* VideoPlayDemo.app */, 76 | ); 77 | name = Products; 78 | sourceTree = ""; 79 | }; 80 | 2B2F291B2248863200D35301 /* VideoPlayDemo */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 2B5A25D6225C8F6300512C8F /* SJVideoCrop */, 84 | 2B2F29352248A9ED00D35301 /* video.mp4 */, 85 | 2B2F291C2248863200D35301 /* AppDelegate.h */, 86 | 2B2F291D2248863200D35301 /* AppDelegate.m */, 87 | 2B2F29252248863300D35301 /* Assets.xcassets */, 88 | 2B2F29272248863300D35301 /* LaunchScreen.storyboard */, 89 | 2B2F292A2248863300D35301 /* Info.plist */, 90 | 2B2F292B2248863300D35301 /* main.m */, 91 | ); 92 | path = VideoPlayDemo; 93 | sourceTree = ""; 94 | }; 95 | 2B5A25D6225C8F6300512C8F /* SJVideoCrop */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 2B5A25D3225C89D700512C8F /* SJCropViewController.h */, 99 | 2B5A25D4225C89D700512C8F /* SJCropViewController.m */, 100 | 2BCABA4D224B8629002E4BF7 /* SJCropCollectionCell.h */, 101 | 2BCABA4E224B8629002E4BF7 /* SJCropCollectionCell.m */, 102 | 2BCABA4C224B80C4002E4BF7 /* SJCommon.h */, 103 | 2BCABA49224B8035002E4BF7 /* UIView+SJExtension.h */, 104 | 2BCABA4A224B8035002E4BF7 /* UIView+SJExtension.m */, 105 | 2B5A25CE225C3CB800512C8F /* UIButton+SJExtension.h */, 106 | 2B5A25CF225C3CB800512C8F /* UIButton+SJExtension.m */, 107 | 2B392A552248F68F002CF7F6 /* SJMediaInfoConfig.h */, 108 | 2B392A562248F68F002CF7F6 /* SJMediaInfoConfig.m */, 109 | 2BCABA46224B7B40002E4BF7 /* SJVideoCropView.h */, 110 | 2BCABA47224B7B40002E4BF7 /* SJVideoCropView.m */, 111 | 2B2F29322248868100D35301 /* SJVideoPlayView.h */, 112 | 2B2F29332248868100D35301 /* SJVideoPlayView.m */, 113 | F073A38F27E4240100716D10 /* ExportVideoUtil.h */, 114 | F073A39027E4240100716D10 /* ExportVideoUtil.m */, 115 | ); 116 | path = SJVideoCrop; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | 2B2F29182248863200D35301 /* VideoPlayDemo */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = 2B2F292F2248863300D35301 /* Build configuration list for PBXNativeTarget "VideoPlayDemo" */; 125 | buildPhases = ( 126 | 2B2F29152248863200D35301 /* Sources */, 127 | 2B2F29162248863200D35301 /* Frameworks */, 128 | 2B2F29172248863200D35301 /* Resources */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = VideoPlayDemo; 135 | productName = VideoPlayDemo; 136 | productReference = 2B2F29192248863200D35301 /* VideoPlayDemo.app */; 137 | productType = "com.apple.product-type.application"; 138 | }; 139 | /* End PBXNativeTarget section */ 140 | 141 | /* Begin PBXProject section */ 142 | 2B2F29112248863200D35301 /* Project object */ = { 143 | isa = PBXProject; 144 | attributes = { 145 | LastUpgradeCheck = 1010; 146 | ORGANIZATIONNAME = "shengjie zhang"; 147 | TargetAttributes = { 148 | 2B2F29182248863200D35301 = { 149 | CreatedOnToolsVersion = 10.1; 150 | }; 151 | }; 152 | }; 153 | buildConfigurationList = 2B2F29142248863200D35301 /* Build configuration list for PBXProject "VideoPlayDemo" */; 154 | compatibilityVersion = "Xcode 9.3"; 155 | developmentRegion = en; 156 | hasScannedForEncodings = 0; 157 | knownRegions = ( 158 | en, 159 | Base, 160 | ); 161 | mainGroup = 2B2F29102248863200D35301; 162 | productRefGroup = 2B2F291A2248863200D35301 /* Products */; 163 | projectDirPath = ""; 164 | projectRoot = ""; 165 | targets = ( 166 | 2B2F29182248863200D35301 /* VideoPlayDemo */, 167 | ); 168 | }; 169 | /* End PBXProject section */ 170 | 171 | /* Begin PBXResourcesBuildPhase section */ 172 | 2B2F29172248863200D35301 /* Resources */ = { 173 | isa = PBXResourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | 2B2F29292248863300D35301 /* LaunchScreen.storyboard in Resources */, 177 | 2B2F29262248863300D35301 /* Assets.xcassets in Resources */, 178 | 2B2F29362248A9ED00D35301 /* video.mp4 in Resources */, 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | /* End PBXResourcesBuildPhase section */ 183 | 184 | /* Begin PBXSourcesBuildPhase section */ 185 | 2B2F29152248863200D35301 /* Sources */ = { 186 | isa = PBXSourcesBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | 2BCABA4B224B8035002E4BF7 /* UIView+SJExtension.m in Sources */, 190 | 2BCABA48224B7B40002E4BF7 /* SJVideoCropView.m in Sources */, 191 | 2B392A572248F690002CF7F6 /* SJMediaInfoConfig.m in Sources */, 192 | 2B2F29342248868100D35301 /* SJVideoPlayView.m in Sources */, 193 | 2B2F292C2248863300D35301 /* main.m in Sources */, 194 | 2BCABA4F224B8629002E4BF7 /* SJCropCollectionCell.m in Sources */, 195 | 2B5A25D5225C89D700512C8F /* SJCropViewController.m in Sources */, 196 | F073A39127E4240100716D10 /* ExportVideoUtil.m in Sources */, 197 | 2B2F291E2248863200D35301 /* AppDelegate.m in Sources */, 198 | 2B5A25D0225C3CB800512C8F /* UIButton+SJExtension.m in Sources */, 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | /* End PBXSourcesBuildPhase section */ 203 | 204 | /* Begin PBXVariantGroup section */ 205 | 2B2F29272248863300D35301 /* LaunchScreen.storyboard */ = { 206 | isa = PBXVariantGroup; 207 | children = ( 208 | 2B2F29282248863300D35301 /* Base */, 209 | ); 210 | name = LaunchScreen.storyboard; 211 | sourceTree = ""; 212 | }; 213 | /* End PBXVariantGroup section */ 214 | 215 | /* Begin XCBuildConfiguration section */ 216 | 2B2F292D2248863300D35301 /* Debug */ = { 217 | isa = XCBuildConfiguration; 218 | buildSettings = { 219 | ALWAYS_SEARCH_USER_PATHS = NO; 220 | CLANG_ANALYZER_NONNULL = YES; 221 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 222 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 223 | CLANG_CXX_LIBRARY = "libc++"; 224 | CLANG_ENABLE_MODULES = YES; 225 | CLANG_ENABLE_OBJC_ARC = YES; 226 | CLANG_ENABLE_OBJC_WEAK = YES; 227 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 228 | CLANG_WARN_BOOL_CONVERSION = YES; 229 | CLANG_WARN_COMMA = YES; 230 | CLANG_WARN_CONSTANT_CONVERSION = YES; 231 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 234 | CLANG_WARN_EMPTY_BODY = YES; 235 | CLANG_WARN_ENUM_CONVERSION = YES; 236 | CLANG_WARN_INFINITE_RECURSION = YES; 237 | CLANG_WARN_INT_CONVERSION = YES; 238 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 239 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 240 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 241 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 242 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 243 | CLANG_WARN_STRICT_PROTOTYPES = YES; 244 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 245 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 246 | CLANG_WARN_UNREACHABLE_CODE = YES; 247 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 248 | CODE_SIGN_IDENTITY = "iPhone Developer"; 249 | COPY_PHASE_STRIP = NO; 250 | DEBUG_INFORMATION_FORMAT = dwarf; 251 | ENABLE_STRICT_OBJC_MSGSEND = YES; 252 | ENABLE_TESTABILITY = YES; 253 | GCC_C_LANGUAGE_STANDARD = gnu11; 254 | GCC_DYNAMIC_NO_PIC = NO; 255 | GCC_NO_COMMON_BLOCKS = YES; 256 | GCC_OPTIMIZATION_LEVEL = 0; 257 | GCC_PREPROCESSOR_DEFINITIONS = ( 258 | "DEBUG=1", 259 | "$(inherited)", 260 | ); 261 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 262 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 263 | GCC_WARN_UNDECLARED_SELECTOR = YES; 264 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 265 | GCC_WARN_UNUSED_FUNCTION = YES; 266 | GCC_WARN_UNUSED_VARIABLE = YES; 267 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 268 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 269 | MTL_FAST_MATH = YES; 270 | ONLY_ACTIVE_ARCH = YES; 271 | SDKROOT = iphoneos; 272 | }; 273 | name = Debug; 274 | }; 275 | 2B2F292E2248863300D35301 /* Release */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | ALWAYS_SEARCH_USER_PATHS = NO; 279 | CLANG_ANALYZER_NONNULL = YES; 280 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_ENABLE_OBJC_WEAK = YES; 286 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 287 | CLANG_WARN_BOOL_CONVERSION = YES; 288 | CLANG_WARN_COMMA = YES; 289 | CLANG_WARN_CONSTANT_CONVERSION = YES; 290 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 291 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 292 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INFINITE_RECURSION = YES; 296 | CLANG_WARN_INT_CONVERSION = YES; 297 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 298 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 299 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 300 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 301 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 302 | CLANG_WARN_STRICT_PROTOTYPES = YES; 303 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 304 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 305 | CLANG_WARN_UNREACHABLE_CODE = YES; 306 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 307 | CODE_SIGN_IDENTITY = "iPhone Developer"; 308 | COPY_PHASE_STRIP = NO; 309 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 310 | ENABLE_NS_ASSERTIONS = NO; 311 | ENABLE_STRICT_OBJC_MSGSEND = YES; 312 | GCC_C_LANGUAGE_STANDARD = gnu11; 313 | GCC_NO_COMMON_BLOCKS = YES; 314 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 315 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 316 | GCC_WARN_UNDECLARED_SELECTOR = YES; 317 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 318 | GCC_WARN_UNUSED_FUNCTION = YES; 319 | GCC_WARN_UNUSED_VARIABLE = YES; 320 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 321 | MTL_ENABLE_DEBUG_INFO = NO; 322 | MTL_FAST_MATH = YES; 323 | SDKROOT = iphoneos; 324 | VALIDATE_PRODUCT = YES; 325 | }; 326 | name = Release; 327 | }; 328 | 2B2F29302248863300D35301 /* Debug */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 332 | CODE_SIGN_STYLE = Automatic; 333 | DEVELOPMENT_TEAM = F28AY7UZK2; 334 | INFOPLIST_FILE = VideoPlayDemo/Info.plist; 335 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 336 | LD_RUNPATH_SEARCH_PATHS = ( 337 | "$(inherited)", 338 | "@executable_path/Frameworks", 339 | ); 340 | PRODUCT_BUNDLE_IDENTIFIER = "shengjie-zhang.VideoPlayDemo"; 341 | PRODUCT_NAME = "$(TARGET_NAME)"; 342 | TARGETED_DEVICE_FAMILY = 1; 343 | }; 344 | name = Debug; 345 | }; 346 | 2B2F29312248863300D35301 /* Release */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 350 | CODE_SIGN_STYLE = Automatic; 351 | DEVELOPMENT_TEAM = F28AY7UZK2; 352 | INFOPLIST_FILE = VideoPlayDemo/Info.plist; 353 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 354 | LD_RUNPATH_SEARCH_PATHS = ( 355 | "$(inherited)", 356 | "@executable_path/Frameworks", 357 | ); 358 | PRODUCT_BUNDLE_IDENTIFIER = "shengjie-zhang.VideoPlayDemo"; 359 | PRODUCT_NAME = "$(TARGET_NAME)"; 360 | TARGETED_DEVICE_FAMILY = 1; 361 | }; 362 | name = Release; 363 | }; 364 | /* End XCBuildConfiguration section */ 365 | 366 | /* Begin XCConfigurationList section */ 367 | 2B2F29142248863200D35301 /* Build configuration list for PBXProject "VideoPlayDemo" */ = { 368 | isa = XCConfigurationList; 369 | buildConfigurations = ( 370 | 2B2F292D2248863300D35301 /* Debug */, 371 | 2B2F292E2248863300D35301 /* Release */, 372 | ); 373 | defaultConfigurationIsVisible = 0; 374 | defaultConfigurationName = Release; 375 | }; 376 | 2B2F292F2248863300D35301 /* Build configuration list for PBXNativeTarget "VideoPlayDemo" */ = { 377 | isa = XCConfigurationList; 378 | buildConfigurations = ( 379 | 2B2F29302248863300D35301 /* Debug */, 380 | 2B2F29312248863300D35301 /* Release */, 381 | ); 382 | defaultConfigurationIsVisible = 0; 383 | defaultConfigurationName = Release; 384 | }; 385 | /* End XCConfigurationList section */ 386 | }; 387 | rootObject = 2B2F29112248863200D35301 /* Project object */; 388 | } 389 | -------------------------------------------------------------------------------- /VideoPlayDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VideoPlayDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VideoPlayDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/25. 6 | // Copyright © 2019 shengjie zhang. 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 | -------------------------------------------------------------------------------- /VideoPlayDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/25. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "SJCropViewController.h" 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 | self.window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds]; 21 | self.window.rootViewController=[[SJCropViewController alloc]init]; 22 | [self.window makeKeyAndVisible]; 23 | return YES; 24 | } 25 | 26 | 27 | - (void)applicationWillResignActive:(UIApplication *)application { 28 | // 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. 29 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 30 | } 31 | 32 | 33 | - (void)applicationDidEnterBackground:(UIApplication *)application { 34 | // 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. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | } 37 | 38 | 39 | - (void)applicationWillEnterForeground:(UIApplication *)application { 40 | // 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. 41 | } 42 | 43 | 44 | - (void)applicationDidBecomeActive:(UIApplication *)application { 45 | // 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. 46 | } 47 | 48 | 49 | - (void)applicationWillTerminate:(UIApplication *)application { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /VideoPlayDemo/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 | } -------------------------------------------------------------------------------- /VideoPlayDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /VideoPlayDemo/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "12.0", 8 | "subtype" : "2688h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "orientation" : "landscape", 13 | "idiom" : "iphone", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "12.0", 16 | "subtype" : "2688h", 17 | "scale" : "3x" 18 | }, 19 | { 20 | "orientation" : "portrait", 21 | "idiom" : "iphone", 22 | "extent" : "full-screen", 23 | "minimum-system-version" : "12.0", 24 | "subtype" : "1792h", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "orientation" : "landscape", 29 | "idiom" : "iphone", 30 | "extent" : "full-screen", 31 | "minimum-system-version" : "12.0", 32 | "subtype" : "1792h", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "orientation" : "portrait", 37 | "idiom" : "iphone", 38 | "extent" : "full-screen", 39 | "minimum-system-version" : "11.0", 40 | "subtype" : "2436h", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "orientation" : "landscape", 45 | "idiom" : "iphone", 46 | "extent" : "full-screen", 47 | "minimum-system-version" : "11.0", 48 | "subtype" : "2436h", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "orientation" : "portrait", 53 | "idiom" : "iphone", 54 | "extent" : "full-screen", 55 | "minimum-system-version" : "8.0", 56 | "subtype" : "736h", 57 | "scale" : "3x" 58 | }, 59 | { 60 | "orientation" : "landscape", 61 | "idiom" : "iphone", 62 | "extent" : "full-screen", 63 | "minimum-system-version" : "8.0", 64 | "subtype" : "736h", 65 | "scale" : "3x" 66 | }, 67 | { 68 | "orientation" : "portrait", 69 | "idiom" : "iphone", 70 | "extent" : "full-screen", 71 | "minimum-system-version" : "8.0", 72 | "subtype" : "667h", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "orientation" : "portrait", 77 | "idiom" : "iphone", 78 | "extent" : "full-screen", 79 | "minimum-system-version" : "7.0", 80 | "scale" : "2x" 81 | }, 82 | { 83 | "orientation" : "portrait", 84 | "idiom" : "iphone", 85 | "extent" : "full-screen", 86 | "minimum-system-version" : "7.0", 87 | "subtype" : "retina4", 88 | "scale" : "2x" 89 | }, 90 | { 91 | "orientation" : "portrait", 92 | "idiom" : "iphone", 93 | "extent" : "full-screen", 94 | "scale" : "1x" 95 | }, 96 | { 97 | "orientation" : "portrait", 98 | "idiom" : "iphone", 99 | "extent" : "full-screen", 100 | "scale" : "2x" 101 | }, 102 | { 103 | "orientation" : "portrait", 104 | "idiom" : "iphone", 105 | "extent" : "full-screen", 106 | "subtype" : "retina4", 107 | "scale" : "2x" 108 | } 109 | ], 110 | "info" : { 111 | "version" : 1, 112 | "author" : "xcode" 113 | } 114 | } -------------------------------------------------------------------------------- /VideoPlayDemo/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 | -------------------------------------------------------------------------------- /VideoPlayDemo/IMG_0950.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1401788197/VideoSliderCrop/0d140c2fe2615024d6a9207f684005bf0131fd31/VideoPlayDemo/IMG_0950.jpg -------------------------------------------------------------------------------- /VideoPlayDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | 33 | UISupportedInterfaceOrientations~ipad 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationPortraitUpsideDown 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/ExportVideoUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExportVideoManager.h 3 | // VideoPlayDemo 4 | // 5 | // Created by Mac027 on 2022/3/18. 6 | // Copyright © 2022 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ExportVideoUtil : NSObject 14 | + (void)export:(AVAsset *)asset range:(CMTimeRange)range complete:(void (^)(NSString *exportFilePath, NSError *error))complete; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/ExportVideoUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExportVideoManager.m 3 | // VideoPlayDemo 4 | // 5 | // Created by Mac027 on 2022/3/18. 6 | // Copyright © 2022 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import "ExportVideoUtil.h" 10 | 11 | 12 | @implementation ExportVideoUtil 13 | + (void)export:(AVAsset *)asset range:(CMTimeRange)range complete:(void (^)(NSString *exportFilePath, NSError *error))complete 14 | { 15 | NSString *exportFilePath =[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%f.mp4",[[NSDate date] timeIntervalSince1970]]]; 16 | 17 | AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetPassthrough]; // AVAssetExportPresetHighestQuality 18 | 19 | NSURL *exportFileUrl = [NSURL fileURLWithPath:exportFilePath]; 20 | 21 | exportSession.outputURL = exportFileUrl; 22 | exportSession.outputFileType = AVFileTypeMPEG4; 23 | exportSession.timeRange = range; 24 | 25 | [exportSession exportAsynchronouslyWithCompletionHandler:^{ 26 | BOOL suc = NO; 27 | switch ([exportSession status]) { 28 | case AVAssetExportSessionStatusFailed: 29 | NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]); 30 | break; 31 | case AVAssetExportSessionStatusCancelled: 32 | NSLog(@"Export canceled"); 33 | break; 34 | 35 | case AVAssetExportSessionStatusCompleted: { 36 | NSLog(@"Export completed"); 37 | suc = YES; 38 | } 39 | break; 40 | 41 | default: 42 | NSLog(@"Export other"); 43 | break; 44 | } 45 | if (complete) { 46 | complete(suc?exportFilePath:nil, suc?nil:exportSession.error); 47 | if (!suc) { 48 | [exportSession cancelExport]; 49 | } 50 | } 51 | }]; 52 | } 53 | @end 54 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJCommon.h: -------------------------------------------------------------------------------- 1 | #define kColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 2 | 3 | #define kColorFromRGBAlpha(rgbValue,al) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:al] 4 | 5 | #define Kfont(size) [UIFont systemFontOfSize:size] 6 | 7 | #define Kboldfont(size) [UIFont boldSystemFontOfSize:size] 8 | 9 | #define KMaxY(view) CGRectGetMaxY(view.frame) 10 | 11 | #define KMaxX(view) CGRectGetMaxX(view.frame) 12 | 13 | 14 | #define KMixY(view) CGRectGetMinY(view.frame) 15 | 16 | #define KMixX(view) CGRectGetMinX(view.frame) 17 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJCropCollectionCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SJCropCollectionCell.h 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/27. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SJCropCollectionCell : UICollectionViewCell 14 | @property (nonatomic, strong) UIImageView *imageView; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJCropCollectionCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SJCropCollectionCell.m 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/27. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import "SJCropCollectionCell.h" 10 | 11 | @implementation SJCropCollectionCell 12 | -(instancetype)initWithFrame:(CGRect)frame{ 13 | if (self=[super initWithFrame:frame]) { 14 | UIImageView *imageView = [[UIImageView alloc]init]; 15 | imageView.frame = self.contentView.bounds; 16 | imageView.contentMode = UIViewContentModeScaleAspectFill; 17 | imageView.clipsToBounds = YES; 18 | _imageView=imageView; 19 | self.backgroundColor=[UIColor clearColor]; 20 | [self addSubview:imageView]; 21 | } 22 | return self; 23 | } 24 | @end 25 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJCropViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SJCropViewController.h 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/4/9. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SJCropViewController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJCropViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SJCropViewController.m 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/4/9. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import "SJCropViewController.h" 10 | #import 11 | #import "SJVideoPlayView.h" 12 | #import "SJVideoCropView.h" 13 | #import "SJMediaInfoConfig.h" 14 | #import "UIView+SJExtension.h" 15 | #import "SJCommon.h" 16 | #import "ExportVideoUtil.h" 17 | 18 | @interface SJCropViewController () 19 | { 20 | NSURL *_localVideoUrl; 21 | SJMediaInfoConfig *_mediaConfig; 22 | } 23 | @property (nonatomic, strong) SJVideoPlayView *palyView; 24 | @property (nonatomic, strong) SJVideoCropView *cropView; 25 | @property (nonatomic, strong) UIButton *exportBtn; 26 | @property (nonatomic, strong) id timeObserver; 27 | 28 | @end 29 | 30 | @implementation SJCropViewController 31 | 32 | -(SJVideoPlayView *)palyView { 33 | if (!_palyView) { 34 | _palyView=[[SJVideoPlayView alloc]initWithFrame:self.view.bounds localUrl:_localVideoUrl]; 35 | _palyView.delegate=self; 36 | } 37 | return _palyView; 38 | } 39 | -(SJVideoCropView *)cropView { 40 | if (!_cropView) { 41 | _cropView=[[SJVideoCropView alloc]initWithFrame:CGRectMake(0, self.view.height - 25 - 95, self.view.width, 95) mediaConfig:_mediaConfig]; 42 | _cropView.avAsset=[AVAsset assetWithURL:_localVideoUrl]; 43 | _cropView.delegate=self; 44 | } 45 | return _cropView; 46 | } 47 | - (UIButton *)exportBtn { 48 | if (!_exportBtn) { 49 | _exportBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.width - 70, 100, 50, 20)]; 50 | [_exportBtn setTitle:@"裁剪" forState:UIControlStateNormal]; 51 | [_exportBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 52 | [_exportBtn addTarget:self action:@selector(exportClick) forControlEvents:UIControlEventTouchUpInside]; 53 | } 54 | return _exportBtn; 55 | } 56 | - (void)viewDidLoad { 57 | [super viewDidLoad]; 58 | self.view.backgroundColor=[UIColor blackColor]; 59 | _localVideoUrl=[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:@"video.mp4" ofType:nil]]; 60 | [self setConfig]; 61 | [self.view addSubview:self.palyView]; 62 | [self.view addSubview:self.cropView]; 63 | [self.view addSubview:self.exportBtn]; 64 | self.palyView.mediaConfig=_mediaConfig; 65 | self.palyView.playerItem.forwardPlaybackEndTime = CMTimeMake(_mediaConfig.endTime * 1000, 1000); 66 | } 67 | -(void)setConfig { 68 | SJMediaInfoConfig *config = [[SJMediaInfoConfig alloc]init]; 69 | config.startTime=0; 70 | config.endTime=15; 71 | config.minDuration=3; 72 | config.maxDuration=15; 73 | config.sourceDuration =[self avAssetVideoTrackDuration:self.palyView.playerItem.asset]; 74 | _mediaConfig=config; 75 | } 76 | #pragma mark 手指滚动 77 | - (void)cutBarDidMovedToTime:(CGFloat)time { 78 | if (time<=0) { 79 | return; 80 | } 81 | if (self.palyView.playerItem.status == AVPlayerItemStatusReadyToPlay) { 82 | [self.palyView.player seekToTime:CMTimeMake(time * 1000, 1000) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; 83 | if (self.palyView.playerStatus == CropPlayerStatusPlaying) { 84 | [self.palyView.player pause]; 85 | self.palyView.playerStatus = CropPlayerStatusPlayingBeforeSeek; 86 | if (_timeObserver) { 87 | [self.palyView.player removeTimeObserver:_timeObserver]; 88 | _timeObserver=nil; 89 | } 90 | } 91 | } 92 | } 93 | #pragma mark 手指滚动结束 94 | - (void)cutBarTouchesDidEnd { 95 | self.palyView.playerItem.forwardPlaybackEndTime = CMTimeMake(_mediaConfig.endTime * 1000, 1000); 96 | if (self.palyView.playerStatus == CropPlayerStatusPlayingBeforeSeek) { 97 | [self playVideo]; 98 | } 99 | } 100 | - (void)playVideo { 101 | if (self.palyView.playerStatus == CropPlayerStatusPlayingBeforeSeek) { 102 | CGFloat time = (self.cropView.siderTime+_mediaConfig.startTime); 103 | if (self.cropView.siderTime+1>=_mediaConfig.endTime-_mediaConfig.startTime) { 104 | time=_mediaConfig.startTime; 105 | } 106 | [self.palyView.player seekToTime:CMTimeMake(time* 1000, 1000) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; 107 | } 108 | 109 | [self.palyView.player play]; 110 | self.palyView.playerStatus = CropPlayerStatusPlaying; 111 | 112 | if (_timeObserver) { 113 | [self.palyView.player removeTimeObserver:_timeObserver]; 114 | _timeObserver=nil; 115 | } 116 | // return; 117 | __weak __typeof(self) weakSelf = self; 118 | 119 | _timeObserver = [self.palyView.player addPeriodicTimeObserverForInterval: 120 | CMTimeMake(1, 10) 121 | queue:dispatch_get_main_queue() 122 | usingBlock:^(CMTime time) { 123 | __strong __typeof(self) strong = weakSelf; 124 | CGFloat crt = CMTimeGetSeconds(time); 125 | if (self.palyView.playerStatus == CropPlayerStatusPlayingBeforeSeek||self.palyView.playerStatus==CropPlayerStatusPause) { 126 | return; 127 | } 128 | [strong.cropView updateProgressViewWithProgress:(crt-self->_mediaConfig.startTime)/(self->_mediaConfig.endTime-self->_mediaConfig.startTime)]; 129 | }]; 130 | } 131 | -(void)SJVideoReadyToPlay { 132 | self.palyView.playerStatus =CropPlayerStatusPlayingBeforeSeek; 133 | [self playVideo]; 134 | [self.cropView loadThumbnailData]; 135 | } 136 | - (CGFloat)avAssetVideoTrackDuration:(AVAsset *)asset { 137 | 138 | NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo]; 139 | if (videoTracks.count) { 140 | AVAssetTrack *track = videoTracks[0]; 141 | return CMTimeGetSeconds(CMTimeRangeGetEnd(track.timeRange)); 142 | } 143 | 144 | NSArray *audioTracks = [asset tracksWithMediaType:AVMediaTypeAudio]; 145 | if (audioTracks.count) { 146 | AVAssetTrack *track = audioTracks[0]; 147 | return CMTimeGetSeconds(CMTimeRangeGetEnd(track.timeRange)); 148 | } 149 | 150 | return -1; 151 | } 152 | /// 导出视频 153 | - (void)exportClick { 154 | [ExportVideoUtil export:self.cropView.avAsset range:[self getTimeRange] complete:^(NSString * _Nonnull exportFilePath, NSError * _Nonnull error) { 155 | NSLog(@"视频地址--%@",exportFilePath); 156 | }]; 157 | 158 | } 159 | 160 | - (CMTimeRange)getTimeRange { 161 | CMTime start = CMTimeMakeWithSeconds(_mediaConfig.startTime, self.palyView.player.currentTime.timescale); 162 | CMTime duration = CMTimeMakeWithSeconds(_mediaConfig.endTime - _mediaConfig.startTime, self.palyView.player.currentTime.timescale); 163 | return CMTimeRangeMake(start, duration); 164 | } 165 | @end 166 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJMediaInfoConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // SJMediaInfoConfig.h 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/25. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SJMediaInfoConfig : NSObject 14 | /** 15 | 视频时长 16 | */ 17 | @property (nonatomic, assign) CGFloat sourceDuration; 18 | /** 19 | 开始时间 20 | */ 21 | @property (nonatomic, assign) CGFloat startTime; 22 | /** 23 | 结束时间 24 | */ 25 | @property (nonatomic, assign) CGFloat endTime; 26 | 27 | /** 28 | 最小时长 29 | */ 30 | @property (nonatomic, assign) CGFloat minDuration; 31 | 32 | /** 33 | 最大时长 34 | */ 35 | @property (nonatomic, assign) CGFloat maxDuration; 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJMediaInfoConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // SJMediaInfoConfig.m 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/25. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import "SJMediaInfoConfig.h" 10 | 11 | @implementation SJMediaInfoConfig 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJVideoCropView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SJVideoCropView.h 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/27. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | @protocol SJVideoCropViewDelegate 12 | 13 | @optional 14 | 15 | /** 16 | 手指移动到的时间 17 | */ 18 | - (void)cutBarDidMovedToTime:(CGFloat)time; 19 | 20 | /** 21 | 松开手指 22 | */ 23 | - (void)cutBarTouchesDidEnd; 24 | @end 25 | 26 | @class SJMediaInfoConfig,AVAsset; 27 | NS_ASSUME_NONNULL_BEGIN 28 | 29 | @interface SJVideoCropView : UIView 30 | -(instancetype)initWithFrame:(CGRect)frame mediaConfig:(SJMediaInfoConfig *)config; 31 | @property (nonatomic, strong) AVAsset *avAsset; 32 | @property (nonatomic,weak) id delegate; 33 | -(void)loadThumbnailData; 34 | @property (nonatomic,assign) CGFloat siderTime; 35 | /** 36 | 更新进度 37 | 38 | @param progress 进度 39 | */ 40 | - (void)updateProgressViewWithProgress:(CGFloat)progress; 41 | - (CMTimeRange)timeRange; 42 | @end 43 | 44 | NS_ASSUME_NONNULL_END 45 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJVideoCropView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SJVideoCropView.m 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/27. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import "SJVideoCropView.h" 10 | #import 11 | #import "UIView+SJExtension.h" 12 | #import "SJMediaInfoConfig.h" 13 | #import "SJCommon.h" 14 | #import "SJCropCollectionCell.h" 15 | #import "UIButton+SJExtension.h" 16 | @interface SJVideoCropView () 17 | { 18 | NSInteger _itemCount; //缩略图个数 19 | CGFloat _perSpWith;// 每秒占宽度 20 | CGFloat _itemWidth; //cellWidth 21 | CGFloat _FivecollectionWidth; // 15s 宽度 22 | CGFloat _maxScreenDuraion;//整个屏幕最大时长 23 | CGFloat _selectTime;//已选择的时间 24 | SJMediaInfoConfig *_mediaConfig; 25 | CGFloat _imageViewWith;//左右图片宽度 26 | UIColor *_itemBackgroundColor;//item整体颜色 27 | CGFloat _margion;//左右间距 28 | } 29 | @property (nonatomic, strong) UICollectionView *collectionView; 30 | @property (nonatomic, strong) UILabel *durationLaber; 31 | @property (nonatomic, strong) AVAssetImageGenerator *imageGenerator; 32 | @property (nonatomic, strong) NSMutableArray *imagesArray; 33 | @property (nonatomic, strong) UIImageView *imageViewLeft; 34 | @property (nonatomic, strong) UIImageView *imageViewRight; 35 | @property (nonatomic, strong) UIView *imageViewSelected; 36 | @property (nonatomic, strong) UIButton *progressView; 37 | @property (nonatomic, strong) UIImageView *topLineView; 38 | @property (nonatomic, strong) UIImageView *underLineView; 39 | @end 40 | 41 | @implementation SJVideoCropView 42 | 43 | -(instancetype)initWithFrame:(CGRect)frame mediaConfig:(SJMediaInfoConfig *)config { 44 | _mediaConfig=config; 45 | if (self=[super initWithFrame:frame] ) { 46 | [self BaseStting]; 47 | } 48 | return self; 49 | } 50 | -(void)BaseStting { 51 | _itemBackgroundColor=kColorFromRGB(0XFBCD42); 52 | self.clipsToBounds=NO; 53 | _FivecollectionWidth=(self.width-60); 54 | 55 | _itemWidth=_FivecollectionWidth / 8.0; 56 | _imagesArray = [NSMutableArray array]; 57 | if (_mediaConfig.sourceDuration<=15) { 58 | _mediaConfig.endTime=_mediaConfig.sourceDuration; 59 | _maxScreenDuraion=_mediaConfig.sourceDuration; 60 | _itemCount=8; 61 | }else{ 62 | _mediaConfig.endTime=15; 63 | _maxScreenDuraion=15; 64 | _itemCount=_mediaConfig.sourceDuration/ (15.0/8.0); 65 | } 66 | _perSpWith=(_itemCount*_itemWidth)/_mediaConfig.sourceDuration; 67 | _imageViewWith = 20; 68 | _margion=(self.width -_mediaConfig.maxDuration*_perSpWith-_imageViewWith*2)/2.0; 69 | [self setupCollectionView]; 70 | [self setupSubviews]; 71 | self.userInteractionEnabled=YES; 72 | } 73 | - (void)setupCollectionView { 74 | UICollectionViewFlowLayout *followLayout = [[UICollectionViewFlowLayout alloc] init]; 75 | followLayout.itemSize = CGSizeMake(_itemWidth, 60); 76 | followLayout.minimumLineSpacing = 0; 77 | followLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 78 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 35, self.width, 60) collectionViewLayout:followLayout]; 79 | _collectionView.contentInset=UIEdgeInsetsMake(0, 30, 0, 30); 80 | _collectionView.backgroundColor=[UIColor clearColor]; 81 | _collectionView.alwaysBounceHorizontal=YES; 82 | _collectionView.showsHorizontalScrollIndicator=NO; 83 | _collectionView.delegate=(id)self; 84 | _collectionView.dataSource = (id)self; 85 | [_collectionView registerClass:[SJCropCollectionCell class] forCellWithReuseIdentifier:@"SJCropCollectionCell"]; 86 | _collectionView.clipsToBounds=NO; 87 | [self addSubview:_collectionView]; 88 | } 89 | 90 | - (void)setupSubviews { 91 | _durationLaber=[[UILabel alloc]initWithFrame:CGRectMake(15, 0, 70, 20)]; 92 | _durationLaber.font=[UIFont systemFontOfSize:12]; 93 | _durationLaber.backgroundColor=kColorFromRGB(0X123456); 94 | _durationLaber.textColor=[UIColor whiteColor]; 95 | _durationLaber.textAlignment=NSTextAlignmentCenter; 96 | [self addSubview:_durationLaber]; 97 | 98 | _imageViewLeft = [[UIImageView alloc] init]; 99 | _imageViewLeft.contentMode=UIViewContentModeScaleAspectFit; 100 | _imageViewLeft.backgroundColor=_itemBackgroundColor; 101 | _imageViewLeft.frame = CGRectMake(_margion, _collectionView.y, _imageViewWith, _collectionView.height); 102 | _imageViewLeft.userInteractionEnabled = YES; 103 | _imageViewRight = [[UIImageView alloc] init]; 104 | _imageViewRight.frame = CGRectMake(self.width - _imageViewWith-_margion, _collectionView.y, _imageViewWith, _collectionView.height); 105 | _imageViewRight.contentMode=UIViewContentModeScaleAspectFit; 106 | _imageViewRight.backgroundColor=_itemBackgroundColor; 107 | _imageViewRight.userInteractionEnabled = YES; 108 | _topLineView = [[UIImageView alloc]initWithFrame:CGRectMake(_margion, _collectionView.y, self.width-2*_margion, 3)]; 109 | _topLineView.backgroundColor = _itemBackgroundColor; 110 | _underLineView = [[UIImageView alloc]initWithFrame:CGRectMake(_topLineView.x, KMaxY(_collectionView)-3, _topLineView.width, 3)]; 111 | _underLineView.backgroundColor=_itemBackgroundColor; 112 | [self addSubview:_topLineView]; 113 | [self addSubview:_underLineView]; 114 | [self addSubview:_imageViewLeft]; 115 | [self addSubview:_imageViewRight]; 116 | _progressView = [[UIButton alloc] init]; 117 | _progressView.backgroundColor=[UIColor whiteColor]; 118 | [_progressView setRadius:3 borderColor:nil borderWidth:0]; 119 | 120 | _progressView.bounds = CGRectMake(0, 0, 5, 70); 121 | _progressView.center = CGPointMake(0, _collectionView.centerY); 122 | _progressView.x=KMaxX(_imageViewLeft); 123 | _progressView.enabled=NO; 124 | _progressView.userInteractionEnabled=YES; 125 | [_progressView setEnlargeEdgeWithTop:0 right:30 bottom:0 left:30]; 126 | [self addSubview:_progressView]; 127 | } 128 | #pragma mark 加载视频截图 129 | - (void)loadThumbnailData { 130 | _durationLaber.text=[NSString stringWithFormat:@"已选取%@s",[NSString stringWithFormat:@"%.0f",_mediaConfig.endTime - _mediaConfig.startTime]]; 131 | _selectTime =_mediaConfig.endTime - _mediaConfig.startTime; 132 | CMTime startTime = kCMTimeZero; 133 | NSMutableArray *array = [NSMutableArray array]; 134 | CMTime addTime = CMTimeMake(1000,1000); 135 | CGFloat d = _mediaConfig.sourceDuration / (_itemCount-1); 136 | int intd = d * 100; 137 | float fd = intd / 100.0; 138 | addTime = CMTimeMakeWithSeconds(fd, 1000); 139 | 140 | CMTime endTime = CMTimeMakeWithSeconds(_mediaConfig.sourceDuration, 1000); 141 | 142 | while (CMTIME_COMPARE_INLINE(startTime, <=, endTime)) { 143 | [array addObject:[NSValue valueWithCMTime:startTime]]; 144 | startTime = CMTimeAdd(startTime, addTime); 145 | } 146 | 147 | // 第一帧取第0.1s 规避有些视频并不是从第0s开始的 148 | array[0] = [NSValue valueWithCMTime:CMTimeMakeWithSeconds(0.1, 1000)]; 149 | __weak __typeof(self) weakSelf = self; 150 | __block int index = 0; 151 | [self.imageGenerator generateCGImagesAsynchronouslyForTimes:array completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) { 152 | 153 | if (result == AVAssetImageGeneratorSucceeded) { 154 | UIImage *img = [[UIImage alloc] initWithCGImage:image]; 155 | dispatch_sync(dispatch_get_main_queue(), ^{ 156 | [weakSelf.imagesArray addObject:img]; 157 | NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; 158 | [weakSelf.collectionView insertItemsAtIndexPaths:@[indexPath]]; 159 | index++; 160 | }); 161 | } 162 | }]; 163 | } 164 | #pragma mark 调整进度条播放位置 165 | - (void)updateProgressViewWithProgress:(CGFloat)progress { 166 | if (_imageViewSelected!=nil) { 167 | return; 168 | } 169 | CGFloat width = KMixX(_imageViewRight)-KMaxX(_imageViewLeft); 170 | CGFloat newX =KMaxX(_imageViewLeft)+ progress *width; 171 | __weak __typeof(self) weakSelf = self; 172 | [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAllowUserInteraction animations:^{ 173 | weakSelf.progressView.x = newX; 174 | } completion:nil]; 175 | } 176 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 177 | return _imagesArray.count; 178 | } 179 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 180 | 181 | SJCropCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"SJCropCollectionCell" forIndexPath:indexPath]; 182 | cell.imageView.image=_imagesArray[indexPath.row]; 183 | return cell; 184 | } 185 | 186 | - (AVAssetImageGenerator *)imageGenerator { 187 | if (!_imageGenerator) { 188 | _imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_avAsset]; 189 | _imageGenerator.appliesPreferredTrackTransform = YES; 190 | _imageGenerator.requestedTimeToleranceBefore = kCMTimeZero; 191 | _imageGenerator.requestedTimeToleranceAfter = kCMTimeZero; 192 | _imageGenerator.maximumSize = CGSizeMake(320, 320); 193 | } 194 | return _imageGenerator; 195 | } 196 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 197 | UITouch *touch = (UITouch *)[touches anyObject]; 198 | CGPoint point = [touch locationInView:self]; 199 | CGRect adjustLeftRespondRect = _imageViewLeft.frame; 200 | CGRect adjustRightRespondRect = _imageViewRight.frame; 201 | CGRect adjustProgressRespondRect = CGRectMake(_progressView.frame.origin.x-30, _progressView.frame.origin.y, _progressView.frame.size.width+60, _progressView.frame.size.height); 202 | if (CGRectContainsPoint(adjustLeftRespondRect, point)) { 203 | _imageViewSelected = _imageViewLeft; 204 | } else if (CGRectContainsPoint(adjustRightRespondRect, point)) { 205 | _imageViewSelected = _imageViewRight; 206 | } else if (CGRectContainsPoint(adjustProgressRespondRect, point)) { 207 | _imageViewSelected = _progressView; 208 | } 209 | else { 210 | _imageViewSelected = nil; 211 | } 212 | } 213 | #pragma mark 手指滑动截取视频 214 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 215 | if (!_imageViewSelected) return; 216 | UITouch *touch = (UITouch *)[touches anyObject]; 217 | CGPoint lp = [touch locationInView:_collectionView]; 218 | CGPoint pp = [touch previousLocationInView:_collectionView]; 219 | CGFloat offset = lp.x - pp.x; 220 | if (_imageViewSelected == _imageViewLeft) { 221 | CGRect frame = _imageViewLeft.frame; 222 | frame.origin.x += offset; 223 | if (frame.origin.x<=_margion) { 224 | offset+=(_margion-frame.origin.x); 225 | frame.origin.x=_margion; 226 | } 227 | if (frame.origin.x>=KMixX(_imageViewRight)-_perSpWith*_mediaConfig.minDuration-_imageViewWith) { 228 | offset-=frame.origin.x-(KMixX(_imageViewRight)-_perSpWith*_mediaConfig.minDuration-_imageViewWith); 229 | 230 | frame.origin.x=KMixX(_imageViewRight)-_perSpWith*_mediaConfig.minDuration-_imageViewWith; 231 | } 232 | CGFloat time = offset/_perSpWith; 233 | CGFloat left = _mediaConfig.startTime + time; 234 | _mediaConfig.startTime=left; 235 | _imageViewLeft.frame = frame; 236 | _progressView.x = KMaxX(_imageViewLeft); 237 | _mediaConfig.startTime = left; 238 | _durationLaber.text=[NSString stringWithFormat:@"已选取%@s",[NSString stringWithFormat:@"%.0f",_mediaConfig.endTime - _mediaConfig.startTime]]; 239 | _selectTime =_mediaConfig.endTime - _mediaConfig.startTime; 240 | if ([_delegate respondsToSelector:@selector(cutBarDidMovedToTime:)]) { 241 | [_delegate cutBarDidMovedToTime:left]; 242 | } 243 | } else if (_imageViewSelected == _imageViewRight) { 244 | 245 | CGRect frame = _imageViewRight.frame; 246 | frame.origin.x += offset; 247 | 248 | if (frame.origin.x>=self.width-30) { 249 | offset-=frame.origin.x-(self.width-30); 250 | frame.origin.x=self.width- 30; 251 | } 252 | 253 | if (frame.origin.x<=KMaxX(_imageViewLeft)+_perSpWith*_mediaConfig.minDuration) { 254 | offset+=(KMaxX(_imageViewLeft)+_perSpWith*_mediaConfig.minDuration)-frame.origin.x; 255 | frame.origin.x=KMaxX(_imageViewLeft)+_perSpWith*_mediaConfig.minDuration; 256 | } 257 | CGFloat time = offset/_perSpWith; 258 | CGFloat right = _mediaConfig.endTime + time; 259 | _mediaConfig.endTime = right; 260 | _imageViewRight.frame = frame; 261 | _progressView.x = KMixX(_imageViewRight); 262 | _durationLaber.text=[NSString stringWithFormat:@"已选取%@s",[NSString stringWithFormat:@"%.0f",_mediaConfig.endTime - _mediaConfig.startTime]]; 263 | _selectTime =_mediaConfig.endTime - _mediaConfig.startTime; 264 | if ([_delegate respondsToSelector:@selector(cutBarDidMovedToTime:)]) { 265 | [_delegate cutBarDidMovedToTime:right]; 266 | } 267 | // } 268 | }else if(_imageViewSelected == _progressView) { 269 | CGRect frame = _progressView.frame; 270 | frame.origin.x += offset; 271 | if (frame.origin.x<=KMaxX(_imageViewLeft)) { 272 | frame.origin.x=KMaxX(_imageViewLeft); 273 | } 274 | if (frame.origin.x>=KMixX(_imageViewRight)) { 275 | frame.origin.x=KMixX(_imageViewRight); 276 | } 277 | _progressView.frame = frame; 278 | if ([_delegate respondsToSelector:@selector(cutBarDidMovedToTime:)]) { 279 | CGFloat progresstime= (KMixX(_progressView)-KMaxX(_imageViewLeft))/_perSpWith; 280 | [_delegate cutBarDidMovedToTime:_mediaConfig.startTime+progresstime]; 281 | } 282 | } 283 | CGRect upFrame = _topLineView.frame; 284 | CGRect downFrame = _underLineView.frame; 285 | 286 | upFrame.origin.x = CGRectGetMaxX(_imageViewLeft.frame) - 3; 287 | downFrame.origin.x = upFrame.origin.x; 288 | 289 | upFrame.size.width = CGRectGetMinX(_imageViewRight.frame) - CGRectGetMaxX(_imageViewLeft.frame) + 6; 290 | downFrame.size.width = upFrame.size.width; 291 | 292 | _topLineView.frame = upFrame; 293 | _underLineView.frame = downFrame; 294 | 295 | } 296 | 297 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 298 | _imageViewSelected = nil; 299 | if ([_delegate respondsToSelector:@selector(cutBarTouchesDidEnd)]) { 300 | [_delegate cutBarTouchesDidEnd]; 301 | } 302 | } 303 | 304 | -(void)scrollViewDidScroll:(UIScrollView *)scrollView { 305 | CGFloat time =(scrollView.contentOffset.x+30)/_perSpWith; 306 | if (time+(KMaxX(_imageViewLeft)-30)/_perSpWith<0) { 307 | return; 308 | } 309 | _mediaConfig.startTime = time+(KMaxX(_imageViewLeft)-30)/_perSpWith; 310 | _mediaConfig.endTime=_mediaConfig.startTime+_selectTime; 311 | if ([_delegate respondsToSelector:@selector(cutBarDidMovedToTime:)]) { 312 | [_delegate cutBarDidMovedToTime:_mediaConfig.startTime+self.siderTime]; 313 | } 314 | } 315 | -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 316 | NSLog(@"停止滚动了"); 317 | if ([_delegate respondsToSelector:@selector(cutBarTouchesDidEnd)]) { 318 | [_delegate cutBarTouchesDidEnd]; 319 | } 320 | } 321 | -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 322 | if (!decelerate) { 323 | if ([_delegate respondsToSelector:@selector(cutBarTouchesDidEnd)]) { 324 | [_delegate cutBarTouchesDidEnd]; 325 | } 326 | } 327 | } 328 | -(CGFloat)siderTime { 329 | return (KMixX(_progressView)-KMaxX(_imageViewLeft))/_perSpWith; 330 | } 331 | //-(CMTimeRange)timeRange { 332 | // return CMTimeRangeMake(CMTimeMakeWithSeconds(_mediaConfig.startTime, 600), CMTimeMakeWithSeconds(_mediaConfig.endTime, 600)); 333 | //} 334 | 335 | //- (CMTime)getStartTime 336 | //{ 337 | // return CMTimeMakeWithSeconds(_mediaConfig.startTime, self.playerLayer.player.currentTime.timescale); 338 | //} 339 | // 340 | //- (CMTimeRange)getTimeRange 341 | //{ 342 | // CMTime start = [self getStartTime]; 343 | // CGFloat d = _interval * self.editView.validRect.size.width / (kItemWidth); 344 | // CMTime duration = CMTimeMakeWithSeconds(d, self.playerLayer.player.currentTime.timescale); 345 | // return CMTimeRangeMake(start, duration); 346 | //} 347 | @end 348 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJVideoPlayView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SJVideoPlayView.h 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/25. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | 10 | 11 | #import 12 | #import 13 | @class SJMediaInfoConfig; 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | typedef NS_ENUM(NSInteger, CropPlayerStatus) { 17 | CropPlayerStatusPause, // 结束或暂停 18 | CropPlayerStatusPlaying, // 播放中 19 | CropPlayerStatusPlayingBeforeSeek // 拖动之前是播放状态 20 | }; 21 | 22 | @protocol SJVideoPlayViewDelegate 23 | 24 | @required 25 | -(void)SJVideoReadyToPlay; 26 | @end 27 | 28 | @interface SJVideoPlayView : UIView 29 | - (id)initWithFrame:(CGRect)frame localUrl:(NSURL *)localUrl; 30 | @property (nonatomic, strong) AVPlayer *player; 31 | @property (nonatomic, assign) CropPlayerStatus playerStatus; 32 | @property (nonatomic ,strong) AVPlayerItem *playerItem; 33 | @property (nonatomic, strong) SJMediaInfoConfig *mediaConfig; 34 | -(void)play; 35 | @property (nonatomic,weak) id delegate; 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/SJVideoPlayView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SJVideoPlayView.m 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/25. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import "SJVideoPlayView.h" 10 | #import "SJMediaInfoConfig.h" 11 | @interface SJVideoPlayView () 12 | { 13 | NSURL *_localUrl; 14 | } 15 | @end 16 | 17 | @implementation SJVideoPlayView 18 | -(id)initWithFrame:(CGRect)frame localUrl:(nonnull NSURL *)localUrl{ 19 | _localUrl=localUrl; 20 | if (self=[super initWithFrame:frame]) { 21 | [self Config]; 22 | } 23 | return self; 24 | } 25 | -(void)Config{ 26 | NSURL *videoUrl = _localUrl; 27 | self.playerItem = [AVPlayerItem playerItemWithURL:videoUrl]; 28 | [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];// 监听status属性 29 | self.player = [AVPlayer playerWithPlayerItem:self.playerItem]; 30 | // 添加视频播放结束通知 31 | [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem]; 32 | self.userInteractionEnabled=YES; 33 | [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playPauseClick)]]; 34 | } 35 | + (Class)layerClass { 36 | return [AVPlayerLayer class]; 37 | } 38 | 39 | - (AVPlayer *)player { 40 | return [(AVPlayerLayer *)[self layer] player]; 41 | } 42 | 43 | - (void)setPlayer:(AVPlayer *)player { 44 | [(AVPlayerLayer *)[self layer] setPlayer:player]; 45 | } 46 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 47 | if ([keyPath isEqualToString:@"status"]) { 48 | AVPlayerItemStatus status = _playerItem.status; 49 | if (status == AVPlayerItemStatusReadyToPlay) { 50 | if ([_delegate respondsToSelector:@selector(SJVideoReadyToPlay)]) { 51 | [_delegate SJVideoReadyToPlay]; 52 | } 53 | [self.playerItem removeObserver:self forKeyPath:@"status"]; 54 | // _cutInfo.sourceDuration = [_playerItem.asset avAssetVideoTrackDuration]; 55 | // if (_cutInfo.endTime == 0) { 56 | // _cutInfo.startTime = 0.0; 57 | // _cutInfo.endTime = _cutInfo.sourceDuration; 58 | // } 59 | // _playerStatus = AliyunCropPlayerStatusPlayingBeforeSeek; 60 | // [self playVideo]; 61 | // [_thumbnailView loadThumbnailData]; 62 | // [self removeObserver:self forKeyPath:PlayerItemStatus]; 63 | // _KVOHasRemoved = YES; 64 | }else if (status == AVPlayerItemStatusFailed){ 65 | NSLog(@"系统播放器无法播放视频=== %@",keyPath); 66 | } 67 | } 68 | } 69 | #pragma mark 播放完成 70 | - (void)moviePlayDidEnd:(NSNotification *)notification { 71 | [self.player pause]; 72 | AVPlayerItem *p = [notification object]; 73 | [p seekToTime:CMTimeMake(self.mediaConfig.startTime * 1000, 1000) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; 74 | [self.player play]; 75 | } 76 | -(void)play{ 77 | [self.player play]; 78 | self.playerStatus=CropPlayerStatusPlaying; 79 | } 80 | -(void)playPauseClick{ 81 | if (self.playerStatus==CropPlayerStatusPause) { 82 | self.playerStatus=CropPlayerStatusPlaying; 83 | [self.player play]; 84 | }else{ 85 | self.playerStatus=CropPlayerStatusPause; 86 | [self.player pause]; 87 | } 88 | } 89 | @end 90 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/UIButton+SJExtension.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+SJExtension.h 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/4/9. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface UIButton (SJExtension) 14 | 15 | - (void)setEnlargeEdgeWithTop:(CGFloat) top right:(CGFloat) right bottom:(CGFloat) bottom left:(CGFloat) left; 16 | 17 | - (void)setEnlargeEdge:(CGFloat) size; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/UIButton+SJExtension.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+SJExtension.m 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/4/9. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import "UIButton+SJExtension.h" 10 | #import 11 | @implementation UIButton (SJExtension) 12 | 13 | static char topNameKey; 14 | static char rightNameKey; 15 | static char bottomNameKey; 16 | static char leftNameKey; 17 | 18 | - (void)setEnlargeEdge:(CGFloat) size 19 | { 20 | objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC); 21 | objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC); 22 | objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC); 23 | objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC); 24 | } 25 | 26 | - (void) setEnlargeEdgeWithTop:(CGFloat) top right:(CGFloat) right bottom:(CGFloat) bottom left:(CGFloat) left 27 | { 28 | objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:top], OBJC_ASSOCIATION_COPY_NONATOMIC); 29 | objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:right], OBJC_ASSOCIATION_COPY_NONATOMIC); 30 | objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:bottom], OBJC_ASSOCIATION_COPY_NONATOMIC); 31 | objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:left], OBJC_ASSOCIATION_COPY_NONATOMIC); 32 | } 33 | 34 | - (CGRect) enlargedRect 35 | { 36 | NSNumber* topEdge = objc_getAssociatedObject(self, &topNameKey); 37 | NSNumber* rightEdge = objc_getAssociatedObject(self, &rightNameKey); 38 | NSNumber* bottomEdge = objc_getAssociatedObject(self, &bottomNameKey); 39 | NSNumber* leftEdge = objc_getAssociatedObject(self, &leftNameKey); 40 | if (topEdge && rightEdge && bottomEdge && leftEdge) 41 | { 42 | return CGRectMake(self.bounds.origin.x - leftEdge.floatValue, 43 | self.bounds.origin.y - topEdge.floatValue, 44 | self.bounds.size.width + leftEdge.floatValue + rightEdge.floatValue, 45 | self.bounds.size.height + topEdge.floatValue + bottomEdge.floatValue); 46 | } 47 | else 48 | { 49 | return self.bounds; 50 | } 51 | } 52 | 53 | - (UIView*) hitTest:(CGPoint) point withEvent:(UIEvent*) event 54 | { 55 | CGRect rect = [self enlargedRect]; 56 | if (CGRectEqualToRect(rect, self.bounds)) 57 | { 58 | return [super hitTest:point withEvent:event]; 59 | } 60 | return CGRectContainsPoint(rect, point) ? self : nil; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/UIView+SJExtension.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+SJExtension.h 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/27. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface UIView (SJExtension) 14 | @property (nonatomic, assign) CGFloat x; 15 | @property (nonatomic, assign) CGFloat y; 16 | @property (nonatomic, assign) CGFloat centerX; 17 | @property (nonatomic, assign) CGFloat centerY; 18 | @property (nonatomic, assign) CGFloat width; 19 | @property (nonatomic, assign) CGFloat height; 20 | @property (nonatomic, assign) CGSize size; 21 | - (void)setRadius:(float)radius borderColor:(nullable UIColor* )color borderWidth:(CGFloat)width; 22 | - (void)setRadius:(float)radius corners: (UIRectCorner)corners; 23 | - (void)setHalfRadius; 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /VideoPlayDemo/SJVideoCrop/UIView+SJExtension.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+SJExtension.m 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/27. 6 | // Copyright © 2019 shengjie zhang. All rights reserved. 7 | // 8 | 9 | #import "UIView+SJExtension.h" 10 | 11 | @implementation UIView (SJExtension) 12 | - (void)setX:(CGFloat)x 13 | { 14 | CGRect frame = self.frame; 15 | frame.origin.x = x; 16 | self.frame = frame; 17 | } 18 | 19 | - (CGFloat)x 20 | { 21 | return self.frame.origin.x; 22 | } 23 | 24 | - (void)setY:(CGFloat)y 25 | { 26 | CGRect frame = self.frame; 27 | frame.origin.y = y; 28 | self.frame = frame; 29 | } 30 | 31 | - (CGFloat)y 32 | { 33 | return self.frame.origin.y; 34 | } 35 | 36 | - (void)setCenterX:(CGFloat)centerX 37 | { 38 | CGPoint center = self.center; 39 | center.x = centerX; 40 | self.center = center; 41 | } 42 | 43 | - (CGFloat)centerX 44 | { 45 | return self.center.x; 46 | } 47 | 48 | - (void)setCenterY:(CGFloat)centerY 49 | { 50 | CGPoint center = self.center; 51 | center.y = centerY; 52 | self.center = center; 53 | } 54 | 55 | - (CGFloat)centerY 56 | { 57 | return self.center.y; 58 | } 59 | 60 | - (void)setWidth:(CGFloat)width 61 | { 62 | CGRect frame = self.frame; 63 | frame.size.width = width; 64 | self.frame = frame; 65 | } 66 | 67 | - (CGFloat)width 68 | { 69 | return self.frame.size.width; 70 | } 71 | 72 | - (void)setHeight:(CGFloat)height 73 | { 74 | CGRect frame = self.frame; 75 | frame.size.height = height; 76 | self.frame = frame; 77 | } 78 | 79 | - (CGFloat)height 80 | { 81 | return self.frame.size.height; 82 | } 83 | 84 | - (void)setSize:(CGSize)size 85 | { 86 | // self.width = size.width; 87 | // self.height = size.height; 88 | CGRect frame = self.frame; 89 | frame.size = size; 90 | self.frame = frame; 91 | } 92 | 93 | - (CGSize)size 94 | { 95 | return self.frame.size; 96 | } 97 | - (void)setRadius:(float)radius borderColor:( nullable UIColor* )color borderWidth:(CGFloat)width 98 | { 99 | self.layer.cornerRadius = radius; 100 | self.layer.masksToBounds = YES; 101 | if (color) { 102 | self.layer.borderColor = color.CGColor; 103 | self.layer.borderWidth = width; 104 | } 105 | } 106 | -(void)setHalfRadius{ 107 | [self setRadius:self.height/2.0 borderColor:nil borderWidth:0]; 108 | } 109 | -(void)setRadius:(float)radius corners:(UIRectCorner)corners{ 110 | UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)]; 111 | CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init]; 112 | maskLayer.frame = self.bounds; 113 | maskLayer.path = maskPath.CGPath; 114 | self.layer.mask = maskLayer; 115 | } 116 | @end 117 | -------------------------------------------------------------------------------- /VideoPlayDemo/eg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1401788197/VideoSliderCrop/0d140c2fe2615024d6a9207f684005bf0131fd31/VideoPlayDemo/eg.gif -------------------------------------------------------------------------------- /VideoPlayDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // VideoPlayDemo 4 | // 5 | // Created by shengjie zhang on 2019/3/25. 6 | // Copyright © 2019 shengjie zhang. 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 | -------------------------------------------------------------------------------- /VideoPlayDemo/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1401788197/VideoSliderCrop/0d140c2fe2615024d6a9207f684005bf0131fd31/VideoPlayDemo/video.mp4 --------------------------------------------------------------------------------