├── .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
--------------------------------------------------------------------------------