├── .gitignore ├── .swift-version ├── AAPlayer.podspec ├── AAPlayer.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── AAPlayer ├── AAPlayButton.swift ├── AAPlayProgressView.swift ├── AAPlayer.swift ├── AAPlayerActivityIndicicatorView.swift ├── AAPlayerRotateButton.swift ├── AAPlayerSlider.swift ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── LICENSE.md ├── README.md ├── Sources ├── AAPlayButton.swift ├── AAPlayProgressView.swift ├── AAPlayer.swift ├── AAPlayerActivityIndicicatorView.swift ├── AAPlayerRotateButton.swift └── AAPlayerSlider.swift └── sampleImage ├── IBOutlet.png ├── customClass.png └── samplePlay.gif /.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 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /AAPlayer.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'AAPlayer' 3 | s.version = '1.0.3' 4 | s.license = { :type => "MIT", :file => "LICENSE.md" } 5 | s.summary = 'Customize Video Player base on AVPlayer' 6 | s.homepage = 'https://github.com/Alan881/AAPlayer' 7 | s.authors = { 'Alan' => 'nakama74@gmail.com' } 8 | s.source = { :git => 'https://github.com/Alan881/AAPlayer.git', :tag => s.version } 9 | s.source_files = 'Sources/*.swift' 10 | s.requires_arc = true 11 | s.ios.deployment_target = '8.0' 12 | end 13 | -------------------------------------------------------------------------------- /AAPlayer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E718C2291F092CD100426ED9 /* AAPlayerSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E718C2281F092CD100426ED9 /* AAPlayerSlider.swift */; }; 11 | E72390C11F07D630000C8AC8 /* AAPlayProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E72390C01F07D630000C8AC8 /* AAPlayProgressView.swift */; }; 12 | E7A2691A1F277B8200F9FF00 /* AAPlayerRotateButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A269191F277B8200F9FF00 /* AAPlayerRotateButton.swift */; }; 13 | E7A6C3F21F03DE130029AFD7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A6C3F11F03DE130029AFD7 /* AppDelegate.swift */; }; 14 | E7A6C3F41F03DE130029AFD7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A6C3F31F03DE130029AFD7 /* ViewController.swift */; }; 15 | E7A6C3F71F03DE130029AFD7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E7A6C3F51F03DE130029AFD7 /* Main.storyboard */; }; 16 | E7A6C3F91F03DE130029AFD7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E7A6C3F81F03DE130029AFD7 /* Assets.xcassets */; }; 17 | E7A6C3FC1F03DE130029AFD7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E7A6C3FA1F03DE130029AFD7 /* LaunchScreen.storyboard */; }; 18 | E7A6C4041F03DEB90029AFD7 /* AAPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A6C4031F03DEB90029AFD7 /* AAPlayer.swift */; }; 19 | E7A6C4061F0781D70029AFD7 /* AAPlayButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A6C4051F0781D70029AFD7 /* AAPlayButton.swift */; }; 20 | E7AB53271F2597D80036B885 /* AAPlayerActivityIndicicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7AB53261F2597D80036B885 /* AAPlayerActivityIndicicatorView.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | E718C2281F092CD100426ED9 /* AAPlayerSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAPlayerSlider.swift; sourceTree = ""; }; 25 | E72390C01F07D630000C8AC8 /* AAPlayProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAPlayProgressView.swift; sourceTree = ""; }; 26 | E7A269191F277B8200F9FF00 /* AAPlayerRotateButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAPlayerRotateButton.swift; sourceTree = ""; }; 27 | E7A6C3EE1F03DE130029AFD7 /* AAPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AAPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | E7A6C3F11F03DE130029AFD7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 29 | E7A6C3F31F03DE130029AFD7 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 30 | E7A6C3F61F03DE130029AFD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | E7A6C3F81F03DE130029AFD7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | E7A6C3FB1F03DE130029AFD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 33 | E7A6C3FD1F03DE130029AFD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | E7A6C4031F03DEB90029AFD7 /* AAPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAPlayer.swift; sourceTree = ""; }; 35 | E7A6C4051F0781D70029AFD7 /* AAPlayButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAPlayButton.swift; sourceTree = ""; }; 36 | E7AB53261F2597D80036B885 /* AAPlayerActivityIndicicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAPlayerActivityIndicicatorView.swift; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | E7A6C3EB1F03DE130029AFD7 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | E71F98241F23727200D263F1 /* AAPlayer */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | E7A6C4031F03DEB90029AFD7 /* AAPlayer.swift */, 54 | E72390C01F07D630000C8AC8 /* AAPlayProgressView.swift */, 55 | E7A6C4051F0781D70029AFD7 /* AAPlayButton.swift */, 56 | E718C2281F092CD100426ED9 /* AAPlayerSlider.swift */, 57 | E7AB53261F2597D80036B885 /* AAPlayerActivityIndicicatorView.swift */, 58 | E7A269191F277B8200F9FF00 /* AAPlayerRotateButton.swift */, 59 | ); 60 | name = AAPlayer; 61 | sourceTree = ""; 62 | }; 63 | E7A6C3E51F03DE130029AFD7 = { 64 | isa = PBXGroup; 65 | children = ( 66 | E7A6C3F01F03DE130029AFD7 /* AAPlayer */, 67 | E7A6C3EF1F03DE130029AFD7 /* Products */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | E7A6C3EF1F03DE130029AFD7 /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | E7A6C3EE1F03DE130029AFD7 /* AAPlayer.app */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | E7A6C3F01F03DE130029AFD7 /* AAPlayer */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | E7A6C3F11F03DE130029AFD7 /* AppDelegate.swift */, 83 | E7A6C3F31F03DE130029AFD7 /* ViewController.swift */, 84 | E7A6C3F51F03DE130029AFD7 /* Main.storyboard */, 85 | E71F98241F23727200D263F1 /* AAPlayer */, 86 | E7A6C3F81F03DE130029AFD7 /* Assets.xcassets */, 87 | E7A6C3FA1F03DE130029AFD7 /* LaunchScreen.storyboard */, 88 | E7A6C3FD1F03DE130029AFD7 /* Info.plist */, 89 | ); 90 | path = AAPlayer; 91 | sourceTree = ""; 92 | }; 93 | /* End PBXGroup section */ 94 | 95 | /* Begin PBXNativeTarget section */ 96 | E7A6C3ED1F03DE130029AFD7 /* AAPlayer */ = { 97 | isa = PBXNativeTarget; 98 | buildConfigurationList = E7A6C4001F03DE130029AFD7 /* Build configuration list for PBXNativeTarget "AAPlayer" */; 99 | buildPhases = ( 100 | E7A6C3EA1F03DE130029AFD7 /* Sources */, 101 | E7A6C3EB1F03DE130029AFD7 /* Frameworks */, 102 | E7A6C3EC1F03DE130029AFD7 /* Resources */, 103 | ); 104 | buildRules = ( 105 | ); 106 | dependencies = ( 107 | ); 108 | name = AAPlayer; 109 | productName = AAPlayer; 110 | productReference = E7A6C3EE1F03DE130029AFD7 /* AAPlayer.app */; 111 | productType = "com.apple.product-type.application"; 112 | }; 113 | /* End PBXNativeTarget section */ 114 | 115 | /* Begin PBXProject section */ 116 | E7A6C3E61F03DE130029AFD7 /* Project object */ = { 117 | isa = PBXProject; 118 | attributes = { 119 | LastSwiftUpdateCheck = 0830; 120 | LastUpgradeCheck = 0900; 121 | ORGANIZATIONNAME = Alan; 122 | TargetAttributes = { 123 | E7A6C3ED1F03DE130029AFD7 = { 124 | CreatedOnToolsVersion = 8.3.3; 125 | DevelopmentTeam = 7D7KNBQFJ4; 126 | LastSwiftMigration = 0900; 127 | ProvisioningStyle = Automatic; 128 | }; 129 | }; 130 | }; 131 | buildConfigurationList = E7A6C3E91F03DE130029AFD7 /* Build configuration list for PBXProject "AAPlayer" */; 132 | compatibilityVersion = "Xcode 3.2"; 133 | developmentRegion = English; 134 | hasScannedForEncodings = 0; 135 | knownRegions = ( 136 | en, 137 | Base, 138 | ); 139 | mainGroup = E7A6C3E51F03DE130029AFD7; 140 | productRefGroup = E7A6C3EF1F03DE130029AFD7 /* Products */; 141 | projectDirPath = ""; 142 | projectRoot = ""; 143 | targets = ( 144 | E7A6C3ED1F03DE130029AFD7 /* AAPlayer */, 145 | ); 146 | }; 147 | /* End PBXProject section */ 148 | 149 | /* Begin PBXResourcesBuildPhase section */ 150 | E7A6C3EC1F03DE130029AFD7 /* Resources */ = { 151 | isa = PBXResourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | E7A6C3FC1F03DE130029AFD7 /* LaunchScreen.storyboard in Resources */, 155 | E7A6C3F91F03DE130029AFD7 /* Assets.xcassets in Resources */, 156 | E7A6C3F71F03DE130029AFD7 /* Main.storyboard in Resources */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXResourcesBuildPhase section */ 161 | 162 | /* Begin PBXSourcesBuildPhase section */ 163 | E7A6C3EA1F03DE130029AFD7 /* Sources */ = { 164 | isa = PBXSourcesBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | E7A6C3F41F03DE130029AFD7 /* ViewController.swift in Sources */, 168 | E718C2291F092CD100426ED9 /* AAPlayerSlider.swift in Sources */, 169 | E7A6C4061F0781D70029AFD7 /* AAPlayButton.swift in Sources */, 170 | E7A6C3F21F03DE130029AFD7 /* AppDelegate.swift in Sources */, 171 | E7A6C4041F03DEB90029AFD7 /* AAPlayer.swift in Sources */, 172 | E72390C11F07D630000C8AC8 /* AAPlayProgressView.swift in Sources */, 173 | E7AB53271F2597D80036B885 /* AAPlayerActivityIndicicatorView.swift in Sources */, 174 | E7A2691A1F277B8200F9FF00 /* AAPlayerRotateButton.swift in Sources */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXSourcesBuildPhase section */ 179 | 180 | /* Begin PBXVariantGroup section */ 181 | E7A6C3F51F03DE130029AFD7 /* Main.storyboard */ = { 182 | isa = PBXVariantGroup; 183 | children = ( 184 | E7A6C3F61F03DE130029AFD7 /* Base */, 185 | ); 186 | name = Main.storyboard; 187 | sourceTree = ""; 188 | }; 189 | E7A6C3FA1F03DE130029AFD7 /* LaunchScreen.storyboard */ = { 190 | isa = PBXVariantGroup; 191 | children = ( 192 | E7A6C3FB1F03DE130029AFD7 /* Base */, 193 | ); 194 | name = LaunchScreen.storyboard; 195 | sourceTree = ""; 196 | }; 197 | /* End PBXVariantGroup section */ 198 | 199 | /* Begin XCBuildConfiguration section */ 200 | E7A6C3FE1F03DE130029AFD7 /* Debug */ = { 201 | isa = XCBuildConfiguration; 202 | buildSettings = { 203 | ALWAYS_SEARCH_USER_PATHS = NO; 204 | CLANG_ANALYZER_NONNULL = YES; 205 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 206 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 207 | CLANG_CXX_LIBRARY = "libc++"; 208 | CLANG_ENABLE_MODULES = YES; 209 | CLANG_ENABLE_OBJC_ARC = YES; 210 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 211 | CLANG_WARN_BOOL_CONVERSION = YES; 212 | CLANG_WARN_COMMA = YES; 213 | CLANG_WARN_CONSTANT_CONVERSION = YES; 214 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 215 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 216 | CLANG_WARN_EMPTY_BODY = YES; 217 | CLANG_WARN_ENUM_CONVERSION = YES; 218 | CLANG_WARN_INFINITE_RECURSION = YES; 219 | CLANG_WARN_INT_CONVERSION = YES; 220 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 221 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 222 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 223 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 224 | CLANG_WARN_STRICT_PROTOTYPES = YES; 225 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 226 | CLANG_WARN_UNREACHABLE_CODE = YES; 227 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 228 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 229 | COPY_PHASE_STRIP = NO; 230 | DEBUG_INFORMATION_FORMAT = dwarf; 231 | ENABLE_STRICT_OBJC_MSGSEND = YES; 232 | ENABLE_TESTABILITY = YES; 233 | GCC_C_LANGUAGE_STANDARD = gnu99; 234 | GCC_DYNAMIC_NO_PIC = NO; 235 | GCC_NO_COMMON_BLOCKS = YES; 236 | GCC_OPTIMIZATION_LEVEL = 0; 237 | GCC_PREPROCESSOR_DEFINITIONS = ( 238 | "DEBUG=1", 239 | "$(inherited)", 240 | ); 241 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 242 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 243 | GCC_WARN_UNDECLARED_SELECTOR = YES; 244 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 245 | GCC_WARN_UNUSED_FUNCTION = YES; 246 | GCC_WARN_UNUSED_VARIABLE = YES; 247 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 248 | MTL_ENABLE_DEBUG_INFO = YES; 249 | ONLY_ACTIVE_ARCH = YES; 250 | SDKROOT = iphoneos; 251 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 252 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 253 | TARGETED_DEVICE_FAMILY = "1,2"; 254 | }; 255 | name = Debug; 256 | }; 257 | E7A6C3FF1F03DE130029AFD7 /* Release */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | ALWAYS_SEARCH_USER_PATHS = NO; 261 | CLANG_ANALYZER_NONNULL = YES; 262 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 263 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 264 | CLANG_CXX_LIBRARY = "libc++"; 265 | CLANG_ENABLE_MODULES = YES; 266 | CLANG_ENABLE_OBJC_ARC = YES; 267 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 268 | CLANG_WARN_BOOL_CONVERSION = YES; 269 | CLANG_WARN_COMMA = YES; 270 | CLANG_WARN_CONSTANT_CONVERSION = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INFINITE_RECURSION = YES; 276 | CLANG_WARN_INT_CONVERSION = YES; 277 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 279 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 280 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 281 | CLANG_WARN_STRICT_PROTOTYPES = YES; 282 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 283 | CLANG_WARN_UNREACHABLE_CODE = YES; 284 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 285 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 286 | COPY_PHASE_STRIP = NO; 287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 288 | ENABLE_NS_ASSERTIONS = NO; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | GCC_C_LANGUAGE_STANDARD = gnu99; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 299 | MTL_ENABLE_DEBUG_INFO = NO; 300 | SDKROOT = iphoneos; 301 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 302 | TARGETED_DEVICE_FAMILY = "1,2"; 303 | VALIDATE_PRODUCT = YES; 304 | }; 305 | name = Release; 306 | }; 307 | E7A6C4011F03DE130029AFD7 /* Debug */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | DEVELOPMENT_TEAM = 7D7KNBQFJ4; 312 | INFOPLIST_FILE = AAPlayer/Info.plist; 313 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 314 | PRODUCT_BUNDLE_IDENTIFIER = com.swift.AAPlayer; 315 | PRODUCT_NAME = "$(TARGET_NAME)"; 316 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 317 | SWIFT_VERSION = 4.0; 318 | }; 319 | name = Debug; 320 | }; 321 | E7A6C4021F03DE130029AFD7 /* Release */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 325 | DEVELOPMENT_TEAM = 7D7KNBQFJ4; 326 | INFOPLIST_FILE = AAPlayer/Info.plist; 327 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 328 | PRODUCT_BUNDLE_IDENTIFIER = com.swift.AAPlayer; 329 | PRODUCT_NAME = "$(TARGET_NAME)"; 330 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 331 | SWIFT_VERSION = 4.0; 332 | }; 333 | name = Release; 334 | }; 335 | /* End XCBuildConfiguration section */ 336 | 337 | /* Begin XCConfigurationList section */ 338 | E7A6C3E91F03DE130029AFD7 /* Build configuration list for PBXProject "AAPlayer" */ = { 339 | isa = XCConfigurationList; 340 | buildConfigurations = ( 341 | E7A6C3FE1F03DE130029AFD7 /* Debug */, 342 | E7A6C3FF1F03DE130029AFD7 /* Release */, 343 | ); 344 | defaultConfigurationIsVisible = 0; 345 | defaultConfigurationName = Release; 346 | }; 347 | E7A6C4001F03DE130029AFD7 /* Build configuration list for PBXNativeTarget "AAPlayer" */ = { 348 | isa = XCConfigurationList; 349 | buildConfigurations = ( 350 | E7A6C4011F03DE130029AFD7 /* Debug */, 351 | E7A6C4021F03DE130029AFD7 /* Release */, 352 | ); 353 | defaultConfigurationIsVisible = 0; 354 | defaultConfigurationName = Release; 355 | }; 356 | /* End XCConfigurationList section */ 357 | }; 358 | rootObject = E7A6C3E61F03DE130029AFD7 /* Project object */; 359 | } 360 | -------------------------------------------------------------------------------- /AAPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AAPlayer/AAPlayButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayButton.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/7/1. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AAPlayButton: UIButton { 12 | 13 | 14 | // Only override draw() if you perform custom drawing. 15 | // An empty implementation adversely affects performance during animation. 16 | override func draw(_ rect: CGRect) { 17 | super.draw(rect) 18 | 19 | if isSelected { 20 | setPauseIconImage(rect) 21 | 22 | } else { 23 | setPlayIconImage(rect) 24 | 25 | } 26 | 27 | } 28 | 29 | fileprivate func setPauseIconImage(_ rect: CGRect) { 30 | 31 | let rect = rect 32 | UIGraphicsBeginImageContext(rect.size) 33 | let context = UIGraphicsGetCurrentContext()! 34 | context.setStrokeColor(UIColor(red: 66/255, green: 114/255, blue: 155/255, alpha: 1).cgColor) 35 | context.setLineWidth(8.0) 36 | context.move(to: CGPoint(x: 8, y: 0)) 37 | context.addLine(to: CGPoint(x: 8, y: 25)) 38 | context.move(to: CGPoint(x: 20, y: 0)) 39 | context.addLine(to: CGPoint(x: 20, y: 25)) 40 | context.strokePath() 41 | let image = UIGraphicsGetImageFromCurrentImageContext() 42 | UIGraphicsEndImageContext() 43 | setImage(image, for: .selected) 44 | } 45 | 46 | fileprivate func setPlayIconImage(_ rect: CGRect) { 47 | 48 | let rect = rect 49 | UIGraphicsBeginImageContext(rect.size) 50 | let context = UIGraphicsGetCurrentContext()! 51 | context.setFillColor(UIColor(red: 66/255, green: 114/255, blue: 155/255, alpha: 1).cgColor) 52 | context.move(to: CGPoint(x: 3, y: 0)) 53 | context.addLine(to: CGPoint(x: 3, y: rect.size.height)) 54 | context.addLine(to: CGPoint(x: rect.size.width, y: rect.size.height / 2)) 55 | context.closePath() 56 | context.fillPath() 57 | let image = UIGraphicsGetImageFromCurrentImageContext() 58 | UIGraphicsEndImageContext() 59 | setImage(image, for: .normal) 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /AAPlayer/AAPlayProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayProgressView.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/7/1. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AAPlayProgressView: UIProgressView { 12 | 13 | 14 | override func sizeThatFits(_ size: CGSize) -> CGSize { 15 | super.sizeThatFits(size) 16 | 17 | let size = CGSize.init(width: size.width, height: 2) 18 | return size 19 | } 20 | 21 | 22 | /* 23 | // Only override draw() if you perform custom drawing. 24 | // An empty implementation adversely affects performance during animation. 25 | override func draw(_ rect: CGRect) { 26 | // Drawing code 27 | } 28 | */ 29 | 30 | } 31 | -------------------------------------------------------------------------------- /AAPlayer/AAPlayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayer.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/6/28. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import AVKit 12 | 13 | public protocol AAPlayerDelegate : class { 14 | 15 | typealias playerItemStatus = AVPlayerItemStatus 16 | func callBackDownloadDidFinish(_ status:AVPlayerItemStatus?) 17 | } 18 | 19 | 20 | public class AAPlayer: UIView { 21 | 22 | public weak var delegate:AAPlayerDelegate? 23 | 24 | fileprivate var player:AVPlayer? 25 | fileprivate var playerLayer:AVPlayerLayer? 26 | fileprivate var playerItem:AVPlayerItem? 27 | fileprivate var playUrl:String! 28 | fileprivate var playButton:AAPlayButton! 29 | fileprivate var playActivityIndicator:AAPlayerActivityIndicicatorView! 30 | fileprivate var smallPlayButton:AAPlayButton! 31 | fileprivate var rotateSizeButton:AAPlayerRotateButton! 32 | fileprivate var playProgressView:AAPlayProgressView! 33 | fileprivate var playerSlider:AAPlayerSlider! 34 | fileprivate var playerBottomView:UIView! 35 | fileprivate var timeLabel:UILabel! 36 | fileprivate var timer:Timer? 37 | fileprivate var playbackObserver:Any? 38 | 39 | 40 | override init(frame: CGRect) { 41 | super.init(frame: frame) 42 | 43 | initWithPlayBottomView() 44 | initWithPlayButton() 45 | initWithPlayProgressView() 46 | initWithSlider() 47 | initWithTimeLabel() 48 | initWithPlayActivityIndicator() 49 | initWithRotateButton() 50 | } 51 | 52 | //MARK:- Interface Builder(Xib,StoryBoard) 53 | override public func awakeFromNib() { 54 | super.awakeFromNib() 55 | 56 | initWithPlayBottomView() 57 | initWithPlayButton() 58 | initWithPlayProgressView() 59 | initWithSlider() 60 | initWithTimeLabel() 61 | initWithPlayActivityIndicator() 62 | initWithRotateButton() 63 | } 64 | 65 | deinit { 66 | 67 | removeAllObserver() 68 | resettingObject() 69 | } 70 | 71 | override public func layoutSubviews() { 72 | super.layoutSubviews() 73 | 74 | setPlayerSubviewsFrame() 75 | detectedInterfaceOrientation() 76 | } 77 | 78 | 79 | required public init?(coder aDecoder: NSCoder) { 80 | super.init(coder: aDecoder) 81 | 82 | } 83 | 84 | //MARK:- initialize method 85 | fileprivate func initWithPlayBottomView() { 86 | 87 | layer.backgroundColor = UIColor(red: 31/255, green: 37/255, blue: 61/255, alpha: 1).cgColor 88 | playerBottomView = UIView() 89 | playerBottomView.backgroundColor = UIColor.black 90 | playerBottomView.alpha = 0 91 | addSubview(playerBottomView) 92 | } 93 | 94 | 95 | fileprivate func initWithPlayButton() { 96 | 97 | playButton = AAPlayButton() 98 | playButton.addTarget(self, action: #selector(startPlay), for: .touchUpInside) 99 | addSubview(playButton) 100 | smallPlayButton = AAPlayButton() 101 | smallPlayButton.addTarget(self, action: #selector(startPlay), for: .touchUpInside) 102 | playerBottomView.addSubview(smallPlayButton) 103 | } 104 | 105 | fileprivate func initWithRotateButton() { 106 | 107 | rotateSizeButton = AAPlayerRotateButton() 108 | playerBottomView.addSubview(rotateSizeButton) 109 | 110 | } 111 | 112 | fileprivate func initWithPlayActivityIndicator() { 113 | 114 | playActivityIndicator = AAPlayerActivityIndicicatorView() 115 | addSubview(playActivityIndicator) 116 | 117 | } 118 | 119 | fileprivate func initWithPlayProgressView() { 120 | 121 | playProgressView = AAPlayProgressView(progressViewStyle: .bar) 122 | playProgressView.progressTintColor = UIColor(red: 102/255, green: 178/255, blue: 255/255, alpha: 0.5) 123 | playProgressView.trackTintColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.6) 124 | playProgressView.setProgress(0.0, animated: true) 125 | playerBottomView.addSubview(playProgressView) 126 | 127 | } 128 | 129 | fileprivate func initWithSlider() { 130 | 131 | playerSlider = AAPlayerSlider() 132 | playerSlider.tintColor = UIColor.clear 133 | playerSlider.backgroundColor = UIColor.clear 134 | playerSlider.maximumTrackTintColor = UIColor.clear 135 | playerSlider.minimumTrackTintColor = UIColor(red: 231/255, green: 107/255, blue: 107/255, alpha: 1) 136 | playerSlider.minimumValue = 0 137 | playerSlider.isContinuous = false 138 | playerSlider.addTarget(self, action: #selector(touchPlayerProgress), for: [.touchDown, .touchUpInside]) 139 | playerBottomView.addSubview(playerSlider) 140 | } 141 | 142 | fileprivate func initWithTimeLabel() { 143 | 144 | timeLabel = UILabel() 145 | timeLabel.textColor = UIColor.white 146 | timeLabel.font = UIFont.boldSystemFont(ofSize: 10) 147 | playerBottomView.addSubview(timeLabel) 148 | } 149 | 150 | //MARK:- frame method 151 | fileprivate func setPlayerSubviewsFrame() { 152 | 153 | playerBottomView.frame = CGRect(x: 0, y: frame.height - 50, width: frame.width, height: 50) 154 | playerLayer?.frame = bounds 155 | playButton.frame = CGRect(x: frame.width / 2 - 25, y: frame.height / 2 - 25, width: 50, height: 50) 156 | playButton.center = CGPoint(x: frame.width / 2 , y: frame.height / 2) 157 | playProgressView.frame = CGRect(x: 60, y: playerBottomView.frame.height / 2, width: playerBottomView.frame.width - 240, height: 2) 158 | playerSlider.frame = CGRect(x: 55, y: playerBottomView.frame.height / 2 - 9, width: playerBottomView.frame.width - 215, height: 20) 159 | smallPlayButton.frame = CGRect(x: 10, y: playerBottomView.frame.height / 2 - 11, width: 30, height: 25) 160 | timeLabel.frame = CGRect(x: playerBottomView.frame.width - 155, y: playerBottomView.frame.height / 2 - 9, width: 110, height: 20) 161 | playActivityIndicator.center = CGPoint(x: frame.width / 2, y: frame.height / 2) 162 | playActivityIndicator.frame.size = CGSize(width: 90, height: 90) 163 | rotateSizeButton.frame = CGRect(x: playerBottomView.frame.width - 45, y: 5, width:40, height: 40) 164 | 165 | } 166 | 167 | //MARL:- interface orientation 168 | fileprivate func detectedInterfaceOrientation() { 169 | 170 | switch UIDevice.current.orientation { 171 | case .portrait: 172 | rotateSizeButton.isSelected = false 173 | break 174 | case .landscapeRight: 175 | rotateSizeButton.isSelected = true 176 | break 177 | case .landscapeLeft: 178 | rotateSizeButton.isSelected = true 179 | break 180 | default: 181 | rotateSizeButton.isSelected = false 182 | break 183 | } 184 | } 185 | 186 | //MARK:- setting player 187 | fileprivate func setPlayRemoteUrl() { 188 | 189 | if playUrl == nil || playUrl == "" { 190 | return 191 | } 192 | removeAllObserver() 193 | resettingObject() 194 | let asset = AVAsset(url: URL(string: playUrl)!) 195 | playerItem = AVPlayerItem(asset: asset) 196 | player = AVPlayer(playerItem: playerItem) 197 | playerLayer = AVPlayerLayer(player: player) 198 | playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspect 199 | playerLayer?.contentsScale = UIScreen.main.scale 200 | layer.insertSublayer(playerLayer!, at: 0) 201 | setAllObserver() 202 | 203 | } 204 | 205 | //MARK:- setting observer 206 | fileprivate func setAllObserver() { 207 | 208 | player?.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil) 209 | playerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: NSKeyValueObservingOptions.new, context: nil) 210 | playerItem?.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil) 211 | 212 | } 213 | 214 | fileprivate func removeAllObserver() { 215 | 216 | player?.removeObserver(self, forKeyPath: "rate") 217 | playerItem?.removeObserver(self, forKeyPath: "loadedTimeRanges") 218 | playerItem?.removeObserver(self, forKeyPath: "status") 219 | 220 | } 221 | 222 | 223 | override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 224 | 225 | if keyPath == "status" { 226 | 227 | observePlayerStatus() 228 | 229 | } else if keyPath == "loadedTimeRanges" { 230 | 231 | let currentTime = getBufferTimeDuration() 232 | let totalTime = CMTimeGetSeconds((playerItem?.duration)!) 233 | let percent = currentTime / totalTime 234 | playProgressView.progress = Float(percent) 235 | 236 | } else if keyPath == "rate" { 237 | 238 | if (object as! AVPlayer).rate == 0 && Int(playerSlider.value) == Int(playerSlider.maximumValue) { 239 | smallPlayButton.isSelected = false 240 | setPlayBottomViewAnimation() 241 | } 242 | 243 | } 244 | } 245 | 246 | 247 | //MARK:- check playItem status 248 | fileprivate func observePlayerStatus() { 249 | 250 | let status:AVPlayerItemStatus = (player?.currentItem?.status)! 251 | switch status { 252 | case .readyToPlay: 253 | 254 | if Float(CMTimeGetSeconds((playerItem?.duration)!)).isNaN == true { break } 255 | playerSlider.addTarget(self, action: #selector(changePlayerProgress), for: .valueChanged) 256 | playerSlider.maximumValue = Float(CMTimeGetSeconds((playerItem?.duration)!)) 257 | let allTimeString = timeFotmatter(Float(CMTimeGetSeconds((playerItem?.duration)!))) 258 | playbackObserver = player?.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 1), queue: nil, using: { (time) in 259 | let during = self.playerItem!.currentTime() 260 | let time = during.value / Int64(during.timescale) 261 | self.timeLabel.text = "\(self.timeFotmatter(Float(time))) / \(allTimeString)" 262 | if !self.playerSlider.isHighlighted { 263 | self.playerSlider.value = Float(time) 264 | } 265 | }) 266 | 267 | break 268 | case .failed: 269 | 270 | break 271 | default: 272 | 273 | break 274 | } 275 | playActivityIndicator.stopAnimation() 276 | downloadDidFinish(status) 277 | } 278 | 279 | 280 | //MARK:- call back playItem status 281 | fileprivate func downloadDidFinish(_ status:AVPlayerItemStatus?) { 282 | 283 | delegate?.callBackDownloadDidFinish(status) 284 | } 285 | 286 | //MARK:- get buffer time duration 287 | fileprivate func getBufferTimeDuration() -> TimeInterval { 288 | 289 | let loadedTimeRanges = player!.currentItem!.loadedTimeRanges 290 | guard let timeRange = loadedTimeRanges.first?.timeRangeValue else { return 0.0 } 291 | let start = CMTimeGetSeconds(timeRange.start) 292 | let duration = CMTimeGetSeconds(timeRange.duration) 293 | let currentTimeDuration = (start + duration) 294 | return currentTimeDuration 295 | 296 | } 297 | 298 | //MARK:- calculate time formatter 299 | fileprivate func timeFotmatter(_ time:Float) -> String { 300 | 301 | var hr:Int! 302 | var min:Int! 303 | var sec:Int! 304 | var timeString:String! 305 | 306 | if time >= 3600 { 307 | hr = Int(time / 3600) 308 | min = Int(time.truncatingRemainder(dividingBy: 3600)) 309 | sec = Int(min % 60) 310 | timeString = String(format: "%02d:%02d:%02d", hr, min, sec) 311 | } else if time >= 60 && time < 3600 { 312 | min = Int(time / 60) 313 | sec = Int(time.truncatingRemainder(dividingBy: 60)) 314 | timeString = String(format: "00:%02d:%02d", min, sec) 315 | } else if time < 60 { 316 | sec = Int(time) 317 | timeString = String(format: "00:00:%02d", sec) 318 | } 319 | 320 | return timeString 321 | } 322 | 323 | //MARK:- setting player display 324 | @objc fileprivate func startPlay() { 325 | 326 | if playButton.isHidden == false { 327 | setPlayRemoteUrl() 328 | setPlayBottomViewAnimation() 329 | playActivityIndicator.startAnimation() 330 | } 331 | 332 | if player?.rate == 0 { 333 | player?.play() 334 | playButton.isSelected = true 335 | playButton.isHidden = true 336 | smallPlayButton.isSelected = true 337 | stopTimer() 338 | startTimer() 339 | 340 | } else { 341 | player?.pause() 342 | playButton.isSelected = false 343 | smallPlayButton.isSelected = false 344 | stopTimer() 345 | } 346 | 347 | } 348 | 349 | override public func touchesBegan(_ touches: Set, with event: UIEvent?) { 350 | 351 | setPlayBottomViewAnimation() 352 | stopTimer() 353 | if player?.rate == 1 && playerBottomView.alpha == 1 { 354 | startTimer() 355 | } 356 | } 357 | 358 | @objc fileprivate func setPlayBottomViewAnimation() { 359 | 360 | UIView.animate(withDuration: 0.5) { 361 | if self.playerBottomView.alpha == 0 { 362 | self.playerBottomView.alpha = 1 363 | } else { 364 | self.playerBottomView.alpha = 0 365 | } 366 | } 367 | 368 | } 369 | 370 | 371 | //MARK:- timer 372 | fileprivate func startTimer() { 373 | 374 | timer = Timer() 375 | timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(setPlayBottomViewAnimation), userInfo: nil, repeats: false) 376 | } 377 | 378 | fileprivate func stopTimer() { 379 | 380 | if timer == nil { 381 | return 382 | } 383 | timer?.invalidate() 384 | timer = nil 385 | } 386 | 387 | //MARK:- change player progress 388 | @objc fileprivate func changePlayerProgress() { 389 | 390 | playActivityIndicator.startAnimation() 391 | let seekDuration = playerSlider.value 392 | player?.seek(to: CMTimeMake(Int64(seekDuration), 1), completionHandler: { (BOOL) in 393 | self.playActivityIndicator.stopAnimation() 394 | }) 395 | 396 | } 397 | 398 | @objc fileprivate func touchPlayerProgress() { 399 | 400 | if playerSlider.isHighlighted { 401 | stopTimer() 402 | } else { 403 | startTimer() 404 | } 405 | } 406 | 407 | //MARK: - resetting display view 408 | fileprivate func resettingObject() { 409 | 410 | player = nil 411 | playerLayer = nil 412 | playbackObserver = nil 413 | playerItem = nil 414 | 415 | } 416 | 417 | //MARK: - public control method 418 | public func playVideo(_ url:String) { 419 | 420 | playUrl = url 421 | playButton.isHidden = false 422 | playButton.isSelected = false 423 | smallPlayButton.isSelected = false 424 | if playbackObserver != nil { 425 | player?.removeTimeObserver(playbackObserver!) 426 | playbackObserver = nil 427 | } 428 | if player?.rate == 1 { 429 | player?.pause() 430 | } 431 | playActivityIndicator.stopAnimation() 432 | playerLayer?.removeFromSuperlayer() 433 | playerSlider.removeTarget(self, action: #selector(changePlayerProgress), for: .valueChanged) 434 | playerSlider.value = 0.0 435 | playProgressView.progress = 0.0 436 | timeLabel.text = "00:00:00 / 00:00:00" 437 | } 438 | 439 | public func startPlayback() { 440 | 441 | player?.play() 442 | } 443 | 444 | public func pausePlayback() { 445 | 446 | player?.pause() 447 | } 448 | 449 | /* 450 | // Only override draw() if you perform custom drawing. 451 | // An empty implementation adversely affects performance during animation. 452 | override func draw(_ rect: CGRect) { 453 | // Drawing code 454 | } 455 | */ 456 | 457 | 458 | } 459 | 460 | 461 | extension AAPlayerDelegate { 462 | 463 | public func callBackDownloadDidFinish(_ status:AVPlayerItemStatus?) { 464 | 465 | 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /AAPlayer/AAPlayerActivityIndicicatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayerActivityIndicicatorView.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/7/24. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AAPlayerActivityIndicicatorView: UIView { 12 | 13 | fileprivate var indicicatorLayer: CALayer! 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | 18 | initWithIndicicatorLayer() 19 | isHidden = true 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | super.init(coder: aDecoder) 24 | 25 | 26 | } 27 | override func layoutSubviews() { 28 | 29 | indicicatorLayer.frame = CGRect(x: 0,y: 0,width: frame.size.width,height: frame.size.height) 30 | indicicatorLayer.contents = createIndicicatorImage().cgImage 31 | } 32 | 33 | fileprivate func initWithIndicicatorLayer() { 34 | 35 | indicicatorLayer = CALayer() 36 | indicicatorLayer.masksToBounds = true 37 | layer.addSublayer(indicicatorLayer) 38 | 39 | } 40 | 41 | fileprivate func createIndicicatorImage() -> UIImage { 42 | 43 | UIGraphicsBeginImageContext(CGSize(width: frame.width, height: frame.height)) 44 | let context = UIGraphicsGetCurrentContext() 45 | let path:CGMutablePath = CGMutablePath() 46 | context!.addArc(center:CGPoint(x: frame.width / 2, y: frame.height / 2), radius: 40, startAngle: 0, endAngle: 1.5 * CGFloat(Double.pi), clockwise: true) 47 | context!.move(to: CGPoint(x: 50, y: 100)) 48 | context!.addLine(to: CGPoint(x: 50, y: 150)) 49 | context!.addLine(to: CGPoint(x: 100, y: 150)) 50 | context!.addPath(path) 51 | let colors = [UIColor(red: 231/255, green: 107/255, blue: 107/255, alpha: 0.6).cgColor,UIColor(red: 231/255, green: 107/255, blue: 107/255, alpha: 0.3).cgColor] 52 | let colorSpace = CGColorSpaceCreateDeviceRGB() 53 | let colorLocations:[CGFloat] = [0.6, 1.0] 54 | let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: colorLocations) 55 | context?.drawRadialGradient(gradient!, startCenter:CGPoint(x: frame.width / 2, y: frame.height / 2), startRadius: 0, endCenter: CGPoint(x: frame.width / 2 + 5, y: frame.height / 2 + 5), endRadius: 10, options: .drawsBeforeStartLocation) 56 | UIColor(red: 231/255, green: 107/255, blue: 107/255, alpha: 1).setStroke() 57 | context?.drawPath(using: .stroke) 58 | let image = UIGraphicsGetImageFromCurrentImageContext() 59 | UIGraphicsEndImageContext() 60 | return image! 61 | } 62 | 63 | fileprivate func setAnimation() -> CABasicAnimation { 64 | 65 | let rotation = CABasicAnimation(keyPath: "transform.rotation.z") 66 | rotation.duration = 1.0 67 | rotation.isRemovedOnCompletion = false 68 | rotation.repeatCount = Float.infinity 69 | rotation.fillMode = kCAFillModeForwards 70 | rotation.fromValue = 0.0 71 | rotation.toValue = Double.pi * 2; 72 | return rotation 73 | } 74 | 75 | fileprivate func pauseAnimation() { 76 | 77 | let pausedTime = indicicatorLayer.convertTime(CACurrentMediaTime(), from: nil) 78 | indicicatorLayer.speed = 0.0 79 | indicicatorLayer.timeOffset = pausedTime 80 | } 81 | 82 | fileprivate func resumeAnimation() { 83 | 84 | let pauseTime = indicicatorLayer.timeOffset 85 | indicicatorLayer.speed = 1.0 86 | indicicatorLayer.timeOffset = 0.0 87 | let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pauseTime 88 | indicicatorLayer.beginTime = timeSincePause 89 | } 90 | 91 | 92 | func startAnimation() { 93 | 94 | if indicicatorLayer.animation(forKey: "rotation") == nil { 95 | indicicatorLayer.add(setAnimation(), forKey: "rotation") 96 | } 97 | 98 | isHidden = false 99 | resumeAnimation() 100 | } 101 | 102 | func stopAnimation() { 103 | 104 | isHidden = true 105 | pauseAnimation() 106 | 107 | } 108 | 109 | /* 110 | // Only override draw() if you perform custom drawing. 111 | // An empty implementation adversely affects performance during animation. 112 | override func draw(_ rect: CGRect) { 113 | // Drawing code 114 | } 115 | */ 116 | 117 | } 118 | -------------------------------------------------------------------------------- /AAPlayer/AAPlayerRotateButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayerRotateButton.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/7/25. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AAPlayerRotateButton: UIButton { 12 | 13 | // Only override draw() if you perform custom drawing. 14 | // An empty implementation adversely affects performance during animation. 15 | override func draw(_ rect: CGRect) { 16 | 17 | if isSelected { 18 | setOriginalSizeIconImage(rect) 19 | } else { 20 | setFullSizeIconImage(rect) 21 | } 22 | 23 | addTarget(self, action: #selector(setDisplaySize), for: .touchUpInside) 24 | } 25 | 26 | fileprivate func setFullSizeIconImage(_ rect: CGRect) { 27 | 28 | let rect = rect 29 | UIGraphicsBeginImageContext(rect.size) 30 | let context = UIGraphicsGetCurrentContext()! 31 | context.setStrokeColor(UIColor.white.cgColor) 32 | context.setLineWidth(2.0) 33 | context.move(to: CGPoint(x: rect.origin.x + 5 + rect.width - 20, y: rect.origin.y + 5)) 34 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + rect.width - 10, y: rect.origin.y + 5)) 35 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + rect.width - 10, y: rect.origin.y + 15)) 36 | context.move(to: CGPoint(x: rect.origin.x + 5 + rect.width - 10, y: rect.origin.y + 5)) 37 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 + 4, y: rect.origin.y + 5 + (rect.height - 10)/2 - 4)) 38 | context.move(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4)) 39 | context.addLine(to: CGPoint(x: rect.origin.x + 5, y: rect.origin.y + 5 + rect.height - 10)) 40 | context.addLine(to: CGPoint(x: rect.origin.x + 5, y: rect.origin.y + 5 + rect.height - 10 - 10)) 41 | context.move(to: CGPoint(x: rect.origin.x + 5, y: rect.origin.y + 5 + rect.height - 10)) 42 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + 10, y: rect.origin.y + 5 + rect.height - 10)) 43 | context.strokePath() 44 | let image = UIGraphicsGetImageFromCurrentImageContext() 45 | UIGraphicsEndImageContext() 46 | contentEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10) 47 | setImage(image, for: .normal) 48 | } 49 | 50 | fileprivate func setOriginalSizeIconImage(_ rect: CGRect) { 51 | 52 | let rect = rect 53 | UIGraphicsBeginImageContext(rect.size) 54 | let context = UIGraphicsGetCurrentContext()! 55 | context.setStrokeColor(UIColor.white.cgColor) 56 | context.setLineWidth(2.0) 57 | context.move(to: CGPoint(x: rect.origin.x + 5 + rect.width - 10, y: rect.origin.y + 5)) 58 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 + 4, y: rect.origin.y + 5 + (rect.height - 10)/2 - 4)) 59 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 + 4 + 10, y: rect.origin.y + 5 + (rect.height - 10)/2 - 4)) 60 | context.move(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 + 4, y: rect.origin.y + 5 + (rect.height - 10)/2 - 4)) 61 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 + 4, y: rect.origin.y + 5 + (rect.height - 10)/2 - 4 - 10)) 62 | context.move(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4)) 63 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4 - 10, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4)) 64 | context.move(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4)) 65 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4 + 10)) 66 | context.move(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4)) 67 | context.addLine(to: CGPoint(x: rect.origin.x + 5, y: rect.origin.y + 5 + rect.height - 10)) 68 | context.strokePath() 69 | let image = UIGraphicsGetImageFromCurrentImageContext() 70 | UIGraphicsEndImageContext() 71 | contentEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10) 72 | setImage(image, for: .selected) 73 | } 74 | 75 | //MARK:- setting display full size or original size 76 | @objc fileprivate func setDisplaySize() { 77 | 78 | if isSelected { 79 | 80 | isSelected = false 81 | let value = UIInterfaceOrientation.portrait.rawValue 82 | UIDevice.current.setValue(value, forKey: "orientation") 83 | UIViewController.attemptRotationToDeviceOrientation() 84 | 85 | } else { 86 | 87 | isSelected = true 88 | let value = UIInterfaceOrientation.landscapeRight.rawValue 89 | UIDevice.current.setValue(value, forKey: "orientation") 90 | UIViewController.attemptRotationToDeviceOrientation() 91 | 92 | } 93 | 94 | } 95 | 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /AAPlayer/AAPlayerSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayerSlider.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/7/2. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AAPlayerSlider: UISlider { 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | setThumbImage() 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect { 23 | let rect = super.thumbRect(forBounds: bounds, trackRect: rect, value: value) 24 | return CGRect(x: rect.origin.x, y: rect.origin.y + 2, width: rect.width, height: rect.height) 25 | } 26 | 27 | fileprivate func setThumbImage() { 28 | 29 | UIGraphicsBeginImageContext(CGSize(width: 25, height: 25)) 30 | let context = UIGraphicsGetCurrentContext() 31 | context!.setFillColor(UIColor(red: 36/255, green: 153/255, blue: 145/255, alpha: 1).cgColor) 32 | context?.addEllipse(in: CGRect(x: 0, y: 0, width: 23, height: 23)) 33 | context!.drawPath(using: .fill) 34 | let image = UIGraphicsGetImageFromCurrentImageContext() 35 | UIGraphicsEndImageContext() 36 | setThumbImage(image, for: .normal) 37 | } 38 | 39 | 40 | /* 41 | // Only override draw() if you perform custom drawing. 42 | // An empty implementation adversely affects performance during animation. 43 | override func draw(_ rect: CGRect) { 44 | // Drawing code 45 | } 46 | */ 47 | 48 | } 49 | -------------------------------------------------------------------------------- /AAPlayer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/6/28. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /AAPlayer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /AAPlayer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AAPlayer/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 | -------------------------------------------------------------------------------- /AAPlayer/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 40 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /AAPlayer/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 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /AAPlayer/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/6/28. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | 13 | class ViewController: UIViewController, AAPlayerDelegate { 14 | @IBOutlet weak var player: AAPlayer! 15 | 16 | fileprivate var sourceArray: Array! 17 | fileprivate var currentIndex = 0 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | sourceArray = ["http://live.zzbtv.com:80/live/live123/800K/tzwj_video.m3u8","http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8","http://bos.nj.bpc.baidu.com/tieba-smallvideo/0173bbaf5acf62b815a7de0544730d6c.mp4","http://bos.nj.bpc.baidu.com/tieba-smallvideo/00a52c5e2213216ce0ce3795d40e9492.mp4","http://bos.nj.bpc.baidu.com/tieba-smallvideo/0045ab5a9e440defb2611658c0914724.mp4"] 23 | player.delegate = self 24 | player.playVideo(sourceArray[currentIndex] as! String) 25 | 26 | } 27 | 28 | @IBAction func beforeBtn(_ sender: Any) { 29 | 30 | currentIndex = currentIndex - 1 31 | if currentIndex < 0 { 32 | currentIndex = 0 33 | return 34 | } 35 | 36 | player.playVideo(sourceArray[currentIndex] as! String) 37 | 38 | } 39 | 40 | @IBAction func nextBtn(_ sender: Any) { 41 | 42 | currentIndex = currentIndex + 1 43 | if currentIndex > sourceArray.count - 1 { 44 | currentIndex = sourceArray.count - 1 45 | return 46 | } 47 | 48 | player.playVideo(sourceArray[currentIndex] as! String) 49 | 50 | } 51 | 52 | 53 | //optional method 54 | func callBackDownloadDidFinish(_ status: playerItemStatus?) { 55 | 56 | let status:playerItemStatus = status! 57 | switch status { 58 | 59 | case .readyToPlay: 60 | 61 | break 62 | case .failed: 63 | 64 | break 65 | default: 66 | break 67 | } 68 | } 69 | 70 | func startPlay() { 71 | //optional method 72 | player.startPlayback() 73 | } 74 | 75 | func stopPlay() { 76 | //optional method 77 | player.pausePlayback() 78 | } 79 | 80 | 81 | override func didReceiveMemoryWarning() { 82 | super.didReceiveMemoryWarning() 83 | // Dispose of any resources that can be recreated. 84 | } 85 | 86 | 87 | } 88 | 89 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alan 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 | # AAPlayer 2 | 3 | #### Customize Video Player base on AVPlayer 4 | 5 | 6 | # Feature 7 | 8 | - [x] Pure swift 3.1 code 9 | - [x] Use UIGraphics to draw icons for buttons 10 | - [x] Supported video formats : HLS, mp4 11 | - [x] Require iOS 8 or later 12 | 13 | #### Playing Status 14 | 15 | ![](./sampleImage/samplePlay.gif) 16 | 17 | # Installation 18 | 19 | #### CocoaPods 20 | 21 | AAPlayer is available through [CocoaPods](http://cocoapods.org). 22 | 23 | Simply add AAPlayer to your `Podfile`. 24 | 25 | ``` 26 | pod 'AAPlayer' 27 | ``` 28 | Enter command instruction into your terminal. 29 | 30 | ``` 31 | pod install 32 | ``` 33 | # Usage 34 | 35 | You must create UIView and it use custom class in the storyboard after install AAPlayer. 36 | 37 | ![](./sampleImage/customClass.png) 38 | 39 | Then you must also create a IBOutlet in your UIViewController Class with AAPlayerDelegate. 40 | 41 | ![](./sampleImage/IBOutlet.png) 42 | 43 | The following sample code for your reference. 44 | 45 | ```swift 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | 49 | sourceArray = ["http://clips.vorwaerts-gmbh.de/VfE_html5.mp4","http://live.zzbtv.com:80/live/live123/800K/tzwj_video.m3u8","http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8","http://bos.nj.bpc.baidu.com/tieba-smallvideo/0173bbaf5acf62b815a7de0544730d6c.mp4","http://bos.nj.bpc.baidu.com/tieba-smallvideo/00a52c5e2213216ce0ce3795d40e9492.mp4","http://bos.nj.bpc.baidu.com/tieba-smallvideo/0045ab5a9e440defb2611658c0914724.mp4"] 50 | player.delegate = self 51 | player.playVideo(sourceArray[currentIndex] as! String) 52 | } 53 | ``` 54 | 55 | ```swift 56 | //optional method 57 | func callBackDownloadDidFinish(_ status: playerItemStatus?) { 58 | 59 | let status:playerItemStatus = status! 60 | switch status { 61 | case .readyToPlay: 62 | break 63 | case .failed: 64 | break 65 | default: 66 | break 67 | } 68 | } 69 | 70 | ``` 71 | 72 | ```swift 73 | func startPlay() { 74 | //optional method 75 | player.startPlayback() 76 | } 77 | 78 | func stopPlay() { 79 | //optional method 80 | player.pausePlayback() 81 | } 82 | 83 | ``` 84 | # License 85 | 86 | AAPlayer is available under the MIT license. See the LICENSE file for more info. 87 | -------------------------------------------------------------------------------- /Sources/AAPlayButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayButton.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/7/1. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AAPlayButton: UIButton { 12 | 13 | 14 | // Only override draw() if you perform custom drawing. 15 | // An empty implementation adversely affects performance during animation. 16 | override func draw(_ rect: CGRect) { 17 | super.draw(rect) 18 | 19 | if isSelected { 20 | setPauseIconImage(rect) 21 | 22 | } else { 23 | setPlayIconImage(rect) 24 | 25 | } 26 | 27 | } 28 | 29 | fileprivate func setPauseIconImage(_ rect: CGRect) { 30 | 31 | let rect = rect 32 | UIGraphicsBeginImageContext(rect.size) 33 | let context = UIGraphicsGetCurrentContext()! 34 | context.setStrokeColor(UIColor(red: 66/255, green: 114/255, blue: 155/255, alpha: 1).cgColor) 35 | context.setLineWidth(8.0) 36 | context.move(to: CGPoint(x: 8, y: 0)) 37 | context.addLine(to: CGPoint(x: 8, y: 25)) 38 | context.move(to: CGPoint(x: 20, y: 0)) 39 | context.addLine(to: CGPoint(x: 20, y: 25)) 40 | context.strokePath() 41 | let image = UIGraphicsGetImageFromCurrentImageContext() 42 | UIGraphicsEndImageContext() 43 | setImage(image, for: .selected) 44 | } 45 | 46 | fileprivate func setPlayIconImage(_ rect: CGRect) { 47 | 48 | let rect = rect 49 | UIGraphicsBeginImageContext(rect.size) 50 | let context = UIGraphicsGetCurrentContext()! 51 | context.setFillColor(UIColor(red: 66/255, green: 114/255, blue: 155/255, alpha: 1).cgColor) 52 | context.move(to: CGPoint(x: 3, y: 0)) 53 | context.addLine(to: CGPoint(x: 3, y: rect.size.height)) 54 | context.addLine(to: CGPoint(x: rect.size.width, y: rect.size.height / 2)) 55 | context.closePath() 56 | context.fillPath() 57 | let image = UIGraphicsGetImageFromCurrentImageContext() 58 | UIGraphicsEndImageContext() 59 | setImage(image, for: .normal) 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/AAPlayProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayProgressView.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/7/1. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AAPlayProgressView: UIProgressView { 12 | 13 | 14 | override func sizeThatFits(_ size: CGSize) -> CGSize { 15 | super.sizeThatFits(size) 16 | 17 | let size = CGSize.init(width: size.width, height: 2) 18 | return size 19 | } 20 | 21 | 22 | /* 23 | // Only override draw() if you perform custom drawing. 24 | // An empty implementation adversely affects performance during animation. 25 | override func draw(_ rect: CGRect) { 26 | // Drawing code 27 | } 28 | */ 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/AAPlayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayer.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/6/28. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import AVKit 12 | 13 | public protocol AAPlayerDelegate : class { 14 | 15 | typealias playerItemStatus = AVPlayerItemStatus 16 | func callBackDownloadDidFinish(_ status:AVPlayerItemStatus?) 17 | } 18 | 19 | 20 | public class AAPlayer: UIView { 21 | 22 | public weak var delegate:AAPlayerDelegate? 23 | 24 | fileprivate var player:AVPlayer? 25 | fileprivate var playerLayer:AVPlayerLayer? 26 | fileprivate var playerItem:AVPlayerItem? 27 | fileprivate var playUrl:String! 28 | fileprivate var playButton:AAPlayButton! 29 | fileprivate var playActivityIndicator:AAPlayerActivityIndicicatorView! 30 | fileprivate var smallPlayButton:AAPlayButton! 31 | fileprivate var rotateSizeButton:AAPlayerRotateButton! 32 | fileprivate var playProgressView:AAPlayProgressView! 33 | fileprivate var playerSlider:AAPlayerSlider! 34 | fileprivate var playerBottomView:UIView! 35 | fileprivate var timeLabel:UILabel! 36 | fileprivate var timer:Timer? 37 | fileprivate var playbackObserver:Any? 38 | 39 | 40 | override init(frame: CGRect) { 41 | super.init(frame: frame) 42 | 43 | initWithPlayBottomView() 44 | initWithPlayButton() 45 | initWithPlayProgressView() 46 | initWithSlider() 47 | initWithTimeLabel() 48 | initWithPlayActivityIndicator() 49 | initWithRotateButton() 50 | } 51 | 52 | //MARK:- Interface Builder(Xib,StoryBoard) 53 | override public func awakeFromNib() { 54 | super.awakeFromNib() 55 | 56 | initWithPlayBottomView() 57 | initWithPlayButton() 58 | initWithPlayProgressView() 59 | initWithSlider() 60 | initWithTimeLabel() 61 | initWithPlayActivityIndicator() 62 | initWithRotateButton() 63 | } 64 | 65 | deinit { 66 | 67 | removeAllObserver() 68 | resettingObject() 69 | } 70 | 71 | override public func layoutSubviews() { 72 | super.layoutSubviews() 73 | 74 | setPlayerSubviewsFrame() 75 | detectedInterfaceOrientation() 76 | } 77 | 78 | 79 | required public init?(coder aDecoder: NSCoder) { 80 | super.init(coder: aDecoder) 81 | 82 | } 83 | 84 | //MARK:- initialize method 85 | fileprivate func initWithPlayBottomView() { 86 | 87 | layer.backgroundColor = UIColor(red: 31/255, green: 37/255, blue: 61/255, alpha: 1).cgColor 88 | playerBottomView = UIView() 89 | playerBottomView.backgroundColor = UIColor.black 90 | playerBottomView.alpha = 0 91 | addSubview(playerBottomView) 92 | } 93 | 94 | 95 | fileprivate func initWithPlayButton() { 96 | 97 | playButton = AAPlayButton() 98 | playButton.addTarget(self, action: #selector(startPlay), for: .touchUpInside) 99 | addSubview(playButton) 100 | smallPlayButton = AAPlayButton() 101 | smallPlayButton.addTarget(self, action: #selector(startPlay), for: .touchUpInside) 102 | playerBottomView.addSubview(smallPlayButton) 103 | } 104 | 105 | fileprivate func initWithRotateButton() { 106 | 107 | rotateSizeButton = AAPlayerRotateButton() 108 | playerBottomView.addSubview(rotateSizeButton) 109 | 110 | } 111 | 112 | fileprivate func initWithPlayActivityIndicator() { 113 | 114 | playActivityIndicator = AAPlayerActivityIndicicatorView() 115 | addSubview(playActivityIndicator) 116 | 117 | } 118 | 119 | fileprivate func initWithPlayProgressView() { 120 | 121 | playProgressView = AAPlayProgressView(progressViewStyle: .bar) 122 | playProgressView.progressTintColor = UIColor(red: 102/255, green: 178/255, blue: 255/255, alpha: 0.5) 123 | playProgressView.trackTintColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.6) 124 | playProgressView.setProgress(0.0, animated: true) 125 | playerBottomView.addSubview(playProgressView) 126 | 127 | } 128 | 129 | fileprivate func initWithSlider() { 130 | 131 | playerSlider = AAPlayerSlider() 132 | playerSlider.tintColor = UIColor.clear 133 | playerSlider.backgroundColor = UIColor.clear 134 | playerSlider.maximumTrackTintColor = UIColor.clear 135 | playerSlider.minimumTrackTintColor = UIColor(red: 231/255, green: 107/255, blue: 107/255, alpha: 1) 136 | playerSlider.minimumValue = 0 137 | playerSlider.isContinuous = false 138 | playerSlider.addTarget(self, action: #selector(touchPlayerProgress), for: [.touchDown, .touchUpInside]) 139 | playerBottomView.addSubview(playerSlider) 140 | } 141 | 142 | fileprivate func initWithTimeLabel() { 143 | 144 | timeLabel = UILabel() 145 | timeLabel.textColor = UIColor.white 146 | timeLabel.font = UIFont.boldSystemFont(ofSize: 10) 147 | playerBottomView.addSubview(timeLabel) 148 | } 149 | 150 | //MARK:- frame method 151 | fileprivate func setPlayerSubviewsFrame() { 152 | 153 | playerBottomView.frame = CGRect(x: 0, y: frame.height - 50, width: frame.width, height: 50) 154 | playerLayer?.frame = bounds 155 | playButton.frame = CGRect(x: frame.width / 2 - 25, y: frame.height / 2 - 25, width: 50, height: 50) 156 | playButton.center = CGPoint(x: frame.width / 2 , y: frame.height / 2) 157 | playProgressView.frame = CGRect(x: 60, y: playerBottomView.frame.height / 2, width: playerBottomView.frame.width - 240, height: 2) 158 | playerSlider.frame = CGRect(x: 55, y: playerBottomView.frame.height / 2 - 9, width: playerBottomView.frame.width - 215, height: 20) 159 | smallPlayButton.frame = CGRect(x: 10, y: playerBottomView.frame.height / 2 - 11, width: 30, height: 25) 160 | timeLabel.frame = CGRect(x: playerBottomView.frame.width - 155, y: playerBottomView.frame.height / 2 - 9, width: 110, height: 20) 161 | playActivityIndicator.center = CGPoint(x: frame.width / 2, y: frame.height / 2) 162 | playActivityIndicator.frame.size = CGSize(width: 90, height: 90) 163 | rotateSizeButton.frame = CGRect(x: playerBottomView.frame.width - 45, y: 5, width:40, height: 40) 164 | 165 | } 166 | 167 | //MARL:- interface orientation 168 | fileprivate func detectedInterfaceOrientation() { 169 | 170 | switch UIDevice.current.orientation { 171 | case .portrait: 172 | rotateSizeButton.isSelected = false 173 | break 174 | case .landscapeRight: 175 | rotateSizeButton.isSelected = true 176 | break 177 | case .landscapeLeft: 178 | rotateSizeButton.isSelected = true 179 | break 180 | default: 181 | rotateSizeButton.isSelected = false 182 | break 183 | } 184 | } 185 | 186 | //MARK:- setting player 187 | fileprivate func setPlayRemoteUrl() { 188 | 189 | if playUrl == nil || playUrl == "" { 190 | return 191 | } 192 | removeAllObserver() 193 | resettingObject() 194 | let asset = AVAsset(url: URL(string: playUrl)!) 195 | playerItem = AVPlayerItem(asset: asset) 196 | player = AVPlayer(playerItem: playerItem) 197 | playerLayer = AVPlayerLayer(player: player) 198 | playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspect 199 | playerLayer?.contentsScale = UIScreen.main.scale 200 | layer.insertSublayer(playerLayer!, at: 0) 201 | setAllObserver() 202 | 203 | } 204 | 205 | //MARK:- setting observer 206 | fileprivate func setAllObserver() { 207 | 208 | player?.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil) 209 | playerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: NSKeyValueObservingOptions.new, context: nil) 210 | playerItem?.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil) 211 | 212 | } 213 | 214 | fileprivate func removeAllObserver() { 215 | 216 | player?.removeObserver(self, forKeyPath: "rate") 217 | playerItem?.removeObserver(self, forKeyPath: "loadedTimeRanges") 218 | playerItem?.removeObserver(self, forKeyPath: "status") 219 | 220 | } 221 | 222 | 223 | override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 224 | 225 | if keyPath == "status" { 226 | 227 | observePlayerStatus() 228 | 229 | } else if keyPath == "loadedTimeRanges" { 230 | 231 | let currentTime = getBufferTimeDuration() 232 | let totalTime = CMTimeGetSeconds((playerItem?.duration)!) 233 | let percent = currentTime / totalTime 234 | playProgressView.progress = Float(percent) 235 | 236 | } else if keyPath == "rate" { 237 | 238 | if (object as! AVPlayer).rate == 0 && Int(playerSlider.value) == Int(playerSlider.maximumValue) { 239 | smallPlayButton.isSelected = false 240 | setPlayBottomViewAnimation() 241 | } 242 | 243 | } 244 | } 245 | 246 | 247 | //MARK:- check playItem status 248 | fileprivate func observePlayerStatus() { 249 | 250 | let status:AVPlayerItemStatus = (player?.currentItem?.status)! 251 | switch status { 252 | case .readyToPlay: 253 | 254 | if Float(CMTimeGetSeconds((playerItem?.duration)!)).isNaN == true { break } 255 | playerSlider.addTarget(self, action: #selector(changePlayerProgress), for: .valueChanged) 256 | playerSlider.maximumValue = Float(CMTimeGetSeconds((playerItem?.duration)!)) 257 | let allTimeString = timeFotmatter(Float(CMTimeGetSeconds((playerItem?.duration)!))) 258 | playbackObserver = player?.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 1), queue: nil, using: { (time) in 259 | let during = self.playerItem!.currentTime() 260 | let time = during.value / Int64(during.timescale) 261 | self.timeLabel.text = "\(self.timeFotmatter(Float(time))) / \(allTimeString)" 262 | if !self.playerSlider.isHighlighted { 263 | self.playerSlider.value = Float(time) 264 | } 265 | }) 266 | 267 | break 268 | case .failed: 269 | 270 | break 271 | default: 272 | 273 | break 274 | } 275 | playActivityIndicator.stopAnimation() 276 | downloadDidFinish(status) 277 | } 278 | 279 | 280 | //MARK:- call back playItem status 281 | fileprivate func downloadDidFinish(_ status:AVPlayerItemStatus?) { 282 | 283 | delegate?.callBackDownloadDidFinish(status) 284 | } 285 | 286 | //MARK:- get buffer time duration 287 | fileprivate func getBufferTimeDuration() -> TimeInterval { 288 | 289 | let loadedTimeRanges = player!.currentItem!.loadedTimeRanges 290 | guard let timeRange = loadedTimeRanges.first?.timeRangeValue else { return 0.0 } 291 | let start = CMTimeGetSeconds(timeRange.start) 292 | let duration = CMTimeGetSeconds(timeRange.duration) 293 | let currentTimeDuration = (start + duration) 294 | return currentTimeDuration 295 | 296 | } 297 | 298 | //MARK:- calculate time formatter 299 | fileprivate func timeFotmatter(_ time:Float) -> String { 300 | 301 | var hr:Int! 302 | var min:Int! 303 | var sec:Int! 304 | var timeString:String! 305 | 306 | if time >= 3600 { 307 | hr = Int(time / 3600) 308 | min = Int(time.truncatingRemainder(dividingBy: 3600)) 309 | sec = Int(min % 60) 310 | timeString = String(format: "%02d:%02d:%02d", hr, min, sec) 311 | } else if time >= 60 && time < 3600 { 312 | min = Int(time / 60) 313 | sec = Int(time.truncatingRemainder(dividingBy: 60)) 314 | timeString = String(format: "00:%02d:%02d", min, sec) 315 | } else if time < 60 { 316 | sec = Int(time) 317 | timeString = String(format: "00:00:%02d", sec) 318 | } 319 | 320 | return timeString 321 | } 322 | 323 | //MARK:- setting player display 324 | @objc fileprivate func startPlay() { 325 | 326 | if playButton.isHidden == false { 327 | setPlayRemoteUrl() 328 | setPlayBottomViewAnimation() 329 | playActivityIndicator.startAnimation() 330 | } 331 | 332 | if player?.rate == 0 { 333 | player?.play() 334 | playButton.isSelected = true 335 | playButton.isHidden = true 336 | smallPlayButton.isSelected = true 337 | stopTimer() 338 | startTimer() 339 | 340 | } else { 341 | player?.pause() 342 | playButton.isSelected = false 343 | smallPlayButton.isSelected = false 344 | stopTimer() 345 | } 346 | 347 | } 348 | 349 | override public func touchesBegan(_ touches: Set, with event: UIEvent?) { 350 | 351 | setPlayBottomViewAnimation() 352 | stopTimer() 353 | if player?.rate == 1 && playerBottomView.alpha == 1 { 354 | startTimer() 355 | } 356 | } 357 | 358 | @objc fileprivate func setPlayBottomViewAnimation() { 359 | 360 | UIView.animate(withDuration: 0.5) { 361 | if self.playerBottomView.alpha == 0 { 362 | self.playerBottomView.alpha = 1 363 | } else { 364 | self.playerBottomView.alpha = 0 365 | } 366 | } 367 | 368 | } 369 | 370 | 371 | //MARK:- timer 372 | fileprivate func startTimer() { 373 | 374 | timer = Timer() 375 | timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(setPlayBottomViewAnimation), userInfo: nil, repeats: false) 376 | } 377 | 378 | fileprivate func stopTimer() { 379 | 380 | if timer == nil { 381 | return 382 | } 383 | timer?.invalidate() 384 | timer = nil 385 | } 386 | 387 | //MARK:- change player progress 388 | @objc fileprivate func changePlayerProgress() { 389 | 390 | playActivityIndicator.startAnimation() 391 | let seekDuration = playerSlider.value 392 | player?.seek(to: CMTimeMake(Int64(seekDuration), 1), completionHandler: { (BOOL) in 393 | self.playActivityIndicator.stopAnimation() 394 | }) 395 | 396 | } 397 | 398 | @objc fileprivate func touchPlayerProgress() { 399 | 400 | if playerSlider.isHighlighted { 401 | stopTimer() 402 | } else { 403 | startTimer() 404 | } 405 | } 406 | 407 | //MARK: - resetting display view 408 | fileprivate func resettingObject() { 409 | 410 | player = nil 411 | playerLayer = nil 412 | playbackObserver = nil 413 | playerItem = nil 414 | 415 | } 416 | 417 | //MARK: - public control method 418 | public func playVideo(_ url:String) { 419 | 420 | playUrl = url 421 | playButton.isHidden = false 422 | playButton.isSelected = false 423 | smallPlayButton.isSelected = false 424 | if playbackObserver != nil { 425 | player?.removeTimeObserver(playbackObserver!) 426 | playbackObserver = nil 427 | } 428 | if player?.rate == 1 { 429 | player?.pause() 430 | } 431 | playActivityIndicator.stopAnimation() 432 | playerLayer?.removeFromSuperlayer() 433 | playerSlider.removeTarget(self, action: #selector(changePlayerProgress), for: .valueChanged) 434 | playerSlider.value = 0.0 435 | playProgressView.progress = 0.0 436 | timeLabel.text = "00:00:00 / 00:00:00" 437 | } 438 | 439 | public func startPlayback() { 440 | 441 | player?.play() 442 | } 443 | 444 | public func pausePlayback() { 445 | 446 | player?.pause() 447 | } 448 | 449 | /* 450 | // Only override draw() if you perform custom drawing. 451 | // An empty implementation adversely affects performance during animation. 452 | override func draw(_ rect: CGRect) { 453 | // Drawing code 454 | } 455 | */ 456 | 457 | 458 | } 459 | 460 | 461 | extension AAPlayerDelegate { 462 | 463 | public func callBackDownloadDidFinish(_ status:AVPlayerItemStatus?) { 464 | 465 | 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /Sources/AAPlayerActivityIndicicatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayerActivityIndicicatorView.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/7/24. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AAPlayerActivityIndicicatorView: UIView { 12 | 13 | fileprivate var indicicatorLayer: CALayer! 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | 18 | initWithIndicicatorLayer() 19 | isHidden = true 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | super.init(coder: aDecoder) 24 | 25 | 26 | } 27 | override func layoutSubviews() { 28 | 29 | indicicatorLayer.frame = CGRect(x: 0,y: 0,width: frame.size.width,height: frame.size.height) 30 | indicicatorLayer.contents = createIndicicatorImage().cgImage 31 | } 32 | 33 | fileprivate func initWithIndicicatorLayer() { 34 | 35 | indicicatorLayer = CALayer() 36 | indicicatorLayer.masksToBounds = true 37 | layer.addSublayer(indicicatorLayer) 38 | 39 | } 40 | 41 | fileprivate func createIndicicatorImage() -> UIImage { 42 | 43 | UIGraphicsBeginImageContext(CGSize(width: frame.width, height: frame.height)) 44 | let context = UIGraphicsGetCurrentContext() 45 | let path:CGMutablePath = CGMutablePath() 46 | context!.addArc(center:CGPoint(x: frame.width / 2, y: frame.height / 2), radius: 40, startAngle: 0, endAngle: 1.5 * CGFloat(Double.pi), clockwise: true) 47 | context!.move(to: CGPoint(x: 50, y: 100)) 48 | context!.addLine(to: CGPoint(x: 50, y: 150)) 49 | context!.addLine(to: CGPoint(x: 100, y: 150)) 50 | context!.addPath(path) 51 | let colors = [UIColor(red: 231/255, green: 107/255, blue: 107/255, alpha: 0.6).cgColor,UIColor(red: 231/255, green: 107/255, blue: 107/255, alpha: 0.3).cgColor] 52 | let colorSpace = CGColorSpaceCreateDeviceRGB() 53 | let colorLocations:[CGFloat] = [0.6, 1.0] 54 | let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: colorLocations) 55 | context?.drawRadialGradient(gradient!, startCenter:CGPoint(x: frame.width / 2, y: frame.height / 2), startRadius: 0, endCenter: CGPoint(x: frame.width / 2 + 5, y: frame.height / 2 + 5), endRadius: 10, options: .drawsBeforeStartLocation) 56 | UIColor(red: 231/255, green: 107/255, blue: 107/255, alpha: 1).setStroke() 57 | context?.drawPath(using: .stroke) 58 | let image = UIGraphicsGetImageFromCurrentImageContext() 59 | UIGraphicsEndImageContext() 60 | return image! 61 | } 62 | 63 | fileprivate func setAnimation() -> CABasicAnimation { 64 | 65 | let rotation = CABasicAnimation(keyPath: "transform.rotation.z") 66 | rotation.duration = 1.0 67 | rotation.isRemovedOnCompletion = false 68 | rotation.repeatCount = Float.infinity 69 | rotation.fillMode = kCAFillModeForwards 70 | rotation.fromValue = 0.0 71 | rotation.toValue = Double.pi * 2; 72 | return rotation 73 | } 74 | 75 | fileprivate func pauseAnimation() { 76 | 77 | let pausedTime = indicicatorLayer.convertTime(CACurrentMediaTime(), from: nil) 78 | indicicatorLayer.speed = 0.0 79 | indicicatorLayer.timeOffset = pausedTime 80 | } 81 | 82 | fileprivate func resumeAnimation() { 83 | 84 | let pauseTime = indicicatorLayer.timeOffset 85 | indicicatorLayer.speed = 1.0 86 | indicicatorLayer.timeOffset = 0.0 87 | let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pauseTime 88 | indicicatorLayer.beginTime = timeSincePause 89 | } 90 | 91 | 92 | func startAnimation() { 93 | 94 | if indicicatorLayer.animation(forKey: "rotation") == nil { 95 | indicicatorLayer.add(setAnimation(), forKey: "rotation") 96 | } 97 | 98 | isHidden = false 99 | resumeAnimation() 100 | } 101 | 102 | func stopAnimation() { 103 | 104 | isHidden = true 105 | pauseAnimation() 106 | 107 | } 108 | 109 | /* 110 | // Only override draw() if you perform custom drawing. 111 | // An empty implementation adversely affects performance during animation. 112 | override func draw(_ rect: CGRect) { 113 | // Drawing code 114 | } 115 | */ 116 | 117 | } 118 | -------------------------------------------------------------------------------- /Sources/AAPlayerRotateButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayerRotateButton.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/7/25. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AAPlayerRotateButton: UIButton { 12 | 13 | // Only override draw() if you perform custom drawing. 14 | // An empty implementation adversely affects performance during animation. 15 | override func draw(_ rect: CGRect) { 16 | 17 | if isSelected { 18 | setOriginalSizeIconImage(rect) 19 | } else { 20 | setFullSizeIconImage(rect) 21 | } 22 | 23 | addTarget(self, action: #selector(setDisplaySize), for: .touchUpInside) 24 | } 25 | 26 | fileprivate func setFullSizeIconImage(_ rect: CGRect) { 27 | 28 | let rect = rect 29 | UIGraphicsBeginImageContext(rect.size) 30 | let context = UIGraphicsGetCurrentContext()! 31 | context.setStrokeColor(UIColor.white.cgColor) 32 | context.setLineWidth(2.0) 33 | context.move(to: CGPoint(x: rect.origin.x + 5 + rect.width - 20, y: rect.origin.y + 5)) 34 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + rect.width - 10, y: rect.origin.y + 5)) 35 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + rect.width - 10, y: rect.origin.y + 15)) 36 | context.move(to: CGPoint(x: rect.origin.x + 5 + rect.width - 10, y: rect.origin.y + 5)) 37 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 + 4, y: rect.origin.y + 5 + (rect.height - 10)/2 - 4)) 38 | context.move(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4)) 39 | context.addLine(to: CGPoint(x: rect.origin.x + 5, y: rect.origin.y + 5 + rect.height - 10)) 40 | context.addLine(to: CGPoint(x: rect.origin.x + 5, y: rect.origin.y + 5 + rect.height - 10 - 10)) 41 | context.move(to: CGPoint(x: rect.origin.x + 5, y: rect.origin.y + 5 + rect.height - 10)) 42 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + 10, y: rect.origin.y + 5 + rect.height - 10)) 43 | context.strokePath() 44 | let image = UIGraphicsGetImageFromCurrentImageContext() 45 | UIGraphicsEndImageContext() 46 | contentEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10) 47 | setImage(image, for: .normal) 48 | } 49 | 50 | fileprivate func setOriginalSizeIconImage(_ rect: CGRect) { 51 | 52 | let rect = rect 53 | UIGraphicsBeginImageContext(rect.size) 54 | let context = UIGraphicsGetCurrentContext()! 55 | context.setStrokeColor(UIColor.white.cgColor) 56 | context.setLineWidth(2.0) 57 | context.move(to: CGPoint(x: rect.origin.x + 5 + rect.width - 10, y: rect.origin.y + 5)) 58 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 + 4, y: rect.origin.y + 5 + (rect.height - 10)/2 - 4)) 59 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 + 4 + 10, y: rect.origin.y + 5 + (rect.height - 10)/2 - 4)) 60 | context.move(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 + 4, y: rect.origin.y + 5 + (rect.height - 10)/2 - 4)) 61 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 + 4, y: rect.origin.y + 5 + (rect.height - 10)/2 - 4 - 10)) 62 | context.move(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4)) 63 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4 - 10, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4)) 64 | context.move(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4)) 65 | context.addLine(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4 + 10)) 66 | context.move(to: CGPoint(x: rect.origin.x + 5 + (rect.width - 10)/2 - 4, y: rect.origin.y + 5 + (rect.height - 10)/2 + 4)) 67 | context.addLine(to: CGPoint(x: rect.origin.x + 5, y: rect.origin.y + 5 + rect.height - 10)) 68 | context.strokePath() 69 | let image = UIGraphicsGetImageFromCurrentImageContext() 70 | UIGraphicsEndImageContext() 71 | contentEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10) 72 | setImage(image, for: .selected) 73 | } 74 | 75 | //MARK:- setting display full size or original size 76 | @objc fileprivate func setDisplaySize() { 77 | 78 | if isSelected { 79 | 80 | isSelected = false 81 | let value = UIInterfaceOrientation.portrait.rawValue 82 | UIDevice.current.setValue(value, forKey: "orientation") 83 | UIViewController.attemptRotationToDeviceOrientation() 84 | 85 | } else { 86 | 87 | isSelected = true 88 | let value = UIInterfaceOrientation.landscapeRight.rawValue 89 | UIDevice.current.setValue(value, forKey: "orientation") 90 | UIViewController.attemptRotationToDeviceOrientation() 91 | 92 | } 93 | 94 | } 95 | 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /Sources/AAPlayerSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AAPlayerSlider.swift 3 | // AAPlayer 4 | // 5 | // Created by Alan on 2017/7/2. 6 | // Copyright © 2017年 Alan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AAPlayerSlider: UISlider { 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | setThumbImage() 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect { 23 | let rect = super.thumbRect(forBounds: bounds, trackRect: rect, value: value) 24 | return CGRect(x: rect.origin.x, y: rect.origin.y + 2, width: rect.width, height: rect.height) 25 | } 26 | 27 | fileprivate func setThumbImage() { 28 | 29 | UIGraphicsBeginImageContext(CGSize(width: 25, height: 25)) 30 | let context = UIGraphicsGetCurrentContext() 31 | context!.setFillColor(UIColor(red: 36/255, green: 153/255, blue: 145/255, alpha: 1).cgColor) 32 | context?.addEllipse(in: CGRect(x: 0, y: 0, width: 23, height: 23)) 33 | context!.drawPath(using: .fill) 34 | let image = UIGraphicsGetImageFromCurrentImageContext() 35 | UIGraphicsEndImageContext() 36 | setThumbImage(image, for: .normal) 37 | } 38 | 39 | 40 | /* 41 | // Only override draw() if you perform custom drawing. 42 | // An empty implementation adversely affects performance during animation. 43 | override func draw(_ rect: CGRect) { 44 | // Drawing code 45 | } 46 | */ 47 | 48 | } 49 | -------------------------------------------------------------------------------- /sampleImage/IBOutlet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alan881/AAPlayer/70318f67468517f90308289c6becc9b4e69a224a/sampleImage/IBOutlet.png -------------------------------------------------------------------------------- /sampleImage/customClass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alan881/AAPlayer/70318f67468517f90308289c6becc9b4e69a224a/sampleImage/customClass.png -------------------------------------------------------------------------------- /sampleImage/samplePlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alan881/AAPlayer/70318f67468517f90308289c6becc9b4e69a224a/sampleImage/samplePlay.gif --------------------------------------------------------------------------------