├── .gitignore ├── LICENSE ├── README.md ├── TBPlayer.podspec ├── TBPlayer.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── TBPlayer ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Classes ├── TBPlayer.bundle │ ├── Root.plist │ ├── en.lproj │ │ └── Root.strings │ └── images │ │ ├── icon_pause@2x.png │ │ ├── icon_pause@3x.png │ │ ├── icon_play@2x.png │ │ ├── icon_play@3x.png │ │ ├── icon_play_hl@2x.png │ │ ├── icon_play_hl@3x.png │ │ ├── quanping@2x.png │ │ └── quanping@3x.png ├── TBPlayer.h ├── TBPlayer.m ├── TBVideoRequestTask.h ├── TBVideoRequestTask.m ├── TBloaderURLConnection.h ├── TBloaderURLConnection.m └── XCToast │ ├── MBProgressHUD.h │ ├── MBProgressHUD.m │ ├── XCHudHelper.h │ └── XCHudHelper.m ├── Info.plist ├── ViewController.h ├── ViewController.m ├── avplayerVC.h ├── avplayerVC.m ├── main.m └── screenShot └── 1.png /.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 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TBPlayer 2 | 3 | 4 | 5 | >添加cocoapods 中,请下载[1.0tag](https://github.com/suifengqjn/TBPlayer/archive/1.0.zip)(https://github.com/suifengqjn/TBPlayer/archive/1.0.zip) 6 | 7 | 8 | 视频变下变播,把播放器播放过的数据流缓存到本地,支持拖动。采用avplayer 9 | 10 | 实现avplayer状态的捕获和细节的处理 11 | 12 | 关于这个dome写的一篇文章:[文章地址:http://www.jianshu.com/p/990ee3db0563](http://www.jianshu.com/p/990ee3db0563) 13 |
14 | 15 | * 如果你觉得不错,还请为我star一个, 16 | * 如果在使用过程中遇到BUG,希望你能Issues我,谢谢 17 | 18 | ###用法 19 | 20 | 需要的变量 21 | url:视频网址 22 | showView:放视频的视图 23 | ``` 24 | [[TBPlayer sharedInstance] playWithUrl:url2 showView:self.view]; 25 | ``` 26 | 27 | 另外自己可以在` TBVideoRequestTask ` 中设置视频的缓存路径,下次播放直接从缓冲读取即可。 28 | 29 | 30 | -------------------------------------------------------------------------------- /TBPlayer.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "TBPlayer" 3 | s.version = "1.0" 4 | s.summary = "An easy way to use avPlayer" 5 | s.description = <<-DESC 6 | TBPlayer is base on AVplayer 7 | DESC 8 | s.homepage = "https://github.com/suifengqjn/TBPlayer" 9 | s.license = { :type => 'MIT', :file => 'LICENSE' } 10 | s.author = { 'qianjianeng' => '329426491@qq.comm' } 11 | s.source = { :git => "https://github.com/suifengqjn/TBPlayer.git", :tag => '1.0' } 12 | s.source_files = 'TBPlayer/Classes/**/*.{h,m}' 13 | #s.resource = 'TBPlayer/Classes/TBPlayer.bundle' 14 | #s.frameworks = "CoreGraphics", "QuartzCore" 15 | s.requires_arc = true 16 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '3.0' } 17 | end -------------------------------------------------------------------------------- /TBPlayer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1D14A90D1DE208600003BBEC /* MBProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D14A9091DE208600003BBEC /* MBProgressHUD.m */; }; 11 | 1D14A90E1DE208600003BBEC /* XCHudHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D14A90B1DE208600003BBEC /* XCHudHelper.m */; }; 12 | 1D4EE0EC1DDBFF0800DB2892 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D4EE0EB1DDBFF0800DB2892 /* main.m */; }; 13 | 1D4EE0EF1DDBFF0800DB2892 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D4EE0EE1DDBFF0800DB2892 /* AppDelegate.m */; }; 14 | 1D4EE0F51DDBFF0800DB2892 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1D4EE0F31DDBFF0800DB2892 /* Main.storyboard */; }; 15 | 1D4EE0F71DDBFF0800DB2892 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1D4EE0F61DDBFF0800DB2892 /* Assets.xcassets */; }; 16 | 1D4EE0FA1DDBFF0800DB2892 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1D4EE0F81DDBFF0800DB2892 /* LaunchScreen.storyboard */; }; 17 | 1D4EE10F1DDBFF7900DB2892 /* TBloaderURLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D4EE1031DDBFF7900DB2892 /* TBloaderURLConnection.m */; }; 18 | 1D4EE1101DDBFF7900DB2892 /* TBPlayer.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 1D4EE1041DDBFF7900DB2892 /* TBPlayer.bundle */; }; 19 | 1D4EE1111DDBFF7900DB2892 /* TBPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D4EE1061DDBFF7900DB2892 /* TBPlayer.m */; }; 20 | 1D4EE1121DDBFF7900DB2892 /* TBVideoRequestTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D4EE1081DDBFF7900DB2892 /* TBVideoRequestTask.m */; }; 21 | 1D4EE1181DDBFF7F00DB2892 /* 1.png in Resources */ = {isa = PBXBuildFile; fileRef = 1D4EE1171DDBFF7F00DB2892 /* 1.png */; }; 22 | 1D4EE11D1DDBFF9400DB2892 /* avplayerVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D4EE11A1DDBFF9400DB2892 /* avplayerVC.m */; }; 23 | 1D4EE11E1DDBFF9400DB2892 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D4EE11C1DDBFF9400DB2892 /* ViewController.m */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 1D14A9081DE208600003BBEC /* MBProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBProgressHUD.h; sourceTree = ""; }; 28 | 1D14A9091DE208600003BBEC /* MBProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBProgressHUD.m; sourceTree = ""; }; 29 | 1D14A90A1DE208600003BBEC /* XCHudHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCHudHelper.h; sourceTree = ""; }; 30 | 1D14A90B1DE208600003BBEC /* XCHudHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCHudHelper.m; sourceTree = ""; }; 31 | 1D4EE0E71DDBFF0800DB2892 /* TBPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TBPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 1D4EE0EB1DDBFF0800DB2892 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 33 | 1D4EE0ED1DDBFF0800DB2892 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 34 | 1D4EE0EE1DDBFF0800DB2892 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 35 | 1D4EE0F41DDBFF0800DB2892 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 36 | 1D4EE0F61DDBFF0800DB2892 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | 1D4EE0F91DDBFF0800DB2892 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 38 | 1D4EE0FB1DDBFF0800DB2892 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 1D4EE1021DDBFF7900DB2892 /* TBloaderURLConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBloaderURLConnection.h; sourceTree = ""; }; 40 | 1D4EE1031DDBFF7900DB2892 /* TBloaderURLConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBloaderURLConnection.m; sourceTree = ""; }; 41 | 1D4EE1041DDBFF7900DB2892 /* TBPlayer.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TBPlayer.bundle; sourceTree = ""; }; 42 | 1D4EE1051DDBFF7900DB2892 /* TBPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBPlayer.h; sourceTree = ""; }; 43 | 1D4EE1061DDBFF7900DB2892 /* TBPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBPlayer.m; sourceTree = ""; }; 44 | 1D4EE1071DDBFF7900DB2892 /* TBVideoRequestTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBVideoRequestTask.h; sourceTree = ""; }; 45 | 1D4EE1081DDBFF7900DB2892 /* TBVideoRequestTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBVideoRequestTask.m; sourceTree = ""; }; 46 | 1D4EE1171DDBFF7F00DB2892 /* 1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 1.png; sourceTree = ""; }; 47 | 1D4EE1191DDBFF9400DB2892 /* avplayerVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = avplayerVC.h; sourceTree = ""; }; 48 | 1D4EE11A1DDBFF9400DB2892 /* avplayerVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = avplayerVC.m; sourceTree = ""; }; 49 | 1D4EE11B1DDBFF9400DB2892 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 50 | 1D4EE11C1DDBFF9400DB2892 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 1D4EE0E41DDBFF0800DB2892 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 1D14A9061DE208600003BBEC /* XCToast */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 1D14A9081DE208600003BBEC /* MBProgressHUD.h */, 68 | 1D14A9091DE208600003BBEC /* MBProgressHUD.m */, 69 | 1D14A90A1DE208600003BBEC /* XCHudHelper.h */, 70 | 1D14A90B1DE208600003BBEC /* XCHudHelper.m */, 71 | ); 72 | path = XCToast; 73 | sourceTree = ""; 74 | }; 75 | 1D4EE0DE1DDBFF0800DB2892 = { 76 | isa = PBXGroup; 77 | children = ( 78 | 1D4EE0E91DDBFF0800DB2892 /* TBPlayer */, 79 | 1D4EE0E81DDBFF0800DB2892 /* Products */, 80 | ); 81 | sourceTree = ""; 82 | }; 83 | 1D4EE0E81DDBFF0800DB2892 /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 1D4EE0E71DDBFF0800DB2892 /* TBPlayer.app */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | 1D4EE0E91DDBFF0800DB2892 /* TBPlayer */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 1D4EE1161DDBFF7F00DB2892 /* screenShot */, 95 | 1D4EE1011DDBFF7900DB2892 /* Classes */, 96 | 1D4EE0ED1DDBFF0800DB2892 /* AppDelegate.h */, 97 | 1D4EE0EE1DDBFF0800DB2892 /* AppDelegate.m */, 98 | 1D4EE1191DDBFF9400DB2892 /* avplayerVC.h */, 99 | 1D4EE11A1DDBFF9400DB2892 /* avplayerVC.m */, 100 | 1D4EE11B1DDBFF9400DB2892 /* ViewController.h */, 101 | 1D4EE11C1DDBFF9400DB2892 /* ViewController.m */, 102 | 1D4EE0F31DDBFF0800DB2892 /* Main.storyboard */, 103 | 1D4EE0F61DDBFF0800DB2892 /* Assets.xcassets */, 104 | 1D4EE0F81DDBFF0800DB2892 /* LaunchScreen.storyboard */, 105 | 1D4EE0FB1DDBFF0800DB2892 /* Info.plist */, 106 | 1D4EE0EA1DDBFF0800DB2892 /* Supporting Files */, 107 | ); 108 | path = TBPlayer; 109 | sourceTree = ""; 110 | }; 111 | 1D4EE0EA1DDBFF0800DB2892 /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 1D4EE0EB1DDBFF0800DB2892 /* main.m */, 115 | ); 116 | name = "Supporting Files"; 117 | sourceTree = ""; 118 | }; 119 | 1D4EE1011DDBFF7900DB2892 /* Classes */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 1D14A9061DE208600003BBEC /* XCToast */, 123 | 1D4EE1021DDBFF7900DB2892 /* TBloaderURLConnection.h */, 124 | 1D4EE1031DDBFF7900DB2892 /* TBloaderURLConnection.m */, 125 | 1D4EE1041DDBFF7900DB2892 /* TBPlayer.bundle */, 126 | 1D4EE1051DDBFF7900DB2892 /* TBPlayer.h */, 127 | 1D4EE1061DDBFF7900DB2892 /* TBPlayer.m */, 128 | 1D4EE1071DDBFF7900DB2892 /* TBVideoRequestTask.h */, 129 | 1D4EE1081DDBFF7900DB2892 /* TBVideoRequestTask.m */, 130 | ); 131 | path = Classes; 132 | sourceTree = ""; 133 | }; 134 | 1D4EE1161DDBFF7F00DB2892 /* screenShot */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 1D4EE1171DDBFF7F00DB2892 /* 1.png */, 138 | ); 139 | path = screenShot; 140 | sourceTree = ""; 141 | }; 142 | /* End PBXGroup section */ 143 | 144 | /* Begin PBXNativeTarget section */ 145 | 1D4EE0E61DDBFF0800DB2892 /* TBPlayer */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = 1D4EE0FE1DDBFF0800DB2892 /* Build configuration list for PBXNativeTarget "TBPlayer" */; 148 | buildPhases = ( 149 | 1D4EE0E31DDBFF0800DB2892 /* Sources */, 150 | 1D4EE0E41DDBFF0800DB2892 /* Frameworks */, 151 | 1D4EE0E51DDBFF0800DB2892 /* Resources */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = TBPlayer; 158 | productName = TBPlayer; 159 | productReference = 1D4EE0E71DDBFF0800DB2892 /* TBPlayer.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | 1D4EE0DF1DDBFF0800DB2892 /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | LastUpgradeCheck = 0810; 169 | ORGANIZATIONNAME = SF; 170 | TargetAttributes = { 171 | 1D4EE0E61DDBFF0800DB2892 = { 172 | CreatedOnToolsVersion = 8.1; 173 | DevelopmentTeam = VN226642G5; 174 | ProvisioningStyle = Automatic; 175 | }; 176 | }; 177 | }; 178 | buildConfigurationList = 1D4EE0E21DDBFF0800DB2892 /* Build configuration list for PBXProject "TBPlayer" */; 179 | compatibilityVersion = "Xcode 3.2"; 180 | developmentRegion = English; 181 | hasScannedForEncodings = 0; 182 | knownRegions = ( 183 | en, 184 | Base, 185 | ); 186 | mainGroup = 1D4EE0DE1DDBFF0800DB2892; 187 | productRefGroup = 1D4EE0E81DDBFF0800DB2892 /* Products */; 188 | projectDirPath = ""; 189 | projectRoot = ""; 190 | targets = ( 191 | 1D4EE0E61DDBFF0800DB2892 /* TBPlayer */, 192 | ); 193 | }; 194 | /* End PBXProject section */ 195 | 196 | /* Begin PBXResourcesBuildPhase section */ 197 | 1D4EE0E51DDBFF0800DB2892 /* Resources */ = { 198 | isa = PBXResourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | 1D4EE0FA1DDBFF0800DB2892 /* LaunchScreen.storyboard in Resources */, 202 | 1D4EE0F71DDBFF0800DB2892 /* Assets.xcassets in Resources */, 203 | 1D4EE1181DDBFF7F00DB2892 /* 1.png in Resources */, 204 | 1D4EE1101DDBFF7900DB2892 /* TBPlayer.bundle in Resources */, 205 | 1D4EE0F51DDBFF0800DB2892 /* Main.storyboard in Resources */, 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | /* End PBXResourcesBuildPhase section */ 210 | 211 | /* Begin PBXSourcesBuildPhase section */ 212 | 1D4EE0E31DDBFF0800DB2892 /* Sources */ = { 213 | isa = PBXSourcesBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | 1D4EE11E1DDBFF9400DB2892 /* ViewController.m in Sources */, 217 | 1D4EE1121DDBFF7900DB2892 /* TBVideoRequestTask.m in Sources */, 218 | 1D4EE0EF1DDBFF0800DB2892 /* AppDelegate.m in Sources */, 219 | 1D4EE1111DDBFF7900DB2892 /* TBPlayer.m in Sources */, 220 | 1D4EE11D1DDBFF9400DB2892 /* avplayerVC.m in Sources */, 221 | 1D14A90E1DE208600003BBEC /* XCHudHelper.m in Sources */, 222 | 1D4EE10F1DDBFF7900DB2892 /* TBloaderURLConnection.m in Sources */, 223 | 1D4EE0EC1DDBFF0800DB2892 /* main.m in Sources */, 224 | 1D14A90D1DE208600003BBEC /* MBProgressHUD.m in Sources */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | /* End PBXSourcesBuildPhase section */ 229 | 230 | /* Begin PBXVariantGroup section */ 231 | 1D4EE0F31DDBFF0800DB2892 /* Main.storyboard */ = { 232 | isa = PBXVariantGroup; 233 | children = ( 234 | 1D4EE0F41DDBFF0800DB2892 /* Base */, 235 | ); 236 | name = Main.storyboard; 237 | sourceTree = ""; 238 | }; 239 | 1D4EE0F81DDBFF0800DB2892 /* LaunchScreen.storyboard */ = { 240 | isa = PBXVariantGroup; 241 | children = ( 242 | 1D4EE0F91DDBFF0800DB2892 /* Base */, 243 | ); 244 | name = LaunchScreen.storyboard; 245 | sourceTree = ""; 246 | }; 247 | /* End PBXVariantGroup section */ 248 | 249 | /* Begin XCBuildConfiguration section */ 250 | 1D4EE0FC1DDBFF0800DB2892 /* Debug */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | CLANG_ANALYZER_NONNULL = YES; 255 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 256 | CLANG_CXX_LIBRARY = "libc++"; 257 | CLANG_ENABLE_MODULES = YES; 258 | CLANG_ENABLE_OBJC_ARC = YES; 259 | CLANG_WARN_BOOL_CONVERSION = YES; 260 | CLANG_WARN_CONSTANT_CONVERSION = YES; 261 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 262 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INFINITE_RECURSION = YES; 266 | CLANG_WARN_INT_CONVERSION = YES; 267 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 268 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 269 | CLANG_WARN_UNREACHABLE_CODE = YES; 270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 271 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 272 | COPY_PHASE_STRIP = NO; 273 | DEBUG_INFORMATION_FORMAT = dwarf; 274 | ENABLE_STRICT_OBJC_MSGSEND = YES; 275 | ENABLE_TESTABILITY = YES; 276 | GCC_C_LANGUAGE_STANDARD = gnu99; 277 | GCC_DYNAMIC_NO_PIC = NO; 278 | GCC_NO_COMMON_BLOCKS = YES; 279 | GCC_OPTIMIZATION_LEVEL = 0; 280 | GCC_PREPROCESSOR_DEFINITIONS = ( 281 | "DEBUG=1", 282 | "$(inherited)", 283 | ); 284 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 285 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 286 | GCC_WARN_UNDECLARED_SELECTOR = YES; 287 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 288 | GCC_WARN_UNUSED_FUNCTION = YES; 289 | GCC_WARN_UNUSED_VARIABLE = YES; 290 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 291 | MTL_ENABLE_DEBUG_INFO = YES; 292 | ONLY_ACTIVE_ARCH = YES; 293 | SDKROOT = iphoneos; 294 | }; 295 | name = Debug; 296 | }; 297 | 1D4EE0FD1DDBFF0800DB2892 /* Release */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ALWAYS_SEARCH_USER_PATHS = NO; 301 | CLANG_ANALYZER_NONNULL = YES; 302 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 303 | CLANG_CXX_LIBRARY = "libc++"; 304 | CLANG_ENABLE_MODULES = YES; 305 | CLANG_ENABLE_OBJC_ARC = YES; 306 | CLANG_WARN_BOOL_CONVERSION = YES; 307 | CLANG_WARN_CONSTANT_CONVERSION = YES; 308 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 309 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 310 | CLANG_WARN_EMPTY_BODY = YES; 311 | CLANG_WARN_ENUM_CONVERSION = YES; 312 | CLANG_WARN_INFINITE_RECURSION = YES; 313 | CLANG_WARN_INT_CONVERSION = YES; 314 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 315 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 316 | CLANG_WARN_UNREACHABLE_CODE = YES; 317 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 318 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 319 | COPY_PHASE_STRIP = NO; 320 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 321 | ENABLE_NS_ASSERTIONS = NO; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | GCC_C_LANGUAGE_STANDARD = gnu99; 324 | GCC_NO_COMMON_BLOCKS = YES; 325 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 326 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 327 | GCC_WARN_UNDECLARED_SELECTOR = YES; 328 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 329 | GCC_WARN_UNUSED_FUNCTION = YES; 330 | GCC_WARN_UNUSED_VARIABLE = YES; 331 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 332 | MTL_ENABLE_DEBUG_INFO = NO; 333 | SDKROOT = iphoneos; 334 | VALIDATE_PRODUCT = YES; 335 | }; 336 | name = Release; 337 | }; 338 | 1D4EE0FF1DDBFF0800DB2892 /* Debug */ = { 339 | isa = XCBuildConfiguration; 340 | buildSettings = { 341 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 342 | DEVELOPMENT_TEAM = VN226642G5; 343 | INFOPLIST_FILE = TBPlayer/Info.plist; 344 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 345 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 346 | PRODUCT_BUNDLE_IDENTIFIER = com.suifeng.TBPlayer; 347 | PRODUCT_NAME = "$(TARGET_NAME)"; 348 | }; 349 | name = Debug; 350 | }; 351 | 1D4EE1001DDBFF0800DB2892 /* Release */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 355 | DEVELOPMENT_TEAM = VN226642G5; 356 | INFOPLIST_FILE = TBPlayer/Info.plist; 357 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 358 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 359 | PRODUCT_BUNDLE_IDENTIFIER = com.suifeng.TBPlayer; 360 | PRODUCT_NAME = "$(TARGET_NAME)"; 361 | }; 362 | name = Release; 363 | }; 364 | /* End XCBuildConfiguration section */ 365 | 366 | /* Begin XCConfigurationList section */ 367 | 1D4EE0E21DDBFF0800DB2892 /* Build configuration list for PBXProject "TBPlayer" */ = { 368 | isa = XCConfigurationList; 369 | buildConfigurations = ( 370 | 1D4EE0FC1DDBFF0800DB2892 /* Debug */, 371 | 1D4EE0FD1DDBFF0800DB2892 /* Release */, 372 | ); 373 | defaultConfigurationIsVisible = 0; 374 | defaultConfigurationName = Release; 375 | }; 376 | 1D4EE0FE1DDBFF0800DB2892 /* Build configuration list for PBXNativeTarget "TBPlayer" */ = { 377 | isa = XCConfigurationList; 378 | buildConfigurations = ( 379 | 1D4EE0FF1DDBFF0800DB2892 /* Debug */, 380 | 1D4EE1001DDBFF0800DB2892 /* Release */, 381 | ); 382 | defaultConfigurationIsVisible = 0; 383 | defaultConfigurationName = Release; 384 | }; 385 | /* End XCConfigurationList section */ 386 | }; 387 | rootObject = 1D4EE0DF1DDBFF0800DB2892 /* Project object */; 388 | } 389 | -------------------------------------------------------------------------------- /TBPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TBPlayer/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TBPlayer 4 | // 5 | // Created by qianjn on 2016/11/16. 6 | // Copyright © 2016年 SF. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /TBPlayer/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TBPlayer 4 | // 5 | // Created by qianjn on 2016/11/16. 6 | // Copyright © 2016年 SF. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /TBPlayer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /TBPlayer/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 | -------------------------------------------------------------------------------- /TBPlayer/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 | -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.bundle/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringsTable 6 | Root 7 | PreferenceSpecifiers 8 | 9 | 10 | Type 11 | PSGroupSpecifier 12 | Title 13 | Group 14 | 15 | 16 | Type 17 | PSTextFieldSpecifier 18 | Title 19 | Name 20 | Key 21 | name_preference 22 | DefaultValue 23 | 24 | IsSecure 25 | 26 | KeyboardType 27 | Alphabet 28 | AutocapitalizationType 29 | None 30 | AutocorrectionType 31 | No 32 | 33 | 34 | Type 35 | PSToggleSwitchSpecifier 36 | Title 37 | Enabled 38 | Key 39 | enabled_preference 40 | DefaultValue 41 | 42 | 43 | 44 | Type 45 | PSSliderSpecifier 46 | Key 47 | slider_preference 48 | DefaultValue 49 | 0.5 50 | MinimumValue 51 | 0 52 | MaximumValue 53 | 1 54 | MinimumValueImage 55 | 56 | MaximumValueImage 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.bundle/en.lproj/Root.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suifengqjn/TBPlayer/89425622e2acc8df400f0ce0fe4d7b8193678107/TBPlayer/Classes/TBPlayer.bundle/en.lproj/Root.strings -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.bundle/images/icon_pause@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suifengqjn/TBPlayer/89425622e2acc8df400f0ce0fe4d7b8193678107/TBPlayer/Classes/TBPlayer.bundle/images/icon_pause@2x.png -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.bundle/images/icon_pause@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suifengqjn/TBPlayer/89425622e2acc8df400f0ce0fe4d7b8193678107/TBPlayer/Classes/TBPlayer.bundle/images/icon_pause@3x.png -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.bundle/images/icon_play@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suifengqjn/TBPlayer/89425622e2acc8df400f0ce0fe4d7b8193678107/TBPlayer/Classes/TBPlayer.bundle/images/icon_play@2x.png -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.bundle/images/icon_play@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suifengqjn/TBPlayer/89425622e2acc8df400f0ce0fe4d7b8193678107/TBPlayer/Classes/TBPlayer.bundle/images/icon_play@3x.png -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.bundle/images/icon_play_hl@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suifengqjn/TBPlayer/89425622e2acc8df400f0ce0fe4d7b8193678107/TBPlayer/Classes/TBPlayer.bundle/images/icon_play_hl@2x.png -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.bundle/images/icon_play_hl@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suifengqjn/TBPlayer/89425622e2acc8df400f0ce0fe4d7b8193678107/TBPlayer/Classes/TBPlayer.bundle/images/icon_play_hl@3x.png -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.bundle/images/quanping@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suifengqjn/TBPlayer/89425622e2acc8df400f0ce0fe4d7b8193678107/TBPlayer/Classes/TBPlayer.bundle/images/quanping@2x.png -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.bundle/images/quanping@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suifengqjn/TBPlayer/89425622e2acc8df400f0ce0fe4d7b8193678107/TBPlayer/Classes/TBPlayer.bundle/images/quanping@3x.png -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBPlayer.h 3 | // TBPlayer 4 | // 5 | // Created by qianjianeng on 16/1/31. 6 | // Copyright © 2016年 SF. All rights reserved. 7 | //// github地址:https://github.com/suifengqjn/TBPlayer 8 | 9 | 10 | #import 11 | #import 12 | 13 | FOUNDATION_EXPORT NSString *const kTBPlayerStateChangedNotification; 14 | FOUNDATION_EXPORT NSString *const kTBPlayerProgressChangedNotification; 15 | FOUNDATION_EXPORT NSString *const kTBPlayerLoadProgressChangedNotification; 16 | 17 | //播放器的几种状态 18 | typedef NS_ENUM(NSInteger, TBPlayerState) { 19 | TBPlayerStateBuffering = 1, 20 | TBPlayerStatePlaying = 2, 21 | TBPlayerStateStopped = 3, 22 | TBPlayerStatePause = 4 23 | }; 24 | 25 | @interface TBPlayer : NSObject 26 | 27 | @property (nonatomic, readonly) TBPlayerState state; 28 | @property (nonatomic, readonly) CGFloat loadedProgress; //缓冲进度 29 | @property (nonatomic, readonly) CGFloat duration; //视频总时间 30 | @property (nonatomic, readonly) CGFloat current; //当前播放时间 31 | @property (nonatomic, readonly) CGFloat progress; //播放进度 0~1 32 | @property (nonatomic ) BOOL stopWhenAppDidEnterBackground;// default is YES 33 | 34 | 35 | + (instancetype)sharedInstance; 36 | - (void)playWithUrl:(NSURL *)url showView:(UIView *)showView; 37 | - (void)seekToTime:(CGFloat)seconds; 38 | 39 | - (void)resume; 40 | - (void)pause; 41 | - (void)stop; 42 | 43 | - (void)fullScreen; //全屏 44 | - (void)halfScreen; //半屏 45 | @end 46 | -------------------------------------------------------------------------------- /TBPlayer/Classes/TBPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBPlayer.m 3 | // TBPlayer 4 | // 5 | // Created by qianjianeng on 16/1/31. 6 | // Copyright © 2016年 SF. All rights reserved. 7 | // 8 | //// github地址:https://github.com/suifengqjn/TBPlayer 9 | 10 | #import "TBPlayer.h" 11 | #import "TBloaderURLConnection.h" 12 | #import "TBVideoRequestTask.h" 13 | #import "XCHudHelper.h" 14 | #define kScreenHeight ([UIScreen mainScreen].bounds.size.height) 15 | #define kScreenWidth ([UIScreen mainScreen].bounds.size.width) 16 | #define IOS_VERSION ([[[UIDevice currentDevice] systemVersion] floatValue]) 17 | NSString *const kTBPlayerStateChangedNotification = @"TBPlayerStateChangedNotification"; 18 | NSString *const kTBPlayerProgressChangedNotification = @"TBPlayerProgressChangedNotification"; 19 | NSString *const kTBPlayerLoadProgressChangedNotification = @"TBPlayerLoadProgressChangedNotification"; 20 | 21 | 22 | @interface TBPlayer () 23 | 24 | @property (nonatomic ) TBPlayerState state; 25 | @property (nonatomic ) CGFloat loadedProgress;//缓冲进度 26 | @property (nonatomic ) CGFloat duration;//视频总时间 27 | @property (nonatomic ) CGFloat current;//当前播放时间 28 | 29 | @property (nonatomic, strong) AVURLAsset *videoURLAsset; 30 | @property (nonatomic, strong) AVAsset *videoAsset; 31 | @property (nonatomic, strong) TBloaderURLConnection *resouerLoader; 32 | @property (nonatomic, strong) AVPlayer *player; 33 | @property (nonatomic, strong) AVPlayerItem *currentPlayerItem; 34 | @property (nonatomic, strong) AVPlayerLayer *currentPlayerLayer; 35 | @property (nonatomic, strong) NSObject *playbackTimeObserver; 36 | @property (nonatomic ) BOOL isPauseByUser; //是否被用户暂停 37 | @property (nonatomic, ) BOOL isLocalVideo; //是否播放本地文件 38 | @property (nonatomic, ) BOOL isFinishLoad; //是否下载完毕 39 | 40 | @property (nonatomic, weak) UIView *showView; 41 | 42 | @property (nonatomic, strong) UIView *navBar; 43 | @property (nonatomic, strong) UILabel *currentTimeLabel; 44 | @property (nonatomic, strong) UILabel *totolTimeLabel; 45 | @property (nonatomic, strong) UIProgressView *videoProgressView; //缓冲进度条 46 | @property (nonatomic, strong) UISlider *playSlider; //滑竿 47 | @property (nonatomic, strong) UIButton *stopButton;//播放暂停按钮 48 | @property (nonatomic, strong) UIButton *screenBUtton;//全屏按钮 49 | @property (nonatomic, assign) BOOL isFullScreen; 50 | 51 | @end 52 | 53 | @implementation TBPlayer 54 | 55 | + (instancetype)sharedInstance { 56 | static dispatch_once_t onceToken; 57 | static id _sInstance; 58 | dispatch_once(&onceToken, ^{ 59 | _sInstance = [[self alloc] init]; 60 | }); 61 | 62 | return _sInstance; 63 | } 64 | 65 | - (instancetype)init 66 | { 67 | self = [super init]; 68 | if (self) { 69 | _isPauseByUser = YES; 70 | _loadedProgress = 0; 71 | _duration = 0; 72 | _current = 0; 73 | _state = TBPlayerStateStopped; 74 | _stopWhenAppDidEnterBackground = YES; 75 | } 76 | return self; 77 | } 78 | 79 | //清空播放器监听属性 80 | - (void)releasePlayer 81 | { 82 | if (!self.currentPlayerItem) { 83 | return; 84 | } 85 | 86 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 87 | [self.currentPlayerItem removeObserver:self forKeyPath:@"status"]; 88 | [self.currentPlayerItem removeObserver:self forKeyPath:@"loadedTimeRanges"]; 89 | [self.currentPlayerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"]; 90 | [self.currentPlayerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"]; 91 | [self.player removeTimeObserver:self.playbackTimeObserver]; 92 | self.playbackTimeObserver = nil; 93 | self.currentPlayerItem = nil; 94 | } 95 | 96 | - (void)playWithUrl:(NSURL *)url showView:(UIView *)showView 97 | { 98 | 99 | [self.player pause]; 100 | [self releasePlayer]; 101 | self.isPauseByUser = NO; 102 | self.loadedProgress = 0; 103 | self.duration = 0; 104 | self.current = 0; 105 | 106 | _showView = showView; 107 | 108 | NSString *str = [url absoluteString]; 109 | //如果是ios < 7 或者是本地资源,直接播放 110 | if (IOS_VERSION < 7.0 || ![str hasPrefix:@"http"]) { 111 | self.videoAsset = [AVURLAsset URLAssetWithURL:url options:nil]; 112 | self.currentPlayerItem = [AVPlayerItem playerItemWithAsset:_videoAsset]; 113 | if (!self.player) { 114 | self.player = [AVPlayer playerWithPlayerItem:self.currentPlayerItem]; 115 | } else { 116 | [self.player replaceCurrentItemWithPlayerItem:self.currentPlayerItem]; 117 | } 118 | self.currentPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; 119 | self.currentPlayerLayer.frame = CGRectMake(0, 44, showView.bounds.size.width, showView.bounds.size.height-44); 120 | _isLocalVideo = YES; 121 | 122 | } else { //ios7以上采用resourceLoader给播放器补充数据 123 | 124 | self.resouerLoader = [[TBloaderURLConnection alloc] init]; 125 | self.resouerLoader.delegate = self; 126 | NSURL *playUrl = [_resouerLoader getSchemeVideoURL:url]; 127 | self.videoURLAsset = [AVURLAsset URLAssetWithURL:playUrl options:nil]; 128 | [_videoURLAsset.resourceLoader setDelegate:_resouerLoader queue:dispatch_get_main_queue()]; 129 | self.currentPlayerItem = [AVPlayerItem playerItemWithAsset:_videoURLAsset]; 130 | 131 | if (!self.player) { 132 | self.player = [AVPlayer playerWithPlayerItem:self.currentPlayerItem]; 133 | } else { 134 | [self.player replaceCurrentItemWithPlayerItem:self.currentPlayerItem]; 135 | } 136 | self.currentPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; 137 | self.currentPlayerLayer.frame = CGRectMake(0, 44, showView.bounds.size.width, showView.bounds.size.height-44); 138 | _isLocalVideo = NO; 139 | 140 | } 141 | 142 | [showView.layer addSublayer:self.currentPlayerLayer]; 143 | 144 | [self.currentPlayerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; 145 | [self.currentPlayerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; 146 | [self.currentPlayerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil]; 147 | [self.currentPlayerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil]; 148 | 149 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationWillResignActiveNotification object:nil]; 150 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterPlayGround) name:UIApplicationDidBecomeActiveNotification object:nil]; 151 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.currentPlayerItem]; 152 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemPlaybackStalled:) name:AVPlayerItemPlaybackStalledNotification object:self.currentPlayerItem]; 153 | 154 | 155 | // 本地文件不设置TBPlayerStateBuffering状态 156 | if ([url.scheme isEqualToString:@"file"]) { 157 | 158 | // 如果已经在TBPlayerStatePlaying,则直接发通知,否则设置状态 159 | if (self.state == TBPlayerStatePlaying) { 160 | [[NSNotificationCenter defaultCenter] postNotificationName:kTBPlayerStateChangedNotification object:nil]; 161 | } else { 162 | self.state = TBPlayerStatePlaying; 163 | } 164 | 165 | } else { 166 | 167 | // 如果已经在TBPlayerStateBuffering,则直接发通知,否则设置状态 168 | if (self.state == TBPlayerStateBuffering) { 169 | [[NSNotificationCenter defaultCenter] postNotificationName:kTBPlayerStateChangedNotification object:nil]; 170 | } else { 171 | self.state = TBPlayerStateBuffering; 172 | } 173 | 174 | } 175 | 176 | 177 | if (!_navBar) { 178 | _navBar = [[UIView alloc] init]; 179 | [showView addSubview:_navBar]; 180 | } 181 | [self buildVideoNavBar]; 182 | 183 | [[XCHudHelper sharedInstance] showHudOnView:_showView caption:nil image:nil acitivity:YES autoHideTime:0]; 184 | 185 | [[NSNotificationCenter defaultCenter] postNotificationName:kTBPlayerProgressChangedNotification object:nil]; 186 | 187 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(halfScreen)]; 188 | 189 | [showView addGestureRecognizer:tap]; 190 | } 191 | 192 | 193 | - (void)fullScreen 194 | { 195 | _navBar.hidden = YES; 196 | self.currentPlayerLayer.transform = CATransform3DMakeRotation(M_PI/2, 0, 0, 1); 197 | self.currentPlayerLayer.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight); 198 | } 199 | 200 | - (void)halfScreen 201 | { 202 | _navBar.hidden = NO; 203 | self.currentPlayerLayer.transform = CATransform3DIdentity; 204 | self.currentPlayerLayer.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight); 205 | } 206 | 207 | - (void)seekToTime:(CGFloat)seconds 208 | { 209 | if (self.state == TBPlayerStateStopped) { 210 | return; 211 | } 212 | 213 | seconds = MAX(0, seconds); 214 | seconds = MIN(seconds, self.duration); 215 | 216 | [self.player pause]; 217 | [self.player seekToTime:CMTimeMakeWithSeconds(seconds, NSEC_PER_SEC) completionHandler:^(BOOL finished) { 218 | self.isPauseByUser = NO; 219 | [self.player play]; 220 | if (!self.currentPlayerItem.isPlaybackLikelyToKeepUp) { 221 | self.state = TBPlayerStateBuffering; 222 | [[XCHudHelper sharedInstance] showHudOnView:_showView caption:nil image:nil acitivity:YES autoHideTime:0]; 223 | } 224 | 225 | }]; 226 | } 227 | 228 | - (void)resumeOrPause 229 | { 230 | if (!self.currentPlayerItem) { 231 | return; 232 | } 233 | if (self.state == TBPlayerStatePlaying) { 234 | [_stopButton setImage:[self imageNamed:@"icon_play"] forState:UIControlStateNormal]; 235 | [_stopButton setImage:[self imageNamed:@"icon_play_hl"] forState:UIControlStateHighlighted]; 236 | [self.player pause]; 237 | self.state = TBPlayerStatePause; 238 | } else if (self.state == TBPlayerStatePause) { 239 | [_stopButton setImage:[self imageNamed:@"icon_pause"] forState:UIControlStateNormal]; 240 | [_stopButton setImage:[self imageNamed:@"icon_pause_hl"] forState:UIControlStateHighlighted]; 241 | [self.player play]; 242 | self.state = TBPlayerStatePlaying; 243 | } 244 | self.isPauseByUser = YES; 245 | } 246 | 247 | - (void)resume 248 | { 249 | if (!self.currentPlayerItem) { 250 | return; 251 | } 252 | 253 | [_stopButton setImage:[self imageNamed:@"icon_pause"] forState:UIControlStateNormal]; 254 | [_stopButton setImage:[self imageNamed:@"icon_pause_hl"] forState:UIControlStateHighlighted]; 255 | self.isPauseByUser = NO; 256 | [self.player play]; 257 | } 258 | 259 | - (void)pause 260 | { 261 | if (!self.currentPlayerItem) { 262 | return; 263 | } 264 | [_stopButton setImage:[self imageNamed:@"icon_play"] forState:UIControlStateNormal]; 265 | [_stopButton setImage:[self imageNamed:@"icon_play_hl"] forState:UIControlStateHighlighted]; 266 | self.isPauseByUser = YES; 267 | self.state = TBPlayerStatePause; 268 | [self.player pause]; 269 | } 270 | 271 | - (void)stop 272 | { 273 | self.isPauseByUser = YES; 274 | self.loadedProgress = 0; 275 | self.duration = 0; 276 | self.current = 0; 277 | self.state = TBPlayerStateStopped; 278 | [self.player pause]; 279 | [self releasePlayer]; 280 | [[NSNotificationCenter defaultCenter] postNotificationName:kTBPlayerProgressChangedNotification object:nil]; 281 | } 282 | 283 | 284 | - (CGFloat)progress 285 | { 286 | if (self.duration > 0) { 287 | return self.current / self.duration; 288 | } 289 | 290 | return 0; 291 | } 292 | 293 | 294 | #pragma mark - observer 295 | 296 | - (void)appDidEnterBackground 297 | { 298 | if (self.stopWhenAppDidEnterBackground) { 299 | [self pause]; 300 | self.state = TBPlayerStatePause; 301 | self.isPauseByUser = NO; 302 | } 303 | } 304 | - (void)appDidEnterPlayGround 305 | { 306 | if (!self.isPauseByUser) { 307 | [self resume]; 308 | self.state = TBPlayerStatePlaying; 309 | } 310 | } 311 | 312 | - (void)playerItemDidPlayToEnd:(NSNotification *)notification 313 | { 314 | [self stop]; 315 | } 316 | 317 | //在监听播放器状态中处理比较准确 318 | - (void)playerItemPlaybackStalled:(NSNotification *)notification 319 | { 320 | // 这里网络不好的时候,就会进入,不做处理,会在playbackBufferEmpty里面缓存之后重新播放 321 | NSLog(@"buffing-----buffing"); 322 | } 323 | 324 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 325 | { 326 | AVPlayerItem *playerItem = (AVPlayerItem *)object; 327 | 328 | if ([keyPath isEqualToString:@"status"]) { 329 | if ([playerItem status] == AVPlayerStatusReadyToPlay) { 330 | [self monitoringPlayback:playerItem];// 给播放器添加计时器 331 | 332 | } else if ([playerItem status] == AVPlayerStatusFailed || [playerItem status] == AVPlayerStatusUnknown) { 333 | [self stop]; 334 | } 335 | 336 | } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) { //监听播放器的下载进度 337 | 338 | [self calculateDownloadProgress:playerItem]; 339 | 340 | } else if ([keyPath isEqualToString:@"playbackBufferEmpty"]) { //监听播放器在缓冲数据的状态 341 | [[XCHudHelper sharedInstance] showHudOnView:_showView caption:nil image:nil acitivity:YES autoHideTime:0]; 342 | if (playerItem.isPlaybackBufferEmpty) { 343 | self.state = TBPlayerStateBuffering; 344 | [self bufferingSomeSecond]; 345 | } 346 | } 347 | } 348 | 349 | - (void)monitoringPlayback:(AVPlayerItem *)playerItem 350 | { 351 | 352 | 353 | self.duration = playerItem.duration.value / playerItem.duration.timescale; //视频总时间 354 | [self.player play]; 355 | [self updateTotolTime:self.duration]; 356 | [self setPlaySliderValue:self.duration]; 357 | 358 | __weak __typeof(self)weakSelf = self; 359 | self.playbackTimeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:NULL usingBlock:^(CMTime time) { 360 | 361 | __strong __typeof(weakSelf)strongSelf = weakSelf; 362 | CGFloat current = playerItem.currentTime.value/playerItem.currentTime.timescale; 363 | [strongSelf updateCurrentTime:current]; 364 | [strongSelf updateVideoSlider:current]; 365 | if (strongSelf.isPauseByUser == NO) { 366 | strongSelf.state = TBPlayerStatePlaying; 367 | } 368 | 369 | // 不相等的时候才更新,并发通知,否则seek时会继续跳动 370 | if (strongSelf.current != current) { 371 | strongSelf.current = current; 372 | if (strongSelf.current > strongSelf.duration) { 373 | strongSelf.duration = strongSelf.current; 374 | } 375 | [[NSNotificationCenter defaultCenter] postNotificationName:kTBPlayerProgressChangedNotification object:nil]; 376 | } 377 | 378 | }]; 379 | 380 | } 381 | 382 | - (void)calculateDownloadProgress:(AVPlayerItem *)playerItem 383 | { 384 | NSArray *loadedTimeRanges = [playerItem loadedTimeRanges]; 385 | CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 获取缓冲区域 386 | float startSeconds = CMTimeGetSeconds(timeRange.start); 387 | float durationSeconds = CMTimeGetSeconds(timeRange.duration); 388 | NSTimeInterval timeInterval = startSeconds + durationSeconds;// 计算缓冲总进度 389 | CMTime duration = playerItem.duration; 390 | CGFloat totalDuration = CMTimeGetSeconds(duration); 391 | self.loadedProgress = timeInterval / totalDuration; 392 | [self.videoProgressView setProgress:timeInterval / totalDuration animated:YES]; 393 | } 394 | - (void)bufferingSomeSecond 395 | { 396 | // playbackBufferEmpty会反复进入,因此在bufferingOneSecond延时播放执行完之前再调用bufferingSomeSecond都忽略 397 | static BOOL isBuffering = NO; 398 | if (isBuffering) { 399 | return; 400 | } 401 | isBuffering = YES; 402 | 403 | // 需要先暂停一小会之后再播放,否则网络状况不好的时候时间在走,声音播放不出来 404 | [self.player pause]; 405 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 406 | 407 | // 如果此时用户已经暂停了,则不再需要开启播放了 408 | if (self.isPauseByUser) { 409 | isBuffering = NO; 410 | return; 411 | } 412 | 413 | [self.player play]; 414 | // 如果执行了play还是没有播放则说明还没有缓存好,则再次缓存一段时间 415 | isBuffering = NO; 416 | if (!self.currentPlayerItem.isPlaybackLikelyToKeepUp) { 417 | [self bufferingSomeSecond]; 418 | } 419 | }); 420 | } 421 | 422 | - (void)setLoadedProgress:(CGFloat)loadedProgress 423 | { 424 | if (_loadedProgress == loadedProgress) { 425 | return; 426 | } 427 | 428 | _loadedProgress = loadedProgress; 429 | [[NSNotificationCenter defaultCenter] postNotificationName:kTBPlayerLoadProgressChangedNotification object:nil]; 430 | } 431 | 432 | - (void)setState:(TBPlayerState)state 433 | { 434 | if (state != TBPlayerStateBuffering) { 435 | [[XCHudHelper sharedInstance] hideHud]; 436 | } 437 | 438 | if (_state == state) { 439 | return; 440 | } 441 | 442 | _state = state; 443 | [[NSNotificationCenter defaultCenter] postNotificationName:kTBPlayerStateChangedNotification object:nil]; 444 | 445 | } 446 | 447 | #pragma mark - UI界面 448 | - (void)buildVideoNavBar 449 | { 450 | _navBar.backgroundColor = [self colorWithHex:0x000000 alpha:0.5]; 451 | _navBar.frame = CGRectMake(0, 0, kScreenWidth, 44); 452 | 453 | 454 | 455 | //当前时间 456 | if (!self.currentTimeLabel) { 457 | self.currentTimeLabel = [[UILabel alloc] init]; 458 | _currentTimeLabel.textColor = [self colorWithHex:0xffffff alpha:1.0]; 459 | _currentTimeLabel.font = [UIFont systemFontOfSize:10.0]; 460 | _currentTimeLabel.frame = CGRectMake(30, 0, 52, 44); 461 | _currentTimeLabel.textAlignment = NSTextAlignmentRight; 462 | [_navBar addSubview:_currentTimeLabel]; 463 | } 464 | 465 | 466 | //总时间 467 | if (!self.totolTimeLabel) { 468 | self.totolTimeLabel = [[UILabel alloc] init]; 469 | _totolTimeLabel.textColor = [self colorWithHex:0xffffff alpha:1.0]; 470 | _totolTimeLabel.font = [UIFont systemFontOfSize:10.0]; 471 | _totolTimeLabel.frame = CGRectMake(kScreenWidth-52-15, 0, 52, 44); 472 | _totolTimeLabel.textAlignment = NSTextAlignmentLeft; 473 | [_navBar addSubview:_totolTimeLabel]; 474 | } 475 | 476 | 477 | //进度条 478 | if (!self.videoProgressView) { 479 | self.videoProgressView = [[UIProgressView alloc] init]; 480 | _videoProgressView.progressTintColor = [self colorWithHex:0xffffff alpha:1.0]; //填充部分颜色 481 | _videoProgressView.trackTintColor = [self colorWithHex:0xffffff alpha:0.18]; // 未填充部分颜色 482 | _videoProgressView.frame = CGRectMake(62+30, 21, kScreenWidth-124-44, 20); 483 | _videoProgressView.layer.cornerRadius = 1.5; 484 | _videoProgressView.layer.masksToBounds = YES; 485 | CGAffineTransform transform = CGAffineTransformMakeScale(1.0, 1.5); 486 | _videoProgressView.transform = transform; 487 | [_navBar addSubview:_videoProgressView]; 488 | } 489 | 490 | 491 | //滑竿 492 | if (!self.playSlider) { 493 | 494 | self.playSlider = [[UISlider alloc] init]; 495 | _playSlider.frame = CGRectMake(62+30, 0, kScreenWidth-124-44, 44); 496 | [_playSlider setThumbImage:[self imageNamed:@"icon_progress"] forState:UIControlStateNormal]; 497 | _playSlider.minimumTrackTintColor = [UIColor clearColor]; 498 | _playSlider.maximumTrackTintColor = [UIColor clearColor]; 499 | [_playSlider addTarget:self action:@selector(playSliderChange:) forControlEvents:UIControlEventValueChanged]; //拖动滑竿更新时间 500 | [_playSlider addTarget:self action:@selector(playSliderChangeEnd:) forControlEvents:UIControlEventTouchUpInside]; //松手,滑块拖动停止 501 | [_playSlider addTarget:self action:@selector(playSliderChangeEnd:) forControlEvents:UIControlEventTouchUpOutside]; 502 | [_playSlider addTarget:self action:@selector(playSliderChangeEnd:) forControlEvents:UIControlEventTouchCancel]; 503 | 504 | [_navBar addSubview:_playSlider]; 505 | } 506 | 507 | //暂停按钮 508 | if (!self.stopButton) { 509 | self.stopButton = [UIButton buttonWithType:UIButtonTypeCustom]; 510 | _stopButton.frame = CGRectMake(0, 0, 44, 44); 511 | [_stopButton addTarget:self action:@selector(resumeOrPause) forControlEvents:UIControlEventTouchUpInside]; 512 | [_stopButton setImage:[self imageNamed:@"icon_pause"] forState:UIControlStateNormal]; 513 | [_stopButton setImage:[self imageNamed:@"icon_pause_hl"] forState:UIControlStateHighlighted]; 514 | [_navBar addSubview:_stopButton]; 515 | } 516 | 517 | //全屏按钮 518 | if (!self.screenBUtton) { 519 | self.screenBUtton = [[UIButton alloc] init]; 520 | _screenBUtton.frame = CGRectMake(kScreenWidth - 40, 0, 44, 44); 521 | [_screenBUtton addTarget:self action:@selector(fullScreen) forControlEvents:UIControlEventTouchUpInside]; 522 | [_screenBUtton setImage:[self imageNamed:@"quanping"] forState:UIControlStateNormal]; 523 | [_screenBUtton setImage:[self imageNamed:@"quanping"] forState:UIControlStateHighlighted]; 524 | [_navBar addSubview:_screenBUtton]; 525 | } 526 | 527 | } 528 | 529 | 530 | //手指结束拖动,播放器从当前点开始播放,开启滑竿的时间走动 531 | - (void)playSliderChangeEnd:(UISlider *)slider 532 | { 533 | [self seekToTime:slider.value]; 534 | [self updateCurrentTime:slider.value]; 535 | [_stopButton setImage:[self imageNamed:@"icon_pause"] forState:UIControlStateNormal]; 536 | [_stopButton setImage:[self imageNamed:@"icon_pause_hl"] forState:UIControlStateHighlighted]; 537 | } 538 | 539 | //手指正在拖动,播放器继续播放,但是停止滑竿的时间走动 540 | - (void)playSliderChange:(UISlider *)slider 541 | { 542 | [self updateCurrentTime:slider.value]; 543 | } 544 | 545 | #pragma mark - 控件拖动 546 | - (void)setPlaySliderValue:(CGFloat)time 547 | { 548 | _playSlider.minimumValue = 0.0; 549 | _playSlider.maximumValue = (NSInteger)time; 550 | } 551 | - (void)updateCurrentTime:(CGFloat)time 552 | { 553 | long videocurrent = ceil(time); 554 | 555 | NSString *str = nil; 556 | if (videocurrent < 3600) { 557 | str = [NSString stringWithFormat:@"%02li:%02li",lround(floor(videocurrent/60.f)),lround(floor(videocurrent/1.f))%60]; 558 | } else { 559 | str = [NSString stringWithFormat:@"%02li:%02li:%02li",lround(floor(videocurrent/3600.f)),lround(floor(videocurrent%3600)/60.f),lround(floor(videocurrent/1.f))%60]; 560 | } 561 | 562 | _currentTimeLabel.text = str; 563 | } 564 | 565 | - (void)updateTotolTime:(CGFloat)time 566 | { 567 | long videoLenth = ceil(time); 568 | NSString *strtotol = nil; 569 | if (videoLenth < 3600) { 570 | strtotol = [NSString stringWithFormat:@"%02li:%02li",lround(floor(videoLenth/60.f)),lround(floor(videoLenth/1.f))%60]; 571 | } else { 572 | strtotol = [NSString stringWithFormat:@"%02li:%02li:%02li",lround(floor(videoLenth/3600.f)),lround(floor(videoLenth%3600)/60.f),lround(floor(videoLenth/1.f))%60]; 573 | } 574 | 575 | _totolTimeLabel.text = strtotol; 576 | } 577 | 578 | 579 | - (void)updateVideoSlider:(CGFloat)currentSecond { 580 | [self.playSlider setValue:currentSecond animated:YES]; 581 | } 582 | 583 | 584 | #pragma mark - TBloaderURLConnectionDelegate 585 | 586 | - (void)didFinishLoadingWithTask:(TBVideoRequestTask *)task 587 | { 588 | _isFinishLoad = task.isFinishLoad; 589 | } 590 | 591 | //网络中断:-1005 592 | //无网络连接:-1009 593 | //请求超时:-1001 594 | //服务器内部错误:-1004 595 | //找不到服务器:-1003 596 | - (void)didFailLoadingWithTask:(TBVideoRequestTask *)task WithError:(NSInteger)errorCode 597 | { 598 | NSString *str = nil; 599 | switch (errorCode) { 600 | case -1001: 601 | str = @"请求超时"; 602 | break; 603 | case -1003: 604 | case -1004: 605 | str = @"服务器错误"; 606 | break; 607 | case -1005: 608 | str = @"网络中断"; 609 | break; 610 | case -1009: 611 | str = @"无网络连接"; 612 | break; 613 | 614 | default: 615 | str = [NSString stringWithFormat:@"%@", @"(_errorCode)"]; 616 | break; 617 | } 618 | 619 | [XCHudHelper showMessage:str]; 620 | 621 | } 622 | 623 | #pragma mark - color 624 | - (UIColor*)colorWithHex:(NSInteger)hexValue alpha:(CGFloat)alphaValue 625 | { 626 | return [UIColor colorWithRed:((float)((hexValue & 0xFF0000) >> 16))/255.0 627 | green:((float)((hexValue & 0xFF00) >> 8))/255.0 628 | blue:((float)(hexValue & 0xFF))/255.0 629 | alpha:alphaValue]; 630 | } 631 | 632 | - (void)dealloc 633 | { 634 | [self releasePlayer]; 635 | } 636 | 637 | 638 | #pragma mark - image addtion 639 | - (UIImage *)imageNamed:(NSString *)imageName { 640 | 641 | NSString *path = [[NSBundle mainBundle] pathForResource:@"TBPlayer" ofType:@"bundle"]; 642 | 643 | NSString *imagePath= [path stringByAppendingPathComponent:@"images"]; 644 | return [UIImage imageWithContentsOfFile:[imagePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", imageName]]]; 645 | 646 | } 647 | 648 | @end 649 | -------------------------------------------------------------------------------- /TBPlayer/Classes/TBVideoRequestTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBVideoRequestTask.h 3 | // avplayerSavebufferData 4 | // 5 | // Created by qianjianeng on 15/9/18. 6 | // Copyright (c) 2015年 qianjianeng. All rights reserved. 7 | // 8 | //// github地址:https://github.com/suifengqjn/TBPlayer 9 | 10 | /// 这个task的功能是从网络请求数据,并把数据保存到本地的一个临时文件,网络请求结束的时候,如果数据完整,则把数据缓存到指定的路径,不完整就删除 11 | #import 12 | #import 13 | 14 | @class TBVideoRequestTask; 15 | @protocol TBVideoRequestTaskDelegate 16 | 17 | - (void)task:(TBVideoRequestTask *)task didReceiveVideoLength:(NSUInteger)ideoLength mimeType:(NSString *)mimeType; 18 | - (void)didReceiveVideoDataWithTask:(TBVideoRequestTask *)task; 19 | - (void)didFinishLoadingWithTask:(TBVideoRequestTask *)task; 20 | - (void)didFailLoadingWithTask:(TBVideoRequestTask *)task WithError:(NSInteger )errorCode; 21 | 22 | @end 23 | 24 | @interface TBVideoRequestTask : NSObject 25 | 26 | @property (nonatomic, strong, readonly) NSURL *url; 27 | @property (nonatomic, readonly ) NSUInteger offset; 28 | 29 | @property (nonatomic, readonly ) NSUInteger videoLength; 30 | @property (nonatomic, readonly ) NSUInteger downLoadingOffset; 31 | @property (nonatomic, strong, readonly) NSString * mimeType; 32 | @property (nonatomic, assign) BOOL isFinishLoad; 33 | 34 | @property (nonatomic, weak ) id delegate; 35 | 36 | 37 | - (void)setUrl:(NSURL *)url offset:(NSUInteger)offset; 38 | 39 | - (void)cancel; 40 | 41 | - (void)continueLoading; 42 | 43 | - (void)clearData; 44 | 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /TBPlayer/Classes/TBVideoRequestTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBVideoRequestTask.m 3 | // avplayerSavebufferData 4 | // 5 | // Created by qianjianeng on 15/9/18. 6 | // Copyright (c) 2015年 qianjianeng. All rights reserved. 7 | // 8 | //// github地址:https://github.com/suifengqjn/TBPlayer 9 | 10 | #import "TBVideoRequestTask.h" 11 | 12 | @interface TBVideoRequestTask () 13 | 14 | @property (nonatomic, strong) NSURL *url; 15 | @property (nonatomic ) NSUInteger offset; 16 | 17 | @property (nonatomic ) NSUInteger videoLength; 18 | @property (nonatomic, strong) NSString *mimeType; 19 | 20 | @property (nonatomic, strong) NSURLConnection *connection; 21 | @property (nonatomic, strong) NSMutableArray *taskArr; 22 | 23 | @property (nonatomic, assign) NSUInteger downLoadingOffset; 24 | @property (nonatomic, assign) BOOL once; 25 | 26 | @property (nonatomic, strong) NSFileHandle *fileHandle; 27 | @property (nonatomic, strong) NSString *tempPath; 28 | 29 | @end 30 | 31 | @implementation TBVideoRequestTask 32 | 33 | - (instancetype)init 34 | { 35 | self = [super init]; 36 | if (self) { 37 | _taskArr = [NSMutableArray array]; 38 | NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject; 39 | _tempPath = [document stringByAppendingPathComponent:@"temp.mp4"]; 40 | if ([[NSFileManager defaultManager] fileExistsAtPath:_tempPath]) { 41 | [[NSFileManager defaultManager] removeItemAtPath:_tempPath error:nil]; 42 | [[NSFileManager defaultManager] createFileAtPath:_tempPath contents:nil attributes:nil]; 43 | 44 | } else { 45 | [[NSFileManager defaultManager] createFileAtPath:_tempPath contents:nil attributes:nil]; 46 | } 47 | 48 | } 49 | return self; 50 | } 51 | 52 | - (void)setUrl:(NSURL *)url offset:(NSUInteger)offset 53 | { 54 | _url = url; 55 | _offset = offset; 56 | 57 | //如果建立第二次请求,先移除原来文件,再创建新的 58 | if (self.taskArr.count >= 1) { 59 | [[NSFileManager defaultManager] removeItemAtPath:_tempPath error:nil]; 60 | [[NSFileManager defaultManager] createFileAtPath:_tempPath contents:nil attributes:nil]; 61 | } 62 | 63 | _downLoadingOffset = 0; 64 | 65 | NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO]; 66 | actualURLComponents.scheme = @"http"; 67 | 68 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[actualURLComponents URL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20.0]; 69 | 70 | if (offset > 0 && self.videoLength > 0) { 71 | [request addValue:[NSString stringWithFormat:@"bytes=%ld-%ld",(unsigned long)offset, (unsigned long)self.videoLength - 1] forHTTPHeaderField:@"Range"]; 72 | } 73 | 74 | [self.connection cancel]; 75 | self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; 76 | [self.connection setDelegateQueue:[NSOperationQueue mainQueue]]; 77 | [self.connection start]; 78 | 79 | } 80 | 81 | 82 | 83 | - (void)cancel 84 | { 85 | [self.connection cancel]; 86 | 87 | } 88 | 89 | 90 | #pragma mark - NSURLConnection Delegate Methods 91 | 92 | 93 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 94 | { 95 | _isFinishLoad = NO; 96 | NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; 97 | 98 | NSDictionary *dic = (NSDictionary *)[httpResponse allHeaderFields] ; 99 | 100 | NSString *content = [dic valueForKey:@"Content-Range"]; 101 | NSArray *array = [content componentsSeparatedByString:@"/"]; 102 | NSString *length = array.lastObject; 103 | 104 | NSUInteger videoLength; 105 | 106 | if ([length integerValue] == 0) { 107 | videoLength = (NSUInteger)httpResponse.expectedContentLength; 108 | } else { 109 | videoLength = [length integerValue]; 110 | } 111 | 112 | self.videoLength = videoLength; 113 | self.mimeType = @"video/mp4"; 114 | 115 | 116 | if ([self.delegate respondsToSelector:@selector(task:didReceiveVideoLength:mimeType:)]) { 117 | [self.delegate task:self didReceiveVideoLength:self.videoLength mimeType:self.mimeType]; 118 | } 119 | 120 | [self.taskArr addObject:connection]; 121 | 122 | 123 | self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:_tempPath]; 124 | 125 | 126 | } 127 | 128 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 129 | { 130 | 131 | [self.fileHandle seekToEndOfFile]; 132 | 133 | [self.fileHandle writeData:data]; 134 | 135 | _downLoadingOffset += data.length; 136 | 137 | 138 | if ([self.delegate respondsToSelector:@selector(didReceiveVideoDataWithTask:)]) { 139 | [self.delegate didReceiveVideoDataWithTask:self]; 140 | } 141 | 142 | 143 | } 144 | 145 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 146 | { 147 | 148 | 149 | if (self.taskArr.count < 2) { 150 | _isFinishLoad = YES; 151 | 152 | //这里自己写需要保存数据的路径 153 | NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject; 154 | NSString *movePath = [document stringByAppendingPathComponent:@"保存数据.mp4"]; 155 | 156 | BOOL isSuccess = [[NSFileManager defaultManager] copyItemAtPath:_tempPath toPath:movePath error:nil]; 157 | if (isSuccess) { 158 | NSLog(@"rename success"); 159 | }else{ 160 | NSLog(@"rename fail"); 161 | } 162 | NSLog(@"----%@", movePath); 163 | } 164 | 165 | if ([self.delegate respondsToSelector:@selector(didFinishLoadingWithTask:)]) { 166 | [self.delegate didFinishLoadingWithTask:self]; 167 | } 168 | 169 | } 170 | 171 | //网络中断:-1005 172 | //无网络连接:-1009 173 | //请求超时:-1001 174 | //服务器内部错误:-1004 175 | //找不到服务器:-1003 176 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 177 | { 178 | if (error.code == -1001 && !_once) { //网络超时,重连一次 179 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 180 | [self continueLoading]; 181 | }); 182 | } 183 | if ([self.delegate respondsToSelector:@selector(didFailLoadingWithTask:WithError:)]) { 184 | [self.delegate didFailLoadingWithTask:self WithError:error.code]; 185 | } 186 | if (error.code == -1009) { 187 | NSLog(@"无网络连接"); 188 | } 189 | } 190 | 191 | 192 | - (void)continueLoading 193 | { 194 | _once = YES; 195 | NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:_url resolvingAgainstBaseURL:NO]; 196 | actualURLComponents.scheme = @"http"; 197 | 198 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[actualURLComponents URL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20.0]; 199 | 200 | [request addValue:[NSString stringWithFormat:@"bytes=%ld-%ld",(unsigned long)_downLoadingOffset, (unsigned long)self.videoLength - 1] forHTTPHeaderField:@"Range"]; 201 | 202 | 203 | [self.connection cancel]; 204 | self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; 205 | [self.connection setDelegateQueue:[NSOperationQueue mainQueue]]; 206 | [self.connection start]; 207 | } 208 | 209 | - (void)clearData 210 | { 211 | [self.connection cancel]; 212 | //移除文件 213 | [[NSFileManager defaultManager] removeItemAtPath:_tempPath error:nil]; 214 | 215 | 216 | 217 | } 218 | @end 219 | -------------------------------------------------------------------------------- /TBPlayer/Classes/TBloaderURLConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBloaderURLConnection.h 3 | // avplayerSavebufferData 4 | // 5 | // Created by qianjianeng on 15/9/15. 6 | // Copyright (c) 2015年 qianjianeng. All rights reserved. 7 | // 8 | //// github地址:https://github.com/suifengqjn/TBPlayer 9 | 10 | /// 这个connenction的功能是把task缓存到本地的临时数据根据播放器需要的 offset和length去取数据并返回给播放器 11 | /// 如果视频文件比较小,就没有必要存到本地,直接用一个变量存储即可 12 | #import 13 | #import 14 | 15 | @class TBVideoRequestTask; 16 | 17 | @protocol TBloaderURLConnectionDelegate 18 | 19 | - (void)didFinishLoadingWithTask:(TBVideoRequestTask *)task; 20 | - (void)didFailLoadingWithTask:(TBVideoRequestTask *)task WithError:(NSInteger )errorCode; 21 | 22 | @end 23 | 24 | @interface TBloaderURLConnection : NSURLConnection 25 | 26 | @property (nonatomic, strong) TBVideoRequestTask *task; 27 | @property (nonatomic, weak ) id delegate; 28 | - (NSURL *)getSchemeVideoURL:(NSURL *)url; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /TBPlayer/Classes/TBloaderURLConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBloaderURLConnection.m 3 | // avplayerSavebufferData 4 | // 5 | // Created by qianjianeng on 15/9/15. 6 | // Copyright (c) 2015年 qianjianeng. All rights reserved. 7 | // 8 | //// github地址:https://github.com/suifengqjn/TBPlayer 9 | 10 | #import "TBloaderURLConnection.h" 11 | #import 12 | #import 13 | #import "TBVideoRequestTask.h" 14 | 15 | @interface TBloaderURLConnection () 16 | 17 | @property (nonatomic, strong) NSMutableArray *pendingRequests; 18 | @property (nonatomic, copy ) NSString *videoPath; 19 | 20 | @end 21 | 22 | @implementation TBloaderURLConnection 23 | 24 | - (instancetype)init 25 | { 26 | self = [super init]; 27 | if (self) { 28 | _pendingRequests = [NSMutableArray array]; 29 | NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject; 30 | _videoPath = [document stringByAppendingPathComponent:@"temp.mp4"]; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)fillInContentInformation:(AVAssetResourceLoadingContentInformationRequest *)contentInformationRequest 36 | { 37 | NSString *mimeType = self.task.mimeType; 38 | CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL); 39 | contentInformationRequest.byteRangeAccessSupported = YES; 40 | contentInformationRequest.contentType = CFBridgingRelease(contentType); 41 | contentInformationRequest.contentLength = self.task.videoLength; 42 | } 43 | 44 | #pragma mark - AVURLAsset resource loader methods 45 | 46 | - (void)processPendingRequests 47 | { 48 | NSMutableArray *requestsCompleted = [NSMutableArray array]; //请求完成的数组 49 | //每次下载一块数据都是一次请求,把这些请求放到数组,遍历数组 50 | for (AVAssetResourceLoadingRequest *loadingRequest in self.pendingRequests) 51 | { 52 | [self fillInContentInformation:loadingRequest.contentInformationRequest]; //对每次请求加上长度,文件类型等信息 53 | 54 | BOOL didRespondCompletely = [self respondWithDataForRequest:loadingRequest.dataRequest]; //判断此次请求的数据是否处理完全 55 | 56 | if (didRespondCompletely) { 57 | 58 | [requestsCompleted addObject:loadingRequest]; //如果完整,把此次请求放进 请求完成的数组 59 | [loadingRequest finishLoading]; 60 | 61 | } 62 | } 63 | 64 | [self.pendingRequests removeObjectsInArray:requestsCompleted]; //在所有请求的数组中移除已经完成的 65 | 66 | } 67 | 68 | 69 | - (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest 70 | { 71 | long long startOffset = dataRequest.requestedOffset; 72 | 73 | if (dataRequest.currentOffset != 0) { 74 | startOffset = dataRequest.currentOffset; 75 | } 76 | 77 | if ((self.task.offset +self.task.downLoadingOffset) < startOffset) 78 | { 79 | //NSLog(@"NO DATA FOR REQUEST"); 80 | return NO; 81 | } 82 | 83 | if (startOffset < self.task.offset) { 84 | return NO; 85 | } 86 | 87 | NSData *filedata = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:_videoPath] options:NSDataReadingMappedIfSafe error:nil]; 88 | 89 | // This is the total data we have from startOffset to whatever has been downloaded so far 90 | NSUInteger unreadBytes = self.task.downLoadingOffset - ((NSInteger)startOffset - self.task.offset); 91 | 92 | // Respond with whatever is available if we can't satisfy the request fully yet 93 | NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes); 94 | 95 | 96 | [dataRequest respondWithData:[filedata subdataWithRange:NSMakeRange((NSUInteger)startOffset- self.task.offset, (NSUInteger)numberOfBytesToRespondWith)]]; 97 | 98 | 99 | 100 | long long endOffset = startOffset + dataRequest.requestedLength; 101 | BOOL didRespondFully = (self.task.offset + self.task.downLoadingOffset) >= endOffset; 102 | 103 | return didRespondFully; 104 | 105 | 106 | } 107 | 108 | 109 | /** 110 | * 必须返回Yes,如果返回NO,则resourceLoader将会加载出现故障的数据 111 | * 这里会出现很多个loadingRequest请求, 需要为每一次请求作出处理 112 | * @param resourceLoader 资源管理器 113 | * @param loadingRequest 每一小块数据的请求 114 | * 115 | */ 116 | - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest 117 | { 118 | [self.pendingRequests addObject:loadingRequest]; 119 | [self dealWithLoadingRequest:loadingRequest]; 120 | NSLog(@"----%@", loadingRequest); 121 | return YES; 122 | } 123 | 124 | 125 | - (void)dealWithLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest 126 | { 127 | NSURL *interceptedURL = [loadingRequest.request URL]; 128 | NSRange range = NSMakeRange((NSUInteger)loadingRequest.dataRequest.currentOffset, NSUIntegerMax); 129 | 130 | if (self.task.downLoadingOffset > 0) { 131 | [self processPendingRequests]; 132 | } 133 | 134 | if (!self.task) { 135 | self.task = [[TBVideoRequestTask alloc] init]; 136 | self.task.delegate = self; 137 | [self.task setUrl:interceptedURL offset:0]; 138 | } else { 139 | // 如果新的rang的起始位置比当前缓存的位置还大300k,则重新按照range请求数据 140 | if (self.task.offset + self.task.downLoadingOffset + 1024 * 300 < range.location || 141 | // 如果往回拖也重新请求 142 | range.location < self.task.offset) { 143 | [self.task setUrl:interceptedURL offset:range.location]; 144 | } 145 | } 146 | 147 | 148 | } 149 | 150 | 151 | - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest 152 | { 153 | [self.pendingRequests removeObject:loadingRequest]; 154 | 155 | } 156 | 157 | - (NSURL *)getSchemeVideoURL:(NSURL *)url 158 | { 159 | NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO]; 160 | components.scheme = @"streaming"; 161 | return [components URL]; 162 | } 163 | 164 | #pragma mark - TBVideoRequestTaskDelegate 165 | 166 | - (void)task:(TBVideoRequestTask *)task didReceiveVideoLength:(NSUInteger)ideoLength mimeType:(NSString *)mimeType 167 | { 168 | 169 | } 170 | 171 | - (void)didReceiveVideoDataWithTask:(TBVideoRequestTask *)task 172 | { 173 | [self processPendingRequests]; 174 | 175 | } 176 | 177 | - (void)didFinishLoadingWithTask:(TBVideoRequestTask *)task 178 | { 179 | if ([self.delegate respondsToSelector:@selector(didFinishLoadingWithTask:)]) { 180 | [self.delegate didFinishLoadingWithTask:task]; 181 | } 182 | } 183 | 184 | - (void)didFailLoadingWithTask:(TBVideoRequestTask *)task WithError:(NSInteger)errorCode 185 | { 186 | if ([self.delegate respondsToSelector:@selector(didFailLoadingWithTask:WithError:)]) { 187 | [self.delegate didFailLoadingWithTask:task WithError:errorCode]; 188 | } 189 | 190 | } 191 | @end 192 | -------------------------------------------------------------------------------- /TBPlayer/Classes/XCToast/MBProgressHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.h 3 | // Version 1.0.0 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | // This code is distributed under the terms and conditions of the MIT license. 8 | 9 | // Copyright © 2009-2016 Matej Bukovinski 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | #import 30 | #import 31 | #import 32 | 33 | @class MBBackgroundView; 34 | @protocol MBProgressHUDDelegate; 35 | 36 | 37 | extern CGFloat const MBProgressMaxOffset; 38 | 39 | typedef NS_ENUM(NSInteger, MBProgressHUDMode) { 40 | /// UIActivityIndicatorView. 41 | MBProgressHUDModeIndeterminate, 42 | /// A round, pie-chart like, progress view. 43 | MBProgressHUDModeDeterminate, 44 | /// Horizontal progress bar. 45 | MBProgressHUDModeDeterminateHorizontalBar, 46 | /// Ring-shaped progress view. 47 | MBProgressHUDModeAnnularDeterminate, 48 | /// Shows a custom view. 49 | MBProgressHUDModeCustomView, 50 | /// Shows only labels. 51 | MBProgressHUDModeText 52 | }; 53 | 54 | typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) { 55 | /// Opacity animation 56 | MBProgressHUDAnimationFade, 57 | /// Opacity + scale animation (zoom in when appearing zoom out when disappearing) 58 | MBProgressHUDAnimationZoom, 59 | /// Opacity + scale animation (zoom out style) 60 | MBProgressHUDAnimationZoomOut, 61 | /// Opacity + scale animation (zoom in style) 62 | MBProgressHUDAnimationZoomIn 63 | }; 64 | 65 | typedef NS_ENUM(NSInteger, MBProgressHUDBackgroundStyle) { 66 | /// Solid color background 67 | MBProgressHUDBackgroundStyleSolidColor, 68 | /// UIVisualEffectView or UIToolbar.layer background view 69 | MBProgressHUDBackgroundStyleBlur 70 | }; 71 | 72 | typedef void (^MBProgressHUDCompletionBlock)(); 73 | 74 | 75 | NS_ASSUME_NONNULL_BEGIN 76 | 77 | 78 | /** 79 | * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. 80 | * 81 | * This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class. 82 | * The MBProgressHUD window spans over the entire space given to it by the initWithFrame: constructor and catches all 83 | * user input on this region, thereby preventing the user operations on components below the view. 84 | * 85 | * @note To still allow touches to pass through the HUD, you can set hud.userInteractionEnabled = NO. 86 | * @attention MBProgressHUD is a UI class and should therefore only be accessed on the main thread. 87 | */ 88 | @interface MBProgressHUD : UIView 89 | 90 | /** 91 | * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. 92 | * 93 | * @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden. 94 | * 95 | * @param view The view that the HUD will be added to 96 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 97 | * animations while appearing. 98 | * @return A reference to the created HUD. 99 | * 100 | * @see hideHUDForView:animated: 101 | * @see animationType 102 | */ 103 | + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; 104 | 105 | /// @name Showing and hiding 106 | 107 | /** 108 | * Finds the top-most HUD subview and hides it. The counterpart to this method is showHUDAddedTo:animated:. 109 | * 110 | * @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden. 111 | * 112 | * @param view The view that is going to be searched for a HUD subview. 113 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 114 | * animations while disappearing. 115 | * @return YES if a HUD was found and removed, NO otherwise. 116 | * 117 | * @see showHUDAddedTo:animated: 118 | * @see animationType 119 | */ 120 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; 121 | 122 | /** 123 | * Finds the top-most HUD subview and returns it. 124 | * 125 | * @param view The view that is going to be searched. 126 | * @return A reference to the last HUD subview discovered. 127 | */ 128 | + (nullable MBProgressHUD *)HUDForView:(UIView *)view; 129 | 130 | /** 131 | * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with 132 | * view.bounds as the parameter. 133 | * 134 | * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as 135 | * the HUD's superview (i.e., the view that the HUD will be added to). 136 | */ 137 | - (instancetype)initWithView:(UIView *)view; 138 | 139 | /** 140 | * Displays the HUD. 141 | * 142 | * @note You need to make sure that the main thread completes its run loop soon after this method call so that 143 | * the user interface can be updated. Call this method when your task is already set up to be executed in a new thread 144 | * (e.g., when using something like NSOperation or making an asynchronous call like NSURLRequest). 145 | * 146 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 147 | * animations while appearing. 148 | * 149 | * @see animationType 150 | */ 151 | - (void)showAnimated:(BOOL)animated; 152 | 153 | /** 154 | * Hides the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 155 | * hide the HUD when your task completes. 156 | * 157 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 158 | * animations while disappearing. 159 | * 160 | * @see animationType 161 | */ 162 | - (void)hideAnimated:(BOOL)animated; 163 | 164 | /** 165 | * Hides the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 166 | * hide the HUD when your task completes. 167 | * 168 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 169 | * animations while disappearing. 170 | * @param delay Delay in seconds until the HUD is hidden. 171 | * 172 | * @see animationType 173 | */ 174 | - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay; 175 | 176 | /** 177 | * The HUD delegate object. Receives HUD state notifications. 178 | */ 179 | @property (weak, nonatomic) id delegate; 180 | 181 | /** 182 | * Called after the HUD is hiden. 183 | */ 184 | @property (copy, nullable) MBProgressHUDCompletionBlock completionBlock; 185 | 186 | /* 187 | * Grace period is the time (in seconds) that the invoked method may be run without 188 | * showing the HUD. If the task finishes before the grace time runs out, the HUD will 189 | * not be shown at all. 190 | * This may be used to prevent HUD display for very short tasks. 191 | * Defaults to 0 (no grace time). 192 | */ 193 | @property (assign, nonatomic) NSTimeInterval graceTime; 194 | 195 | /** 196 | * The minimum time (in seconds) that the HUD is shown. 197 | * This avoids the problem of the HUD being shown and than instantly hidden. 198 | * Defaults to 0 (no minimum show time). 199 | */ 200 | @property (assign, nonatomic) NSTimeInterval minShowTime; 201 | 202 | /** 203 | * Removes the HUD from its parent view when hidden. 204 | * Defaults to NO. 205 | */ 206 | @property (assign, nonatomic) BOOL removeFromSuperViewOnHide; 207 | 208 | /// @name Appearance 209 | 210 | /** 211 | * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. 212 | */ 213 | @property (assign, nonatomic) MBProgressHUDMode mode; 214 | 215 | /** 216 | * A color that gets forwarded to all labels and supported indicators. Also sets the tintColor 217 | * for custom views on iOS 7+. Set to nil to manage color individually. 218 | * Defaults to semi-translucent black on iOS 7 and later and white on earlier iOS versions. 219 | */ 220 | @property (strong, nonatomic, nullable) UIColor *contentColor UI_APPEARANCE_SELECTOR; 221 | 222 | /** 223 | * The animation type that should be used when the HUD is shown and hidden. 224 | */ 225 | @property (assign, nonatomic) MBProgressHUDAnimation animationType UI_APPEARANCE_SELECTOR; 226 | 227 | /** 228 | * The bezel offset relative to the center of the view. You can use MBProgressMaxOffset 229 | * and -MBProgressMaxOffset to move the HUD all the way to the screen edge in each direction. 230 | * E.g., CGPointMake(0.f, MBProgressMaxOffset) would position the HUD centered on the bottom edge. 231 | */ 232 | @property (assign, nonatomic) CGPoint offset UI_APPEARANCE_SELECTOR; 233 | 234 | /** 235 | * The amount of space between the HUD edge and the HUD elements (labels, indicators or custom views). 236 | * This also represents the minimum bezel distance to the edge of the HUD view. 237 | * Defaults to 20.f 238 | */ 239 | @property (assign, nonatomic) CGFloat margin UI_APPEARANCE_SELECTOR; 240 | 241 | /** 242 | * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). 243 | */ 244 | @property (assign, nonatomic) CGSize minSize UI_APPEARANCE_SELECTOR; 245 | 246 | /** 247 | * Force the HUD dimensions to be equal if possible. 248 | */ 249 | @property (assign, nonatomic, getter = isSquare) BOOL square UI_APPEARANCE_SELECTOR; 250 | 251 | /** 252 | * When enabled, the bezel center gets slightly affected by the device accelerometer data. 253 | * Has no effect on iOS < 7.0. Defaults to YES. 254 | */ 255 | @property (assign, nonatomic, getter=areDefaultMotionEffectsEnabled) BOOL defaultMotionEffectsEnabled UI_APPEARANCE_SELECTOR; 256 | 257 | /// @name Progress 258 | 259 | /** 260 | * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. 261 | */ 262 | @property (assign, nonatomic) float progress; 263 | 264 | /// @name ProgressObject 265 | 266 | /** 267 | * The NSProgress object feeding the progress information to the progress indicator. 268 | */ 269 | @property (strong, nonatomic, nullable) NSProgress *progressObject; 270 | 271 | /// @name Views 272 | 273 | /** 274 | * The view containing the labels and indicator (or customView). 275 | */ 276 | @property (strong, nonatomic, readonly) MBBackgroundView *bezelView; 277 | 278 | /** 279 | * View covering the entire HUD area, placed behind bezelView. 280 | */ 281 | @property (strong, nonatomic, readonly) MBBackgroundView *backgroundView; 282 | 283 | /** 284 | * The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView. 285 | * The view should implement intrinsicContentSize for proper sizing. For best results use approximately 37 by 37 pixels. 286 | */ 287 | @property (strong, nonatomic, nullable) UIView *customView; 288 | 289 | /** 290 | * A label that holds an optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit 291 | * the entire text. 292 | */ 293 | @property (strong, nonatomic, readonly) UILabel *label; 294 | 295 | /** 296 | * A label that holds an optional details message displayed below the labelText message. The details text can span multiple lines. 297 | */ 298 | @property (strong, nonatomic, readonly) UILabel *detailsLabel; 299 | 300 | /** 301 | * A button that is placed below the labels. Visible only if a target / action is added. 302 | */ 303 | @property (strong, nonatomic, readonly) UIButton *button; 304 | 305 | @end 306 | 307 | 308 | @protocol MBProgressHUDDelegate 309 | 310 | @optional 311 | 312 | /** 313 | * Called after the HUD was fully hidden from the screen. 314 | */ 315 | - (void)hudWasHidden:(MBProgressHUD *)hud; 316 | 317 | @end 318 | 319 | 320 | /** 321 | * A progress view for showing definite progress by filling up a circle (pie chart). 322 | */ 323 | @interface MBRoundProgressView : UIView 324 | 325 | /** 326 | * Progress (0.0 to 1.0) 327 | */ 328 | @property (nonatomic, assign) float progress; 329 | 330 | /** 331 | * Indicator progress color. 332 | * Defaults to white [UIColor whiteColor]. 333 | */ 334 | @property (nonatomic, strong) UIColor *progressTintColor; 335 | 336 | /** 337 | * Indicator background (non-progress) color. 338 | * Only applicable on iOS versions older than iOS 7. 339 | * Defaults to translucent white (alpha 0.1). 340 | */ 341 | @property (nonatomic, strong) UIColor *backgroundTintColor; 342 | 343 | /* 344 | * Display mode - NO = round or YES = annular. Defaults to round. 345 | */ 346 | @property (nonatomic, assign, getter = isAnnular) BOOL annular; 347 | 348 | @end 349 | 350 | 351 | /** 352 | * A flat bar progress view. 353 | */ 354 | @interface MBBarProgressView : UIView 355 | 356 | /** 357 | * Progress (0.0 to 1.0) 358 | */ 359 | @property (nonatomic, assign) float progress; 360 | 361 | /** 362 | * Bar border line color. 363 | * Defaults to white [UIColor whiteColor]. 364 | */ 365 | @property (nonatomic, strong) UIColor *lineColor; 366 | 367 | /** 368 | * Bar background color. 369 | * Defaults to clear [UIColor clearColor]; 370 | */ 371 | @property (nonatomic, strong) UIColor *progressRemainingColor; 372 | 373 | /** 374 | * Bar progress color. 375 | * Defaults to white [UIColor whiteColor]. 376 | */ 377 | @property (nonatomic, strong) UIColor *progressColor; 378 | 379 | @end 380 | 381 | 382 | @interface MBBackgroundView : UIView 383 | 384 | /** 385 | * The background style. 386 | * Defaults to MBProgressHUDBackgroundStyleBlur on iOS 7 or later and MBProgressHUDBackgroundStyleSolidColor otherwise. 387 | * @note Due to iOS 7 not supporting UIVisualEffectView, the blur effect differs slightly between iOS 7 and later versions. 388 | */ 389 | @property (nonatomic) MBProgressHUDBackgroundStyle style; 390 | 391 | /** 392 | * The background color or the blur tint color. 393 | * @note Due to iOS 7 not supporting UIVisualEffectView, the blur effect differs slightly between iOS 7 and later versions. 394 | */ 395 | @property (nonatomic, strong) UIColor *color; 396 | 397 | @end 398 | 399 | @interface MBProgressHUD (Deprecated) 400 | 401 | + (NSArray *)allHUDsForView:(UIView *)view __attribute__((deprecated("Store references when using more than one HUD per view."))); 402 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated __attribute__((deprecated("Store references when using more than one HUD per view."))); 403 | 404 | - (id)initWithWindow:(UIWindow *)window __attribute__((deprecated("Use initWithView: instead."))); 405 | 406 | - (void)show:(BOOL)animated __attribute__((deprecated("Use showAnimated: instead."))); 407 | - (void)hide:(BOOL)animated __attribute__((deprecated("Use hideAnimated: instead."))); 408 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay __attribute__((deprecated("Use hideAnimated:afterDelay: instead."))); 409 | 410 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated __attribute__((deprecated("Use GCD directly."))); 411 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block __attribute__((deprecated("Use GCD directly."))); 412 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(nullable MBProgressHUDCompletionBlock)completion __attribute__((deprecated("Use GCD directly."))); 413 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue __attribute__((deprecated("Use GCD directly."))); 414 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue 415 | completionBlock:(nullable MBProgressHUDCompletionBlock)completion __attribute__((deprecated("Use GCD directly."))); 416 | @property (assign) BOOL taskInProgress __attribute__((deprecated("No longer needed."))); 417 | 418 | @property (nonatomic, copy) NSString *labelText __attribute__((deprecated("Use label.text instead."))); 419 | @property (nonatomic, strong) UIFont *labelFont __attribute__((deprecated("Use label.font instead."))); 420 | @property (nonatomic, strong) UIColor *labelColor __attribute__((deprecated("Use label.textColor instead."))); 421 | @property (nonatomic, copy) NSString *detailsLabelText __attribute__((deprecated("Use detailsLabel.text instead."))); 422 | @property (nonatomic, strong) UIFont *detailsLabelFont __attribute__((deprecated("Use detailsLabel.font instead."))); 423 | @property (nonatomic, strong) UIColor *detailsLabelColor __attribute__((deprecated("Use detailsLabel.textColor instead."))); 424 | @property (assign, nonatomic) CGFloat opacity __attribute__((deprecated("Customize bezelView properties instead."))); 425 | @property (strong, nonatomic) UIColor *color __attribute__((deprecated("Customize the bezelView color instead."))); 426 | @property (assign, nonatomic) CGFloat xOffset __attribute__((deprecated("Set offset.x instead."))); 427 | @property (assign, nonatomic) CGFloat yOffset __attribute__((deprecated("Set offset.y instead."))); 428 | @property (assign, nonatomic) CGFloat cornerRadius __attribute__((deprecated("Set bezelView.layer.cornerRadius instead."))); 429 | @property (assign, nonatomic) BOOL dimBackground __attribute__((deprecated("Customize HUD background properties instead."))); 430 | @property (strong, nonatomic) UIColor *activityIndicatorColor __attribute__((deprecated("Use UIAppearance to customize UIActivityIndicatorView. E.g.: [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil].color = [UIColor redColor];"))); 431 | @property (atomic, assign, readonly) CGSize size __attribute__((deprecated("Get the bezelView.frame.size instead."))); 432 | 433 | @end 434 | 435 | NS_ASSUME_NONNULL_END 436 | -------------------------------------------------------------------------------- /TBPlayer/Classes/XCToast/MBProgressHUD.m: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.m 3 | // Version 1.0.0 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | #import "MBProgressHUD.h" 8 | #import 9 | 10 | 11 | #ifndef kCFCoreFoundationVersionNumber_iOS_7_0 12 | #define kCFCoreFoundationVersionNumber_iOS_7_0 847.20 13 | #endif 14 | 15 | #ifndef kCFCoreFoundationVersionNumber_iOS_8_0 16 | #define kCFCoreFoundationVersionNumber_iOS_8_0 1129.15 17 | #endif 18 | 19 | #define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread."); 20 | 21 | CGFloat const MBProgressMaxOffset = 1000000.f; 22 | 23 | static const CGFloat MBDefaultPadding = 4.f; 24 | static const CGFloat MBDefaultLabelFontSize = 16.f; 25 | static const CGFloat MBDefaultDetailsLabelFontSize = 12.f; 26 | 27 | 28 | @interface MBProgressHUD () { 29 | // Deprecated 30 | UIColor *_activityIndicatorColor; 31 | CGFloat _opacity; 32 | } 33 | 34 | @property (nonatomic, assign) BOOL useAnimation; 35 | @property (nonatomic, assign, getter=hasFinished) BOOL finished; 36 | @property (nonatomic, strong) UIView *indicator; 37 | @property (nonatomic, strong) NSDate *showStarted; 38 | @property (nonatomic, strong) NSArray *paddingConstraints; 39 | @property (nonatomic, strong) NSArray *bezelConstraints; 40 | @property (nonatomic, strong) UIView *topSpacer; 41 | @property (nonatomic, strong) UIView *bottomSpacer; 42 | @property (nonatomic, weak) NSTimer *graceTimer; 43 | @property (nonatomic, weak) NSTimer *minShowTimer; 44 | @property (nonatomic, weak) NSTimer *hideDelayTimer; 45 | @property (nonatomic, weak) CADisplayLink *progressObjectDisplayLink; 46 | 47 | // Deprecated 48 | @property (assign) BOOL taskInProgress; 49 | 50 | @end 51 | 52 | 53 | @interface MBProgressHUDRoundedButton : UIButton 54 | @end 55 | 56 | 57 | @implementation MBProgressHUD 58 | 59 | #pragma mark - Class methods 60 | 61 | + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { 62 | MBProgressHUD *hud = [[self alloc] initWithView:view]; 63 | hud.removeFromSuperViewOnHide = YES; 64 | [view addSubview:hud]; 65 | [hud showAnimated:animated]; 66 | return hud; 67 | } 68 | 69 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { 70 | MBProgressHUD *hud = [self HUDForView:view]; 71 | if (hud != nil) { 72 | hud.removeFromSuperViewOnHide = YES; 73 | [hud hideAnimated:animated]; 74 | return YES; 75 | } 76 | return NO; 77 | } 78 | 79 | + (MBProgressHUD *)HUDForView:(UIView *)view { 80 | NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; 81 | for (UIView *subview in subviewsEnum) { 82 | if ([subview isKindOfClass:self]) { 83 | return (MBProgressHUD *)subview; 84 | } 85 | } 86 | return nil; 87 | } 88 | 89 | #pragma mark - Lifecycle 90 | 91 | - (void)commonInit { 92 | // Set default values for properties 93 | _animationType = MBProgressHUDAnimationFade; 94 | _mode = MBProgressHUDModeIndeterminate; 95 | _margin = 20.0f; 96 | _opacity = 1.f; 97 | _defaultMotionEffectsEnabled = YES; 98 | 99 | // Default color, depending on the current iOS version 100 | BOOL isLegacy = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; 101 | _contentColor = isLegacy ? [UIColor whiteColor] : [UIColor colorWithWhite:0.f alpha:0.7f]; 102 | // Transparent background 103 | self.opaque = NO; 104 | self.backgroundColor = [UIColor clearColor]; 105 | // Make it invisible for now 106 | self.alpha = 0.0f; 107 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 108 | self.layer.allowsGroupOpacity = NO; 109 | 110 | [self setupViews]; 111 | [self updateIndicators]; 112 | [self registerForNotifications]; 113 | } 114 | 115 | - (instancetype)initWithFrame:(CGRect)frame { 116 | if ((self = [super initWithFrame:frame])) { 117 | [self commonInit]; 118 | } 119 | return self; 120 | } 121 | 122 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 123 | if ((self = [super initWithCoder:aDecoder])) { 124 | [self commonInit]; 125 | } 126 | return self; 127 | } 128 | 129 | - (id)initWithView:(UIView *)view { 130 | NSAssert(view, @"View must not be nil."); 131 | return [self initWithFrame:view.bounds]; 132 | } 133 | 134 | - (void)dealloc { 135 | [self unregisterFromNotifications]; 136 | } 137 | 138 | #pragma mark - Show & hide 139 | 140 | - (void)showAnimated:(BOOL)animated { 141 | MBMainThreadAssert(); 142 | [self.minShowTimer invalidate]; 143 | self.useAnimation = animated; 144 | self.finished = NO; 145 | // If the grace time is set, postpone the HUD display 146 | if (self.graceTime > 0.0) { 147 | NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; 148 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 149 | self.graceTimer = timer; 150 | } 151 | // ... otherwise show the HUD immediately 152 | else { 153 | [self showUsingAnimation:self.useAnimation]; 154 | } 155 | } 156 | 157 | - (void)hideAnimated:(BOOL)animated { 158 | MBMainThreadAssert(); 159 | [self.graceTimer invalidate]; 160 | self.useAnimation = animated; 161 | self.finished = YES; 162 | // If the minShow time is set, calculate how long the HUD was shown, 163 | // and postpone the hiding operation if necessary 164 | if (self.minShowTime > 0.0 && self.showStarted) { 165 | NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted]; 166 | if (interv < self.minShowTime) { 167 | NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; 168 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 169 | self.minShowTimer = timer; 170 | return; 171 | } 172 | } 173 | // ... otherwise hide the HUD immediately 174 | [self hideUsingAnimation:self.useAnimation]; 175 | } 176 | 177 | - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay { 178 | NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO]; 179 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 180 | self.hideDelayTimer = timer; 181 | } 182 | 183 | #pragma mark - Timer callbacks 184 | 185 | - (void)handleGraceTimer:(NSTimer *)theTimer { 186 | // Show the HUD only if the task is still running 187 | if (!self.hasFinished) { 188 | [self showUsingAnimation:self.useAnimation]; 189 | } 190 | } 191 | 192 | - (void)handleMinShowTimer:(NSTimer *)theTimer { 193 | [self hideUsingAnimation:self.useAnimation]; 194 | } 195 | 196 | - (void)handleHideTimer:(NSTimer *)timer { 197 | [self hideAnimated:[timer.userInfo boolValue]]; 198 | } 199 | 200 | #pragma mark - View Hierrarchy 201 | 202 | - (void)didMoveToSuperview { 203 | [self updateForCurrentOrientationAnimated:NO]; 204 | } 205 | 206 | #pragma mark - Internal show & hide operations 207 | 208 | - (void)showUsingAnimation:(BOOL)animated { 209 | // Cancel any previous animations 210 | [self.bezelView.layer removeAllAnimations]; 211 | [self.backgroundView.layer removeAllAnimations]; 212 | 213 | // Cancel any scheduled hideDelayed: calls 214 | [self.hideDelayTimer invalidate]; 215 | 216 | self.showStarted = [NSDate date]; 217 | self.alpha = 1.f; 218 | 219 | // Needed in case we hide and re-show with the same NSProgress object attached. 220 | [self setNSProgressDisplayLinkEnabled:YES]; 221 | 222 | if (animated) { 223 | [self animateIn:YES withType:self.animationType completion:NULL]; 224 | } else { 225 | #pragma clang diagnostic push 226 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 227 | self.bezelView.alpha = self.opacity; 228 | #pragma clang diagnostic pop 229 | self.backgroundView.alpha = 1.f; 230 | } 231 | } 232 | 233 | - (void)hideUsingAnimation:(BOOL)animated { 234 | if (animated && self.showStarted) { 235 | self.showStarted = nil; 236 | [self animateIn:NO withType:self.animationType completion:^(BOOL finished) { 237 | [self done]; 238 | }]; 239 | } else { 240 | self.showStarted = nil; 241 | self.bezelView.alpha = 0.f; 242 | self.backgroundView.alpha = 1.f; 243 | [self done]; 244 | } 245 | } 246 | 247 | - (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion { 248 | // Automatically determine the correct zoom animation type 249 | if (type == MBProgressHUDAnimationZoom) { 250 | type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut; 251 | } 252 | 253 | CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f); 254 | CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f); 255 | 256 | // Set starting state 257 | UIView *bezelView = self.bezelView; 258 | if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) { 259 | bezelView.transform = small; 260 | } else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) { 261 | bezelView.transform = large; 262 | } 263 | 264 | // Perform animations 265 | dispatch_block_t animations = ^{ 266 | if (animatingIn) { 267 | bezelView.transform = CGAffineTransformIdentity; 268 | } else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) { 269 | bezelView.transform = large; 270 | } else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) { 271 | bezelView.transform = small; 272 | } 273 | #pragma clang diagnostic push 274 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 275 | bezelView.alpha = animatingIn ? self.opacity : 0.f; 276 | #pragma clang diagnostic pop 277 | self.backgroundView.alpha = animatingIn ? 1.f : 0.f; 278 | }; 279 | 280 | // Spring animations are nicer, but only available on iOS 7+ 281 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV 282 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) { 283 | [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion]; 284 | return; 285 | } 286 | #endif 287 | [UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion]; 288 | } 289 | 290 | - (void)done { 291 | // Cancel any scheduled hideDelayed: calls 292 | [self.hideDelayTimer invalidate]; 293 | [self setNSProgressDisplayLinkEnabled:NO]; 294 | 295 | if (self.hasFinished) { 296 | self.alpha = 0.0f; 297 | if (self.removeFromSuperViewOnHide) { 298 | [self removeFromSuperview]; 299 | } 300 | } 301 | MBProgressHUDCompletionBlock completionBlock = self.completionBlock; 302 | if (completionBlock) { 303 | completionBlock(); 304 | } 305 | id delegate = self.delegate; 306 | if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { 307 | [delegate performSelector:@selector(hudWasHidden:) withObject:self]; 308 | } 309 | } 310 | 311 | #pragma mark - UI 312 | 313 | - (void)setupViews { 314 | UIColor *defaultColor = self.contentColor; 315 | 316 | MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds]; 317 | backgroundView.style = MBProgressHUDBackgroundStyleSolidColor; 318 | backgroundView.backgroundColor = [UIColor clearColor]; 319 | backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 320 | backgroundView.alpha = 0.f; 321 | [self addSubview:backgroundView]; 322 | _backgroundView = backgroundView; 323 | 324 | MBBackgroundView *bezelView = [MBBackgroundView new]; 325 | bezelView.translatesAutoresizingMaskIntoConstraints = NO; 326 | bezelView.layer.cornerRadius = 5.f; 327 | bezelView.alpha = 0.f; 328 | [self addSubview:bezelView]; 329 | _bezelView = bezelView; 330 | [self updateBezelMotionEffects]; 331 | 332 | UILabel *label = [UILabel new]; 333 | label.adjustsFontSizeToFitWidth = NO; 334 | label.textAlignment = NSTextAlignmentCenter; 335 | label.textColor = defaultColor; 336 | label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize]; 337 | label.opaque = NO; 338 | label.backgroundColor = [UIColor clearColor]; 339 | _label = label; 340 | 341 | UILabel *detailsLabel = [UILabel new]; 342 | detailsLabel.adjustsFontSizeToFitWidth = NO; 343 | detailsLabel.textAlignment = NSTextAlignmentCenter; 344 | detailsLabel.textColor = defaultColor; 345 | detailsLabel.numberOfLines = 0; 346 | detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize]; 347 | detailsLabel.opaque = NO; 348 | detailsLabel.backgroundColor = [UIColor clearColor]; 349 | _detailsLabel = detailsLabel; 350 | 351 | UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom]; 352 | button.titleLabel.textAlignment = NSTextAlignmentCenter; 353 | button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize]; 354 | [button setTitleColor:defaultColor forState:UIControlStateNormal]; 355 | _button = button; 356 | 357 | for (UIView *view in @[label, detailsLabel, button]) { 358 | view.translatesAutoresizingMaskIntoConstraints = NO; 359 | [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal]; 360 | [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical]; 361 | [bezelView addSubview:view]; 362 | } 363 | 364 | UIView *topSpacer = [UIView new]; 365 | topSpacer.translatesAutoresizingMaskIntoConstraints = NO; 366 | topSpacer.hidden = YES; 367 | [bezelView addSubview:topSpacer]; 368 | _topSpacer = topSpacer; 369 | 370 | UIView *bottomSpacer = [UIView new]; 371 | bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO; 372 | bottomSpacer.hidden = YES; 373 | [bezelView addSubview:bottomSpacer]; 374 | _bottomSpacer = bottomSpacer; 375 | } 376 | 377 | - (void)updateIndicators { 378 | UIView *indicator = self.indicator; 379 | BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; 380 | BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]]; 381 | 382 | MBProgressHUDMode mode = self.mode; 383 | if (mode == MBProgressHUDModeIndeterminate) { 384 | if (!isActivityIndicator) { 385 | // Update to indeterminate indicator 386 | [indicator removeFromSuperview]; 387 | indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; 388 | [(UIActivityIndicatorView *)indicator startAnimating]; 389 | [self.bezelView addSubview:indicator]; 390 | } 391 | } 392 | else if (mode == MBProgressHUDModeDeterminateHorizontalBar) { 393 | // Update to bar determinate indicator 394 | [indicator removeFromSuperview]; 395 | indicator = [[MBBarProgressView alloc] init]; 396 | [self.bezelView addSubview:indicator]; 397 | } 398 | else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { 399 | if (!isRoundIndicator) { 400 | // Update to determinante indicator 401 | [indicator removeFromSuperview]; 402 | indicator = [[MBRoundProgressView alloc] init]; 403 | [self.bezelView addSubview:indicator]; 404 | } 405 | if (mode == MBProgressHUDModeAnnularDeterminate) { 406 | [(MBRoundProgressView *)indicator setAnnular:YES]; 407 | } 408 | } 409 | else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) { 410 | // Update custom view indicator 411 | [indicator removeFromSuperview]; 412 | indicator = self.customView; 413 | [self.bezelView addSubview:indicator]; 414 | } 415 | else if (mode == MBProgressHUDModeText) { 416 | [indicator removeFromSuperview]; 417 | indicator = nil; 418 | } 419 | indicator.translatesAutoresizingMaskIntoConstraints = NO; 420 | self.indicator = indicator; 421 | 422 | if ([indicator respondsToSelector:@selector(setProgress:)]) { 423 | [(id)indicator setValue:@(self.progress) forKey:@"progress"]; 424 | } 425 | 426 | [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal]; 427 | [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical]; 428 | 429 | [self updateViewsForColor:self.contentColor]; 430 | [self setNeedsUpdateConstraints]; 431 | } 432 | 433 | - (void)updateViewsForColor:(UIColor *)color { 434 | if (!color) return; 435 | 436 | self.label.textColor = color; 437 | self.detailsLabel.textColor = color; 438 | [self.button setTitleColor:color forState:UIControlStateNormal]; 439 | 440 | #pragma clang diagnostic push 441 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 442 | if (self.activityIndicatorColor) { 443 | color = self.activityIndicatorColor; 444 | } 445 | #pragma clang diagnostic pop 446 | 447 | // UIAppearance settings are prioritized. If they are preset the set color is ignored. 448 | 449 | UIView *indicator = self.indicator; 450 | if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) { 451 | UIActivityIndicatorView *appearance = nil; 452 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 453 | appearance = [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil]; 454 | #else 455 | // For iOS 9+ 456 | appearance = [UIActivityIndicatorView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; 457 | #endif 458 | 459 | if (appearance.color == nil) { 460 | ((UIActivityIndicatorView *)indicator).color = color; 461 | } 462 | } else if ([indicator isKindOfClass:[MBRoundProgressView class]]) { 463 | MBRoundProgressView *appearance = nil; 464 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 465 | appearance = [MBRoundProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil]; 466 | #else 467 | appearance = [MBRoundProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; 468 | #endif 469 | if (appearance.progressTintColor == nil) { 470 | ((MBRoundProgressView *)indicator).progressTintColor = color; 471 | } 472 | if (appearance.backgroundTintColor == nil) { 473 | ((MBRoundProgressView *)indicator).backgroundTintColor = [color colorWithAlphaComponent:0.1]; 474 | } 475 | } else if ([indicator isKindOfClass:[MBBarProgressView class]]) { 476 | MBBarProgressView *appearance = nil; 477 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 478 | appearance = [MBBarProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil]; 479 | #else 480 | appearance = [MBBarProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; 481 | #endif 482 | if (appearance.progressColor == nil) { 483 | ((MBBarProgressView *)indicator).progressColor = color; 484 | } 485 | if (appearance.lineColor == nil) { 486 | ((MBBarProgressView *)indicator).lineColor = color; 487 | } 488 | } else { 489 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV 490 | if ([indicator respondsToSelector:@selector(setTintColor:)]) { 491 | [indicator setTintColor:color]; 492 | } 493 | #endif 494 | } 495 | } 496 | 497 | - (void)updateBezelMotionEffects { 498 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV 499 | MBBackgroundView *bezelView = self.bezelView; 500 | if (![bezelView respondsToSelector:@selector(addMotionEffect:)]) return; 501 | 502 | if (self.defaultMotionEffectsEnabled) { 503 | CGFloat effectOffset = 10.f; 504 | UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; 505 | effectX.maximumRelativeValue = @(effectOffset); 506 | effectX.minimumRelativeValue = @(-effectOffset); 507 | 508 | UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; 509 | effectY.maximumRelativeValue = @(effectOffset); 510 | effectY.minimumRelativeValue = @(-effectOffset); 511 | 512 | UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init]; 513 | group.motionEffects = @[effectX, effectY]; 514 | 515 | [bezelView addMotionEffect:group]; 516 | } else { 517 | NSArray *effects = [bezelView motionEffects]; 518 | for (UIMotionEffect *effect in effects) { 519 | [bezelView removeMotionEffect:effect]; 520 | } 521 | } 522 | #endif 523 | } 524 | 525 | #pragma mark - Layout 526 | 527 | - (void)updateConstraints { 528 | UIView *bezel = self.bezelView; 529 | UIView *topSpacer = self.topSpacer; 530 | UIView *bottomSpacer = self.bottomSpacer; 531 | CGFloat margin = self.margin; 532 | NSMutableArray *bezelConstraints = [NSMutableArray array]; 533 | NSDictionary *metrics = @{@"margin": @(margin)}; 534 | 535 | NSMutableArray *subviews = [NSMutableArray arrayWithObjects:self.topSpacer, self.label, self.detailsLabel, self.button, self.bottomSpacer, nil]; 536 | if (self.indicator) [subviews insertObject:self.indicator atIndex:1]; 537 | 538 | // Remove existing constraints 539 | [self removeConstraints:self.constraints]; 540 | [topSpacer removeConstraints:topSpacer.constraints]; 541 | [bottomSpacer removeConstraints:bottomSpacer.constraints]; 542 | if (self.bezelConstraints) { 543 | [bezel removeConstraints:self.bezelConstraints]; 544 | self.bezelConstraints = nil; 545 | } 546 | 547 | // Center bezel in container (self), applying the offset if set 548 | CGPoint offset = self.offset; 549 | NSMutableArray *centeringConstraints = [NSMutableArray array]; 550 | [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:offset.x]]; 551 | [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:offset.y]]; 552 | [self applyPriority:998.f toConstraints:centeringConstraints]; 553 | [self addConstraints:centeringConstraints]; 554 | 555 | // Ensure minimum side margin is kept 556 | NSMutableArray *sideConstraints = [NSMutableArray array]; 557 | [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]]; 558 | [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]]; 559 | [self applyPriority:999.f toConstraints:sideConstraints]; 560 | [self addConstraints:sideConstraints]; 561 | 562 | // Minimum bezel size, if set 563 | CGSize minimumSize = self.minSize; 564 | if (!CGSizeEqualToSize(minimumSize, CGSizeZero)) { 565 | NSMutableArray *minSizeConstraints = [NSMutableArray array]; 566 | [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.width]]; 567 | [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.height]]; 568 | [self applyPriority:997.f toConstraints:minSizeConstraints]; 569 | [bezelConstraints addObjectsFromArray:minSizeConstraints]; 570 | } 571 | 572 | // Square aspect ratio, if set 573 | if (self.square) { 574 | NSLayoutConstraint *square = [NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeWidth multiplier:1.f constant:0]; 575 | square.priority = 997.f; 576 | [bezelConstraints addObject:square]; 577 | } 578 | 579 | // Top and bottom spacing 580 | [topSpacer addConstraint:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]]; 581 | [bottomSpacer addConstraint:[NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]]; 582 | // Top and bottom spaces should be equal 583 | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f]]; 584 | 585 | // Layout subviews in bezel 586 | NSMutableArray *paddingConstraints = [NSMutableArray new]; 587 | [subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) { 588 | // Center in bezel 589 | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f]]; 590 | // Ensure the minimum edge margin is kept 591 | [bezelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[view]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(view)]]; 592 | // Element spacing 593 | if (idx == 0) { 594 | // First, ensure spacing to bezel edge 595 | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]]; 596 | } else if (idx == subviews.count - 1) { 597 | // Last, ensure spacing to bezel edge 598 | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]]; 599 | } 600 | if (idx > 0) { 601 | // Has previous 602 | NSLayoutConstraint *padding = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:subviews[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]; 603 | [bezelConstraints addObject:padding]; 604 | [paddingConstraints addObject:padding]; 605 | } 606 | }]; 607 | 608 | [bezel addConstraints:bezelConstraints]; 609 | self.bezelConstraints = bezelConstraints; 610 | 611 | self.paddingConstraints = [paddingConstraints copy]; 612 | [self updatePaddingConstraints]; 613 | 614 | [super updateConstraints]; 615 | } 616 | 617 | - (void)layoutSubviews { 618 | // There is no need to update constraints if they are going to 619 | // be recreated in [super layoutSubviews] due to needsUpdateConstraints being set. 620 | // This also avoids an issue on iOS 8, where updatePaddingConstraints 621 | // would trigger a zombie object access. 622 | if (!self.needsUpdateConstraints) { 623 | [self updatePaddingConstraints]; 624 | } 625 | [super layoutSubviews]; 626 | } 627 | 628 | - (void)updatePaddingConstraints { 629 | // Set padding dynamically, depending on whether the view is visible or not 630 | __block BOOL hasVisibleAncestors = NO; 631 | [self.paddingConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *padding, NSUInteger idx, BOOL *stop) { 632 | UIView *firstView = (UIView *)padding.firstItem; 633 | UIView *secondView = (UIView *)padding.secondItem; 634 | BOOL firstVisible = !firstView.hidden && !CGSizeEqualToSize(firstView.intrinsicContentSize, CGSizeZero); 635 | BOOL secondVisible = !secondView.hidden && !CGSizeEqualToSize(secondView.intrinsicContentSize, CGSizeZero); 636 | // Set if both views are visible or if there's a visible view on top that doesn't have padding 637 | // added relative to the current view yet 638 | padding.constant = (firstVisible && (secondVisible || hasVisibleAncestors)) ? MBDefaultPadding : 0.f; 639 | hasVisibleAncestors |= secondVisible; 640 | }]; 641 | } 642 | 643 | - (void)applyPriority:(UILayoutPriority)priority toConstraints:(NSArray *)constraints { 644 | for (NSLayoutConstraint *constraint in constraints) { 645 | constraint.priority = priority; 646 | } 647 | } 648 | 649 | #pragma mark - Properties 650 | 651 | - (void)setMode:(MBProgressHUDMode)mode { 652 | if (mode != _mode) { 653 | _mode = mode; 654 | [self updateIndicators]; 655 | } 656 | } 657 | 658 | - (void)setCustomView:(UIView *)customView { 659 | if (customView != _customView) { 660 | _customView = customView; 661 | if (self.mode == MBProgressHUDModeCustomView) { 662 | [self updateIndicators]; 663 | } 664 | } 665 | } 666 | 667 | - (void)setOffset:(CGPoint)offset { 668 | if (!CGPointEqualToPoint(offset, _offset)) { 669 | _offset = offset; 670 | [self setNeedsUpdateConstraints]; 671 | } 672 | } 673 | 674 | - (void)setMargin:(CGFloat)margin { 675 | if (margin != _margin) { 676 | _margin = margin; 677 | [self setNeedsUpdateConstraints]; 678 | } 679 | } 680 | 681 | - (void)setMinSize:(CGSize)minSize { 682 | if (!CGSizeEqualToSize(minSize, _minSize)) { 683 | _minSize = minSize; 684 | [self setNeedsUpdateConstraints]; 685 | } 686 | } 687 | 688 | - (void)setSquare:(BOOL)square { 689 | if (square != _square) { 690 | _square = square; 691 | [self setNeedsUpdateConstraints]; 692 | } 693 | } 694 | 695 | - (void)setProgressObjectDisplayLink:(CADisplayLink *)progressObjectDisplayLink { 696 | if (progressObjectDisplayLink != _progressObjectDisplayLink) { 697 | [_progressObjectDisplayLink invalidate]; 698 | 699 | _progressObjectDisplayLink = progressObjectDisplayLink; 700 | 701 | [_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; 702 | } 703 | } 704 | 705 | - (void)setProgressObject:(NSProgress *)progressObject { 706 | if (progressObject != _progressObject) { 707 | _progressObject = progressObject; 708 | [self setNSProgressDisplayLinkEnabled:YES]; 709 | } 710 | } 711 | 712 | - (void)setProgress:(float)progress { 713 | if (progress != _progress) { 714 | _progress = progress; 715 | UIView *indicator = self.indicator; 716 | if ([indicator respondsToSelector:@selector(setProgress:)]) { 717 | [(id)indicator setValue:@(self.progress) forKey:@"progress"]; 718 | } 719 | } 720 | } 721 | 722 | - (void)setContentColor:(UIColor *)contentColor { 723 | if (contentColor != _contentColor && ![contentColor isEqual:_contentColor]) { 724 | _contentColor = contentColor; 725 | [self updateViewsForColor:contentColor]; 726 | } 727 | } 728 | 729 | - (void)setDefaultMotionEffectsEnabled:(BOOL)defaultMotionEffectsEnabled { 730 | if (defaultMotionEffectsEnabled != _defaultMotionEffectsEnabled) { 731 | _defaultMotionEffectsEnabled = defaultMotionEffectsEnabled; 732 | [self updateBezelMotionEffects]; 733 | } 734 | } 735 | 736 | #pragma mark - NSProgress 737 | 738 | - (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled { 739 | // We're using CADisplayLink, because NSProgress can change very quickly and observing it may starve the main thread, 740 | // so we're refreshing the progress only every frame draw 741 | if (enabled && self.progressObject) { 742 | // Only create if not already active. 743 | if (!self.progressObjectDisplayLink) { 744 | self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)]; 745 | } 746 | } else { 747 | self.progressObjectDisplayLink = nil; 748 | } 749 | } 750 | 751 | - (void)updateProgressFromProgressObject { 752 | self.progress = self.progressObject.fractionCompleted; 753 | } 754 | 755 | #pragma mark - Notifications 756 | 757 | - (void)registerForNotifications { 758 | #if !TARGET_OS_TV 759 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 760 | 761 | [nc addObserver:self selector:@selector(statusBarOrientationDidChange:) 762 | name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 763 | #endif 764 | } 765 | 766 | - (void)unregisterFromNotifications { 767 | #if !TARGET_OS_TV 768 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 769 | [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 770 | #endif 771 | } 772 | 773 | #if !TARGET_OS_TV 774 | - (void)statusBarOrientationDidChange:(NSNotification *)notification { 775 | UIView *superview = self.superview; 776 | if (!superview) { 777 | return; 778 | } else { 779 | [self updateForCurrentOrientationAnimated:YES]; 780 | } 781 | } 782 | #endif 783 | 784 | - (void)updateForCurrentOrientationAnimated:(BOOL)animated { 785 | // Stay in sync with the superview in any case 786 | if (self.superview) { 787 | self.frame = self.superview.bounds; 788 | } 789 | 790 | // Not needed on iOS 8+, compile out when the deployment target allows, 791 | // to avoid sharedApplication problems on extension targets 792 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 793 | // Only needed pre iOS 8 when added to a window 794 | BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0; 795 | if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return; 796 | 797 | // Make extension friendly. Will not get called on extensions (iOS 8+) due to the above check. 798 | // This just ensures we don't get a warning about extension-unsafe API. 799 | Class UIApplicationClass = NSClassFromString(@"UIApplication"); 800 | if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) return; 801 | 802 | UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; 803 | UIInterfaceOrientation orientation = application.statusBarOrientation; 804 | CGFloat radians = 0; 805 | 806 | if (UIInterfaceOrientationIsLandscape(orientation)) { 807 | radians = orientation == UIInterfaceOrientationLandscapeLeft ? -(CGFloat)M_PI_2 : (CGFloat)M_PI_2; 808 | // Window coordinates differ! 809 | self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); 810 | } else { 811 | radians = orientation == UIInterfaceOrientationPortraitUpsideDown ? (CGFloat)M_PI : 0.f; 812 | } 813 | 814 | if (animated) { 815 | [UIView animateWithDuration:0.3 animations:^{ 816 | self.transform = CGAffineTransformMakeRotation(radians); 817 | }]; 818 | } else { 819 | self.transform = CGAffineTransformMakeRotation(radians); 820 | } 821 | #endif 822 | } 823 | 824 | @end 825 | 826 | 827 | @implementation MBRoundProgressView 828 | 829 | #pragma mark - Lifecycle 830 | 831 | - (id)init { 832 | return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)]; 833 | } 834 | 835 | - (id)initWithFrame:(CGRect)frame { 836 | self = [super initWithFrame:frame]; 837 | if (self) { 838 | self.backgroundColor = [UIColor clearColor]; 839 | self.opaque = NO; 840 | _progress = 0.f; 841 | _annular = NO; 842 | _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f]; 843 | _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f]; 844 | } 845 | return self; 846 | } 847 | 848 | #pragma mark - Layout 849 | 850 | - (CGSize)intrinsicContentSize { 851 | return CGSizeMake(37.f, 37.f); 852 | } 853 | 854 | #pragma mark - Properties 855 | 856 | - (void)setProgress:(float)progress { 857 | if (progress != _progress) { 858 | _progress = progress; 859 | [self setNeedsDisplay]; 860 | } 861 | } 862 | 863 | - (void)setProgressTintColor:(UIColor *)progressTintColor { 864 | NSAssert(progressTintColor, @"The color should not be nil."); 865 | if (progressTintColor != _progressTintColor && ![progressTintColor isEqual:_progressTintColor]) { 866 | _progressTintColor = progressTintColor; 867 | [self setNeedsDisplay]; 868 | } 869 | } 870 | 871 | - (void)setBackgroundTintColor:(UIColor *)backgroundTintColor { 872 | NSAssert(backgroundTintColor, @"The color should not be nil."); 873 | if (backgroundTintColor != _backgroundTintColor && ![backgroundTintColor isEqual:_backgroundTintColor]) { 874 | _backgroundTintColor = backgroundTintColor; 875 | [self setNeedsDisplay]; 876 | } 877 | } 878 | 879 | #pragma mark - Drawing 880 | 881 | - (void)drawRect:(CGRect)rect { 882 | CGContextRef context = UIGraphicsGetCurrentContext(); 883 | BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; 884 | 885 | if (_annular) { 886 | // Draw background 887 | CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f; 888 | UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath]; 889 | processBackgroundPath.lineWidth = lineWidth; 890 | processBackgroundPath.lineCapStyle = kCGLineCapButt; 891 | CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); 892 | CGFloat radius = (self.bounds.size.width - lineWidth)/2; 893 | CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees 894 | CGFloat endAngle = (2 * (float)M_PI) + startAngle; 895 | [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; 896 | [_backgroundTintColor set]; 897 | [processBackgroundPath stroke]; 898 | // Draw progress 899 | UIBezierPath *processPath = [UIBezierPath bezierPath]; 900 | processPath.lineCapStyle = isPreiOS7 ? kCGLineCapRound : kCGLineCapSquare; 901 | processPath.lineWidth = lineWidth; 902 | endAngle = (self.progress * 2 * (float)M_PI) + startAngle; 903 | [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; 904 | [_progressTintColor set]; 905 | [processPath stroke]; 906 | } else { 907 | // Draw background 908 | CGFloat lineWidth = 2.f; 909 | CGRect allRect = self.bounds; 910 | CGRect circleRect = CGRectInset(allRect, lineWidth/2.f, lineWidth/2.f); 911 | CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); 912 | [_progressTintColor setStroke]; 913 | [_backgroundTintColor setFill]; 914 | CGContextSetLineWidth(context, lineWidth); 915 | if (isPreiOS7) { 916 | CGContextFillEllipseInRect(context, circleRect); 917 | } 918 | CGContextStrokeEllipseInRect(context, circleRect); 919 | // 90 degrees 920 | CGFloat startAngle = - ((float)M_PI / 2.f); 921 | // Draw progress 922 | if (isPreiOS7) { 923 | CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - lineWidth; 924 | CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle; 925 | [_progressTintColor setFill]; 926 | CGContextMoveToPoint(context, center.x, center.y); 927 | CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0); 928 | CGContextClosePath(context); 929 | CGContextFillPath(context); 930 | } else { 931 | UIBezierPath *processPath = [UIBezierPath bezierPath]; 932 | processPath.lineCapStyle = kCGLineCapButt; 933 | processPath.lineWidth = lineWidth * 2.f; 934 | CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - (processPath.lineWidth / 2.f); 935 | CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle; 936 | [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; 937 | // Ensure that we don't get color overlaping when _progressTintColor alpha < 1.f. 938 | CGContextSetBlendMode(context, kCGBlendModeCopy); 939 | [_progressTintColor set]; 940 | [processPath stroke]; 941 | } 942 | } 943 | } 944 | 945 | @end 946 | 947 | 948 | @implementation MBBarProgressView 949 | 950 | #pragma mark - Lifecycle 951 | 952 | - (id)init { 953 | return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)]; 954 | } 955 | 956 | - (id)initWithFrame:(CGRect)frame { 957 | self = [super initWithFrame:frame]; 958 | if (self) { 959 | _progress = 0.f; 960 | _lineColor = [UIColor whiteColor]; 961 | _progressColor = [UIColor whiteColor]; 962 | _progressRemainingColor = [UIColor clearColor]; 963 | self.backgroundColor = [UIColor clearColor]; 964 | self.opaque = NO; 965 | } 966 | return self; 967 | } 968 | 969 | #pragma mark - Layout 970 | 971 | - (CGSize)intrinsicContentSize { 972 | BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; 973 | return CGSizeMake(120.f, isPreiOS7 ? 20.f : 10.f); 974 | } 975 | 976 | #pragma mark - Properties 977 | 978 | - (void)setProgress:(float)progress { 979 | if (progress != _progress) { 980 | _progress = progress; 981 | [self setNeedsDisplay]; 982 | } 983 | } 984 | 985 | - (void)setProgressColor:(UIColor *)progressColor { 986 | NSAssert(progressColor, @"The color should not be nil."); 987 | if (progressColor != _progressColor && ![progressColor isEqual:_progressColor]) { 988 | _progressColor = progressColor; 989 | [self setNeedsDisplay]; 990 | } 991 | } 992 | 993 | - (void)setProgressRemainingColor:(UIColor *)progressRemainingColor { 994 | NSAssert(progressRemainingColor, @"The color should not be nil."); 995 | if (progressRemainingColor != _progressRemainingColor && ![progressRemainingColor isEqual:_progressRemainingColor]) { 996 | _progressRemainingColor = progressRemainingColor; 997 | [self setNeedsDisplay]; 998 | } 999 | } 1000 | 1001 | #pragma mark - Drawing 1002 | 1003 | - (void)drawRect:(CGRect)rect { 1004 | CGContextRef context = UIGraphicsGetCurrentContext(); 1005 | 1006 | CGContextSetLineWidth(context, 2); 1007 | CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]); 1008 | CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]); 1009 | 1010 | // Draw background 1011 | CGFloat radius = (rect.size.height / 2) - 2; 1012 | CGContextMoveToPoint(context, 2, rect.size.height/2); 1013 | CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); 1014 | CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2); 1015 | CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); 1016 | CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); 1017 | CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2); 1018 | CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); 1019 | CGContextFillPath(context); 1020 | 1021 | // Draw border 1022 | CGContextMoveToPoint(context, 2, rect.size.height/2); 1023 | CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); 1024 | CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2); 1025 | CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); 1026 | CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); 1027 | CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2); 1028 | CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); 1029 | CGContextStrokePath(context); 1030 | 1031 | CGContextSetFillColorWithColor(context, [_progressColor CGColor]); 1032 | radius = radius - 2; 1033 | CGFloat amount = self.progress * rect.size.width; 1034 | 1035 | // Progress in the middle area 1036 | if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) { 1037 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1038 | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); 1039 | CGContextAddLineToPoint(context, amount, 4); 1040 | CGContextAddLineToPoint(context, amount, radius + 4); 1041 | 1042 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1043 | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); 1044 | CGContextAddLineToPoint(context, amount, rect.size.height - 4); 1045 | CGContextAddLineToPoint(context, amount, radius + 4); 1046 | 1047 | CGContextFillPath(context); 1048 | } 1049 | 1050 | // Progress in the right arc 1051 | else if (amount > radius + 4) { 1052 | CGFloat x = amount - (rect.size.width - radius - 4); 1053 | 1054 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1055 | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); 1056 | CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4); 1057 | CGFloat angle = -acos(x/radius); 1058 | if (isnan(angle)) angle = 0; 1059 | CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0); 1060 | CGContextAddLineToPoint(context, amount, rect.size.height/2); 1061 | 1062 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1063 | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); 1064 | CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4); 1065 | angle = acos(x/radius); 1066 | if (isnan(angle)) angle = 0; 1067 | CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1); 1068 | CGContextAddLineToPoint(context, amount, rect.size.height/2); 1069 | 1070 | CGContextFillPath(context); 1071 | } 1072 | 1073 | // Progress is in the left arc 1074 | else if (amount < radius + 4 && amount > 0) { 1075 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1076 | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); 1077 | CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); 1078 | 1079 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1080 | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); 1081 | CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); 1082 | 1083 | CGContextFillPath(context); 1084 | } 1085 | } 1086 | 1087 | @end 1088 | 1089 | 1090 | @interface MBBackgroundView () 1091 | 1092 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV 1093 | @property UIVisualEffectView *effectView; 1094 | #endif 1095 | #if !TARGET_OS_TV 1096 | @property UIToolbar *toolbar; 1097 | #endif 1098 | 1099 | @end 1100 | 1101 | 1102 | @implementation MBBackgroundView 1103 | 1104 | #pragma mark - Lifecycle 1105 | 1106 | - (instancetype)initWithFrame:(CGRect)frame { 1107 | if ((self = [super initWithFrame:frame])) { 1108 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) { 1109 | _style = MBProgressHUDBackgroundStyleBlur; 1110 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { 1111 | _color = [UIColor colorWithWhite:0.8f alpha:0.6f]; 1112 | } else { 1113 | _color = [UIColor colorWithWhite:0.95f alpha:0.6f]; 1114 | } 1115 | } else { 1116 | _style = MBProgressHUDBackgroundStyleSolidColor; 1117 | _color = [[UIColor blackColor] colorWithAlphaComponent:0.8]; 1118 | } 1119 | 1120 | self.clipsToBounds = YES; 1121 | 1122 | [self updateForBackgroundStyle]; 1123 | } 1124 | return self; 1125 | } 1126 | 1127 | #pragma mark - Layout 1128 | 1129 | - (CGSize)intrinsicContentSize { 1130 | // Smallest size possible. Content pushes against this. 1131 | return CGSizeZero; 1132 | } 1133 | 1134 | #pragma mark - Appearance 1135 | 1136 | - (void)setStyle:(MBProgressHUDBackgroundStyle)style { 1137 | if (style == MBProgressHUDBackgroundStyleBlur && kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0) { 1138 | style = MBProgressHUDBackgroundStyleSolidColor; 1139 | } 1140 | if (_style != style) { 1141 | _style = style; 1142 | [self updateForBackgroundStyle]; 1143 | } 1144 | } 1145 | 1146 | - (void)setColor:(UIColor *)color { 1147 | NSAssert(color, @"The color should not be nil."); 1148 | if (color != _color && ![color isEqual:_color]) { 1149 | _color = color; 1150 | [self updateViewsForColor:color]; 1151 | } 1152 | } 1153 | 1154 | /////////////////////////////////////////////////////////////////////////////////////////// 1155 | #pragma mark - Views 1156 | 1157 | - (void)updateForBackgroundStyle { 1158 | MBProgressHUDBackgroundStyle style = self.style; 1159 | if (style == MBProgressHUDBackgroundStyleBlur) { 1160 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV 1161 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { 1162 | UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 1163 | UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect]; 1164 | [self addSubview:effectView]; 1165 | effectView.frame = self.bounds; 1166 | effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 1167 | self.backgroundColor = self.color; 1168 | self.layer.allowsGroupOpacity = NO; 1169 | self.effectView = effectView; 1170 | } else { 1171 | #endif 1172 | #if !TARGET_OS_TV 1173 | UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectInset(self.bounds, -100.f, -100.f)]; 1174 | toolbar.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 1175 | toolbar.barTintColor = self.color; 1176 | toolbar.translucent = YES; 1177 | [self addSubview:toolbar]; 1178 | self.toolbar = toolbar; 1179 | #endif 1180 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV 1181 | } 1182 | #endif 1183 | } else { 1184 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV 1185 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { 1186 | [self.effectView removeFromSuperview]; 1187 | self.effectView = nil; 1188 | } else { 1189 | #endif 1190 | #if !TARGET_OS_TV 1191 | [self.toolbar removeFromSuperview]; 1192 | self.toolbar = nil; 1193 | #endif 1194 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV 1195 | } 1196 | #endif 1197 | self.backgroundColor = self.color; 1198 | } 1199 | } 1200 | 1201 | - (void)updateViewsForColor:(UIColor *)color { 1202 | if (self.style == MBProgressHUDBackgroundStyleBlur) { 1203 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { 1204 | self.backgroundColor = self.color; 1205 | } else { 1206 | #if !TARGET_OS_TV 1207 | self.toolbar.barTintColor = color; 1208 | #endif 1209 | } 1210 | } else { 1211 | self.backgroundColor = self.color; 1212 | } 1213 | } 1214 | 1215 | @end 1216 | 1217 | 1218 | @implementation MBProgressHUD (Deprecated) 1219 | 1220 | #pragma mark - Class 1221 | 1222 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated { 1223 | NSArray *huds = [MBProgressHUD allHUDsForView:view]; 1224 | for (MBProgressHUD *hud in huds) { 1225 | hud.removeFromSuperViewOnHide = YES; 1226 | [hud hideAnimated:animated]; 1227 | } 1228 | return [huds count]; 1229 | } 1230 | 1231 | + (NSArray *)allHUDsForView:(UIView *)view { 1232 | NSMutableArray *huds = [NSMutableArray array]; 1233 | NSArray *subviews = view.subviews; 1234 | for (UIView *aView in subviews) { 1235 | if ([aView isKindOfClass:self]) { 1236 | [huds addObject:aView]; 1237 | } 1238 | } 1239 | return [NSArray arrayWithArray:huds]; 1240 | } 1241 | 1242 | #pragma mark - Lifecycle 1243 | 1244 | - (id)initWithWindow:(UIWindow *)window { 1245 | return [self initWithView:window]; 1246 | } 1247 | 1248 | #pragma mark - Show & hide 1249 | 1250 | - (void)show:(BOOL)animated { 1251 | [self showAnimated:animated]; 1252 | } 1253 | 1254 | - (void)hide:(BOOL)animated { 1255 | [self hideAnimated:animated]; 1256 | } 1257 | 1258 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay { 1259 | [self hideAnimated:animated afterDelay:delay]; 1260 | } 1261 | 1262 | #pragma mark - Threading 1263 | 1264 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { 1265 | [self showAnimated:animated whileExecutingBlock:^{ 1266 | #pragma clang diagnostic push 1267 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 1268 | // Start executing the requested task 1269 | [target performSelector:method withObject:object]; 1270 | #pragma clang diagnostic pop 1271 | }]; 1272 | } 1273 | 1274 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block { 1275 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 1276 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; 1277 | } 1278 | 1279 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)())completion { 1280 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 1281 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion]; 1282 | } 1283 | 1284 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue { 1285 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; 1286 | } 1287 | 1288 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue completionBlock:(nullable MBProgressHUDCompletionBlock)completion { 1289 | self.taskInProgress = YES; 1290 | self.completionBlock = completion; 1291 | dispatch_async(queue, ^(void) { 1292 | block(); 1293 | dispatch_async(dispatch_get_main_queue(), ^(void) { 1294 | [self cleanUp]; 1295 | }); 1296 | }); 1297 | [self showAnimated:animated]; 1298 | } 1299 | 1300 | - (void)cleanUp { 1301 | self.taskInProgress = NO; 1302 | [self hideAnimated:self.useAnimation]; 1303 | } 1304 | 1305 | #pragma mark - Labels 1306 | 1307 | - (NSString *)labelText { 1308 | return self.label.text; 1309 | } 1310 | 1311 | - (void)setLabelText:(NSString *)labelText { 1312 | MBMainThreadAssert(); 1313 | self.label.text = labelText; 1314 | } 1315 | 1316 | - (UIFont *)labelFont { 1317 | return self.label.font; 1318 | } 1319 | 1320 | - (void)setLabelFont:(UIFont *)labelFont { 1321 | MBMainThreadAssert(); 1322 | self.label.font = labelFont; 1323 | } 1324 | 1325 | - (UIColor *)labelColor { 1326 | return self.label.textColor; 1327 | } 1328 | 1329 | - (void)setLabelColor:(UIColor *)labelColor { 1330 | MBMainThreadAssert(); 1331 | self.label.textColor = labelColor; 1332 | } 1333 | 1334 | - (NSString *)detailsLabelText { 1335 | return self.detailsLabel.text; 1336 | } 1337 | 1338 | - (void)setDetailsLabelText:(NSString *)detailsLabelText { 1339 | MBMainThreadAssert(); 1340 | self.detailsLabel.text = detailsLabelText; 1341 | } 1342 | 1343 | - (UIFont *)detailsLabelFont { 1344 | return self.detailsLabel.font; 1345 | } 1346 | 1347 | - (void)setDetailsLabelFont:(UIFont *)detailsLabelFont { 1348 | MBMainThreadAssert(); 1349 | self.detailsLabel.font = detailsLabelFont; 1350 | } 1351 | 1352 | - (UIColor *)detailsLabelColor { 1353 | return self.detailsLabel.textColor; 1354 | } 1355 | 1356 | - (void)setDetailsLabelColor:(UIColor *)detailsLabelColor { 1357 | MBMainThreadAssert(); 1358 | self.detailsLabel.textColor = detailsLabelColor; 1359 | } 1360 | 1361 | - (CGFloat)opacity { 1362 | return _opacity; 1363 | } 1364 | 1365 | - (void)setOpacity:(CGFloat)opacity { 1366 | MBMainThreadAssert(); 1367 | _opacity = opacity; 1368 | } 1369 | 1370 | - (UIColor *)color { 1371 | return self.bezelView.color; 1372 | } 1373 | 1374 | - (void)setColor:(UIColor *)color { 1375 | MBMainThreadAssert(); 1376 | self.bezelView.color = color; 1377 | } 1378 | 1379 | - (CGFloat)yOffset { 1380 | return self.offset.y; 1381 | } 1382 | 1383 | - (void)setYOffset:(CGFloat)yOffset { 1384 | MBMainThreadAssert(); 1385 | self.offset = CGPointMake(self.offset.x, yOffset); 1386 | } 1387 | 1388 | - (CGFloat)xOffset { 1389 | return self.offset.x; 1390 | } 1391 | 1392 | - (void)setXOffset:(CGFloat)xOffset { 1393 | MBMainThreadAssert(); 1394 | self.offset = CGPointMake(xOffset, self.offset.y); 1395 | } 1396 | 1397 | - (CGFloat)cornerRadius { 1398 | return self.bezelView.layer.cornerRadius; 1399 | } 1400 | 1401 | - (void)setCornerRadius:(CGFloat)cornerRadius { 1402 | MBMainThreadAssert(); 1403 | self.bezelView.layer.cornerRadius = cornerRadius; 1404 | } 1405 | 1406 | - (BOOL)dimBackground { 1407 | MBBackgroundView *backgroundView = self.backgroundView; 1408 | UIColor *dimmedColor = [UIColor colorWithWhite:0.f alpha:.2f]; 1409 | return backgroundView.style == MBProgressHUDBackgroundStyleSolidColor && [backgroundView.color isEqual:dimmedColor]; 1410 | } 1411 | 1412 | - (void)setDimBackground:(BOOL)dimBackground { 1413 | MBMainThreadAssert(); 1414 | self.backgroundView.style = MBProgressHUDBackgroundStyleSolidColor; 1415 | self.backgroundView.color = dimBackground ? [UIColor colorWithWhite:0.f alpha:.2f] : [UIColor clearColor]; 1416 | } 1417 | 1418 | - (CGSize)size { 1419 | return self.bezelView.frame.size; 1420 | } 1421 | 1422 | - (UIColor *)activityIndicatorColor { 1423 | return _activityIndicatorColor; 1424 | } 1425 | 1426 | - (void)setActivityIndicatorColor:(UIColor *)activityIndicatorColor { 1427 | if (activityIndicatorColor != _activityIndicatorColor) { 1428 | _activityIndicatorColor = activityIndicatorColor; 1429 | UIActivityIndicatorView *indicator = (UIActivityIndicatorView *)self.indicator; 1430 | if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) { 1431 | [indicator setColor:activityIndicatorColor]; 1432 | } 1433 | } 1434 | } 1435 | 1436 | @end 1437 | 1438 | @implementation MBProgressHUDRoundedButton 1439 | 1440 | #pragma mark - Lifecycle 1441 | 1442 | - (instancetype)initWithFrame:(CGRect)frame { 1443 | self = [super initWithFrame:frame]; 1444 | if (self) { 1445 | CALayer *layer = self.layer; 1446 | layer.borderWidth = 1.f; 1447 | } 1448 | return self; 1449 | } 1450 | 1451 | #pragma mark - Layout 1452 | 1453 | - (void)layoutSubviews { 1454 | [super layoutSubviews]; 1455 | // Fully rounded corners 1456 | CGFloat height = CGRectGetHeight(self.bounds); 1457 | self.layer.cornerRadius = ceil(height / 2.f); 1458 | } 1459 | 1460 | - (CGSize)intrinsicContentSize { 1461 | // Only show if we have associated control events 1462 | if (self.allControlEvents == 0) return CGSizeZero; 1463 | CGSize size = [super intrinsicContentSize]; 1464 | // Add some side padding 1465 | size.width += 20.f; 1466 | return size; 1467 | } 1468 | 1469 | #pragma mark - Color 1470 | 1471 | - (void)setTitleColor:(UIColor *)color forState:(UIControlState)state { 1472 | [super setTitleColor:color forState:state]; 1473 | // Update related colors 1474 | [self setHighlighted:self.highlighted]; 1475 | self.layer.borderColor = color.CGColor; 1476 | } 1477 | 1478 | - (void)setHighlighted:(BOOL)highlighted { 1479 | [super setHighlighted:highlighted]; 1480 | UIColor *baseColor = [self titleColorForState:UIControlStateSelected]; 1481 | self.backgroundColor = highlighted ? [baseColor colorWithAlphaComponent:0.1f] : [UIColor clearColor]; 1482 | } 1483 | 1484 | @end 1485 | -------------------------------------------------------------------------------- /TBPlayer/Classes/XCToast/XCHudHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCHudHelper.h 3 | // 4 | // Created by TopDev on 10/23/14. 5 | // Copyright (c) 2014 TopDev. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #import "MBProgressHUD.h" 11 | 12 | @interface XCHudHelper : NSObject 13 | 14 | @property(nonatomic, strong) MBProgressHUD *hud; 15 | 16 | // 单例 17 | + (XCHudHelper *)sharedInstance; 18 | 19 | // 在window上显示菊花转hud 20 | - (void)showHudAcitivityOnWindow; 21 | 22 | // 在window上显示hud 23 | // 参数: 24 | // caption:标题 25 | // bActive:是否显示转圈动画 26 | // time:自动消失时间,如果为0,则不自动消失 27 | 28 | - (void)showHudOnWindow:(NSString *)caption 29 | image:(UIImage *)image 30 | acitivity:(BOOL)bAcitve 31 | autoHideTime:(NSTimeInterval)time; 32 | 33 | // 在当前的view上显示hud 34 | // 参数: 35 | // view:要添加hud的view 36 | // caption:标题 37 | // image:图片 38 | // bActive:是否显示转圈动画 39 | // time:自动消失时间,如果为0,则不自动消失 40 | - (void)showHudOnView:(UIView *)view 41 | caption:(NSString *)caption 42 | image:(UIImage *)image 43 | acitivity:(BOOL)bAcitve 44 | autoHideTime:(NSTimeInterval)time; 45 | 46 | - (void)setCaption:(NSString *)caption; 47 | 48 | // 隐藏hud 49 | - (void)hideHud; 50 | 51 | - (void)hideHudAfter:(NSTimeInterval)time; 52 | 53 | 54 | //成功,失败,普通信息 55 | + (void)showSuccess:(NSString *)success; 56 | + (void)showError:(NSString *)error; 57 | + (void)showMessage:(NSString *)message; 58 | + (void)hideHUD; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /TBPlayer/Classes/XCToast/XCHudHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // XFHudHelper.m 3 | // 4 | // Created by TopDev on 10/23/14. 5 | // Copyright (c) 2014 TopDev. All rights reserved. 6 | // 7 | 8 | #import "XCHudHelper.h" 9 | 10 | @implementation XCHudHelper 11 | 12 | + (XCHudHelper *)sharedInstance { 13 | static XCHudHelper *_instance = nil; 14 | 15 | @synchronized (self) { 16 | if (_instance == nil) { 17 | _instance = [[self alloc] init]; 18 | } 19 | } 20 | 21 | return _instance; 22 | } 23 | 24 | - (void)showHudAcitivityOnWindow 25 | { 26 | [self showHudOnWindow:nil image:nil acitivity:YES autoHideTime:0]; 27 | } 28 | 29 | - (void)showHudOnWindow:(NSString *)caption 30 | image:(UIImage *)image 31 | acitivity:(BOOL)active 32 | autoHideTime:(NSTimeInterval)time1 { 33 | 34 | if (_hud) { 35 | [_hud hideAnimated:NO]; 36 | } 37 | 38 | 39 | self.hud = [[MBProgressHUD alloc] initWithView:[[UIApplication sharedApplication] delegate].window]; 40 | [[[UIApplication sharedApplication] delegate].window addSubview:self.hud]; 41 | self.hud.label.text = caption; 42 | self.hud.customView = [[UIImageView alloc] initWithImage:image]; 43 | self.hud.customView.bounds = CGRectMake(0, 0, 100, 100); 44 | self.hud.mode = image ? MBProgressHUDModeCustomView : MBProgressHUDModeIndeterminate; 45 | self.hud.animationType = MBProgressHUDAnimationFade; 46 | self.hud.removeFromSuperViewOnHide = YES; 47 | [self.hud showAnimated:YES]; 48 | if (time1 > 0) { 49 | [self.hud hideAnimated:YES afterDelay:time1]; 50 | } 51 | } 52 | 53 | - (void)showHudOnView:(UIView *)view 54 | caption:(NSString *)caption 55 | image:(UIImage *)image 56 | acitivity:(BOOL)active 57 | autoHideTime:(NSTimeInterval)time1 { 58 | 59 | if (_hud) { 60 | [_hud hideAnimated:NO]; 61 | } 62 | 63 | self.hud = [[MBProgressHUD alloc] initWithView:view]; 64 | [view addSubview:self.hud]; 65 | self.hud.label.text = caption; 66 | self.hud.customView = [[UIImageView alloc] initWithImage:image]; 67 | self.hud.customView.bounds = CGRectMake(0, 0, 100, 100); 68 | self.hud.mode = image ? MBProgressHUDModeCustomView : MBProgressHUDModeIndeterminate; 69 | self.hud.animationType = MBProgressHUDAnimationFade; 70 | [self.hud showAnimated:YES]; 71 | if (time1 > 0) { 72 | [self.hud hideAnimated:YES afterDelay:time1]; 73 | } 74 | } 75 | 76 | - (void)setCaption:(NSString *)caption { 77 | self.hud.label.text = caption; 78 | } 79 | 80 | 81 | - (void)hideHud { 82 | if (_hud) { 83 | [_hud hideAnimated:YES]; 84 | } 85 | } 86 | 87 | - (void)hideHudAfter:(NSTimeInterval)time1 { 88 | if (_hud) { 89 | [_hud hideAnimated:YES afterDelay:time1]; 90 | } 91 | } 92 | 93 | 94 | + (void)showSuccess:(NSString *)success 95 | { 96 | [self showSuccess:success toView:nil]; 97 | } 98 | 99 | + (void)showError:(NSString *)error 100 | { 101 | [self showError:error toView:nil]; 102 | } 103 | 104 | 105 | 106 | + (void)showError:(NSString *)error toView:(UIView *)view{ 107 | [self show:error icon:@"error.png" view:view]; 108 | } 109 | 110 | + (void)showSuccess:(NSString *)success toView:(UIView *)view 111 | { 112 | [self show:success icon:@"success.png" view:view]; 113 | } 114 | 115 | #pragma mark 显示信息 116 | + (void)show:(NSString *)text icon:(NSString *)icon view:(UIView *)view 117 | { 118 | if (view == nil) view = [[UIApplication sharedApplication].windows lastObject]; 119 | // 快速显示一个提示信息 120 | MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:view animated:YES]; 121 | hud.label.text = text; 122 | // 设置图片 123 | hud.customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:@"MBProgressHUD.bundle/%@", icon]]]; 124 | // 再设置模式 125 | hud.mode = MBProgressHUDModeCustomView; 126 | 127 | // 隐藏时候从父控件中移除 128 | hud.removeFromSuperViewOnHide = YES; 129 | 130 | // 1.0秒之后再消失 131 | [hud hideAnimated:YES afterDelay:1.0]; 132 | } 133 | 134 | 135 | + (void)showMessage:(NSString *)message 136 | { 137 | [self show:message icon:@"" view:nil]; 138 | } 139 | 140 | + (void)showMessage:(NSString *)message toView:(UIView *)view { 141 | if (view == nil) view = [[UIApplication sharedApplication].windows lastObject]; 142 | // 快速显示一个提示信息 143 | MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:view animated:YES]; 144 | hud.label.text = message; 145 | // 隐藏时候从父控件中移除 146 | hud.removeFromSuperViewOnHide = YES; 147 | // YES代表需要蒙版效果 148 | //hud.dimBackground = YES; 149 | // 100秒之后再消失 150 | [hud hideAnimated:YES afterDelay:100.0]; 151 | } 152 | 153 | 154 | + (void)hideHUD 155 | { 156 | UIView *view = [[UIApplication sharedApplication].windows lastObject]; 157 | [MBProgressHUD hideHUDForView:view animated:YES]; 158 | } 159 | @end 160 | -------------------------------------------------------------------------------- /TBPlayer/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 | 43 | 44 | -------------------------------------------------------------------------------- /TBPlayer/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TBPlayer 4 | // 5 | // Created by qianjianeng on 16/1/31. 6 | // Copyright © 2016年 SF. All rights reserved. 7 | // 8 | //// github地址:https://github.com/suifengqjn/TBPlayer 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /TBPlayer/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TBPlayer 4 | // 5 | // Created by qianjianeng on 16/1/31. 6 | // Copyright © 2016年 SF. All rights reserved. 7 | // 8 | //// github地址:https://github.com/suifengqjn/TBPlayer 9 | 10 | #import "ViewController.h" 11 | #import "TBPlayer.h" 12 | #import "XCHudHelper.h" 13 | #import "avplayerVC.h" 14 | 15 | @interface ViewController () 16 | 17 | 18 | 19 | @end 20 | 21 | @implementation ViewController 22 | 23 | - (BOOL)prefersStatusBarHidden 24 | { 25 | return YES; 26 | } 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | 30 | 31 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 32 | button.frame = CGRectMake(100, 100, 100, 100); 33 | button.center = self.view.center; 34 | [button setTitle:@"播放" forState:UIControlStateNormal]; 35 | button.backgroundColor = [UIColor darkGrayColor]; 36 | [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 37 | [button addTarget:self action:@selector(ButtonClick) forControlEvents:UIControlEventTouchUpInside]; 38 | [self.view addSubview:button]; 39 | 40 | } 41 | 42 | - (void)ButtonClick 43 | { 44 | avplayerVC *vc = [avplayerVC new]; 45 | 46 | [self presentViewController:vc animated:NO completion:nil]; 47 | } 48 | 49 | 50 | 51 | 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /TBPlayer/avplayerVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // avplayerVC.h 3 | // TBPlayer 4 | // 5 | // Created by qianjianeng on 16/2/27. 6 | // Copyright © 2016年 SF. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface avplayerVC : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TBPlayer/avplayerVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // avplayerVC.m 3 | // TBPlayer 4 | // 5 | // Created by qianjianeng on 16/2/27. 6 | // Copyright © 2016年 SF. All rights reserved. 7 | // 8 | 9 | #import "avplayerVC.h" 10 | #import "TBPlayer.h" 11 | 12 | #define kScreenWidth [UIScreen mainScreen].bounds.size.width 13 | #define kScreenHeight [UIScreen mainScreen].bounds.size.height 14 | 15 | 16 | @interface avplayerVC () 17 | 18 | @property (nonatomic, strong) TBPlayer *player; 19 | @property (nonatomic, strong) UIView *showView; 20 | @end 21 | 22 | @implementation avplayerVC 23 | 24 | - (BOOL)prefersStatusBarHidden 25 | { 26 | return YES; 27 | } 28 | 29 | - (void)viewDidLoad { 30 | [super viewDidLoad]; 31 | 32 | 33 | self.showView = [[UIView alloc] init]; 34 | self.showView.backgroundColor = [UIColor redColor]; 35 | self.showView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight); 36 | [self.view addSubview:self.showView]; 37 | 38 | 39 | 40 | NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject; 41 | NSString *movePath = [document stringByAppendingPathComponent:@"保存数据.mp4"]; 42 | 43 | NSURL *localURL = [NSURL fileURLWithPath:movePath]; 44 | 45 | NSURL *url2 = [NSURL URLWithString:@"http://zyvideo1.oss-cn-qingdao.aliyuncs.com/zyvd/7c/de/04ec95f4fd42d9d01f63b9683ad0"]; 46 | url2 = [NSURL URLWithString:@"http://v4ttyey-10001453.video.myqcloud.com/Microblog/288-4-1452304375video1466172731.mp4"]; 47 | 48 | [[TBPlayer sharedInstance] playWithUrl:url2 showView:self.showView]; 49 | 50 | } 51 | 52 | 53 | 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /TBPlayer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TBPlayer 4 | // 5 | // Created by qianjn on 2016/11/16. 6 | // Copyright © 2016年 SF. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TBPlayer/screenShot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suifengqjn/TBPlayer/89425622e2acc8df400f0ce0fe4d7b8193678107/TBPlayer/screenShot/1.png --------------------------------------------------------------------------------