├── .gitignore ├── MHPlayer ├── MHPlayer.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── MHPlayer │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── MHPlayer │ │ ├── Adaptation │ │ │ └── MHTools.swift │ │ ├── Gesture │ │ │ └── MHGestureButton.swift │ │ ├── MHConfig.swift │ │ ├── MHPlayer.swift │ │ ├── MHPlayerBottomMenu.swift │ │ ├── MHPlayerSDK.swift │ │ ├── MHTopMenu.swift │ │ ├── Rotate │ │ │ └── MHDevice.swift │ │ └── icon │ │ │ ├── BackBtn.png │ │ │ ├── Next.png │ │ │ ├── exitFullScreen.png │ │ │ ├── fullScreen.png │ │ │ ├── pause.png │ │ │ ├── play.png │ │ │ └── progressDot.png │ └── ViewController.swift ├── MHPlayerTests │ ├── Info.plist │ └── MHPlayerTests.swift └── MHPlayerUITests │ ├── Info.plist │ └── MHPlayerUITests.swift └── README.md /.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 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A21F602A1DFF8CD400BAC589 /* next.png in Resources */ = {isa = PBXBuildFile; fileRef = A21F60291DFF8CD400BAC589 /* next.png */; }; 11 | A21F602C1DFF8CE900BAC589 /* fullScreen.png in Resources */ = {isa = PBXBuildFile; fileRef = A21F602B1DFF8CE900BAC589 /* fullScreen.png */; }; 12 | A21F602E1DFF8CF500BAC589 /* exitFullScreen.png in Resources */ = {isa = PBXBuildFile; fileRef = A21F602D1DFF8CF500BAC589 /* exitFullScreen.png */; }; 13 | A21F60301DFF8D0600BAC589 /* progressDot.png in Resources */ = {isa = PBXBuildFile; fileRef = A21F602F1DFF8D0600BAC589 /* progressDot.png */; }; 14 | A21F60331DFF8D3500BAC589 /* pause.png in Resources */ = {isa = PBXBuildFile; fileRef = A21F60311DFF8D3500BAC589 /* pause.png */; }; 15 | A21F60341DFF8D3500BAC589 /* play.png in Resources */ = {isa = PBXBuildFile; fileRef = A21F60321DFF8D3500BAC589 /* play.png */; }; 16 | A2AE30DE1DE80B1C009BD9DE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE30DD1DE80B1C009BD9DE /* AppDelegate.swift */; }; 17 | A2AE30E01DE80B1C009BD9DE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE30DF1DE80B1C009BD9DE /* ViewController.swift */; }; 18 | A2AE30E31DE80B1C009BD9DE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A2AE30E11DE80B1C009BD9DE /* Main.storyboard */; }; 19 | A2AE30E51DE80B1C009BD9DE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A2AE30E41DE80B1C009BD9DE /* Assets.xcassets */; }; 20 | A2AE30E81DE80B1C009BD9DE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A2AE30E61DE80B1C009BD9DE /* LaunchScreen.storyboard */; }; 21 | A2AE30F31DE80B1C009BD9DE /* MHPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE30F21DE80B1C009BD9DE /* MHPlayerTests.swift */; }; 22 | A2AE30FE1DE80B1C009BD9DE /* MHPlayerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE30FD1DE80B1C009BD9DE /* MHPlayerUITests.swift */; }; 23 | A2AE31111DE80D9A009BD9DE /* MHPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE31101DE80D9A009BD9DE /* MHPlayer.swift */; }; 24 | A2AE31131DE80DC0009BD9DE /* MHPlayerSDK.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE31121DE80DC0009BD9DE /* MHPlayerSDK.swift */; }; 25 | A2AE31151DE80DD7009BD9DE /* MHTopMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE31141DE80DD7009BD9DE /* MHTopMenu.swift */; }; 26 | A2AE31171DE80DF1009BD9DE /* MHPlayerBottomMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE31161DE80DF1009BD9DE /* MHPlayerBottomMenu.swift */; }; 27 | A2AE31321DE81458009BD9DE /* BackBtn.png in Resources */ = {isa = PBXBuildFile; fileRef = A2AE31301DE81458009BD9DE /* BackBtn.png */; }; 28 | A2AE313B1DE81853009BD9DE /* MHConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE313A1DE81853009BD9DE /* MHConfig.swift */; }; 29 | A2AE313D1DEBBF5B009BD9DE /* MHDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE313C1DEBBF5B009BD9DE /* MHDevice.swift */; }; 30 | A2AE31431DEBCE42009BD9DE /* MHGestureButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE31421DEBCE42009BD9DE /* MHGestureButton.swift */; }; 31 | A2AE31471DEC13CB009BD9DE /* MHTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AE31461DEC13CB009BD9DE /* MHTools.swift */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | A2AE30EF1DE80B1C009BD9DE /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = A2AE30D21DE80B1C009BD9DE /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = A2AE30D91DE80B1C009BD9DE; 40 | remoteInfo = MHPlayer; 41 | }; 42 | A2AE30FA1DE80B1C009BD9DE /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = A2AE30D21DE80B1C009BD9DE /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = A2AE30D91DE80B1C009BD9DE; 47 | remoteInfo = MHPlayer; 48 | }; 49 | /* End PBXContainerItemProxy section */ 50 | 51 | /* Begin PBXFileReference section */ 52 | A21F60291DFF8CD400BAC589 /* next.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = next.png; sourceTree = ""; }; 53 | A21F602B1DFF8CE900BAC589 /* fullScreen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = fullScreen.png; sourceTree = ""; }; 54 | A21F602D1DFF8CF500BAC589 /* exitFullScreen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = exitFullScreen.png; sourceTree = ""; }; 55 | A21F602F1DFF8D0600BAC589 /* progressDot.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = progressDot.png; sourceTree = ""; }; 56 | A21F60311DFF8D3500BAC589 /* pause.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pause.png; sourceTree = ""; }; 57 | A21F60321DFF8D3500BAC589 /* play.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = play.png; sourceTree = ""; }; 58 | A2AE30DA1DE80B1C009BD9DE /* MHPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MHPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | A2AE30DD1DE80B1C009BD9DE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 60 | A2AE30DF1DE80B1C009BD9DE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 61 | A2AE30E21DE80B1C009BD9DE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 62 | A2AE30E41DE80B1C009BD9DE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 63 | A2AE30E71DE80B1C009BD9DE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 64 | A2AE30E91DE80B1C009BD9DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | A2AE30EE1DE80B1C009BD9DE /* MHPlayerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MHPlayerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | A2AE30F21DE80B1C009BD9DE /* MHPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MHPlayerTests.swift; sourceTree = ""; }; 67 | A2AE30F41DE80B1C009BD9DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68 | A2AE30F91DE80B1C009BD9DE /* MHPlayerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MHPlayerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | A2AE30FD1DE80B1C009BD9DE /* MHPlayerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MHPlayerUITests.swift; sourceTree = ""; }; 70 | A2AE30FF1DE80B1C009BD9DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | A2AE31101DE80D9A009BD9DE /* MHPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MHPlayer.swift; sourceTree = ""; }; 72 | A2AE31121DE80DC0009BD9DE /* MHPlayerSDK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MHPlayerSDK.swift; sourceTree = ""; }; 73 | A2AE31141DE80DD7009BD9DE /* MHTopMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MHTopMenu.swift; sourceTree = ""; }; 74 | A2AE31161DE80DF1009BD9DE /* MHPlayerBottomMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MHPlayerBottomMenu.swift; sourceTree = ""; }; 75 | A2AE31301DE81458009BD9DE /* BackBtn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = BackBtn.png; sourceTree = ""; }; 76 | A2AE313A1DE81853009BD9DE /* MHConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MHConfig.swift; sourceTree = ""; }; 77 | A2AE313C1DEBBF5B009BD9DE /* MHDevice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MHDevice.swift; sourceTree = ""; }; 78 | A2AE31421DEBCE42009BD9DE /* MHGestureButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MHGestureButton.swift; sourceTree = ""; }; 79 | A2AE31461DEC13CB009BD9DE /* MHTools.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MHTools.swift; sourceTree = ""; }; 80 | /* End PBXFileReference section */ 81 | 82 | /* Begin PBXFrameworksBuildPhase section */ 83 | A2AE30D71DE80B1C009BD9DE /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | A2AE30EB1DE80B1C009BD9DE /* Frameworks */ = { 91 | isa = PBXFrameworksBuildPhase; 92 | buildActionMask = 2147483647; 93 | files = ( 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | A2AE30F61DE80B1C009BD9DE /* Frameworks */ = { 98 | isa = PBXFrameworksBuildPhase; 99 | buildActionMask = 2147483647; 100 | files = ( 101 | ); 102 | runOnlyForDeploymentPostprocessing = 0; 103 | }; 104 | /* End PBXFrameworksBuildPhase section */ 105 | 106 | /* Begin PBXGroup section */ 107 | A2AE30D11DE80B1C009BD9DE = { 108 | isa = PBXGroup; 109 | children = ( 110 | A2AE30DC1DE80B1C009BD9DE /* MHPlayer */, 111 | A2AE30F11DE80B1C009BD9DE /* MHPlayerTests */, 112 | A2AE30FC1DE80B1C009BD9DE /* MHPlayerUITests */, 113 | A2AE30DB1DE80B1C009BD9DE /* Products */, 114 | ); 115 | sourceTree = ""; 116 | }; 117 | A2AE30DB1DE80B1C009BD9DE /* Products */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | A2AE30DA1DE80B1C009BD9DE /* MHPlayer.app */, 121 | A2AE30EE1DE80B1C009BD9DE /* MHPlayerTests.xctest */, 122 | A2AE30F91DE80B1C009BD9DE /* MHPlayerUITests.xctest */, 123 | ); 124 | name = Products; 125 | sourceTree = ""; 126 | }; 127 | A2AE30DC1DE80B1C009BD9DE /* MHPlayer */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | A2AE310B1DE80D8E009BD9DE /* MHPlayer */, 131 | A2AE30DD1DE80B1C009BD9DE /* AppDelegate.swift */, 132 | A2AE30DF1DE80B1C009BD9DE /* ViewController.swift */, 133 | A2AE30E11DE80B1C009BD9DE /* Main.storyboard */, 134 | A2AE30E41DE80B1C009BD9DE /* Assets.xcassets */, 135 | A2AE30E61DE80B1C009BD9DE /* LaunchScreen.storyboard */, 136 | A2AE30E91DE80B1C009BD9DE /* Info.plist */, 137 | ); 138 | path = MHPlayer; 139 | sourceTree = ""; 140 | }; 141 | A2AE30F11DE80B1C009BD9DE /* MHPlayerTests */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | A2AE30F21DE80B1C009BD9DE /* MHPlayerTests.swift */, 145 | A2AE30F41DE80B1C009BD9DE /* Info.plist */, 146 | ); 147 | path = MHPlayerTests; 148 | sourceTree = ""; 149 | }; 150 | A2AE30FC1DE80B1C009BD9DE /* MHPlayerUITests */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | A2AE30FD1DE80B1C009BD9DE /* MHPlayerUITests.swift */, 154 | A2AE30FF1DE80B1C009BD9DE /* Info.plist */, 155 | ); 156 | path = MHPlayerUITests; 157 | sourceTree = ""; 158 | }; 159 | A2AE310B1DE80D8E009BD9DE /* MHPlayer */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | A2AE31101DE80D9A009BD9DE /* MHPlayer.swift */, 163 | A2AE31121DE80DC0009BD9DE /* MHPlayerSDK.swift */, 164 | A2AE31141DE80DD7009BD9DE /* MHTopMenu.swift */, 165 | A2AE31161DE80DF1009BD9DE /* MHPlayerBottomMenu.swift */, 166 | A2AE313A1DE81853009BD9DE /* MHConfig.swift */, 167 | A2AE310C1DE80D8E009BD9DE /* Adaptation */, 168 | A2AE310D1DE80D8E009BD9DE /* Gesture */, 169 | A2AE310E1DE80D8E009BD9DE /* icon */, 170 | A2AE310F1DE80D8E009BD9DE /* Rotate */, 171 | ); 172 | path = MHPlayer; 173 | sourceTree = ""; 174 | }; 175 | A2AE310C1DE80D8E009BD9DE /* Adaptation */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | A2AE31461DEC13CB009BD9DE /* MHTools.swift */, 179 | ); 180 | path = Adaptation; 181 | sourceTree = ""; 182 | }; 183 | A2AE310D1DE80D8E009BD9DE /* Gesture */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | A2AE31421DEBCE42009BD9DE /* MHGestureButton.swift */, 187 | ); 188 | path = Gesture; 189 | sourceTree = ""; 190 | }; 191 | A2AE310E1DE80D8E009BD9DE /* icon */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | A21F60291DFF8CD400BAC589 /* next.png */, 195 | A21F602B1DFF8CE900BAC589 /* fullScreen.png */, 196 | A21F602D1DFF8CF500BAC589 /* exitFullScreen.png */, 197 | A21F602F1DFF8D0600BAC589 /* progressDot.png */, 198 | A2AE31301DE81458009BD9DE /* BackBtn.png */, 199 | A21F60311DFF8D3500BAC589 /* pause.png */, 200 | A21F60321DFF8D3500BAC589 /* play.png */, 201 | ); 202 | path = icon; 203 | sourceTree = ""; 204 | }; 205 | A2AE310F1DE80D8E009BD9DE /* Rotate */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | A2AE313C1DEBBF5B009BD9DE /* MHDevice.swift */, 209 | ); 210 | path = Rotate; 211 | sourceTree = ""; 212 | }; 213 | /* End PBXGroup section */ 214 | 215 | /* Begin PBXNativeTarget section */ 216 | A2AE30D91DE80B1C009BD9DE /* MHPlayer */ = { 217 | isa = PBXNativeTarget; 218 | buildConfigurationList = A2AE31021DE80B1C009BD9DE /* Build configuration list for PBXNativeTarget "MHPlayer" */; 219 | buildPhases = ( 220 | A2AE30D61DE80B1C009BD9DE /* Sources */, 221 | A2AE30D71DE80B1C009BD9DE /* Frameworks */, 222 | A2AE30D81DE80B1C009BD9DE /* Resources */, 223 | ); 224 | buildRules = ( 225 | ); 226 | dependencies = ( 227 | ); 228 | name = MHPlayer; 229 | productName = MHPlayer; 230 | productReference = A2AE30DA1DE80B1C009BD9DE /* MHPlayer.app */; 231 | productType = "com.apple.product-type.application"; 232 | }; 233 | A2AE30ED1DE80B1C009BD9DE /* MHPlayerTests */ = { 234 | isa = PBXNativeTarget; 235 | buildConfigurationList = A2AE31051DE80B1C009BD9DE /* Build configuration list for PBXNativeTarget "MHPlayerTests" */; 236 | buildPhases = ( 237 | A2AE30EA1DE80B1C009BD9DE /* Sources */, 238 | A2AE30EB1DE80B1C009BD9DE /* Frameworks */, 239 | A2AE30EC1DE80B1C009BD9DE /* Resources */, 240 | ); 241 | buildRules = ( 242 | ); 243 | dependencies = ( 244 | A2AE30F01DE80B1C009BD9DE /* PBXTargetDependency */, 245 | ); 246 | name = MHPlayerTests; 247 | productName = MHPlayerTests; 248 | productReference = A2AE30EE1DE80B1C009BD9DE /* MHPlayerTests.xctest */; 249 | productType = "com.apple.product-type.bundle.unit-test"; 250 | }; 251 | A2AE30F81DE80B1C009BD9DE /* MHPlayerUITests */ = { 252 | isa = PBXNativeTarget; 253 | buildConfigurationList = A2AE31081DE80B1C009BD9DE /* Build configuration list for PBXNativeTarget "MHPlayerUITests" */; 254 | buildPhases = ( 255 | A2AE30F51DE80B1C009BD9DE /* Sources */, 256 | A2AE30F61DE80B1C009BD9DE /* Frameworks */, 257 | A2AE30F71DE80B1C009BD9DE /* Resources */, 258 | ); 259 | buildRules = ( 260 | ); 261 | dependencies = ( 262 | A2AE30FB1DE80B1C009BD9DE /* PBXTargetDependency */, 263 | ); 264 | name = MHPlayerUITests; 265 | productName = MHPlayerUITests; 266 | productReference = A2AE30F91DE80B1C009BD9DE /* MHPlayerUITests.xctest */; 267 | productType = "com.apple.product-type.bundle.ui-testing"; 268 | }; 269 | /* End PBXNativeTarget section */ 270 | 271 | /* Begin PBXProject section */ 272 | A2AE30D21DE80B1C009BD9DE /* Project object */ = { 273 | isa = PBXProject; 274 | attributes = { 275 | LastSwiftUpdateCheck = 0800; 276 | LastUpgradeCheck = 0820; 277 | ORGANIZATIONNAME = CMCC; 278 | TargetAttributes = { 279 | A2AE30D91DE80B1C009BD9DE = { 280 | CreatedOnToolsVersion = 8.0; 281 | ProvisioningStyle = Automatic; 282 | }; 283 | A2AE30ED1DE80B1C009BD9DE = { 284 | CreatedOnToolsVersion = 8.0; 285 | ProvisioningStyle = Automatic; 286 | TestTargetID = A2AE30D91DE80B1C009BD9DE; 287 | }; 288 | A2AE30F81DE80B1C009BD9DE = { 289 | CreatedOnToolsVersion = 8.0; 290 | ProvisioningStyle = Automatic; 291 | TestTargetID = A2AE30D91DE80B1C009BD9DE; 292 | }; 293 | }; 294 | }; 295 | buildConfigurationList = A2AE30D51DE80B1C009BD9DE /* Build configuration list for PBXProject "MHPlayer" */; 296 | compatibilityVersion = "Xcode 3.2"; 297 | developmentRegion = English; 298 | hasScannedForEncodings = 0; 299 | knownRegions = ( 300 | en, 301 | Base, 302 | ); 303 | mainGroup = A2AE30D11DE80B1C009BD9DE; 304 | productRefGroup = A2AE30DB1DE80B1C009BD9DE /* Products */; 305 | projectDirPath = ""; 306 | projectRoot = ""; 307 | targets = ( 308 | A2AE30D91DE80B1C009BD9DE /* MHPlayer */, 309 | A2AE30ED1DE80B1C009BD9DE /* MHPlayerTests */, 310 | A2AE30F81DE80B1C009BD9DE /* MHPlayerUITests */, 311 | ); 312 | }; 313 | /* End PBXProject section */ 314 | 315 | /* Begin PBXResourcesBuildPhase section */ 316 | A2AE30D81DE80B1C009BD9DE /* Resources */ = { 317 | isa = PBXResourcesBuildPhase; 318 | buildActionMask = 2147483647; 319 | files = ( 320 | A21F602E1DFF8CF500BAC589 /* exitFullScreen.png in Resources */, 321 | A21F60341DFF8D3500BAC589 /* play.png in Resources */, 322 | A21F602C1DFF8CE900BAC589 /* fullScreen.png in Resources */, 323 | A2AE30E81DE80B1C009BD9DE /* LaunchScreen.storyboard in Resources */, 324 | A2AE31321DE81458009BD9DE /* BackBtn.png in Resources */, 325 | A21F60301DFF8D0600BAC589 /* progressDot.png in Resources */, 326 | A21F60331DFF8D3500BAC589 /* pause.png in Resources */, 327 | A21F602A1DFF8CD400BAC589 /* next.png in Resources */, 328 | A2AE30E51DE80B1C009BD9DE /* Assets.xcassets in Resources */, 329 | A2AE30E31DE80B1C009BD9DE /* Main.storyboard in Resources */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | A2AE30EC1DE80B1C009BD9DE /* Resources */ = { 334 | isa = PBXResourcesBuildPhase; 335 | buildActionMask = 2147483647; 336 | files = ( 337 | ); 338 | runOnlyForDeploymentPostprocessing = 0; 339 | }; 340 | A2AE30F71DE80B1C009BD9DE /* Resources */ = { 341 | isa = PBXResourcesBuildPhase; 342 | buildActionMask = 2147483647; 343 | files = ( 344 | ); 345 | runOnlyForDeploymentPostprocessing = 0; 346 | }; 347 | /* End PBXResourcesBuildPhase section */ 348 | 349 | /* Begin PBXSourcesBuildPhase section */ 350 | A2AE30D61DE80B1C009BD9DE /* Sources */ = { 351 | isa = PBXSourcesBuildPhase; 352 | buildActionMask = 2147483647; 353 | files = ( 354 | A2AE31471DEC13CB009BD9DE /* MHTools.swift in Sources */, 355 | A2AE31131DE80DC0009BD9DE /* MHPlayerSDK.swift in Sources */, 356 | A2AE31171DE80DF1009BD9DE /* MHPlayerBottomMenu.swift in Sources */, 357 | A2AE31151DE80DD7009BD9DE /* MHTopMenu.swift in Sources */, 358 | A2AE31431DEBCE42009BD9DE /* MHGestureButton.swift in Sources */, 359 | A2AE31111DE80D9A009BD9DE /* MHPlayer.swift in Sources */, 360 | A2AE30E01DE80B1C009BD9DE /* ViewController.swift in Sources */, 361 | A2AE30DE1DE80B1C009BD9DE /* AppDelegate.swift in Sources */, 362 | A2AE313D1DEBBF5B009BD9DE /* MHDevice.swift in Sources */, 363 | A2AE313B1DE81853009BD9DE /* MHConfig.swift in Sources */, 364 | ); 365 | runOnlyForDeploymentPostprocessing = 0; 366 | }; 367 | A2AE30EA1DE80B1C009BD9DE /* Sources */ = { 368 | isa = PBXSourcesBuildPhase; 369 | buildActionMask = 2147483647; 370 | files = ( 371 | A2AE30F31DE80B1C009BD9DE /* MHPlayerTests.swift in Sources */, 372 | ); 373 | runOnlyForDeploymentPostprocessing = 0; 374 | }; 375 | A2AE30F51DE80B1C009BD9DE /* Sources */ = { 376 | isa = PBXSourcesBuildPhase; 377 | buildActionMask = 2147483647; 378 | files = ( 379 | A2AE30FE1DE80B1C009BD9DE /* MHPlayerUITests.swift in Sources */, 380 | ); 381 | runOnlyForDeploymentPostprocessing = 0; 382 | }; 383 | /* End PBXSourcesBuildPhase section */ 384 | 385 | /* Begin PBXTargetDependency section */ 386 | A2AE30F01DE80B1C009BD9DE /* PBXTargetDependency */ = { 387 | isa = PBXTargetDependency; 388 | target = A2AE30D91DE80B1C009BD9DE /* MHPlayer */; 389 | targetProxy = A2AE30EF1DE80B1C009BD9DE /* PBXContainerItemProxy */; 390 | }; 391 | A2AE30FB1DE80B1C009BD9DE /* PBXTargetDependency */ = { 392 | isa = PBXTargetDependency; 393 | target = A2AE30D91DE80B1C009BD9DE /* MHPlayer */; 394 | targetProxy = A2AE30FA1DE80B1C009BD9DE /* PBXContainerItemProxy */; 395 | }; 396 | /* End PBXTargetDependency section */ 397 | 398 | /* Begin PBXVariantGroup section */ 399 | A2AE30E11DE80B1C009BD9DE /* Main.storyboard */ = { 400 | isa = PBXVariantGroup; 401 | children = ( 402 | A2AE30E21DE80B1C009BD9DE /* Base */, 403 | ); 404 | name = Main.storyboard; 405 | sourceTree = ""; 406 | }; 407 | A2AE30E61DE80B1C009BD9DE /* LaunchScreen.storyboard */ = { 408 | isa = PBXVariantGroup; 409 | children = ( 410 | A2AE30E71DE80B1C009BD9DE /* Base */, 411 | ); 412 | name = LaunchScreen.storyboard; 413 | sourceTree = ""; 414 | }; 415 | /* End PBXVariantGroup section */ 416 | 417 | /* Begin XCBuildConfiguration section */ 418 | A2AE31001DE80B1C009BD9DE /* Debug */ = { 419 | isa = XCBuildConfiguration; 420 | buildSettings = { 421 | ALWAYS_SEARCH_USER_PATHS = NO; 422 | CLANG_ANALYZER_NONNULL = YES; 423 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 424 | CLANG_CXX_LIBRARY = "libc++"; 425 | CLANG_ENABLE_MODULES = YES; 426 | CLANG_ENABLE_OBJC_ARC = YES; 427 | CLANG_WARN_BOOL_CONVERSION = YES; 428 | CLANG_WARN_CONSTANT_CONVERSION = YES; 429 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 430 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 431 | CLANG_WARN_EMPTY_BODY = YES; 432 | CLANG_WARN_ENUM_CONVERSION = YES; 433 | CLANG_WARN_INFINITE_RECURSION = YES; 434 | CLANG_WARN_INT_CONVERSION = YES; 435 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 436 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 437 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 438 | CLANG_WARN_UNREACHABLE_CODE = YES; 439 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 440 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 441 | COPY_PHASE_STRIP = NO; 442 | DEBUG_INFORMATION_FORMAT = dwarf; 443 | ENABLE_STRICT_OBJC_MSGSEND = YES; 444 | ENABLE_TESTABILITY = YES; 445 | GCC_C_LANGUAGE_STANDARD = gnu99; 446 | GCC_DYNAMIC_NO_PIC = NO; 447 | GCC_NO_COMMON_BLOCKS = YES; 448 | GCC_OPTIMIZATION_LEVEL = 0; 449 | GCC_PREPROCESSOR_DEFINITIONS = ( 450 | "DEBUG=1", 451 | "$(inherited)", 452 | ); 453 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 454 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 455 | GCC_WARN_UNDECLARED_SELECTOR = YES; 456 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 457 | GCC_WARN_UNUSED_FUNCTION = YES; 458 | GCC_WARN_UNUSED_VARIABLE = YES; 459 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 460 | MTL_ENABLE_DEBUG_INFO = YES; 461 | ONLY_ACTIVE_ARCH = YES; 462 | SDKROOT = iphoneos; 463 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 464 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 465 | }; 466 | name = Debug; 467 | }; 468 | A2AE31011DE80B1C009BD9DE /* Release */ = { 469 | isa = XCBuildConfiguration; 470 | buildSettings = { 471 | ALWAYS_SEARCH_USER_PATHS = NO; 472 | CLANG_ANALYZER_NONNULL = YES; 473 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 474 | CLANG_CXX_LIBRARY = "libc++"; 475 | CLANG_ENABLE_MODULES = YES; 476 | CLANG_ENABLE_OBJC_ARC = YES; 477 | CLANG_WARN_BOOL_CONVERSION = YES; 478 | CLANG_WARN_CONSTANT_CONVERSION = YES; 479 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 480 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 481 | CLANG_WARN_EMPTY_BODY = YES; 482 | CLANG_WARN_ENUM_CONVERSION = YES; 483 | CLANG_WARN_INFINITE_RECURSION = YES; 484 | CLANG_WARN_INT_CONVERSION = YES; 485 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 486 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 487 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 488 | CLANG_WARN_UNREACHABLE_CODE = YES; 489 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 490 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 491 | COPY_PHASE_STRIP = NO; 492 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 493 | ENABLE_NS_ASSERTIONS = NO; 494 | ENABLE_STRICT_OBJC_MSGSEND = YES; 495 | GCC_C_LANGUAGE_STANDARD = gnu99; 496 | GCC_NO_COMMON_BLOCKS = YES; 497 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 498 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 499 | GCC_WARN_UNDECLARED_SELECTOR = YES; 500 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 501 | GCC_WARN_UNUSED_FUNCTION = YES; 502 | GCC_WARN_UNUSED_VARIABLE = YES; 503 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 504 | MTL_ENABLE_DEBUG_INFO = NO; 505 | SDKROOT = iphoneos; 506 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 507 | VALIDATE_PRODUCT = YES; 508 | }; 509 | name = Release; 510 | }; 511 | A2AE31031DE80B1C009BD9DE /* Debug */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 515 | INFOPLIST_FILE = MHPlayer/Info.plist; 516 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 517 | PRODUCT_BUNDLE_IDENTIFIER = CMCC.com.MHPlayer; 518 | PRODUCT_NAME = "$(TARGET_NAME)"; 519 | SWIFT_VERSION = 3.0; 520 | }; 521 | name = Debug; 522 | }; 523 | A2AE31041DE80B1C009BD9DE /* Release */ = { 524 | isa = XCBuildConfiguration; 525 | buildSettings = { 526 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 527 | INFOPLIST_FILE = MHPlayer/Info.plist; 528 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 529 | PRODUCT_BUNDLE_IDENTIFIER = CMCC.com.MHPlayer; 530 | PRODUCT_NAME = "$(TARGET_NAME)"; 531 | SWIFT_VERSION = 3.0; 532 | }; 533 | name = Release; 534 | }; 535 | A2AE31061DE80B1C009BD9DE /* Debug */ = { 536 | isa = XCBuildConfiguration; 537 | buildSettings = { 538 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 539 | BUNDLE_LOADER = "$(TEST_HOST)"; 540 | INFOPLIST_FILE = MHPlayerTests/Info.plist; 541 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 542 | PRODUCT_BUNDLE_IDENTIFIER = CMCC.com.MHPlayerTests; 543 | PRODUCT_NAME = "$(TARGET_NAME)"; 544 | SWIFT_VERSION = 3.0; 545 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MHPlayer.app/MHPlayer"; 546 | }; 547 | name = Debug; 548 | }; 549 | A2AE31071DE80B1C009BD9DE /* Release */ = { 550 | isa = XCBuildConfiguration; 551 | buildSettings = { 552 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 553 | BUNDLE_LOADER = "$(TEST_HOST)"; 554 | INFOPLIST_FILE = MHPlayerTests/Info.plist; 555 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 556 | PRODUCT_BUNDLE_IDENTIFIER = CMCC.com.MHPlayerTests; 557 | PRODUCT_NAME = "$(TARGET_NAME)"; 558 | SWIFT_VERSION = 3.0; 559 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MHPlayer.app/MHPlayer"; 560 | }; 561 | name = Release; 562 | }; 563 | A2AE31091DE80B1C009BD9DE /* Debug */ = { 564 | isa = XCBuildConfiguration; 565 | buildSettings = { 566 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 567 | INFOPLIST_FILE = MHPlayerUITests/Info.plist; 568 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 569 | PRODUCT_BUNDLE_IDENTIFIER = CMCC.com.MHPlayerUITests; 570 | PRODUCT_NAME = "$(TARGET_NAME)"; 571 | SWIFT_VERSION = 3.0; 572 | TEST_TARGET_NAME = MHPlayer; 573 | }; 574 | name = Debug; 575 | }; 576 | A2AE310A1DE80B1C009BD9DE /* Release */ = { 577 | isa = XCBuildConfiguration; 578 | buildSettings = { 579 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 580 | INFOPLIST_FILE = MHPlayerUITests/Info.plist; 581 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 582 | PRODUCT_BUNDLE_IDENTIFIER = CMCC.com.MHPlayerUITests; 583 | PRODUCT_NAME = "$(TARGET_NAME)"; 584 | SWIFT_VERSION = 3.0; 585 | TEST_TARGET_NAME = MHPlayer; 586 | }; 587 | name = Release; 588 | }; 589 | /* End XCBuildConfiguration section */ 590 | 591 | /* Begin XCConfigurationList section */ 592 | A2AE30D51DE80B1C009BD9DE /* Build configuration list for PBXProject "MHPlayer" */ = { 593 | isa = XCConfigurationList; 594 | buildConfigurations = ( 595 | A2AE31001DE80B1C009BD9DE /* Debug */, 596 | A2AE31011DE80B1C009BD9DE /* Release */, 597 | ); 598 | defaultConfigurationIsVisible = 0; 599 | defaultConfigurationName = Release; 600 | }; 601 | A2AE31021DE80B1C009BD9DE /* Build configuration list for PBXNativeTarget "MHPlayer" */ = { 602 | isa = XCConfigurationList; 603 | buildConfigurations = ( 604 | A2AE31031DE80B1C009BD9DE /* Debug */, 605 | A2AE31041DE80B1C009BD9DE /* Release */, 606 | ); 607 | defaultConfigurationIsVisible = 0; 608 | defaultConfigurationName = Release; 609 | }; 610 | A2AE31051DE80B1C009BD9DE /* Build configuration list for PBXNativeTarget "MHPlayerTests" */ = { 611 | isa = XCConfigurationList; 612 | buildConfigurations = ( 613 | A2AE31061DE80B1C009BD9DE /* Debug */, 614 | A2AE31071DE80B1C009BD9DE /* Release */, 615 | ); 616 | defaultConfigurationIsVisible = 0; 617 | defaultConfigurationName = Release; 618 | }; 619 | A2AE31081DE80B1C009BD9DE /* Build configuration list for PBXNativeTarget "MHPlayerUITests" */ = { 620 | isa = XCConfigurationList; 621 | buildConfigurations = ( 622 | A2AE31091DE80B1C009BD9DE /* Debug */, 623 | A2AE310A1DE80B1C009BD9DE /* Release */, 624 | ); 625 | defaultConfigurationIsVisible = 0; 626 | defaultConfigurationName = Release; 627 | }; 628 | /* End XCConfigurationList section */ 629 | }; 630 | rootObject = A2AE30D21DE80B1C009BD9DE /* Project object */; 631 | } 632 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MHPlayer 4 | // 5 | // Created by kidd on 16/11/25. 6 | // Copyright © 2016年 CMCC. 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 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/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 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/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 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/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 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleVersion 25 | 1 26 | LSRequiresIPhoneOS 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 | 43 | 44 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/Adaptation/MHTools.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MHTools.swift 3 | // MHPlayer 4 | // 5 | // Created by kidd on 16/11/28. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// MARK - UIView 12 | extension UIView { 13 | 14 | // MARK: - 常用位置属性 15 | 16 | public var left:CGFloat { 17 | get { 18 | return self.frame.origin.x 19 | } 20 | set(newLeft) { 21 | var frame = self.frame 22 | frame.origin.x = newLeft 23 | self.frame = frame 24 | } 25 | } 26 | 27 | public var top:CGFloat { 28 | get { 29 | return self.frame.origin.y 30 | } 31 | 32 | set(newTop) { 33 | var frame = self.frame 34 | frame.origin.y = newTop 35 | self.frame = frame 36 | } 37 | } 38 | 39 | public var width:CGFloat { 40 | get { 41 | return self.frame.size.width 42 | } 43 | 44 | set(newWidth) { 45 | var frame = self.frame 46 | frame.size.width = newWidth 47 | self.frame = frame 48 | } 49 | } 50 | 51 | public var height:CGFloat { 52 | get { 53 | return self.frame.size.height 54 | } 55 | 56 | set(newHeight) { 57 | var frame = self.frame 58 | frame.size.height = newHeight 59 | self.frame = frame 60 | } 61 | } 62 | 63 | public var right:CGFloat { 64 | get { 65 | return self.left + self.width 66 | } 67 | } 68 | 69 | public var bottom:CGFloat { 70 | get { 71 | return self.top + self.height 72 | } 73 | } 74 | 75 | public var centerX:CGFloat { 76 | get { 77 | return self.center.x 78 | } 79 | 80 | set(newCenterX) { 81 | var center = self.center 82 | center.x = newCenterX 83 | self.center = center 84 | } 85 | } 86 | 87 | public var centerY:CGFloat { 88 | get { 89 | return self.center.y 90 | } 91 | 92 | set(newCenterY) { 93 | var center = self.center 94 | center.y = newCenterY 95 | self.center = center 96 | } 97 | } 98 | 99 | } 100 | 101 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/Gesture/MHGestureButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MHGestureButton.swift 3 | // MHPlayer 4 | // 5 | // Created by kidd on 16/11/28. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MediaPlayer 11 | 12 | enum Direction { 13 | 14 | case leftOrRight, upOrDown, none 15 | } 16 | 17 | class MHGestureButton: UIButton { 18 | 19 | /// 单击时/双击时,判断tap的numberOfTapsRequired 20 | var userTapGestureBlock: ((_ number: NSInteger, _ flag: Bool) -> Void)? 21 | 22 | /// 开始触摸 23 | var touchesBeganWithPointBlock: (() -> CGFloat)? 24 | 25 | /// 结束触摸 26 | var touchesEndWithPointBlock: ((_ rate: CGFloat) -> Void)? 27 | 28 | fileprivate var isGesHidden: Bool? 29 | 30 | /// 上下左右手势操作 31 | fileprivate var direction: Direction? 32 | 33 | /// 手势触摸起始位置 34 | fileprivate var startPoint: CGPoint? 35 | 36 | /// 记录当前音量/亮度 37 | fileprivate var startVB: CGFloat? 38 | 39 | /// 控制音量的view 40 | fileprivate lazy var volumeView: MPVolumeView = { [weak self] in 41 | 42 | let volumeView = MPVolumeView() 43 | volumeView.sizeToFit() 44 | for subview in volumeView.subviews { 45 | if subview.self.classForCoder.description() == "MPVolumeSlider" { 46 | self?.volumeViewSlider = subview as? UISlider 47 | break 48 | } 49 | } 50 | return volumeView 51 | }() 52 | 53 | /// 控制音量 54 | fileprivate var volumeViewSlider: UISlider? 55 | 56 | /// 开始进度 57 | fileprivate var startVideoRate: CGFloat? 58 | 59 | /// 当期视频播放的进度 60 | fileprivate var currentRate: CGFloat? 61 | 62 | 63 | override init(frame: CGRect) { 64 | 65 | super.init(frame: frame) 66 | addGestureAction() 67 | isGesHidden = true 68 | } 69 | 70 | required init?(coder aDecoder: NSCoder) { 71 | fatalError("init(coder:) has not been implemented") 72 | } 73 | 74 | override func layoutSubviews() { 75 | super.layoutSubviews() 76 | 77 | volumeView.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.width * 9.0 / 16.0); 78 | } 79 | 80 | private func addGestureAction() { 81 | 82 | let mhTapGesture = UITapGestureRecognizer(target: self, action: #selector(userTapGestureAction(_:))) 83 | 84 | mhTapGesture.numberOfTapsRequired = 1 85 | mhTapGesture.delegate = self 86 | addGestureRecognizer(mhTapGesture) 87 | 88 | let mhTwoTapGesture = UITapGestureRecognizer(target: self, action: #selector(userTapGestureAction(_:))) 89 | mhTwoTapGesture.numberOfTapsRequired = 2 90 | mhTwoTapGesture.delegate = self 91 | addGestureRecognizer(mhTwoTapGesture) 92 | 93 | //没有检测到双击才进行单击事件 94 | mhTapGesture.require(toFail: mhTwoTapGesture) 95 | } 96 | } 97 | 98 | 99 | // MARK: - 手势事件 100 | extension MHGestureButton { 101 | 102 | @objc fileprivate func userTapGestureAction(_ tap: UITapGestureRecognizer) { 103 | 104 | if tap.numberOfTapsRequired == 1 { 105 | isGesHidden = !isGesHidden! 106 | } 107 | if userTapGestureBlock != nil { 108 | userTapGestureBlock!(tap.numberOfTapsRequired, isGesHidden!) 109 | } 110 | } 111 | 112 | 113 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 114 | 115 | //获取触摸开始的坐标 116 | let touch = touches.first 117 | let point = touch?.location(in: self) 118 | 119 | //记录首次触摸坐标 120 | startPoint = point 121 | //检测用户是触摸屏幕的左边还是右边,以此判断用户是要调节音量还是亮度,左边是音量,右边是亮度 122 | if (startPoint?.x)! <= frame.size.width / 2.0 { 123 | // 音量 124 | startVB = CGFloat((volumeViewSlider?.value)!) 125 | }else { //亮度 126 | startVB = UIScreen.main.brightness 127 | } 128 | 129 | //方向置为无 130 | direction = .none 131 | 132 | if touchesBeganWithPointBlock != nil { 133 | startVideoRate = touchesBeganWithPointBlock!() 134 | } 135 | } 136 | 137 | 138 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 139 | 140 | let touch = touches.first 141 | let panPoint = touch?.location(in: self) 142 | 143 | //得出手指在Button上移动的距离 144 | let point = CGPoint(x: (panPoint?.x)! - (startPoint?.x)!, y: (panPoint?.y)! - (startPoint?.y)!) 145 | 146 | // 分析出用户滑动的方向 147 | if direction == .none { 148 | 149 | if point.x >= 30 || point.x <= -30 { 150 | //进度 151 | direction = .leftOrRight 152 | }else if point.y >= 30 || point.y <= -30 { // 音量和亮度 153 | direction = .upOrDown 154 | } 155 | 156 | } 157 | 158 | if direction == .none { 159 | return 160 | }else if direction == .upOrDown { 161 | // 音量和亮度 162 | if (startPoint?.x)! <= frame.size.width / 2.0 { //音量 163 | if point.y < 0 { // 增加音量 164 | let value = Float(startVB! + (-point.y / 30.0 / 10)) 165 | volumeViewSlider?.setValue(value, animated: true) 166 | if value - (volumeViewSlider?.value)! >= 0.1 { 167 | volumeViewSlider?.setValue(0.1, animated: false) 168 | volumeViewSlider?.setValue(value, animated: true) 169 | } 170 | } else { //减少音量 171 | let value = Float(startVB! - (point.y / 30.0 / 10)) 172 | volumeViewSlider?.setValue(value, animated: true) 173 | } 174 | } else { // 调节亮度 175 | 176 | if point.y < 0 { 177 | // 增加亮度 178 | let value = startVB! + (-point.y / 30.0 / 10) 179 | UIScreen.main.brightness = value 180 | }else { // 减少亮度 181 | let value = startVB! - (-point.y / 30.0 / 10) 182 | UIScreen.main.brightness = value 183 | } 184 | } 185 | }else if direction == .leftOrRight { //进度 186 | 187 | var rate = startVideoRate! + (point.x / 30.0 / 20.0) 188 | if rate > 1 { 189 | rate = 1 190 | }else if rate < 0 { 191 | rate = 0 192 | } 193 | currentRate = rate 194 | } 195 | } 196 | 197 | 198 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 199 | 200 | if direction == .leftOrRight { 201 | 202 | if touchesEndWithPointBlock != nil { 203 | touchesEndWithPointBlock!(currentRate!) 204 | } 205 | } 206 | } 207 | 208 | } 209 | 210 | 211 | // MARK: - UIGestureRecognizerDelegate 212 | extension MHGestureButton: UIGestureRecognizerDelegate { 213 | 214 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { 215 | 216 | for subView in subviews { 217 | 218 | if (touch.view?.isDescendant(of: subView))! { 219 | return false 220 | } 221 | } 222 | return true 223 | } 224 | 225 | } 226 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/MHConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MHConfig.swift 3 | // MHPlayer 4 | // 5 | // Created by kidd on 16/11/25. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct MHClosure { 12 | 13 | /// 播放成功回调 14 | static var mhSuccessClosure: (() ->())? 15 | 16 | /// 播放失败回调 17 | static var mhPlayerFailClosure: (() ->())? 18 | 19 | /// 取得加载进度 20 | static var mhLoadTimeClosure: ((_ time: CGFloat) -> ())? 21 | 22 | /// 取得当前播放时间(回调,刷新时间栏) 23 | static var mhCurrentTimeClosure: ((_ time: CGFloat) -> ())? 24 | 25 | /// 取得媒体总时长(为了回调) 26 | static var mhTotalTimeClosure: ((_ time: CGFloat) -> ())? 27 | 28 | /// 播放完 29 | static var mhPlayEndClosure: (() ->())? 30 | 31 | /// 播放器关闭回调 32 | static var mhPlayerStopClosure: (() ->())? 33 | 34 | /// 方向改变 35 | static var mhDirectionChangeClosure: ((_ origent: UIDeviceOrientation) -> ())? 36 | 37 | /// 播放是否延迟 38 | static var mhDelayPlay: ((_ flag: Bool) -> ())? 39 | 40 | /// 是否自动横屏 41 | static var mhAutoOrigin: (() -> ())? 42 | } 43 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/MHPlayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MHPlayer.swift 3 | // MHPlayer 4 | // 5 | // Created by kidd on 16/11/25. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class MHPlayer: UIView { 13 | 14 | // MARK: - 属性 15 | /// 视频链接 16 | open var mhPlayerURL: String? { 17 | didSet { 18 | mhPlayerInit() 19 | } 20 | } 21 | 22 | 23 | fileprivate var isSuccess: Bool? 24 | fileprivate var isPlayNow: Bool? 25 | 26 | fileprivate var mhPlayer: AVPlayer? 27 | fileprivate var mhPlayerItem: AVPlayerItem? 28 | fileprivate var videoURLAsset: AVURLAsset? 29 | fileprivate var mhPlayerLayer: AVPlayerLayer? 30 | 31 | fileprivate var playbackTimeObserver: Any? //界面更新时间ID 32 | fileprivate var link: CADisplayLink? //以屏幕刷新率进行定时操作 33 | 34 | fileprivate var lastTime: TimeInterval? 35 | 36 | fileprivate var filePath: URL? 37 | 38 | // MARK: - 方法 39 | override init(frame: CGRect) { 40 | 41 | super.init(frame: frame) 42 | 43 | self.backgroundColor = UIColor.black 44 | 45 | let audioSession = AVAudioSession.sharedInstance() 46 | try! audioSession.setCategory(AVAudioSessionCategoryPlayback) 47 | NotificationCenter.default.addObserver(self, selector: #selector(orientChange(_:)), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) //注册监听,屏幕方向改变 48 | 49 | 50 | } 51 | 52 | required init?(coder aDecoder: NSCoder) { 53 | fatalError("init(coder:) has not been implemented") 54 | } 55 | 56 | 57 | // MARK: - Open Function 58 | /// 播放 59 | public func mhPlaye() { 60 | 61 | mhPlayer?.play() 62 | isPlayNow = true 63 | if link == nil { 64 | link = CADisplayLink(target: self, selector: #selector(upadte)) //和屏幕频率刷新相同的定时器 65 | link?.add(to: RunLoop.main, forMode: .defaultRunLoopMode) 66 | } 67 | } 68 | 69 | 70 | /// 暂停 71 | public func mhPause() { 72 | 73 | isPlayNow = false 74 | if link != nil { 75 | link?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode) 76 | link = nil 77 | } 78 | 79 | mhPlayer?.pause() 80 | } 81 | 82 | /// 关闭 83 | public func mhStop() { 84 | // 开启锁屏 85 | UIApplication.shared.isIdleTimerDisabled = false 86 | mhRemoveObserver() 87 | mhPause() 88 | isPlayNow = false 89 | mhPlayer?.rate = 0 90 | mhPlayer?.replaceCurrentItem(with: nil) 91 | mhPlayerItem = nil 92 | mhPlayer = nil 93 | 94 | if MHClosure.mhPlayerStopClosure != nil { 95 | MHClosure.mhPlayerStopClosure!() 96 | } 97 | } 98 | 99 | /// 定位视频播放时间 100 | /// 101 | /// - parameter seconds: 定位的时间 102 | public func mhSeekToTimeWithSeconds(_ seconds: Float) { 103 | mhPlayer?.seek(to: CMTime.init(seconds: Double(seconds), preferredTimescale: CMTimeScale(NSEC_PER_SEC))) 104 | } 105 | 106 | 107 | /// 取得当前播放时间 108 | public func mhCurrentTime() -> CGFloat { 109 | return CGFloat(CMTimeGetSeconds(mhPlayer!.currentTime())) 110 | } 111 | 112 | 113 | /// 取得视频总时长 114 | public func mhTotalTime() -> CGFloat { 115 | return CGFloat(CMTimeGetSeconds((mhPlayer!.currentItem?.duration)!)) 116 | } 117 | 118 | /// 当前视屏播放进度 119 | /// 120 | /// - returns: 进度 121 | public func mhCurrentRate() -> CGFloat { 122 | 123 | let cTime = mhPlayer?.currentTime() 124 | if isSuccess == true { 125 | return CGFloat((cTime?.value)!) / CGFloat((cTime?.timescale)!) / CGFloat(CMTimeGetSeconds((mhPlayer?.currentItem?.duration)!)) 126 | }else { 127 | return 0 128 | } 129 | } 130 | 131 | public func setPlayLayerBounds(_ Bounds: CGRect) { 132 | mhPlayerLayer?.frame = Bounds 133 | } 134 | } 135 | 136 | 137 | extension MHPlayer { 138 | 139 | /// 初始化 140 | fileprivate func mhPlayerInit() { 141 | // 限制锁屏 142 | UIApplication.shared.isIdleTimerDisabled = true 143 | 144 | if mhPlayer != nil { 145 | mhPlayer = nil 146 | mhRemoveObserver() 147 | } 148 | 149 | fileExistsAtPath(mhPlayerURL!) 150 | 151 | videoURLAsset = AVURLAsset(url: filePath!, options: nil) 152 | mhPlayerItem = AVPlayerItem(asset: videoURLAsset!) 153 | 154 | if mhPlayer?.currentItem != nil { 155 | mhPlayer?.replaceCurrentItem(with: mhPlayerItem) 156 | }else { 157 | mhPlayer = AVPlayer(playerItem: mhPlayerItem) 158 | } 159 | 160 | if mhPlayerLayer != nil { 161 | let playerLayer = self.layer.sublayers?.first 162 | (playerLayer as! AVPlayerLayer).player = mhPlayer 163 | }else { 164 | mhPlayerLayer = AVPlayerLayer(player: mhPlayer) 165 | self.layer.insertSublayer(mhPlayerLayer!, at: 0) 166 | } 167 | 168 | 169 | //监听status属性变化 170 | mhPlayerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil) 171 | //监听loadedTimeRanges属性变化 172 | mhPlayerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil) 173 | //注册监听,视屏播放完成 174 | NotificationCenter.default.addObserver(self, selector: #selector(mhPlayerEndPlay(_:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: mhPlayerItem) 175 | NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: Notification.Name.UIApplicationWillResignActive, object: mhPlayerItem) 176 | NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterPlayGround), name: Notification.Name.UIApplicationDidBecomeActive, object: mhPlayerItem) 177 | } 178 | 179 | } 180 | 181 | 182 | // MARK: - 监听事件 183 | extension MHPlayer { 184 | 185 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 186 | 187 | let playerItem = object as? AVPlayerItem 188 | if keyPath == "status" { 189 | 190 | if MHClosure.mhAutoOrigin != nil { 191 | MHClosure.mhAutoOrigin!() 192 | } 193 | 194 | if playerItem?.status == .readyToPlay { 195 | print("播放成功") 196 | isSuccess = true 197 | 198 | let duration = mhPlayerItem?.duration // 获取视屏总长 199 | let totalSecond = CMTimeGetSeconds(duration!) //转换成秒 200 | if MHClosure.mhTotalTimeClosure != nil { 201 | MHClosure.mhTotalTimeClosure!(CGFloat(totalSecond)) 202 | } 203 | 204 | if MHClosure.mhSuccessClosure != nil { 205 | MHClosure.mhSuccessClosure!() 206 | } 207 | 208 | let currentSecond = CGFloat((mhPlayerItem?.currentTime().value)!) / CGFloat((mhPlayerItem?.currentTime().timescale)!) // 获得当前时间 209 | if MHClosure.mhCurrentTimeClosure != nil { 210 | MHClosure.mhCurrentTimeClosure!(currentSecond) 211 | } 212 | 213 | monitoringXjPlayerBack() //监听播放状态 214 | 215 | }else if playerItem?.status == .unknown { 216 | print("播放未知") 217 | isSuccess = false 218 | if MHClosure.mhPlayerFailClosure != nil { 219 | MHClosure.mhPlayerFailClosure!() 220 | } 221 | }else if playerItem?.status == .failed { 222 | print("播放失败") 223 | isSuccess = false 224 | if MHClosure.mhPlayerFailClosure != nil { 225 | MHClosure.mhPlayerFailClosure!() 226 | } 227 | } 228 | }else if keyPath == "loadedTimeRanges" { 229 | 230 | let timeInterval = mhPlayerAvailableDuration() 231 | let duration = mhPlayerItem?.duration 232 | let totalDuration = CMTimeGetSeconds(duration!) 233 | 234 | if MHClosure.mhLoadTimeClosure != nil { 235 | MHClosure.mhLoadTimeClosure!(CGFloat(timeInterval / totalDuration)) 236 | } 237 | } 238 | } 239 | 240 | 241 | 242 | /// 屏幕方向改变时的监听 243 | @objc fileprivate func orientChange(_ notification: NSNotification) { 244 | 245 | let origent = (notification.object as! UIDevice).orientation 246 | if MHClosure.mhDirectionChangeClosure != nil{ 247 | MHClosure.mhDirectionChangeClosure!(origent) 248 | } 249 | } 250 | 251 | 252 | /// 视屏播放完后的通知事件。从头开始播放 253 | @objc fileprivate func mhPlayerEndPlay(_ notification: NSNotification) { 254 | 255 | mhPlayer?.seek(to: kCMTimeZero, completionHandler: {_ in 256 | MHClosure.mhPlayEndClosure!() 257 | }) 258 | 259 | } 260 | 261 | /// 程序进入后台(如果播放,则暂停,否则不管) 262 | @objc fileprivate func appDidEnterBackground() { 263 | if isPlayNow == true { 264 | mhPlayer?.pause() 265 | } 266 | } 267 | 268 | 269 | /// 程序进入前台(退出前播放,进来后继续播放,否则不管) 270 | @objc fileprivate func appDidEnterPlayGround() { 271 | if isPlayNow == true { 272 | mhPlayer?.play() 273 | } 274 | } 275 | 276 | 277 | /// 实时监听播放状态 278 | fileprivate func monitoringXjPlayerBack() { 279 | 280 | playbackTimeObserver = mhPlayer?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 1), queue: nil, using: { [weak self](time) in 281 | guard let sself = self else {return} 282 | let currentSecond = CGFloat((sself.mhPlayerItem?.currentTime().value)!) / CGFloat((sself.mhPlayerItem?.currentTime().timescale)!) // 获得当前时间 283 | 284 | if MHClosure.mhCurrentTimeClosure != nil { 285 | MHClosure.mhCurrentTimeClosure!(currentSecond) 286 | } 287 | 288 | }) 289 | } 290 | 291 | 292 | /// 刷新,看播放是否卡顿 293 | @objc fileprivate func upadte() { 294 | let current: TimeInterval = CMTimeGetSeconds((mhPlayer?.currentTime())!) 295 | if current == lastTime { 296 | // 卡顿 297 | if MHClosure.mhDelayPlay != nil { 298 | MHClosure.mhDelayPlay!(true) 299 | } 300 | }else { //不卡顿 301 | if MHClosure.mhDelayPlay != nil { 302 | MHClosure.mhDelayPlay!(false) 303 | } 304 | } 305 | 306 | lastTime = current 307 | } 308 | 309 | } 310 | 311 | 312 | extension MHPlayer { 313 | 314 | /// 计算缓冲区 315 | fileprivate func mhPlayerAvailableDuration() -> TimeInterval { 316 | 317 | let loadedTimeRanges = mhPlayer?.currentItem?.loadedTimeRanges 318 | let timeRange = (loadedTimeRanges?.first)?.timeRangeValue //获取缓冲区域 319 | let startSeconds = CMTimeGetSeconds((timeRange?.start)!) 320 | let durationSeconds = CMTimeGetSeconds((timeRange?.duration)!) 321 | let result = startSeconds + durationSeconds //尖酸缓冲进度 322 | 323 | return result 324 | } 325 | 326 | /// 判断是否存在已下载好的文件 327 | fileprivate func fileExistsAtPath(_ url: String) { 328 | let fileManager = FileManager.default 329 | 330 | if fileManager.fileExists(atPath: url) == true { 331 | filePath = URL(fileURLWithPath: url) 332 | print(filePath ?? "url为空") 333 | }else { 334 | filePath = NSURL(string: url) as URL? 335 | print("没有本地文件") 336 | } 337 | } 338 | 339 | 340 | fileprivate func mhRemoveObserver() { 341 | 342 | if link != nil { 343 | link?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode) 344 | link = nil 345 | } 346 | mhPlayerItem?.removeObserver(self, forKeyPath: "status", context: nil) 347 | mhPlayerItem?.removeObserver(self, forKeyPath: "loadedTimeRanges", context: nil) 348 | mhPlayer?.removeTimeObserver(playbackTimeObserver!) 349 | playbackTimeObserver = nil 350 | NotificationCenter.default.removeObserver(self) 351 | 352 | NotificationCenter.default.addObserver(self, selector: #selector(orientChange(_:)), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) //注册监听,屏幕方向改变 353 | } 354 | } 355 | 356 | 357 | 358 | extension MHPlayer { 359 | // 360 | // fileprivate func player() -> AVPlayer? { 361 | // 362 | // for sublayer in layer.sublayers! { 363 | // if sublayer.classForCoder == AVPlayerLayer.self { 364 | // return (sublayer as! AVPlayerLayer).player 365 | // } 366 | // } 367 | // 368 | // return nil 369 | // } 370 | // 371 | // fileprivate func setPlayer(_ p: AVPlayer) { 372 | // 373 | // for sublayer in layer.sublayers! { 374 | // if sublayer.classForCoder == AVPlayerLayer.self { 375 | // sublayer.removeFromSuperlayer() 376 | // } 377 | // } 378 | // 379 | // let playLayer = AVPlayerLayer(player: p) 380 | // layer.insertSublayer(playLayer, at: 0) 381 | // } 382 | 383 | } 384 | 385 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/MHPlayerBottomMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MHPlayerBottomMenu.swift 3 | // MHPlayer 4 | // 5 | // Created by kidd on 16/11/25. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MHPlayerBottomMenu: UIView { 12 | 13 | var mhFull: Bool? 14 | 15 | var mhPlay: Bool? { // 双击播放暂停 16 | didSet { 17 | guard mhPlay != nil else { return } 18 | playOrPauseAction() 19 | } 20 | } 21 | 22 | var mhPlayEnd: Bool? { 23 | didSet { 24 | guard let isEnd = mhPlayEnd else { return } 25 | if isEnd == true { 26 | isPlay = false 27 | playOrPauseBtn.setImage(UIImage(named: "play"), for: .normal) 28 | playSlider.setValue(0.0, animated: true) 29 | loadProgressView.setProgress(0.0, animated: true) 30 | let time = mhPlayerTimeStyle(mhTotalTime!) 31 | timeLabel.text = "00:00:00/00:\(time)" 32 | } 33 | } 34 | } 35 | 36 | var mhLoadedTimeRanges: CGFloat? { //已加载 37 | didSet { 38 | guard let LoadedTimeRanges = mhLoadedTimeRanges else { return } 39 | loadProgressView.setProgress(Float(LoadedTimeRanges), animated: true) 40 | } 41 | } 42 | 43 | var mhCurrentTime: CGFloat? { //已播放 44 | didSet { 45 | guard let CurrentTime = mhCurrentTime else { return } 46 | playSlider.setValue(Float(CurrentTime), animated: true) 47 | let time1 = mhPlayerTimeStyle(CurrentTime) 48 | let time2 = mhPlayerTimeStyle(mhTotalTime!) 49 | if isHour == true { 50 | timeLabel.text = "\(time1)/\(time2)" 51 | }else { 52 | timeLabel.text = "00:\(time1)/00:\(time2)" 53 | } 54 | } 55 | } 56 | 57 | var mhTotalTime: CGFloat? { //总时长 58 | 59 | didSet { 60 | 61 | guard let totalTime = mhTotalTime else { return } 62 | let time = mhPlayerTimeStyle(totalTime) 63 | if isHour == true { 64 | timeLabel.text = "00:00:00/\(time)" 65 | }else { 66 | timeLabel.text = "00:00:00/00:\(time)" 67 | } 68 | //设置slider的最大值就是总时长 69 | playSlider.maximumValue = Float(totalTime) 70 | } 71 | } 72 | 73 | /// 播放/暂停 74 | var mhPlayOrPauseBlock: ((_ flag: Bool) -> Void)? 75 | /// 下一个 76 | var mhNextPlayerBlock: (() -> ())? 77 | /// 滑动条滑动时 78 | var mhSliderValueChangeBlock: ((_ value: CGFloat) -> Void)? 79 | /// 滑动条滑动完成 80 | var mhSliderValueChangeEndBlock: ((_ value: CGFloat) -> Void)? 81 | /// 放大/缩小 82 | var mhFullOrSmallBlock: ((_ flag: Bool) -> Void)? 83 | 84 | fileprivate var isPlay: Bool = false 85 | fileprivate var isHour: Bool = false 86 | 87 | ///播放/暂停 88 | fileprivate lazy var playOrPauseBtn: UIButton = { 89 | let button = UIButton() 90 | button.setImage(UIImage(named: "play"), for: .normal) 91 | button.contentMode = UIViewContentMode.center 92 | button.addTarget(self, action: #selector(playOrPauseAction), for: .touchUpInside) 93 | return button 94 | }() 95 | 96 | ///下一个视屏(全屏时有) 97 | fileprivate lazy var nextPlayerBtn: UIButton = { 98 | let button = UIButton() 99 | button.setImage(UIImage(named: "next"), for: .normal) 100 | button.contentMode = UIViewContentMode.center 101 | button.addTarget(self, action: #selector(nextPlayerAction), for: .touchUpInside) 102 | return button 103 | }() 104 | 105 | ///缓冲进度条 106 | fileprivate lazy var loadProgressView: UIProgressView = { 107 | let progressView = UIProgressView() 108 | return progressView 109 | }() 110 | 111 | ///播放滑动条 112 | fileprivate lazy var playSlider: UISlider = { 113 | let slider = UISlider() 114 | slider.minimumValue = 0.0 115 | UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), false, 0.0) 116 | let transparentImage = UIGraphicsGetImageFromCurrentImageContext() 117 | UIGraphicsEndImageContext() 118 | 119 | slider.setThumbImage(UIImage(named: "progressDot"), for: .normal) 120 | slider.setMaximumTrackImage(transparentImage, for: .normal) 121 | slider.setMinimumTrackImage(transparentImage, for: .normal) 122 | 123 | slider.addTarget(self, action: #selector(playSliderValueChanging(_:)), for: .valueChanged) 124 | slider.addTarget(self, action: #selector(playSliderValueDidChanged(_:)), for: .touchUpInside) 125 | return slider 126 | }() 127 | 128 | ///时间标签 129 | fileprivate lazy var timeLabel: UILabel = { 130 | let label = UILabel() 131 | label.textColor = UIColor.white 132 | label.font = UIFont.systemFont(ofSize: 11.0) 133 | label.textAlignment = .center 134 | label.text = "00:00:00/00:00:00" 135 | return label 136 | }() 137 | 138 | ///放大/缩小按钮 139 | fileprivate lazy var fullOrSmallBtn: UIButton = { 140 | let button = UIButton() 141 | button.setImage(UIImage(named: "fullScreen"), for: .normal) 142 | button.contentMode = UIViewContentMode.center 143 | button.addTarget(self, action: #selector(fullOrSmallAction), for: .touchUpInside) 144 | return button 145 | }() 146 | 147 | override init(frame: CGRect) { 148 | super.init(frame: frame) 149 | 150 | addAllView() 151 | } 152 | 153 | required init?(coder aDecoder: NSCoder) { 154 | fatalError("init(coder:) has not been implemented") 155 | } 156 | 157 | private func addAllView() { 158 | addSubview(playOrPauseBtn) 159 | addSubview(nextPlayerBtn) 160 | addSubview(loadProgressView) 161 | addSubview(playSlider) 162 | addSubview(timeLabel) 163 | addSubview(fullOrSmallBtn) 164 | } 165 | 166 | } 167 | 168 | 169 | // MARK: - 控制事件 170 | extension MHPlayerBottomMenu { 171 | 172 | /// 开始/暂停 173 | @objc fileprivate func playOrPauseAction() { 174 | 175 | if isPlay == true { 176 | isPlay = false 177 | playOrPauseBtn.setImage(UIImage(named: "play"), for: .normal) 178 | }else { 179 | isPlay = true 180 | playOrPauseBtn.setImage(UIImage(named: "pause"), for: .normal) 181 | } 182 | if mhPlayOrPauseBlock != nil { 183 | mhPlayOrPauseBlock!(isPlay) 184 | } 185 | } 186 | 187 | 188 | /// 下一个 189 | @objc fileprivate func nextPlayerAction() { 190 | 191 | if mhNextPlayerBlock != nil { 192 | mhNextPlayerBlock!() 193 | } 194 | } 195 | 196 | /// 定义视频时长样式 197 | fileprivate func mhPlayerTimeStyle(_ time: CGFloat) -> String { 198 | 199 | let date = Date(timeIntervalSince1970: TimeInterval(time)) 200 | let formatter = DateFormatter() 201 | if time / 3600 > 1 { 202 | isHour = true 203 | formatter.dateFormat = "HH:mm:ss" 204 | }else { 205 | formatter.dateFormat = "mm:ss" 206 | } 207 | return formatter.string(from: date) 208 | } 209 | 210 | /// 放大/缩小 211 | @objc fileprivate func fullOrSmallAction() { 212 | mhFull = !mhFull! 213 | if mhFullOrSmallBlock != nil { 214 | mhFullOrSmallBlock!(mhFull!) 215 | } 216 | } 217 | 218 | /// slider拖动时 219 | @objc fileprivate func playSliderValueChanging(_ sender: AnyObject) { 220 | isPlay = false 221 | let slider = sender as! UISlider 222 | if mhSliderValueChangeBlock != nil { 223 | mhSliderValueChangeBlock!(CGFloat(slider.value)) 224 | } 225 | } 226 | 227 | /// slider完成拖动时 228 | @objc fileprivate func playSliderValueDidChanged(_ sender: AnyObject) { 229 | let slider = sender as! UISlider 230 | if mhSliderValueChangeEndBlock != nil { 231 | mhSliderValueChangeEndBlock!(CGFloat(slider.value)) 232 | } 233 | playOrPauseAction() 234 | } 235 | 236 | } 237 | 238 | 239 | // MARK: - 布局信息 240 | extension MHPlayerBottomMenu { 241 | 242 | override func layoutSubviews() { 243 | super.layoutSubviews() 244 | 245 | playOrPauseBtn.frame = CGRect(x: self.left, y: 8, width: 25, height: 25) 246 | if mhFull == true { 247 | nextPlayerBtn.frame = CGRect(x: playOrPauseBtn.right, y: 8, width: 25, height: 25) 248 | fullOrSmallBtn.setImage(UIImage(named: "exitFullScreen"), for: .normal) 249 | }else { 250 | nextPlayerBtn.frame = CGRect(x: playOrPauseBtn.right + 5, y: 5, width: 0, height: 0) 251 | fullOrSmallBtn.setImage(UIImage(named: "fullScreen"), for: .normal) 252 | } 253 | 254 | fullOrSmallBtn.frame = CGRect(x: self.width - 35, y: (self.height - 25) / 2 , width: 25, height: 25) 255 | timeLabel.frame = CGRect(x: fullOrSmallBtn.left - 108, y: 10, width: 108, height: 20) 256 | loadProgressView.frame = CGRect(x: playOrPauseBtn.right + nextPlayerBtn.width + 7, y: 20, width: timeLabel.left - playOrPauseBtn.right - nextPlayerBtn.width - 14, height: 20) 257 | playSlider.frame = CGRect(x: playOrPauseBtn.right + nextPlayerBtn.width + 5, y: 5, width: loadProgressView.width + 4, height: 31) 258 | } 259 | } 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/MHPlayerSDK.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MHPlayerSDK.swift 3 | // MHPlayer 4 | // 5 | // Created by kidd on 16/11/25. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc protocol MHAVPlayerSDKDelegate: NSObjectProtocol { 12 | /// 返回按钮 13 | @objc optional func mhGoBack() 14 | 15 | /// 下一个 16 | @objc optional func mhNextPlayer() 17 | } 18 | 19 | class MHAVPlayerSDK: UIView { 20 | /// 代理 21 | weak var MHAVPlayerSDKDelegate: MHAVPlayerSDKDelegate? 22 | 23 | /// 视频播放链接 24 | var mhPlayerURL: String? { 25 | didSet{ 26 | guard let url = mhPlayerURL else { return } 27 | saveURL = url 28 | mhPlayer.mhPlayerURL = url 29 | } 30 | } 31 | 32 | /// 视频标题 33 | var mhPlayerTitle: String? { 34 | didSet { 35 | guard let title = mhPlayerTitle else { return } 36 | saveTitle = title 37 | topMenu.mhAVTitle = title 38 | } 39 | } 40 | 41 | /// 定位上次播放时间 42 | var mhLastTime: Float? { 43 | didSet{ 44 | guard let lastTime = mhLastTime else { return } 45 | mhPlayer.mhSeekToTimeWithSeconds(Float(lastTime)) 46 | } 47 | } 48 | 49 | /// 是否开启自动横屏,默认NO 50 | var mhAutoOrient: Bool = false 51 | 52 | ///是否关闭过播放器(关闭,不是暂停) 53 | fileprivate var isStop: Bool? 54 | 55 | fileprivate lazy var mhPlayer: MHPlayer = { 56 | let mhPlayer = MHPlayer() 57 | return mhPlayer 58 | }() 59 | 60 | fileprivate lazy var backView: MHGestureButton = { 61 | let backView = MHGestureButton() 62 | return backView 63 | }() 64 | 65 | fileprivate lazy var topMenu: MHTopMenu = { 66 | let topMenu = MHTopMenu() 67 | topMenu.backgroundColor = UIColor(red: 50.0/255.0, green: 50.0/255.0, blue: 50.0/255.0, alpha: 1.0) 68 | topMenu.isHidden = true 69 | return topMenu 70 | }() 71 | 72 | fileprivate lazy var bottomMenu: MHPlayerBottomMenu = { 73 | let bottomMenu = MHPlayerBottomMenu() 74 | bottomMenu.backgroundColor = UIColor(colorLiteralRed: 50.0/255.0, green: 50.0/255.0, blue: 50.0/255.0, alpha: 1.0) 75 | bottomMenu.isHidden = true 76 | return bottomMenu 77 | }() 78 | 79 | /// 初始化的视屏大小 80 | fileprivate var firstFrame: CGRect! 81 | /// 保存url 82 | fileprivate var saveURL: String? 83 | /// 保存标题 84 | fileprivate var saveTitle: String? 85 | /// 等待指示器 86 | fileprivate lazy var loadingView: UIActivityIndicatorView = { 87 | let loadingView = UIActivityIndicatorView.init(activityIndicatorStyle: UIActivityIndicatorViewStyle.whiteLarge) 88 | loadingView.startAnimating() 89 | return loadingView 90 | }() 91 | 92 | 93 | override init(frame: CGRect) { 94 | super.init(frame: frame) 95 | 96 | UIDevice.setOrientation(false) 97 | self.firstFrame = frame 98 | addAllView() 99 | } 100 | 101 | required init?(coder aDecoder: NSCoder) { 102 | fatalError("init(coder:) has not been implemented") 103 | } 104 | 105 | private func addAllView() { 106 | backView.addSubview(topMenu) 107 | backView.addSubview(bottomMenu) 108 | backView.addSubview(loadingView) 109 | mhPlayer.addSubview(backView) 110 | addSubview(mhPlayer) 111 | 112 | mhAVPlayerBLock() 113 | mhGestureButtonBlock() 114 | mhTopMenuBlock() 115 | mhBottomMenuBlock() 116 | } 117 | 118 | /// 关闭播放器 119 | public func mhStopPlayer() { 120 | mhPlayer.mhStop() 121 | } 122 | 123 | 124 | /// 获取当前播放时间 125 | public func mhCurrentTime() -> CGFloat { 126 | return mhPlayer.mhCurrentTime() 127 | } 128 | 129 | 130 | /// 获取视屏总长 131 | public func mhTotalTime() -> CGFloat { 132 | return mhPlayer.mhTotalTime() 133 | } 134 | 135 | } 136 | 137 | 138 | // MARK: - MHPlayer 方法 139 | extension MHAVPlayerSDK { 140 | 141 | fileprivate func mhAVPlayerBLock() { 142 | 143 | //加载成功回调 144 | MHClosure.mhSuccessClosure = { [weak self] in 145 | guard let sself = self else {return} 146 | // sself.bottomMenu.mhPlay = true //如果想一进来就播放,就放开注释 147 | sself.loadingView.stopAnimating() 148 | sself.loadingView.hidesWhenStopped = true 149 | } 150 | 151 | //播放失败回调 152 | MHClosure.mhPlayerFailClosure = { [weak self] in 153 | guard let sself = self else {return} 154 | sself.isStop = true //保证点击播放按钮能播放 155 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5.0, execute: { 156 | sself.loadingView.stopAnimating() 157 | sself.loadingView.hidesWhenStopped = true 158 | }) 159 | } 160 | 161 | // 加载进度 162 | MHClosure.mhLoadTimeClosure = { [weak self] (time) in 163 | guard let sself = self else {return} 164 | sself.bottomMenu.mhLoadedTimeRanges = time 165 | } 166 | 167 | // 视屏总长 168 | MHClosure.mhTotalTimeClosure = { [weak self] (time) in 169 | guard let sself = self else {return} 170 | sself.bottomMenu.mhTotalTime = time 171 | } 172 | 173 | 174 | // 当前时间 175 | MHClosure.mhCurrentTimeClosure = { [weak self] (time) in 176 | guard let sself = self else {return} 177 | sself.bottomMenu.mhCurrentTime = time 178 | } 179 | 180 | 181 | // 播放完 182 | MHClosure.mhPlayEndClosure = { [weak self] in 183 | guard let sself = self else {return} 184 | sself.bottomMenu.mhPlayEnd = true 185 | if (sself.MHAVPlayerSDKDelegate?.responds(to: NSSelectorFromString("mhNextPlayer")))! { 186 | sself.MHAVPlayerSDKDelegate?.mhNextPlayer!() 187 | } 188 | } 189 | 190 | 191 | //关闭控件 192 | MHClosure.mhPlayerStopClosure = { [weak self] in 193 | guard let sself = self else {return} 194 | sself.isStop = true 195 | sself.bottomMenu.mhPlayEnd = true 196 | } 197 | 198 | 199 | //方向改变 200 | MHClosure.mhDirectionChangeClosure = {[weak self] (origent) in 201 | guard let sself = self else {return} 202 | if sself.mhAutoOrient == true { 203 | if origent == UIDeviceOrientation.portrait { 204 | sself.frame = sself.firstFrame 205 | sself.bottomMenu.mhFull = false 206 | }else if origent == UIDeviceOrientation.landscapeLeft || origent == UIDeviceOrientation.landscapeRight { 207 | sself.frame = UIScreen.main.bounds 208 | sself.bottomMenu.mhFull = true 209 | } 210 | } 211 | } 212 | 213 | 214 | // 自动改变屏幕方向 215 | MHClosure.mhAutoOrigin = { [weak self] in 216 | guard let sself = self else {return} 217 | UIDevice.setOrientation(sself.mhAutoOrient) 218 | } 219 | 220 | //播放延迟 221 | MHClosure.mhDelayPlay = { [weak self] (flag) in 222 | guard let sself = self else {return} 223 | if flag == true && sself.isStop == true { 224 | sself.loadingView.startAnimating() 225 | }else { 226 | sself.loadingView.stopAnimating() 227 | sself.loadingView.hidesWhenStopped = true 228 | } 229 | } 230 | 231 | } 232 | 233 | } 234 | 235 | 236 | // MARK: - GestureButton方法 237 | extension MHAVPlayerSDK { 238 | 239 | fileprivate func mhGestureButtonBlock() { 240 | //单击/双击事件 241 | backView.userTapGestureBlock = {[weak self] (number, flag) in 242 | guard let sself = self else {return} 243 | if number == 1 { 244 | UIView.animate(withDuration: 0.3, animations: { 245 | sself.topMenu.isHidden = flag 246 | sself.bottomMenu.isHidden = flag 247 | }) 248 | }else if number == 2 { 249 | sself.bottomMenu.mhPlay = flag //不受flag影响 250 | } 251 | } 252 | 253 | func open(_ blovk: () -> CGFloat) { 254 | 255 | } 256 | 257 | //开始触摸 258 | backView.touchesBeganWithPointBlock = { [weak self] () -> CGFloat in 259 | guard let sself = self else {return 0} 260 | //返回当前播放进度 261 | return sself.mhPlayer.mhCurrentRate() 262 | } 263 | 264 | //结束触摸 265 | backView.touchesEndWithPointBlock = { [weak self] (rate) in 266 | guard let sself = self else {return} 267 | //进度 268 | let seconds = sself.mhPlayer.mhTotalTime() * rate 269 | sself.mhPlayer.mhSeekToTimeWithSeconds(Float(seconds)) 270 | } 271 | } 272 | 273 | } 274 | 275 | // MARK: - TopMenu方法 276 | extension MHAVPlayerSDK { 277 | 278 | fileprivate func mhTopMenuBlock() { 279 | 280 | topMenu.mhTopGoBack = { [weak self] in 281 | guard let sself = self else {return} 282 | //返回 283 | if sself.bottomMenu.mhFull == true { 284 | UIDevice.setOrientation(false) 285 | sself.frame = sself.firstFrame 286 | sself.bottomMenu.mhFull = false 287 | }else { 288 | if (sself.MHAVPlayerSDKDelegate?.responds(to: NSSelectorFromString("mhGoBack")))! { 289 | sself.MHAVPlayerSDKDelegate?.mhGoBack!() 290 | } 291 | } 292 | } 293 | } 294 | 295 | } 296 | 297 | 298 | // MARK: - BottomMenu方法 299 | extension MHAVPlayerSDK { 300 | 301 | fileprivate func mhBottomMenuBlock() { 302 | 303 | //播放/暂停 304 | bottomMenu.mhPlayOrPauseBlock = { [weak self] (isPlay) in 305 | guard let sself = self else {return} 306 | if sself.isStop == true { 307 | sself.isStop = false 308 | sself.mhPlayer.mhPlayerURL = sself.saveURL 309 | sself.topMenu.mhAVTitle = sself.saveTitle 310 | } 311 | if isPlay == true { 312 | sself.mhPlayer.mhPlaye() 313 | }else { 314 | sself.mhPlayer.mhPause() 315 | } 316 | } 317 | 318 | //下一个 319 | bottomMenu.mhNextPlayerBlock = { [weak self] in 320 | guard let sself = self else {return} 321 | sself.bottomMenu.mhPlayEnd = true 322 | if (sself.MHAVPlayerSDKDelegate?.responds(to: NSSelectorFromString("mhNextPlayer")))! { 323 | sself.MHAVPlayerSDKDelegate?.mhNextPlayer!() 324 | } 325 | } 326 | 327 | //滑动条滑动时 328 | bottomMenu.mhSliderValueChangeBlock = { [weak self] (time) in 329 | guard let sself = self else {return} 330 | sself.mhPlayer.mhSeekToTimeWithSeconds(Float(time)) 331 | sself.mhPlayer.mhPause() 332 | } 333 | 334 | //滑动条拖动完成 335 | bottomMenu.mhSliderValueChangeEndBlock = { [weak self] (time) in 336 | guard let sself = self else {return} 337 | sself.mhPlayer.mhSeekToTimeWithSeconds(Float(time)) 338 | } 339 | 340 | //放大/缩小 341 | bottomMenu.mhFullOrSmallBlock = { [weak self] (isFull) in 342 | guard let sself = self else {return} 343 | UIDevice.setOrientation(isFull) 344 | if isFull == true { 345 | sself.frame = (sself.window?.bounds)! 346 | }else { 347 | sself.frame = sself.firstFrame 348 | } 349 | } 350 | } 351 | 352 | } 353 | 354 | 355 | // MARK: - 布局信息 356 | extension MHAVPlayerSDK { 357 | 358 | override func layoutSubviews() { 359 | super.layoutSubviews() 360 | 361 | mhPlayer.frame = CGRect(x: 0, y: 0, width: self.width, height: self.height) 362 | backView.frame = mhPlayer.frame 363 | topMenu.frame = CGRect(x: 0, y: backView.top, width: backView.width, height: 40) 364 | bottomMenu.frame = CGRect(x: 0, y: backView.height - 40, width: backView.width, height: 40) 365 | loadingView.center = CGPoint(x: backView.centerX, y: backView.centerY) 366 | 367 | mhPlayer.setPlayLayerBounds(self.layer.bounds) 368 | } 369 | 370 | } 371 | 372 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/MHTopMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MHTopMenu.swift 3 | // MHPlayer 4 | // 5 | // Created by kidd on 16/11/25. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MHTopMenu: UIView { 12 | 13 | /// 标题 14 | var mhAVTitle: String? { 15 | didSet { 16 | guard let title = mhAVTitle else { return } 17 | titleLabel.text = title 18 | } 19 | } 20 | 21 | /// 隐藏返回按钮,默认为NO 22 | var mhHiddenBackBtn: Bool? 23 | /// 返回按钮操作 24 | var mhTopGoBack: (() ->Void)? 25 | 26 | fileprivate lazy var backButton: UIButton = { 27 | let button = UIButton() 28 | button.setBackgroundImage(UIImage(named: "BackBtn"), for: .normal) 29 | button.addTarget(self, action: #selector(goBack), for: .touchUpInside) 30 | return button 31 | }() 32 | 33 | fileprivate lazy var titleLabel: UILabel = { 34 | let label = UILabel() 35 | label.font = UIFont.systemFont(ofSize: 18) 36 | label.textColor = UIColor.white 37 | return label 38 | }() 39 | 40 | override init(frame: CGRect) { 41 | 42 | super.init(frame: frame) 43 | addAllView() 44 | } 45 | 46 | required init?(coder aDecoder: NSCoder) { 47 | fatalError("init(coder:) has not been implemented") 48 | } 49 | 50 | 51 | private func addAllView() { 52 | addSubview(backButton) 53 | addSubview(titleLabel) 54 | } 55 | } 56 | 57 | 58 | // MARK: - 控制事件 59 | extension MHTopMenu { 60 | 61 | @objc fileprivate func goBack() { 62 | 63 | if mhTopGoBack != nil { 64 | mhTopGoBack!() 65 | } 66 | } 67 | 68 | } 69 | 70 | 71 | // MARK: - 布局 72 | extension MHTopMenu { 73 | 74 | override func layoutSubviews() { 75 | super.layoutSubviews() 76 | 77 | if mhHiddenBackBtn == true { 78 | backButton.removeFromSuperview() 79 | titleLabel.frame = CGRect(x: 10, y: 5, width: 200, height: 30) 80 | }else { 81 | backButton.frame = CGRect(x: 10, y: 10, width: 18, height: 18) 82 | backButton.contentMode = .center 83 | titleLabel.frame = CGRect(x: 50, y: 5, width: 200, height: 30) 84 | } 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/Rotate/MHDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MHDevice.swift 3 | // MHPlayer 4 | // 5 | // Created by kidd on 16/11/28. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | extension UIDevice { 13 | 14 | class func setOrientation(_ isFull: Bool) { 15 | 16 | if isFull == true { 17 | 18 | UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation") 19 | }else { 20 | UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/icon/BackBtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiddhmh/MHPlayer/4b261526243761d01a108d12f6e00790ddbba25c/MHPlayer/MHPlayer/MHPlayer/icon/BackBtn.png -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/icon/Next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiddhmh/MHPlayer/4b261526243761d01a108d12f6e00790ddbba25c/MHPlayer/MHPlayer/MHPlayer/icon/Next.png -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/icon/exitFullScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiddhmh/MHPlayer/4b261526243761d01a108d12f6e00790ddbba25c/MHPlayer/MHPlayer/MHPlayer/icon/exitFullScreen.png -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/icon/fullScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiddhmh/MHPlayer/4b261526243761d01a108d12f6e00790ddbba25c/MHPlayer/MHPlayer/MHPlayer/icon/fullScreen.png -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/icon/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiddhmh/MHPlayer/4b261526243761d01a108d12f6e00790ddbba25c/MHPlayer/MHPlayer/MHPlayer/icon/pause.png -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/icon/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiddhmh/MHPlayer/4b261526243761d01a108d12f6e00790ddbba25c/MHPlayer/MHPlayer/MHPlayer/icon/play.png -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/MHPlayer/icon/progressDot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiddhmh/MHPlayer/4b261526243761d01a108d12f6e00790ddbba25c/MHPlayer/MHPlayer/MHPlayer/icon/progressDot.png -------------------------------------------------------------------------------- /MHPlayer/MHPlayer/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MHPlayer 4 | // 5 | // Created by kidd on 16/11/25. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | var mhPlayer: MHAVPlayerSDK? 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | view.backgroundColor = UIColor.white 19 | 20 | mhPlayer = MHAVPlayerSDK(frame: CGRect(x: 0, y: 40, width: view.frame.size.width, height: view.frame.size.width / 2)) 21 | mhPlayer?.mhPlayerURL = "http://baobab.wdjcdn.com/14562919706254.mp4" 22 | mhPlayer?.mhPlayerTitle = "第一部" 23 | mhPlayer?.MHAVPlayerSDKDelegate = self 24 | mhPlayer?.mhLastTime = 50 25 | mhPlayer?.mhAutoOrient = true 26 | view.addSubview(mhPlayer!) 27 | 28 | } 29 | 30 | override func didReceiveMemoryWarning() { 31 | super.didReceiveMemoryWarning() 32 | // Dispose of any resources that can be recreated. 33 | } 34 | 35 | 36 | } 37 | 38 | 39 | extension ViewController: MHAVPlayerSDKDelegate { 40 | 41 | func mhGoBack() { 42 | // mhPlayer?.mhStopPlayer() 43 | // self.dismiss(animated: true, completion: nil) 44 | } 45 | 46 | func mhNextPlayer() { 47 | mhPlayer?.mhPlayerURL = "http://baobab.wdjcdn.com/1455782903700jy.mp4" 48 | mhPlayer?.mhPlayerTitle = "第二部"; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayerTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayerTests/MHPlayerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MHPlayerTests.swift 3 | // MHPlayerTests 4 | // 5 | // Created by kidd on 16/11/25. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MHPlayer 11 | 12 | class MHPlayerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayerUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MHPlayer/MHPlayerUITests/MHPlayerUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MHPlayerUITests.swift 3 | // MHPlayerUITests 4 | // 5 | // Created by kidd on 16/11/25. 6 | // Copyright © 2016年 CMCC. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class MHPlayerUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # MHPlayer 3 | 一句代码集成视频播放器 `Swift 3.0`; 4 | 本播放器是根据`AVPlayer`进行封装的; 5 | 6 | 7 | ## 主要功能: 8 | * 1.一句代码就能调用播放

9 | * 2.支持开始/暂停

10 | * 3.支持放大/缩小

11 | * 4.支持随屏幕旋转

12 | * 5.支持拖拽进度

13 | * 6.时间显示

14 | * 7.左边上下滑调节音量

15 | * 8.右边上下滑调节亮度

16 | * 9.左右滑调节播放进度

17 | * 10.播放屏幕保持常亮

18 | * 11.双击播放/暂停

19 | 20 | ## How To Use? 21 | ```Swift 22 | mhPlayer = MHAVPlayerSDK(frame: CGRect(x: 0, y: 40, width: view.frame.size.width, height: view.frame.size.width / 2)) 23 | mhPlayer?.mhPlayerURL = "http://static.tripbe.com/videofiles/20121214/9533522808.f4v.mp4" 24 | mhPlayer?.mhPlayerTitle = "MHPlayer" 25 | mhPlayer?.mhAutoOrient = true 26 | mhPlayer?.MHAVPlayerSDKDelegate = self 27 | view.addSubview(mhPlayer!) 28 | ``` 29 | ##Tips: 30 |  * 欢迎大家使用,有问题的话可以加我QQ1156154406,或者提Issues都可以,我会及时处理。多多交流~ 31 | --------------------------------------------------------------------------------