├── .DS_Store ├── .gitignore ├── .swift-version ├── LICENSE ├── Pods ├── Manifest.lock ├── Pods.xcodeproj │ └── project.pbxproj └── Target Support Files │ └── Pods-TTPlayerCache │ ├── Pods-TTPlayerCache-acknowledgements.markdown │ ├── Pods-TTPlayerCache-acknowledgements.plist │ ├── Pods-TTPlayerCache-dummy.m │ ├── Pods-TTPlayerCache-frameworks.sh │ └── Pods-TTPlayerCache-resources.sh ├── README.md ├── Source ├── .DS_Store └── TTPlayerCache │ ├── .DS_Store │ ├── Category │ ├── .DS_Store │ ├── AVAssetResourceLoadingDataRequest+TTCategory.h │ └── AVAssetResourceLoadingDataRequest+TTCategory.m │ ├── Reachability │ ├── TTReachability.h │ ├── TTReachability.m │ ├── TTReachabilityManager.h │ └── TTReachabilityManager.m │ ├── TTPlayerCache.h │ ├── TTPlayerCacheMacro.h │ ├── TTResourceLoaderCache.h │ ├── TTResourceLoaderCache.m │ ├── TTResourceLoaderData.h │ ├── TTResourceLoaderData.m │ ├── TTResourceLoaderDelegate.h │ └── TTResourceLoaderDelegate.m ├── TTPlayerCache.podspec ├── TTPlayerCache.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── WorkspaceSettings.xcsettings │ └── xcuserdata │ │ └── szt.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── WorkspaceSettings.xcsettings └── xcuserdata │ └── szt.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── TTPlayerCache.xcscheme │ └── xcschememanagement.plist └── TTPlayerCache ├── .DS_Store ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── MainViewController.h ├── MainViewController.m ├── TTPlayerView ├── TTPlayerView.h └── TTPlayerView.m ├── ViewController.h ├── ViewController.m └── main.m /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun8801/TTPlayerCache/fe512d17ef8935190f530d7fc02fb5be29b7f4c2/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 sun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODFILE CHECKSUM: 2736343cb092baef818d8f1ac55c2e89e0cd4175 2 | 3 | COCOAPODS: 1.4.0.beta.2 4 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8447C2D9BC804879C4B8DEC1E4EF8DB9 /* Pods-TTPlayerCache-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AD863BAFA47D52172788D04E19602A0 /* Pods-TTPlayerCache-dummy.m */; }; 11 | 9482559D145C5ED423DDDBF5D184E910 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6604A7D69453B4569E4E4827FB9155A9 /* Foundation.framework */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXFileReference section */ 15 | 073D04C8F0310BF3EA58BD0423B4EE21 /* Pods-TTPlayerCache.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-TTPlayerCache.release.xcconfig"; sourceTree = ""; }; 16 | 0AD863BAFA47D52172788D04E19602A0 /* Pods-TTPlayerCache-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-TTPlayerCache-dummy.m"; sourceTree = ""; }; 17 | 1005349B033EA7C6B5B33443F8993AFF /* Pods-TTPlayerCache-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-TTPlayerCache-resources.sh"; sourceTree = ""; }; 18 | 1C0FB522E067F9114D7F5A8AA5D4E0CB /* libPods-TTPlayerCache.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; name = "libPods-TTPlayerCache.a"; path = "libPods-TTPlayerCache.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 4439D402346AA1E2D77D027A2B70B0C9 /* Pods-TTPlayerCache.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-TTPlayerCache.debug.xcconfig"; sourceTree = ""; }; 20 | 6604A7D69453B4569E4E4827FB9155A9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 21 | 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 22 | A7F3215C3AB7D4F2F8D989397494B553 /* Pods-TTPlayerCache-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-TTPlayerCache-frameworks.sh"; sourceTree = ""; }; 23 | AF6098A9A0C1227A720DAD27BA66650B /* Pods-TTPlayerCache-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-TTPlayerCache-acknowledgements.markdown"; sourceTree = ""; }; 24 | E275287D7E4CE744BD940E63F2D21B96 /* Pods-TTPlayerCache-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-TTPlayerCache-acknowledgements.plist"; sourceTree = ""; }; 25 | /* End PBXFileReference section */ 26 | 27 | /* Begin PBXFrameworksBuildPhase section */ 28 | E711886414825AFB2745495843E787EB /* Frameworks */ = { 29 | isa = PBXFrameworksBuildPhase; 30 | buildActionMask = 2147483647; 31 | files = ( 32 | 9482559D145C5ED423DDDBF5D184E910 /* Foundation.framework in Frameworks */, 33 | ); 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXFrameworksBuildPhase section */ 37 | 38 | /* Begin PBXGroup section */ 39 | 7DB346D0F39D3F0E887471402A8071AB = { 40 | isa = PBXGroup; 41 | children = ( 42 | 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */, 43 | BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */, 44 | D6660B43F3C075C41FFA0F29A2463AB6 /* Products */, 45 | D1CC252DE2A84335011A6665BC7A1F0D /* Targets Support Files */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | BA6841E753FC5D0C12C091F446B35994 /* Pods-TTPlayerCache */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | AF6098A9A0C1227A720DAD27BA66650B /* Pods-TTPlayerCache-acknowledgements.markdown */, 53 | E275287D7E4CE744BD940E63F2D21B96 /* Pods-TTPlayerCache-acknowledgements.plist */, 54 | 0AD863BAFA47D52172788D04E19602A0 /* Pods-TTPlayerCache-dummy.m */, 55 | A7F3215C3AB7D4F2F8D989397494B553 /* Pods-TTPlayerCache-frameworks.sh */, 56 | 1005349B033EA7C6B5B33443F8993AFF /* Pods-TTPlayerCache-resources.sh */, 57 | 4439D402346AA1E2D77D027A2B70B0C9 /* Pods-TTPlayerCache.debug.xcconfig */, 58 | 073D04C8F0310BF3EA58BD0423B4EE21 /* Pods-TTPlayerCache.release.xcconfig */, 59 | ); 60 | name = "Pods-TTPlayerCache"; 61 | path = "Target Support Files/Pods-TTPlayerCache"; 62 | sourceTree = ""; 63 | }; 64 | BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | D35AF013A5F0BAD4F32504907A52519E /* iOS */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | D1CC252DE2A84335011A6665BC7A1F0D /* Targets Support Files */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | BA6841E753FC5D0C12C091F446B35994 /* Pods-TTPlayerCache */, 76 | ); 77 | name = "Targets Support Files"; 78 | sourceTree = ""; 79 | }; 80 | D35AF013A5F0BAD4F32504907A52519E /* iOS */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 6604A7D69453B4569E4E4827FB9155A9 /* Foundation.framework */, 84 | ); 85 | name = iOS; 86 | sourceTree = ""; 87 | }; 88 | D6660B43F3C075C41FFA0F29A2463AB6 /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 1C0FB522E067F9114D7F5A8AA5D4E0CB /* libPods-TTPlayerCache.a */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | /* End PBXGroup section */ 97 | 98 | /* Begin PBXNativeTarget section */ 99 | DFDAD404176CA1F41F03AACF80561E9A /* Pods-TTPlayerCache */ = { 100 | isa = PBXNativeTarget; 101 | buildConfigurationList = 6C7A0C75EBDE9E18906810DECAE09AD7 /* Build configuration list for PBXNativeTarget "Pods-TTPlayerCache" */; 102 | buildPhases = ( 103 | 82609333B635E46A37E759CB2F401E2C /* Sources */, 104 | E711886414825AFB2745495843E787EB /* Frameworks */, 105 | ); 106 | buildRules = ( 107 | ); 108 | dependencies = ( 109 | ); 110 | name = "Pods-TTPlayerCache"; 111 | productName = "Pods-TTPlayerCache"; 112 | productReference = 1C0FB522E067F9114D7F5A8AA5D4E0CB /* libPods-TTPlayerCache.a */; 113 | productType = "com.apple.product-type.library.static"; 114 | }; 115 | /* End PBXNativeTarget section */ 116 | 117 | /* Begin PBXProject section */ 118 | D41D8CD98F00B204E9800998ECF8427E /* Project object */ = { 119 | isa = PBXProject; 120 | attributes = { 121 | LastSwiftUpdateCheck = 0830; 122 | LastUpgradeCheck = 0700; 123 | }; 124 | buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */; 125 | compatibilityVersion = "Xcode 3.2"; 126 | developmentRegion = English; 127 | hasScannedForEncodings = 0; 128 | knownRegions = ( 129 | en, 130 | ); 131 | mainGroup = 7DB346D0F39D3F0E887471402A8071AB; 132 | productRefGroup = D6660B43F3C075C41FFA0F29A2463AB6 /* Products */; 133 | projectDirPath = ""; 134 | projectRoot = ""; 135 | targets = ( 136 | DFDAD404176CA1F41F03AACF80561E9A /* Pods-TTPlayerCache */, 137 | ); 138 | }; 139 | /* End PBXProject section */ 140 | 141 | /* Begin PBXSourcesBuildPhase section */ 142 | 82609333B635E46A37E759CB2F401E2C /* Sources */ = { 143 | isa = PBXSourcesBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | 8447C2D9BC804879C4B8DEC1E4EF8DB9 /* Pods-TTPlayerCache-dummy.m in Sources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXSourcesBuildPhase section */ 151 | 152 | /* Begin XCBuildConfiguration section */ 153 | 12C4B864767C59600DBFA238F1E2F8BC /* Release */ = { 154 | isa = XCBuildConfiguration; 155 | buildSettings = { 156 | ALWAYS_SEARCH_USER_PATHS = NO; 157 | CLANG_ANALYZER_NONNULL = YES; 158 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 159 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 160 | CLANG_CXX_LIBRARY = "libc++"; 161 | CLANG_ENABLE_MODULES = YES; 162 | CLANG_ENABLE_OBJC_ARC = YES; 163 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 164 | CLANG_WARN_BOOL_CONVERSION = YES; 165 | CLANG_WARN_COMMA = YES; 166 | CLANG_WARN_CONSTANT_CONVERSION = YES; 167 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 168 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 169 | CLANG_WARN_EMPTY_BODY = YES; 170 | CLANG_WARN_ENUM_CONVERSION = YES; 171 | CLANG_WARN_INFINITE_RECURSION = YES; 172 | CLANG_WARN_INT_CONVERSION = YES; 173 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 174 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 175 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 176 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 177 | CLANG_WARN_STRICT_PROTOTYPES = YES; 178 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 179 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 180 | CLANG_WARN_UNREACHABLE_CODE = YES; 181 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 182 | CODE_SIGNING_REQUIRED = NO; 183 | COPY_PHASE_STRIP = NO; 184 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 185 | ENABLE_NS_ASSERTIONS = NO; 186 | ENABLE_STRICT_OBJC_MSGSEND = YES; 187 | GCC_C_LANGUAGE_STANDARD = gnu11; 188 | GCC_NO_COMMON_BLOCKS = YES; 189 | GCC_PREPROCESSOR_DEFINITIONS = ( 190 | "POD_CONFIGURATION_RELEASE=1", 191 | "$(inherited)", 192 | ); 193 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 194 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 195 | GCC_WARN_UNDECLARED_SELECTOR = YES; 196 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 197 | GCC_WARN_UNUSED_FUNCTION = YES; 198 | GCC_WARN_UNUSED_VARIABLE = YES; 199 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 200 | MTL_ENABLE_DEBUG_INFO = NO; 201 | PRODUCT_NAME = "$(TARGET_NAME)"; 202 | PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/; 203 | STRIP_INSTALLED_PRODUCT = NO; 204 | SYMROOT = "${SRCROOT}/../build"; 205 | }; 206 | name = Release; 207 | }; 208 | 59F0302F2371FCC6B73EF0A418D40734 /* Debug */ = { 209 | isa = XCBuildConfiguration; 210 | buildSettings = { 211 | ALWAYS_SEARCH_USER_PATHS = NO; 212 | CLANG_ANALYZER_NONNULL = YES; 213 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 214 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 215 | CLANG_CXX_LIBRARY = "libc++"; 216 | CLANG_ENABLE_MODULES = YES; 217 | CLANG_ENABLE_OBJC_ARC = YES; 218 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 219 | CLANG_WARN_BOOL_CONVERSION = YES; 220 | CLANG_WARN_COMMA = YES; 221 | CLANG_WARN_CONSTANT_CONVERSION = YES; 222 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 223 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 224 | CLANG_WARN_EMPTY_BODY = YES; 225 | CLANG_WARN_ENUM_CONVERSION = YES; 226 | CLANG_WARN_INFINITE_RECURSION = YES; 227 | CLANG_WARN_INT_CONVERSION = YES; 228 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 229 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 230 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 231 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 232 | CLANG_WARN_STRICT_PROTOTYPES = YES; 233 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 234 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 235 | CLANG_WARN_UNREACHABLE_CODE = YES; 236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 237 | CODE_SIGNING_REQUIRED = NO; 238 | COPY_PHASE_STRIP = NO; 239 | DEBUG_INFORMATION_FORMAT = dwarf; 240 | ENABLE_STRICT_OBJC_MSGSEND = YES; 241 | ENABLE_TESTABILITY = YES; 242 | GCC_C_LANGUAGE_STANDARD = gnu11; 243 | GCC_DYNAMIC_NO_PIC = NO; 244 | GCC_NO_COMMON_BLOCKS = YES; 245 | GCC_OPTIMIZATION_LEVEL = 0; 246 | GCC_PREPROCESSOR_DEFINITIONS = ( 247 | "POD_CONFIGURATION_DEBUG=1", 248 | "DEBUG=1", 249 | "$(inherited)", 250 | ); 251 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 252 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 253 | GCC_WARN_UNDECLARED_SELECTOR = YES; 254 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 255 | GCC_WARN_UNUSED_FUNCTION = YES; 256 | GCC_WARN_UNUSED_VARIABLE = YES; 257 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 258 | MTL_ENABLE_DEBUG_INFO = YES; 259 | ONLY_ACTIVE_ARCH = YES; 260 | PRODUCT_NAME = "$(TARGET_NAME)"; 261 | PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/; 262 | STRIP_INSTALLED_PRODUCT = NO; 263 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 264 | SYMROOT = "${SRCROOT}/../build"; 265 | }; 266 | name = Debug; 267 | }; 268 | 8A37B62CB7628D1168AD855D306048C8 /* Release */ = { 269 | isa = XCBuildConfiguration; 270 | baseConfigurationReference = 073D04C8F0310BF3EA58BD0423B4EE21 /* Pods-TTPlayerCache.release.xcconfig */; 271 | buildSettings = { 272 | CODE_SIGN_IDENTITY = "iPhone Developer"; 273 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 274 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 275 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 276 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 277 | MACH_O_TYPE = staticlib; 278 | OTHER_LDFLAGS = ""; 279 | OTHER_LIBTOOLFLAGS = ""; 280 | PODS_ROOT = "$(SRCROOT)"; 281 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; 282 | SDKROOT = iphoneos; 283 | SKIP_INSTALL = YES; 284 | TARGETED_DEVICE_FAMILY = "1,2"; 285 | VALIDATE_PRODUCT = YES; 286 | }; 287 | name = Release; 288 | }; 289 | FFAB536598C8BB79C2C677E198B95F30 /* Debug */ = { 290 | isa = XCBuildConfiguration; 291 | baseConfigurationReference = 4439D402346AA1E2D77D027A2B70B0C9 /* Pods-TTPlayerCache.debug.xcconfig */; 292 | buildSettings = { 293 | CODE_SIGN_IDENTITY = "iPhone Developer"; 294 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 295 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 296 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 297 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 298 | MACH_O_TYPE = staticlib; 299 | OTHER_LDFLAGS = ""; 300 | OTHER_LIBTOOLFLAGS = ""; 301 | PODS_ROOT = "$(SRCROOT)"; 302 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; 303 | SDKROOT = iphoneos; 304 | SKIP_INSTALL = YES; 305 | TARGETED_DEVICE_FAMILY = "1,2"; 306 | }; 307 | name = Debug; 308 | }; 309 | /* End XCBuildConfiguration section */ 310 | 311 | /* Begin XCConfigurationList section */ 312 | 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { 313 | isa = XCConfigurationList; 314 | buildConfigurations = ( 315 | 59F0302F2371FCC6B73EF0A418D40734 /* Debug */, 316 | 12C4B864767C59600DBFA238F1E2F8BC /* Release */, 317 | ); 318 | defaultConfigurationIsVisible = 0; 319 | defaultConfigurationName = Release; 320 | }; 321 | 6C7A0C75EBDE9E18906810DECAE09AD7 /* Build configuration list for PBXNativeTarget "Pods-TTPlayerCache" */ = { 322 | isa = XCConfigurationList; 323 | buildConfigurations = ( 324 | FFAB536598C8BB79C2C677E198B95F30 /* Debug */, 325 | 8A37B62CB7628D1168AD855D306048C8 /* Release */, 326 | ); 327 | defaultConfigurationIsVisible = 0; 328 | defaultConfigurationName = Release; 329 | }; 330 | /* End XCConfigurationList section */ 331 | }; 332 | rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */; 333 | } 334 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TTPlayerCache/Pods-TTPlayerCache-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TTPlayerCache/Pods-TTPlayerCache-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TTPlayerCache/Pods-TTPlayerCache-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_TTPlayerCache : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_TTPlayerCache 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TTPlayerCache/Pods-TTPlayerCache-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 10 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 11 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 12 | 13 | install_framework() 14 | { 15 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 16 | local source="${BUILT_PRODUCTS_DIR}/$1" 17 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 18 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 19 | elif [ -r "$1" ]; then 20 | local source="$1" 21 | fi 22 | 23 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 24 | 25 | if [ -L "${source}" ]; then 26 | echo "Symlinked..." 27 | source="$(readlink "${source}")" 28 | fi 29 | 30 | # Use filter instead of exclude so missing patterns don't throw errors. 31 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 32 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 33 | 34 | local basename 35 | basename="$(basename -s .framework "$1")" 36 | binary="${destination}/${basename}.framework/${basename}" 37 | if ! [ -r "$binary" ]; then 38 | binary="${destination}/${basename}" 39 | fi 40 | 41 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 42 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 43 | strip_invalid_archs "$binary" 44 | fi 45 | 46 | # Resign the code if required by the build settings to avoid unstable apps 47 | code_sign_if_enabled "${destination}/$(basename "$1")" 48 | 49 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 50 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 51 | local swift_runtime_libs 52 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 53 | for lib in $swift_runtime_libs; do 54 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 55 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 56 | code_sign_if_enabled "${destination}/${lib}" 57 | done 58 | fi 59 | } 60 | 61 | # Copies the dSYM of a vendored framework 62 | install_dsym() { 63 | local source="$1" 64 | if [ -r "$source" ]; then 65 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DWARF_DSYM_FOLDER_PATH}\"" 66 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DWARF_DSYM_FOLDER_PATH}" 67 | fi 68 | 69 | local basename 70 | basename="$(basename -s .framework.dSYM "$source")" 71 | binary="${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | } 78 | 79 | # Signs a framework with the provided identity 80 | code_sign_if_enabled() { 81 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 82 | # Use the current code_sign_identitiy 83 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 84 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 85 | 86 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 87 | code_sign_cmd="$code_sign_cmd &" 88 | fi 89 | echo "$code_sign_cmd" 90 | eval "$code_sign_cmd" 91 | fi 92 | } 93 | 94 | # Strip invalid architectures 95 | strip_invalid_archs() { 96 | binary="$1" 97 | # Get architectures for current file 98 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 99 | stripped="" 100 | for arch in $archs; do 101 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 102 | # Strip non-valid architectures in-place 103 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 104 | stripped="$stripped $arch" 105 | fi 106 | done 107 | if [[ "$stripped" ]]; then 108 | echo "Stripped $binary of architectures:$stripped" 109 | fi 110 | } 111 | 112 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 113 | wait 114 | fi 115 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-TTPlayerCache/Pods-TTPlayerCache-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 14 | 15 | case "${TARGETED_DEVICE_FAMILY}" in 16 | 1,2) 17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 18 | ;; 19 | 1) 20 | TARGET_DEVICE_ARGS="--target-device iphone" 21 | ;; 22 | 2) 23 | TARGET_DEVICE_ARGS="--target-device ipad" 24 | ;; 25 | 3) 26 | TARGET_DEVICE_ARGS="--target-device tv" 27 | ;; 28 | 4) 29 | TARGET_DEVICE_ARGS="--target-device watch" 30 | ;; 31 | *) 32 | TARGET_DEVICE_ARGS="--target-device mac" 33 | ;; 34 | esac 35 | 36 | install_resource() 37 | { 38 | if [[ "$1" = /* ]] ; then 39 | RESOURCE_PATH="$1" 40 | else 41 | RESOURCE_PATH="${PODS_ROOT}/$1" 42 | fi 43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 44 | cat << EOM 45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 46 | EOM 47 | exit 1 48 | fi 49 | case $RESOURCE_PATH in 50 | *.storyboard) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 53 | ;; 54 | *.xib) 55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 57 | ;; 58 | *.framework) 59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 63 | ;; 64 | *.xcdatamodel) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 67 | ;; 68 | *.xcdatamodeld) 69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 71 | ;; 72 | *.xcmappingmodel) 73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 75 | ;; 76 | *.xcassets) 77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 79 | ;; 80 | *) 81 | echo "$RESOURCE_PATH" || true 82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 83 | ;; 84 | esac 85 | } 86 | 87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 92 | fi 93 | rm -f "$RESOURCES_TO_COPY" 94 | 95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 96 | then 97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 99 | while read line; do 100 | if [[ $line != "${PODS_ROOT}*" ]]; then 101 | XCASSET_FILES+=("$line") 102 | fi 103 | done <<<"$OTHER_XCASSETS" 104 | 105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 106 | fi 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TTPlayerCache 2 | AVPlayer 视频离线缓存、可以边下边播放、部分缓存、断网处理、AVAssetResourceLoaderDelegate 3 | 4 | # [博客](https://juejin.im/post/5a31ce23f265da4327183c3d) 5 | 6 | # CocoaPods 7 | > pod 'TTPlayerCache', '~> 0.2.0' 8 | 9 | ### 用法: 10 | ``` 11 | #import 12 | ... 13 | //把视频播放地址转成系统不能识别的URL 14 | NSString *videoUrl = @"http://...."; 15 | BOOL isMP4 = YES; //是否是mp4格式 16 | videoUrl = isMP4? TTResourceUrlFromOrigianllUrl(videoUrl): videoUrl; 17 | ... 18 | ... 19 | //设置AVPLayer播放 20 | //初始化代理 21 | self.resourceLoaderDelegate = [TTResourceLoaderDelegate new]; 22 | self.urlAsset = [AVURLAsset assetWithURL:self.videoURL]; 23 | [self.urlAsset.resourceLoader setDelegate:self.resourceLoaderDelegate queue:TT_resourceLoader_delegate_queue()]; 24 | ... 25 | ``` 26 | ### 其他 27 | 播放除MP4外的格式时,url不需要转换(现不支持MP4格式外的缓存) 28 | 29 | -------------------------------------------------------------------------------- /Source/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun8801/TTPlayerCache/fe512d17ef8935190f530d7fc02fb5be29b7f4c2/Source/.DS_Store -------------------------------------------------------------------------------- /Source/TTPlayerCache/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun8801/TTPlayerCache/fe512d17ef8935190f530d7fc02fb5be29b7f4c2/Source/TTPlayerCache/.DS_Store -------------------------------------------------------------------------------- /Source/TTPlayerCache/Category/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun8801/TTPlayerCache/fe512d17ef8935190f530d7fc02fb5be29b7f4c2/Source/TTPlayerCache/Category/.DS_Store -------------------------------------------------------------------------------- /Source/TTPlayerCache/Category/AVAssetResourceLoadingDataRequest+TTCategory.h: -------------------------------------------------------------------------------- 1 | // 2 | // AVAssetResourceLoadingDataRequest+TTCategory.h 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/12/5. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | //requestsAllDataToEndOfResource 在iOS 9以下 8 | 9 | #import 10 | 11 | @interface AVAssetResourceLoadingDataRequest (TTCategory) 12 | 13 | //- (BOOL)TT_requestsAllDataToEndOfResource; 14 | 15 | @property (nonatomic, assign) BOOL TT_requestsAllDataToEndOfResource; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/Category/AVAssetResourceLoadingDataRequest+TTCategory.m: -------------------------------------------------------------------------------- 1 | // 2 | // AVAssetResourceLoadingDataRequest+TTCategory.m 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/12/5. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import "AVAssetResourceLoadingDataRequest+TTCategory.h" 10 | #import 11 | 12 | @implementation AVAssetResourceLoadingDataRequest (TTCategory) 13 | 14 | - (BOOL)TT_requestsAllDataToEndOfResource { 15 | return [objc_getAssociatedObject(self, @selector(setTT_requestsAllDataToEndOfResource:)) boolValue]; 16 | } 17 | 18 | - (void)setTT_requestsAllDataToEndOfResource:(BOOL)TT_requestsAllDataToEndOfResource { 19 | objc_setAssociatedObject(self, _cmd, @(TT_requestsAllDataToEndOfResource), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/Reachability/TTReachability.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Tony Million. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | #import 30 | 31 | //! Project version number for MacOSReachability. 32 | FOUNDATION_EXPORT double ReachabilityVersionNumber; 33 | 34 | //! Project version string for MacOSReachability. 35 | FOUNDATION_EXPORT const unsigned char ReachabilityVersionString[]; 36 | 37 | /** 38 | * Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X. 39 | * 40 | * @see http://nshipster.com/ns_enum-ns_options/ 41 | **/ 42 | #ifndef NS_ENUM 43 | #define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type 44 | #endif 45 | 46 | extern NSString *const kTTReachabilityChangedNotification; 47 | 48 | typedef NS_ENUM(NSInteger, TTNetworkStatus) { 49 | // Apple NetworkStatus Compatible Names. 50 | TTNotReachable = 0, 51 | TTReachableViaWiFi = 2, 52 | TTReachableViaWWAN = 1 53 | }; 54 | 55 | @class TTReachability; 56 | 57 | typedef void (^TTNetworkReachable)(TTReachability * reachability); 58 | typedef void (^TTNetworkUnreachable)(TTReachability * reachability); 59 | typedef void (^TTNetworkReachability)(TTReachability * reachability, SCNetworkConnectionFlags flags); 60 | 61 | 62 | @interface TTReachability : NSObject 63 | 64 | @property (nonatomic, copy) TTNetworkReachable reachableBlock; 65 | @property (nonatomic, copy) TTNetworkUnreachable unreachableBlock; 66 | @property (nonatomic, copy) TTNetworkReachability reachabilityBlock; 67 | 68 | @property (nonatomic, assign) BOOL reachableOnWWAN; 69 | 70 | 71 | +(instancetype)reachabilityWithHostname:(NSString*)hostname; 72 | // This is identical to the function above, but is here to maintain 73 | //compatibility with Apples original code. (see .m) 74 | +(instancetype)reachabilityWithHostName:(NSString*)hostname; 75 | +(instancetype)reachabilityForInternetConnection; 76 | +(instancetype)reachabilityWithAddress:(void *)hostAddress; 77 | +(instancetype)reachabilityForLocalWiFi; 78 | 79 | -(instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; 80 | 81 | -(BOOL)startNotifier; 82 | -(void)stopNotifier; 83 | 84 | -(BOOL)isReachable; 85 | -(BOOL)isReachableViaWWAN; 86 | -(BOOL)isReachableViaWiFi; 87 | 88 | // WWAN may be available, but not active until a connection has been established. 89 | // WiFi may require a connection for VPN on Demand. 90 | -(BOOL)isConnectionRequired; // Identical DDG variant. 91 | -(BOOL)connectionRequired; // Apple's routine. 92 | // Dynamic, on demand connection? 93 | -(BOOL)isConnectionOnDemand; 94 | // Is user intervention required? 95 | -(BOOL)isInterventionRequired; 96 | 97 | -(TTNetworkStatus)currentReachabilityStatus; 98 | -(SCNetworkReachabilityFlags)reachabilityFlags; 99 | -(NSString*)currentReachabilityString; 100 | -(NSString*)currentReachabilityFlags; 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/Reachability/TTReachability.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Tony Million. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "TTReachability.h" 29 | 30 | #import 31 | #import 32 | #import 33 | #import 34 | #import 35 | #import 36 | 37 | 38 | NSString *const kTTReachabilityChangedNotification = @"kTTReachabilityChangedNotification"; 39 | 40 | 41 | @interface TTReachability () 42 | 43 | @property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; 44 | @property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue; 45 | @property (nonatomic, strong) id reachabilityObject; 46 | 47 | -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; 48 | -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; 49 | 50 | @end 51 | 52 | 53 | static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) 54 | { 55 | return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c", 56 | #if TARGET_OS_IPHONE 57 | (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', 58 | #else 59 | 'X', 60 | #endif 61 | (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', 62 | (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', 63 | (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', 64 | (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', 65 | (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', 66 | (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', 67 | (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', 68 | (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; 69 | } 70 | 71 | // Start listening for reachability notifications on the current run loop 72 | static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) 73 | { 74 | #pragma unused (target) 75 | 76 | TTReachability *reachability = ((__bridge TTReachability*)info); 77 | 78 | // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool, 79 | // but what the heck eh? 80 | @autoreleasepool 81 | { 82 | [reachability reachabilityChanged:flags]; 83 | } 84 | } 85 | 86 | 87 | @implementation TTReachability 88 | 89 | #pragma mark - Class Constructor Methods 90 | 91 | +(instancetype)reachabilityWithHostName:(NSString*)hostname 92 | { 93 | return [TTReachability reachabilityWithHostname:hostname]; 94 | } 95 | 96 | +(instancetype)reachabilityWithHostname:(NSString*)hostname 97 | { 98 | SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]); 99 | if (ref) 100 | { 101 | id reachability = [[self alloc] initWithReachabilityRef:ref]; 102 | 103 | return reachability; 104 | } 105 | 106 | return nil; 107 | } 108 | 109 | +(instancetype)reachabilityWithAddress:(void *)hostAddress 110 | { 111 | SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); 112 | if (ref) 113 | { 114 | id reachability = [[self alloc] initWithReachabilityRef:ref]; 115 | 116 | return reachability; 117 | } 118 | 119 | return nil; 120 | } 121 | 122 | +(instancetype)reachabilityForInternetConnection 123 | { 124 | struct sockaddr_in zeroAddress; 125 | bzero(&zeroAddress, sizeof(zeroAddress)); 126 | zeroAddress.sin_len = sizeof(zeroAddress); 127 | zeroAddress.sin_family = AF_INET; 128 | 129 | return [self reachabilityWithAddress:&zeroAddress]; 130 | } 131 | 132 | +(instancetype)reachabilityForLocalWiFi 133 | { 134 | struct sockaddr_in localWifiAddress; 135 | bzero(&localWifiAddress, sizeof(localWifiAddress)); 136 | localWifiAddress.sin_len = sizeof(localWifiAddress); 137 | localWifiAddress.sin_family = AF_INET; 138 | // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 139 | localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); 140 | 141 | return [self reachabilityWithAddress:&localWifiAddress]; 142 | } 143 | 144 | 145 | // Initialization methods 146 | 147 | -(instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref 148 | { 149 | self = [super init]; 150 | if (self != nil) 151 | { 152 | self.reachableOnWWAN = YES; 153 | self.reachabilityRef = ref; 154 | 155 | // We need to create a serial queue. 156 | // We allocate this once for the lifetime of the notifier. 157 | 158 | self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); 159 | } 160 | 161 | return self; 162 | } 163 | 164 | -(void)dealloc 165 | { 166 | [self stopNotifier]; 167 | 168 | if(self.reachabilityRef) 169 | { 170 | CFRelease(self.reachabilityRef); 171 | self.reachabilityRef = nil; 172 | } 173 | 174 | self.reachableBlock = nil; 175 | self.unreachableBlock = nil; 176 | self.reachabilityBlock = nil; 177 | self.reachabilitySerialQueue = nil; 178 | } 179 | 180 | #pragma mark - Notifier Methods 181 | 182 | // Notifier 183 | // NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD 184 | // - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS. 185 | // INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want) 186 | 187 | -(BOOL)startNotifier 188 | { 189 | // allow start notifier to be called multiple times 190 | if(self.reachabilityObject && (self.reachabilityObject == self)) 191 | { 192 | return YES; 193 | } 194 | 195 | 196 | SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL }; 197 | context.info = (__bridge void *)self; 198 | 199 | if(SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)) 200 | { 201 | // Set it as our reachability queue, which will retain the queue 202 | if(SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue)) 203 | { 204 | // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves 205 | // woah 206 | self.reachabilityObject = self; 207 | return YES; 208 | } 209 | else 210 | { 211 | #ifdef DEBUG 212 | NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError())); 213 | #endif 214 | 215 | // UH OH - FAILURE - stop any callbacks! 216 | SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); 217 | } 218 | } 219 | else 220 | { 221 | #ifdef DEBUG 222 | NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError())); 223 | #endif 224 | } 225 | 226 | // if we get here we fail at the internet 227 | self.reachabilityObject = nil; 228 | return NO; 229 | } 230 | 231 | -(void)stopNotifier 232 | { 233 | // First stop, any callbacks! 234 | SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); 235 | 236 | // Unregister target from the GCD serial dispatch queue. 237 | SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); 238 | 239 | self.reachabilityObject = nil; 240 | } 241 | 242 | #pragma mark - reachability tests 243 | 244 | // This is for the case where you flick the airplane mode; 245 | // you end up getting something like this: 246 | //Reachability: WR ct----- 247 | //Reachability: -- ------- 248 | //Reachability: WR ct----- 249 | //Reachability: -- ------- 250 | // We treat this as 4 UNREACHABLE triggers - really apple should do better than this 251 | 252 | #define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) 253 | 254 | -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags 255 | { 256 | BOOL connectionUP = YES; 257 | 258 | if(!(flags & kSCNetworkReachabilityFlagsReachable)) 259 | connectionUP = NO; 260 | 261 | if( (flags & testcase) == testcase ) 262 | connectionUP = NO; 263 | 264 | #if TARGET_OS_IPHONE 265 | if(flags & kSCNetworkReachabilityFlagsIsWWAN) 266 | { 267 | // We're on 3G. 268 | if(!self.reachableOnWWAN) 269 | { 270 | // We don't want to connect when on 3G. 271 | connectionUP = NO; 272 | } 273 | } 274 | #endif 275 | 276 | return connectionUP; 277 | } 278 | 279 | -(BOOL)isReachable 280 | { 281 | SCNetworkReachabilityFlags flags; 282 | 283 | if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) 284 | return NO; 285 | 286 | return [self isReachableWithFlags:flags]; 287 | } 288 | 289 | -(BOOL)isReachableViaWWAN 290 | { 291 | #if TARGET_OS_IPHONE 292 | 293 | SCNetworkReachabilityFlags flags = 0; 294 | 295 | if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) 296 | { 297 | // Check we're REACHABLE 298 | if(flags & kSCNetworkReachabilityFlagsReachable) 299 | { 300 | // Now, check we're on WWAN 301 | if(flags & kSCNetworkReachabilityFlagsIsWWAN) 302 | { 303 | return YES; 304 | } 305 | } 306 | } 307 | #endif 308 | 309 | return NO; 310 | } 311 | 312 | -(BOOL)isReachableViaWiFi 313 | { 314 | SCNetworkReachabilityFlags flags = 0; 315 | 316 | if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) 317 | { 318 | // Check we're reachable 319 | if((flags & kSCNetworkReachabilityFlagsReachable)) 320 | { 321 | #if TARGET_OS_IPHONE 322 | // Check we're NOT on WWAN 323 | if((flags & kSCNetworkReachabilityFlagsIsWWAN)) 324 | { 325 | return NO; 326 | } 327 | #endif 328 | return YES; 329 | } 330 | } 331 | 332 | return NO; 333 | } 334 | 335 | 336 | // WWAN may be available, but not active until a connection has been established. 337 | // WiFi may require a connection for VPN on Demand. 338 | -(BOOL)isConnectionRequired 339 | { 340 | return [self connectionRequired]; 341 | } 342 | 343 | -(BOOL)connectionRequired 344 | { 345 | SCNetworkReachabilityFlags flags; 346 | 347 | if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) 348 | { 349 | return (flags & kSCNetworkReachabilityFlagsConnectionRequired); 350 | } 351 | 352 | return NO; 353 | } 354 | 355 | // Dynamic, on demand connection? 356 | -(BOOL)isConnectionOnDemand 357 | { 358 | SCNetworkReachabilityFlags flags; 359 | 360 | if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) 361 | { 362 | return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && 363 | (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand))); 364 | } 365 | 366 | return NO; 367 | } 368 | 369 | // Is user intervention required? 370 | -(BOOL)isInterventionRequired 371 | { 372 | SCNetworkReachabilityFlags flags; 373 | 374 | if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) 375 | { 376 | return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && 377 | (flags & kSCNetworkReachabilityFlagsInterventionRequired)); 378 | } 379 | 380 | return NO; 381 | } 382 | 383 | 384 | #pragma mark - reachability status stuff 385 | 386 | -(TTNetworkStatus)currentReachabilityStatus 387 | { 388 | if([self isReachable]) 389 | { 390 | if([self isReachableViaWiFi]) 391 | return TTReachableViaWiFi; 392 | 393 | #if TARGET_OS_IPHONE 394 | return TTReachableViaWWAN; 395 | #endif 396 | } 397 | 398 | return TTNotReachable; 399 | } 400 | 401 | -(SCNetworkReachabilityFlags)reachabilityFlags 402 | { 403 | SCNetworkReachabilityFlags flags = 0; 404 | 405 | if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) 406 | { 407 | return flags; 408 | } 409 | 410 | return 0; 411 | } 412 | 413 | -(NSString*)currentReachabilityString 414 | { 415 | TTNetworkStatus temp = [self currentReachabilityStatus]; 416 | 417 | if(temp == TTReachableViaWWAN) 418 | { 419 | // Updated for the fact that we have CDMA phones now! 420 | return NSLocalizedString(@"Cellular", @""); 421 | } 422 | if (temp == TTReachableViaWiFi) 423 | { 424 | return NSLocalizedString(@"WiFi", @""); 425 | } 426 | 427 | return NSLocalizedString(@"No Connection", @""); 428 | } 429 | 430 | -(NSString*)currentReachabilityFlags 431 | { 432 | return reachabilityFlags([self reachabilityFlags]); 433 | } 434 | 435 | #pragma mark - Callback function calls this method 436 | 437 | -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags 438 | { 439 | if([self isReachableWithFlags:flags]) 440 | { 441 | if(self.reachableBlock) 442 | { 443 | self.reachableBlock(self); 444 | } 445 | } 446 | else 447 | { 448 | if(self.unreachableBlock) 449 | { 450 | self.unreachableBlock(self); 451 | } 452 | } 453 | 454 | if(self.reachabilityBlock) 455 | { 456 | self.reachabilityBlock(self, flags); 457 | } 458 | 459 | // this makes sure the change notification happens on the MAIN THREAD 460 | dispatch_async(dispatch_get_main_queue(), ^{ 461 | [[NSNotificationCenter defaultCenter] postNotificationName:kTTReachabilityChangedNotification 462 | object:self]; 463 | }); 464 | } 465 | 466 | #pragma mark - Debug Description 467 | 468 | - (NSString *) description 469 | { 470 | NSString *description = [NSString stringWithFormat:@"<%@: %#x (%@)>", 471 | NSStringFromClass([self class]), (unsigned int) self, [self currentReachabilityFlags]]; 472 | return description; 473 | } 474 | 475 | @end 476 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/Reachability/TTReachabilityManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TTReachabilityManager.h 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/21. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void(^TTReachabilityStateChangedBlock)(BOOL isReachable); 12 | 13 | @interface TTReachabilityManager : NSObject 14 | 15 | + (instancetype)sharedReachabilityManager; 16 | 17 | @property (nonatomic, copy) TTReachabilityStateChangedBlock reachableStatusChanged; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/Reachability/TTReachabilityManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // TTReachabilityManager.m 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/21. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import "TTReachabilityManager.h" 10 | #import "TTReachability.h" 11 | 12 | @implementation TTReachabilityManager 13 | { 14 | TTReachability *_reachability; 15 | } 16 | 17 | static TTReachabilityManager *_instance = nil; 18 | + (instancetype)sharedReachabilityManager { 19 | static dispatch_once_t onceToken; 20 | dispatch_once(&onceToken, ^{ 21 | _instance = [[TTReachabilityManager alloc] init]; 22 | }); 23 | return _instance; 24 | } 25 | 26 | - (instancetype)init { 27 | if (self = [super init]) { 28 | [self setupReachability]; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)setupReachability { 34 | _reachability = [TTReachability reachabilityForInternetConnection]; 35 | [_reachability startNotifier]; 36 | 37 | __weak typeof(self) weakSelf = self; 38 | _reachability.reachabilityBlock = ^(TTReachability *reachability, SCNetworkConnectionFlags flags) { 39 | if (weakSelf.reachableStatusChanged) { 40 | weakSelf.reachableStatusChanged(reachability.isReachable); 41 | } 42 | }; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/TTPlayerCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // TTPlayerCache.h 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/12/11. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #ifndef TTPlayerCache_h 10 | #define TTPlayerCache_h 11 | 12 | #import "TTResourceLoaderDelegate.h" 13 | #import "TTResourceLoaderCache.h" 14 | #import "TTPlayerCacheMacro.h" 15 | 16 | 17 | #endif /* TTPlayerCache_h */ 18 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/TTPlayerCacheMacro.h: -------------------------------------------------------------------------------- 1 | // 2 | // TTPlayerCacheMacro.h 3 | // TTPlayerCacheMacro 4 | // 5 | // Created by sunzongtang on 2017/11/9. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #ifndef TTPlayerCacheMacro_h 10 | #define TTPlayerCacheMacro_h 11 | 12 | /** 在视频URL前添加的标识scheme(MMTT) */ 13 | extern NSString * const TTPlayerCustomScheme; 14 | /** MMTT:// 使系统不识别URL 自己代理数据 */ 15 | extern NSString * const TTPlayerCustomProtocol; 16 | 17 | /** 把正常URL 转换成 当前代理识别URL 即在url前加上MMTT:// */ 18 | extern NSString *TTResourceUrlFromOrigianllUrl(NSString * originalUrl); 19 | 20 | /** 视频存储目录 */ 21 | extern NSString *TTCacheLocalDirectory(void); 22 | /** 返回视频文件地址 (沙盒+md5(url)) */ 23 | extern NSString *TTLocalFilePath(NSString *url); 24 | /** 存储视频类型地址 */ 25 | extern NSString *TTLocalMediaInfoPath(NSString *url); 26 | 27 | /** 获得视频下载速度 通知 */ 28 | extern NSString * const TTVideoDownloadSpeedNotification; 29 | /** 下载视频资源失败 */ 30 | extern NSString * const TTVideoDownloadFailNotification; 31 | 32 | //TTVideoDownloadSpeedNotification info 33 | extern NSString * const TTDownloadSpeed; 34 | extern NSString * const TTDownloadFinished; 35 | extern NSString * const TTDownloadError; 36 | 37 | //打印加载日志 38 | extern BOOL TTOpenLog; //YES打印, 默认NO 39 | #define TTLog(format, ...) \ 40 | if (TTOpenLog) { \ 41 | NSString *fileName = [[[NSString stringWithUTF8String: __FILE__] lastPathComponent] stringByReplacingOccurrencesOfString:@".m" withString:@""];\ 42 | fprintf(stderr, "\n>>%s:[%d]:\n%s\n", [fileName UTF8String] , __LINE__ , [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String]);\ 43 | } 44 | 45 | #endif /* TTPlayerCache_h */ 46 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/TTResourceLoaderCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // TTResourceLoaderCache.h 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/12/1. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 缓存管理 8 | 9 | #import 10 | 11 | @interface TTResourceLoaderCache : NSObject 12 | 13 | /** 14 | 获取视频缓存size 15 | 16 | @return <#return value description#> 17 | */ 18 | + (NSUInteger)getCacheSize; 19 | 20 | /** 21 | 清除本地视频缓存 22 | */ 23 | + (void)clearCache; 24 | 25 | /** 26 | 删除具体某个视频缓存 27 | 28 | @param url <#url description#> 29 | */ 30 | + (void)removeCacheVideoForUrl:(NSString *)url; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/TTResourceLoaderCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // TTResourceLoaderCache.m 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/12/1. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import "TTResourceLoaderCache.h" 10 | #import "TTPlayerCacheMacro.h" 11 | #import 12 | 13 | /** 缓存本地存储目录名 */ 14 | static NSString * const TTVideoCachePath = @"TTVideoCache"; 15 | 16 | static NSString *TTCachedFileNameForKey(NSString *key) { 17 | const char *str = [key UTF8String]; 18 | if (str == NULL) { 19 | str = ""; 20 | } 21 | unsigned char r[CC_MD5_DIGEST_LENGTH]; 22 | CC_MD5(str, (CC_LONG)strlen(str), r); 23 | NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 24 | r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], 25 | r[11], r[12], r[13], r[14], r[15]]; 26 | 27 | return filename; 28 | } 29 | 30 | /** 视频存储目录 */ 31 | NSString *TTCacheLocalDirectory(void) { 32 | static NSString *loaclDirectory; 33 | static dispatch_once_t onceToken; 34 | dispatch_once(&onceToken, ^{ 35 | loaclDirectory = [NSString stringWithFormat:@"%@/%@",NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject,TTVideoCachePath]; 36 | }); 37 | return loaclDirectory; 38 | } 39 | 40 | /** 返回视频文件地址 (沙盒+md5(url)) */ 41 | NSString *TTLocalFilePath(NSString *url) { 42 | NSString *localFilePath = TTCacheLocalDirectory(); 43 | localFilePath = [localFilePath stringByAppendingPathComponent:TTCachedFileNameForKey(url)]; 44 | return localFilePath; 45 | } 46 | /** 存储视频类型地址*/ 47 | NSString *TTLocalMediaInfoPath(NSString *url) { 48 | return [NSString stringWithFormat:@"%@_media.plist",TTLocalFilePath(url)]; 49 | } 50 | 51 | 52 | NSString * const TTPlayerCustomScheme = @"MMTT"; 53 | NSString * const TTPlayerCustomProtocol = @"MMTT://"; 54 | NSString * const TTVideoDownloadSpeedNotification = @"TTVideoDownloadSpeedNotification"; 55 | NSString * const TTVideoDownloadFailNotification = @"TTVideoDownloadFailNotification"; 56 | NSString * const TTDownloadSpeed = @"TTDownloadSpeed"; 57 | NSString * const TTDownloadFinished = @"TTDownloadFinished"; 58 | NSString * const TTDownloadError = @"TTDownloadError"; 59 | BOOL TTOpenLog = NO; 60 | 61 | /** 把正常URL 转换成 当前代理识别URL 即在url前加上MMTT:// */ 62 | NSString *TTResourceUrlFromOrigianllUrl(NSString * originalUrl) { 63 | if (!originalUrl) { 64 | return nil; 65 | } 66 | if (![originalUrl hasPrefix:TTPlayerCustomProtocol]) { 67 | originalUrl = [NSString stringWithFormat:@"%@%@",TTPlayerCustomProtocol,originalUrl]; 68 | } 69 | return originalUrl; 70 | } 71 | 72 | @implementation TTResourceLoaderCache 73 | 74 | + (NSUInteger)getCacheSize { 75 | NSString *folderPath = TTCacheLocalDirectory(); 76 | NSFileManager* manager = [NSFileManager defaultManager]; 77 | if (![manager fileExistsAtPath:folderPath]) return 0; 78 | __block NSUInteger folderSize = 0; 79 | [[manager subpathsAtPath:folderPath] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 80 | NSString* fileAbsolutePath = [folderPath stringByAppendingPathComponent:obj]; 81 | folderSize += [self fileSizeAtPath:fileAbsolutePath]; 82 | }]; 83 | return folderSize; 84 | } 85 | 86 | + (NSUInteger) fileSizeAtPath:(NSString*) filePath{ 87 | NSFileManager* manager = [NSFileManager defaultManager]; 88 | if ([manager fileExistsAtPath:filePath]){ 89 | return (NSUInteger)[[manager attributesOfItemAtPath:filePath error:nil] fileSize]; 90 | } 91 | return 0; 92 | } 93 | 94 | + (void)clearCache { 95 | NSString *folderPath = TTCacheLocalDirectory(); 96 | NSFileManager* fileManager = [NSFileManager defaultManager]; 97 | [fileManager removeItemAtPath:folderPath error:nil]; 98 | [fileManager createDirectoryAtPath:folderPath 99 | withIntermediateDirectories:YES 100 | attributes:nil 101 | error:NULL]; 102 | } 103 | 104 | + (void)removeCacheVideoForUrl:(NSString *)url { 105 | if (!url) { 106 | return; 107 | } 108 | url = TTResourceUrlFromOrigianllUrl(url); 109 | 110 | NSFileManager* fileManager = [NSFileManager defaultManager]; 111 | [fileManager removeItemAtPath:TTLocalFilePath(url) error:nil]; 112 | [fileManager removeItemAtPath:TTLocalMediaInfoPath(url) error:nil]; 113 | 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/TTResourceLoaderData.h: -------------------------------------------------------------------------------- 1 | // 2 | // TTData.h 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/14. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class AVAssetResourceLoadingRequest; 12 | @interface TTResourceLoaderData : NSObject 13 | 14 | + (instancetype)new NS_UNAVAILABLE; 15 | - (instancetype)init NS_UNAVAILABLE; 16 | 17 | 18 | /** 19 | 初始化 20 | 21 | @param url 原始URL 有MMTT:// 22 | @return <#return value description#> 23 | */ 24 | - (instancetype)initWithURL:(NSString *)url; 25 | 26 | /** 27 | 判断是否设置过视频contentLength、MIMEType 28 | */ 29 | @property (nonatomic, assign, readonly) BOOL hasConfigured; 30 | 31 | @property (nonatomic, assign, readonly) long long contentLength; 32 | 33 | /** 34 | 设置视频数据 contentLength 与MIMEType 35 | 36 | 当收到网络响应的时候先判断 hasConfigured ,if NO call configContentLength:MIMEType: 37 | 38 | @param contentLength 视频完整的长度 39 | @param MIMEType MIMEType response.MIMEType 40 | */ 41 | - (void)configContentLength:(long long)contentLength MIMEType:(NSString *)MIMEType; 42 | 43 | /** 44 | 当前AVAssetResourceLoadingRequest 是否需要继续做网络连接 45 | 46 | @param loadingRequest <#loadingRequest description#> 47 | @return NO(直接return 不需要) 48 | */ 49 | - (BOOL)needContinueNetworking:(AVAssetResourceLoadingRequest *)loadingRequest; 50 | 51 | /** 52 | 组装下载任务与loadingRequest 53 | 54 | @param loadingRequest <#loadingRequest description#> 55 | @param dataTask <#dataTask description#> 56 | */ 57 | - (void)addLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest dataTask:(NSURLSessionDataTask *)dataTask; 58 | 59 | /** 60 | 当网络下载完成的时候调用, 61 | 62 | @param error <#error description#> 63 | @param taskIdentifier <#taskIdentifier description#> 64 | */ 65 | - (void)taskCompleteWithError:(NSError *)error taskId:(NSUInteger)taskIdentifier; 66 | 67 | /** 68 | 当网络重新连接上时,刷新播放器 69 | */ 70 | - (void)reloadLoadingRequestWhenHasNetError; 71 | 72 | /** 73 | 是否有正在进行下载在task 74 | 75 | @return <#return value description#> 76 | */ 77 | - (BOOL)hasDataTaskRequesting; 78 | 79 | /** 80 | 设置当前的任务下载的数据,不填充loadingRequest 81 | 82 | @param taskIdentifier <#taskIdentifier description#> 83 | */ 84 | - (void)cancelLoadingRequestWithTaskId:(NSUInteger)taskIdentifier; 85 | 86 | /** 87 | 取消TT_requestsAllDataToEndOfResource == NO 的下载任务 88 | 89 | 当开始新的下载任务(TT_requestsAllDataToEndOfResource == YES )时调用, 90 | */ 91 | - (void)cancelNoRequestsAllDataToEndOfResourceTask; 92 | 93 | /** 94 | 根据下载任务ID 保存收到的数据 95 | 96 | @param data <#data description#> 97 | @param taskIdentifier <#taskIdentifier description#> 98 | */ 99 | - (void)appendData:(NSData *)data taskId:(NSUInteger)taskIdentifier; 100 | 101 | 102 | 103 | 104 | 105 | #pragma mark - CLASS METHOD 106 | /** 107 | 根据响应计算数据总长度 108 | 109 | @param response <#response description#> 110 | @return <#return value description#> 111 | */ 112 | + (long long)caculateVideoResponseContentLength:(NSHTTPURLResponse *)response; 113 | 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/TTResourceLoaderData.m: -------------------------------------------------------------------------------- 1 | // 2 | // TTData.m 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/14. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import "TTResourceLoaderData.h" 10 | #import "TTPlayerCacheMacro.h" 11 | #import "AVAssetResourceLoadingDataRequest+TTCategory.h" 12 | #import 13 | #import 14 | #import 15 | 16 | //////////////缓存数据区间///////// 17 | static NSUInteger TTCacheCellBytes = 1024; 18 | static NSUInteger TTNotNeedNetMaxIntervalBytes = 2048; //相差1024 *2 B不需继续请求 19 | static NSUInteger TTCachedMinIntervalBytes = 1<<20; //1M 20 | 21 | static NSString *const TTReceivedDataInfoText = @"receivedDataInfo"; 22 | static NSString *const TTMediaContentLengthText= @"contentLength"; 23 | static NSString *const TTMediaMIMETypeText = @"MIMEType"; 24 | 25 | struct _TTReceivedDataPoint { 26 | long long begin; 27 | long long end; 28 | } ; 29 | typedef struct _TTReceivedDataPoint TTReceivedDataPointType; 30 | typedef struct _TTReceivedDataPoint * TTReceivedDataPoint; 31 | 32 | TTReceivedDataPointType TTMakeReceivedDataPoint(long long begin, long long end) { 33 | TTReceivedDataPointType r; 34 | r.begin = begin; 35 | r.end = end; 36 | return r; 37 | } 38 | 39 | 40 | CFComparisonResult TTComparatorFunction( void *val1, void *val2, void *context) { 41 | TTReceivedDataPoint t0 = val1; 42 | TTReceivedDataPoint t1 = val2; 43 | if (t0->begin > t1->begin) { 44 | return kCFCompareGreaterThan; 45 | } 46 | if (t0->begin == t1->begin) { 47 | return kCFCompareEqualTo; 48 | } 49 | return kCFCompareLessThan; 50 | } 51 | 52 | #pragma mark - ****TTTaskModel**** 53 | @interface TTTaskModel : NSObject 54 | @property (nonatomic, strong) AVAssetResourceLoadingRequest *loadingRequest; 55 | @property (nonatomic, strong) NSURLSessionDataTask *dataTask; 56 | // allHTTPHeaderFields-> Range [requestRangeBegin - ] 57 | @property (nonatomic, assign) long long requestRangeBegin; 58 | @property (nonatomic, assign) BOOL isCanceled; 59 | 60 | @end 61 | @implementation TTTaskModel 62 | @end 63 | #pragma mark - ****TTResourceLoaderData**** 64 | @interface TTResourceLoaderData () 65 | { 66 | NSMutableData *_data; 67 | NSMutableDictionary *_taskModelDict; 68 | 69 | NSString *_MIMEType; 70 | long long _contentLength; 71 | NSString *_url; 72 | NSString *_localFilePath; 73 | NSString *_localMediaInfoPath; 74 | NSString *_lastSaveInfo; 75 | 76 | NSFileManager *_fileManager; 77 | 78 | CFMutableArrayRef _receivedDataPointArray; 79 | 80 | long long _downAllBytes; 81 | NSUInteger _errorTaskIdentifier; 82 | NSUInteger _bufferLength; 83 | 84 | dispatch_semaphore_t _cancel_finish_semaphore_t; 85 | BOOL _shouldCancel; 86 | } 87 | @end 88 | @implementation TTResourceLoaderData 89 | 90 | - (void)dealloc { 91 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 92 | 93 | //存储数据 94 | [self saveFileToLocal:nil]; 95 | 96 | CFIndex count = CFArrayGetCount(_receivedDataPointArray); 97 | for (CFIndex i = 0; i < count; i++) { 98 | TTReceivedDataPoint dataPoint = (TTReceivedDataPoint)CFArrayGetValueAtIndex(_receivedDataPointArray, i); 99 | free(dataPoint); 100 | } 101 | CFRelease(_receivedDataPointArray); 102 | _receivedDataPointArray = NULL; 103 | [_taskModelDict removeAllObjects]; 104 | _taskModelDict = nil; 105 | } 106 | 107 | - (instancetype)initWithURL:(NSString *)url { 108 | if (self = [super init]) { 109 | _receivedDataPointArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); 110 | 111 | _taskModelDict = [NSMutableDictionary dictionary]; 112 | _hasConfigured = NO; 113 | _url = url; 114 | 115 | _localFilePath = TTLocalFilePath(_url); 116 | _localMediaInfoPath = TTLocalMediaInfoPath(_url); 117 | 118 | _fileManager = [NSFileManager defaultManager]; 119 | if (![_fileManager fileExistsAtPath:TTCacheLocalDirectory()]) { 120 | [_fileManager createDirectoryAtPath:TTCacheLocalDirectory() withIntermediateDirectories:YES attributes:nil error:nil]; 121 | } 122 | [self readFromLocalFile]; 123 | 124 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveFileToLocal:) name:UIApplicationWillTerminateNotification object:nil]; 125 | } 126 | return self; 127 | } 128 | 129 | - (void)configContentLength:(long long)contentLength MIMEType:(NSString *)MIMEType{ 130 | if (contentLength == _contentLength && [MIMEType isEqualToString:_MIMEType]) { 131 | return; 132 | } 133 | _contentLength = contentLength; 134 | _MIMEType = MIMEType; 135 | _data = [NSMutableData dataWithLength:(NSUInteger)contentLength]; 136 | _hasConfigured = YES; 137 | TTLog(@"****视频内存长度%lld MIMETyep:%@",contentLength,MIMEType); 138 | TTLog(@"****视频大小%.2fM",contentLength/1024.0/1024.0); 139 | } 140 | 141 | - (long long)contentLength { 142 | return _contentLength; 143 | } 144 | 145 | - (BOOL)needContinueNetworking:(AVAssetResourceLoadingRequest *)loadingRequest { 146 | 147 | if (_contentLength != 0 && loadingRequest.dataRequest.requestedOffset >= _contentLength) { 148 | return NO; 149 | } 150 | 151 | __block BOOL need = YES; //继续网络任务 152 | 153 | TTLog(@"\n\n****判断是否本地加载开始:***"); 154 | TTLog(@"%@",[self transformCFArrayToNSObject]); 155 | TTLog(@"%@",loadingRequest.dataRequest); 156 | 157 | long long requestedOffset = loadingRequest.dataRequest.requestedOffset; 158 | long long requestedLength = loadingRequest.dataRequest.requestedLength; 159 | long long requestEnd = requestedOffset + requestedLength -1; 160 | 161 | /***********判断本地数据-> 填充***********/ 162 | 163 | [self trimReceivedDataOffsetDict]; 164 | 165 | CFIndex arrayCount = CFArrayGetCount(_receivedDataPointArray); 166 | for (CFIndex index = 0; index < arrayCount; index ++) { 167 | TTReceivedDataPoint dataPoint = (TTReceivedDataPoint)CFArrayGetValueAtIndex(_receivedDataPointArray, index); 168 | long long begin = dataPoint->begin; 169 | long long end = dataPoint->end; 170 | if (begin != end) { 171 | if (requestedOffset >= begin && requestedOffset < end) { 172 | if (requestEnd <= end) { 173 | //loadingRequest 加载数据 174 | TTLog(@"***********全部从本地数据加载**********"); 175 | [self respondDataForLoadingRequest:loadingRequest responsedLength:0 continued:NULL]; 176 | [loadingRequest finishLoading]; 177 | need = NO; 178 | break; 179 | }else { 180 | BOOL isContinueLoop = [self isContinueLoopWithRequest:loadingRequest dataPoint:dataPoint needNet:&need]; 181 | if (!isContinueLoop) break; 182 | } 183 | } 184 | } 185 | } 186 | 187 | if (need) { 188 | /***********判断新的loadingRequest 是否能替换上次的request***************/ 189 | TTTaskModel *taskModel = [self getTaskModelWithLoadingRequest:loadingRequest isEqual:NO]; 190 | if (taskModel) { 191 | BOOL inMaxSpace = (requestedOffset - (taskModel.requestRangeBegin + taskModel.dataTask.countOfBytesReceived)) <= TTNotNeedNetMaxIntervalBytes; 192 | if (inMaxSpace) { 193 | taskModel.loadingRequest = loadingRequest; 194 | taskModel.isCanceled = NO; 195 | need = NO; 196 | } 197 | } 198 | } 199 | 200 | if (_cancel_finish_semaphore_t) { 201 | _shouldCancel = NO; 202 | dispatch_semaphore_signal(_cancel_finish_semaphore_t); 203 | } 204 | 205 | return need; 206 | } 207 | 208 | - (BOOL)isContinueLoopWithRequest:(AVAssetResourceLoadingRequest *)loadingRequest dataPoint:(TTReceivedDataPoint)dataPoint needNet:(BOOL *)needNet{ 209 | BOOL loop = YES; 210 | long long requestOffset = loadingRequest.dataRequest.requestedOffset; 211 | long long end = dataPoint->end; 212 | 213 | TTTaskModel *taskModel = [self getTaskModelWithLoadingRequest:loadingRequest isEqual:NO]; 214 | if (taskModel) { 215 | //填充数据 ,把当前loadingRequest 。。。 216 | TTLog(@"**********中间填充数据**********"); 217 | taskModel.loadingRequest = loadingRequest; 218 | taskModel.isCanceled = NO; 219 | [self respondDataForLoadingRequest:loadingRequest responsedLength:0 continued:NULL]; 220 | *needNet = NO; 221 | loop = NO; 222 | }else { 223 | if ((requestOffset + TTCacheCellBytes) <= end) { 224 | //如果缓存区间大于1024*1024 1M 先不下载 225 | if ((requestOffset + TTCachedMinIntervalBytes) <= end) { 226 | *needNet = NO; 227 | } 228 | TTLog(@"******部分加载》》 %@*******",((*needNet)?@"继续网络":@"只加载本地")); 229 | //不暂停0.5秒,无法播放 或用下面分段加载 230 | if (requestOffset == 0) { 231 | [NSThread sleepForTimeInterval:0.5]; 232 | } 233 | [self respondDataForLoadingRequest:loadingRequest responsedLength:0 continued:NULL]; 234 | if (!(*needNet)) { 235 | [loadingRequest finishLoading]; 236 | } 237 | 238 | 239 | // //模拟网络下载分段加载数据 240 | // [self respondDataForLoadingRequest:loadingRequest responsedLength:TTCachedMinIn/4]; 241 | // [self respondDataForLoadingRequest:loadingRequest responsedLength:0]; 242 | // if (!need) { 243 | // [loadingRequest finishLoading]; 244 | // } 245 | loop = NO; 246 | } 247 | } 248 | return loop; 249 | } 250 | 251 | /** 252 | 获取在 loadingRequest【requestRangeBegin < requestedOffset】 内的 TT_requestsAllDataToEndOfResource == YES 的 TTTaskModel 253 | 254 | @param loadingRequest <#loadingRequest description#> 255 | @param needEqual 用于判断是否需要requestoffset 与lastLoadingRequest.crrentOffset 相等 256 | @return <#return value description#> 257 | */ 258 | - (TTTaskModel *)getTaskModelWithLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest 259 | isEqual:(BOOL)needEqual{ 260 | long long requestedOffset = loadingRequest.dataRequest.requestedOffset; 261 | 262 | __block TTTaskModel *taskModel = nil; 263 | [_taskModelDict enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, TTTaskModel * _Nonnull obj, BOOL * _Nonnull stop) { 264 | if ((obj.loadingRequest.dataRequest. 265 | TT_requestsAllDataToEndOfResource || 266 | obj.requestRangeBegin == 0) && 267 | (obj.dataTask.state == NSURLSessionTaskStateRunning || obj.dataTask.state == NSURLSessionTaskStateSuspended) 268 | ) { 269 | if (obj.requestRangeBegin <= requestedOffset) { 270 | taskModel = obj; 271 | *stop = YES; 272 | } 273 | } 274 | }]; 275 | return taskModel; 276 | } 277 | 278 | - (void)addLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest dataTask:(NSURLSessionDataTask *)dataTask { 279 | TTTaskModel *taskModel = [TTTaskModel new]; 280 | taskModel.loadingRequest = loadingRequest; 281 | taskModel.dataTask = dataTask; 282 | taskModel.requestRangeBegin = loadingRequest.dataRequest.currentOffset; 283 | taskModel.isCanceled = NO; 284 | _taskModelDict[@(dataTask.taskIdentifier)] = taskModel; 285 | } 286 | 287 | - (void)taskCompleteWithError:(NSError *)error taskId:(NSUInteger)taskIdentifier { 288 | TTLog(@"共下载多少字节 %lld == %.2fM",_downAllBytes,_downAllBytes/1024.0/1024.0); 289 | //全部下载完保存 290 | [self autoSaveCacheToLocal:taskIdentifier]; 291 | if (error) { 292 | if (error.code == NSURLErrorCancelled) { 293 | TTLog(@"*******Cancelled request**********"); 294 | }else if(error.code == NSURLErrorNotConnectedToInternet && _downAllBytes > 1) { 295 | TTLog(@"\n*******网络错误%@*************\n",error); 296 | //网络问题的话,网络自动连接后会重新下载,所以不finishLoadingWithError 297 | //如果数据没有,finish 298 | [self removeTask:_errorTaskIdentifier]; 299 | _errorTaskIdentifier = taskIdentifier; 300 | return; 301 | }else { 302 | TTLog(@"****请求error:%@",error); 303 | TTTaskModel *taskModel = _taskModelDict[@(taskIdentifier)]; 304 | taskModel.loadingRequest.response = taskModel.dataTask.response; 305 | [taskModel.loadingRequest finishLoadingWithError:error]; 306 | } 307 | }else { 308 | TTLog(@"*********网络请求完成--succeed-**********"); 309 | TTTaskModel *taskModel = _taskModelDict[@(taskIdentifier)]; 310 | if (!taskModel.loadingRequest.isFinished) { //先判断是否finished 311 | [taskModel.loadingRequest finishLoading]; 312 | } 313 | } 314 | [self removeTask:taskIdentifier]; 315 | } 316 | 317 | - (void)reloadLoadingRequestWhenHasNetError { 318 | TTTaskModel *taskModel = _taskModelDict[@(_errorTaskIdentifier)]; 319 | [taskModel.loadingRequest finishLoadingWithError:taskModel.dataTask.error]; 320 | [self removeTask:_errorTaskIdentifier]; 321 | } 322 | 323 | - (BOOL)hasDataTaskRequesting { 324 | __block BOOL has = NO; 325 | [_taskModelDict enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, TTTaskModel * _Nonnull obj, BOOL * _Nonnull stop) { 326 | if (obj.dataTask.state == NSURLSessionTaskStateRunning) { 327 | has = YES; 328 | *stop = YES; 329 | } 330 | }]; 331 | return has; 332 | } 333 | 334 | - (void)cancelLoadingRequestWithTaskId:(NSUInteger)taskIdentifier { 335 | TTTaskModel *taskModel = _taskModelDict[@(taskIdentifier)]; 336 | taskModel.isCanceled = YES; 337 | } 338 | 339 | - (void)cancelNoRequestsAllDataToEndOfResourceTask { 340 | [_taskModelDict enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, TTTaskModel * _Nonnull obj, BOOL * _Nonnull stop) { 341 | if (obj.loadingRequest.dataRequest.requestedOffset != 0 && obj.loadingRequest.dataRequest. 342 | TT_requestsAllDataToEndOfResource == NO) { 343 | obj.isCanceled = YES; 344 | [obj.dataTask cancel]; 345 | } 346 | }]; 347 | } 348 | 349 | - (void)appendData:(NSData *)data taskId:(NSUInteger)taskIdentifier { 350 | 351 | _downAllBytes += data.length; 352 | 353 | if (_contentLength == 0 || _MIMEType == nil) { 354 | NSAssert(NO, @"需先配置视频信息-- configContentLength: mimeType:"); 355 | } 356 | 357 | TTTaskModel *taskModel = _taskModelDict[@(taskIdentifier)]; 358 | 359 | if (!taskModel) { 360 | TTLog(@"****taskModel 被删除了*****"); 361 | return; 362 | } 363 | 364 | AVAssetResourceLoadingRequest *loadingRequest = taskModel.loadingRequest; 365 | NSURLSessionDataTask *dataTask = taskModel.dataTask; 366 | 367 | long long startOffset = taskModel.requestRangeBegin + dataTask.countOfBytesReceived - data.length; 368 | 369 | if (!data || data.length == 0) { 370 | TTLog(@"不能存储数据。。。。。"); 371 | return; 372 | } 373 | 374 | long long end = startOffset + data.length -1; 375 | if ((end +1) > _contentLength) { 376 | end = _contentLength-1; 377 | } 378 | 379 | CFIndex sameBeginIndex = [self findSameRequestOffsetFromReceivedDataPointArray:taskModel.requestRangeBegin]; 380 | if (sameBeginIndex != -1) { 381 | TTReceivedDataPoint findData = (TTReceivedDataPoint)CFArrayGetValueAtIndex(_receivedDataPointArray, sameBeginIndex); 382 | findData->end = end; 383 | }else { 384 | TTReceivedDataPoint dataPoint = malloc(sizeof(TTReceivedDataPointType)); 385 | dataPoint->begin = taskModel.requestRangeBegin; 386 | dataPoint->end = end; 387 | CFArrayAppendValue(_receivedDataPointArray, dataPoint); 388 | } 389 | 390 | NSRange replaceRange = NSMakeRange((NSUInteger)startOffset, (NSUInteger)(end -startOffset +1)); 391 | if (replaceRange.length > 0) { 392 | [_data replaceBytesInRange:replaceRange withBytes:data.bytes length:replaceRange.length]; 393 | } 394 | 395 | if (taskModel.isCanceled) { 396 | return; 397 | } 398 | BOOL isContinue; 399 | [self trimReceivedDataOffsetDict]; 400 | BOOL didRespondFinished = [self respondDataForLoadingRequest:loadingRequest responsedLength:0 continued:&isContinue]; 401 | if (!isContinue) { 402 | return; 403 | } 404 | loadingRequest.response = dataTask.response; 405 | if (didRespondFinished) { 406 | if (loadingRequest.dataRequest.TT_requestsAllDataToEndOfResource) { 407 | _cancel_finish_semaphore_t = dispatch_semaphore_create(0); 408 | //是否需要取消 409 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 410 | _shouldCancel = YES; 411 | dispatch_semaphore_wait(_cancel_finish_semaphore_t, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC)); 412 | _cancel_finish_semaphore_t = nil; 413 | if (_shouldCancel) [taskModel.dataTask cancel]; 414 | }); 415 | } 416 | TTLog(@"填充完毕==offset==%lld",loadingRequest.dataRequest.requestedOffset); 417 | [loadingRequest finishLoading]; 418 | return; 419 | } 420 | if (loadingRequest.dataRequest.TT_requestsAllDataToEndOfResource) { 421 | long long currentResponedEnd = taskModel.requestRangeBegin + dataTask.countOfBytesReceived; 422 | if ((loadingRequest.dataRequest.currentOffset - currentResponedEnd) >= TTNotNeedNetMaxIntervalBytes) { 423 | //当响应到重叠下载部分 424 | [loadingRequest finishLoadingWithError:[NSError errorWithDomain:@"中间有缓存。。重新开始。。" code:NSURLErrorUnknown userInfo:nil]]; 425 | return; 426 | } 427 | } 428 | } 429 | 430 | /** 431 | 解除下载关联 432 | 433 | @param taskIdentifier <#taskIdentifier description#> 434 | */ 435 | - (void)removeTask:(NSUInteger)taskIdentifier { 436 | TTLog(@"删除任务"); 437 | TTTaskModel *taskModel = _taskModelDict[@(taskIdentifier)]; 438 | if (taskModel.dataTask.state == NSURLSessionTaskStateRunning && taskModel.dataTask.countOfBytesExpectedToReceive != taskModel.dataTask.countOfBytesReceived) { 439 | TTLog(@"不删除。。。"); 440 | return; 441 | } 442 | [_taskModelDict removeObjectForKey:@(taskIdentifier)]; 443 | } 444 | 445 | /** 446 | 保存缓存信息到本地 --delloc或 程序杀死是调用 447 | */ 448 | - (void)saveFileToLocal:(NSNotification *)noti { 449 | 450 | if (_contentLength == 0 || !_MIMEType || !_data) { 451 | return; 452 | } 453 | [self trimReceivedDataOffsetDict]; 454 | NSString *receivedDataInfoText = [self transformCFArrayToNSObject]; 455 | if ([receivedDataInfoText isEqualToString:_lastSaveInfo]) { 456 | return; 457 | } 458 | _lastSaveInfo = receivedDataInfoText; 459 | 460 | TTLog(@"*****存储视频到->>%@",_localFilePath); 461 | 462 | NSData *writeData = [_data copy]; 463 | NSString *localFilePath = [_localFilePath copy]; 464 | NSString *localMediaInfoPath = [_localMediaInfoPath copy]; 465 | 466 | NSDictionary *meidaInfoDict = 467 | @{ 468 | TTReceivedDataInfoText:receivedDataInfoText, 469 | TTMediaContentLengthText:@(_contentLength), 470 | TTMediaMIMETypeText:_MIMEType 471 | }; 472 | 473 | void (^callBlock)(void) = ^{ 474 | [[NSFileManager defaultManager] removeItemAtPath:localMediaInfoPath error:nil]; 475 | if (![[NSFileManager defaultManager] fileExistsAtPath:localFilePath]) { 476 | [[NSFileManager defaultManager] createFileAtPath:localFilePath contents:nil attributes:nil]; 477 | } 478 | NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:localFilePath]; 479 | [fileHandle seekToFileOffset:0]; 480 | [fileHandle writeData:writeData]; 481 | [fileHandle closeFile]; 482 | [meidaInfoDict writeToFile:localMediaInfoPath atomically:YES]; 483 | }; 484 | 485 | if ([NSThread isMainThread]) { 486 | dispatch_async(dispatch_get_global_queue(0, 0), callBlock); 487 | }else { 488 | callBlock(); 489 | } 490 | } 491 | 492 | /** 493 | 读取本地缓存信息 494 | */ 495 | - (void)readFromLocalFile { 496 | 497 | NSError *error; 498 | _data = [[NSMutableData alloc] initWithContentsOfFile:TTLocalFilePath(_url) options:NSDataReadingMappedIfSafe error:&error]; 499 | 500 | NSDictionary *mediaInfo = [NSDictionary dictionaryWithContentsOfFile:TTLocalMediaInfoPath(_url)]; 501 | 502 | if (error || !mediaInfo) { 503 | return; 504 | } 505 | TTLog(@"本地缓存信息:%@",mediaInfo); 506 | 507 | _contentLength = [mediaInfo[TTMediaContentLengthText] longLongValue]; 508 | _MIMEType = mediaInfo[TTMediaMIMETypeText]; 509 | 510 | NSString *receivedDataInfoString = mediaInfo[TTReceivedDataInfoText]; 511 | _lastSaveInfo = receivedDataInfoString; 512 | if (_contentLength > 0 && _data.length > 0) { 513 | _hasConfigured = YES; 514 | if (receivedDataInfoString.length > 1) { 515 | NSArray *separatedArray0 = [receivedDataInfoString componentsSeparatedByString:@";"]; 516 | [separatedArray0 enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 517 | NSRange range = [obj rangeOfString:@"-"]; 518 | if (range.location != NSNotFound) { 519 | long long begin = [[obj substringToIndex:range.location] longLongValue]; 520 | long long end = [[obj substringFromIndex:range.location+1] longLongValue]; 521 | TTReceivedDataPoint dataPoint = malloc(sizeof(TTReceivedDataPointType)); 522 | dataPoint->begin = begin; 523 | dataPoint->end = end; 524 | CFArrayAppendValue(_receivedDataPointArray, dataPoint); 525 | } 526 | }]; 527 | } 528 | } 529 | } 530 | 531 | //当视频全部下载完后自动保存 532 | - (void)autoSaveCacheToLocal:(NSUInteger)taskIdentifier { 533 | TTTaskModel *taskModel = _taskModelDict[@(taskIdentifier)]; 534 | if (taskModel.loadingRequest.dataRequest.requestedLength > TTCacheCellBytes) { 535 | [self trimReceivedDataOffsetDict]; 536 | CFIndex arrayCount = CFArrayGetCount(_receivedDataPointArray); 537 | if (arrayCount == 1) { 538 | TTReceivedDataPoint dataPoint = (TTReceivedDataPoint)CFArrayGetValueAtIndex(_receivedDataPointArray, 0); 539 | if (dataPoint->begin == 0 && (dataPoint->end +1) == _contentLength) { 540 | [self saveFileToLocal:nil]; 541 | _data = nil; 542 | } 543 | } 544 | } 545 | } 546 | 547 | #pragma mark --- 548 | /** 在数组中查找相同的begin */ 549 | - (CFIndex)findSameRequestOffsetFromReceivedDataPointArray:(long long)requestOffset { 550 | CFIndex index = -1; 551 | CFIndex count = CFArrayGetCount(_receivedDataPointArray); 552 | for (CFIndex i = count -1; i >= 0; i--) { 553 | TTReceivedDataPoint dataPoint = (TTReceivedDataPoint)CFArrayGetValueAtIndex(_receivedDataPointArray, i); 554 | if (dataPoint->begin == requestOffset) { 555 | return i; 556 | } 557 | } 558 | return index; 559 | } 560 | 561 | //整理 _receivedDataOffsetDict 交叉数据 组合下载进度 562 | - (void)trimReceivedDataOffsetDict { 563 | 564 | CFIndex arrayCount = CFArrayGetCount(_receivedDataPointArray); 565 | 566 | if (arrayCount < 2) { 567 | return; 568 | } 569 | CFArraySortValues(_receivedDataPointArray, CFRangeMake(0, arrayCount), (CFComparatorFunction)(&TTComparatorFunction), NULL); 570 | 571 | TTReceivedDataPoint dataPoint_i; 572 | TTReceivedDataPoint dataPoint_j; 573 | long long end_i = 0; 574 | for (CFIndex i = 0; i <= arrayCount -2; i++) { 575 | dataPoint_i = (TTReceivedDataPoint)CFArrayGetValueAtIndex(_receivedDataPointArray, i); 576 | end_i = dataPoint_i->end +1; 577 | for (CFIndex j = i+1; j <= arrayCount -1; j++) { 578 | dataPoint_j = (TTReceivedDataPoint)CFArrayGetValueAtIndex(_receivedDataPointArray, j); 579 | 580 | if (dataPoint_j->begin <= end_i) { 581 | if (dataPoint_j->end <= end_i) { 582 | }else { 583 | dataPoint_i->end = dataPoint_j->end; 584 | end_i = dataPoint_j->end + 1;; 585 | } 586 | arrayCount --; 587 | CFArrayRemoveValueAtIndex(_receivedDataPointArray, j); 588 | free(dataPoint_j); 589 | j--; 590 | } 591 | } 592 | } 593 | [self transformCFArrayToNSObject]; 594 | } 595 | 596 | //计算填充数据的结束位置 597 | - (long long)filledRequestEndPoint:(AVAssetResourceLoadingRequest *)loadingRequest { 598 | 599 | long long requestOffset = loadingRequest.dataRequest.requestedOffset; 600 | long long requestEnd = loadingRequest.dataRequest.requestedLength + requestOffset -1; 601 | long long requestEnd_r = 0; 602 | 603 | CFIndex sameBeginIndex = [self findSameRequestOffsetFromReceivedDataPointArray:requestOffset]; 604 | if (sameBeginIndex != -1) { 605 | TTReceivedDataPoint dataPoint = (TTReceivedDataPoint)CFArrayGetValueAtIndex(_receivedDataPointArray, sameBeginIndex); 606 | requestEnd_r = dataPoint->end; 607 | } 608 | 609 | if (requestEnd_r == requestOffset) { 610 | return 0; 611 | } 612 | if (requestEnd_r == 0) { 613 | CFIndex arrayCount = CFArrayGetCount(_receivedDataPointArray); 614 | TTReceivedDataPoint dataPoint; 615 | for (CFIndex i = 0; i < arrayCount; i++) { 616 | dataPoint = (TTReceivedDataPoint)CFArrayGetValueAtIndex(_receivedDataPointArray, i); 617 | long long keyLong = dataPoint->begin; 618 | long long objLong = dataPoint->end; 619 | if (requestOffset >= keyLong && requestOffset <= objLong) { 620 | if (requestEnd > objLong) { 621 | requestEnd_r = objLong; 622 | }else { 623 | requestEnd_r = requestEnd; 624 | } 625 | break; 626 | } 627 | } 628 | } 629 | return requestEnd_r; 630 | } 631 | 632 | /** 633 | <#Description#> 634 | 635 | @param loadingRequest <#loadingRequest description#> 636 | @param responsedLength 0时忽略 否则使用,当缓存未完成是,使播放器响应(一次填充,当快进时有问题) 637 | @param isContinue 用于返回判断 638 | @return 当前loadingRequest是否填充完数据 639 | */ 640 | - (BOOL)respondDataForLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest responsedLength:(long)responsedLength continued:(BOOL *)isContinue{ 641 | if (isContinue != NULL) { 642 | *isContinue = NO; 643 | } 644 | if (!loadingRequest || loadingRequest.isFinished || loadingRequest.isCancelled ||loadingRequest.dataRequest.currentOffset >= (loadingRequest.dataRequest.requestedOffset+ loadingRequest.dataRequest.requestedLength)) { 645 | return NO; 646 | } 647 | 648 | [self setResourceLoadingContentInformationRequestInformation:loadingRequest.contentInformationRequest]; 649 | 650 | long long filled_endPoint = [self filledRequestEndPoint:loadingRequest] +1; 651 | 652 | if (filled_endPoint > _contentLength) { 653 | filled_endPoint = _contentLength; 654 | } 655 | 656 | long long startOffset = loadingRequest.dataRequest.currentOffset; 657 | long long requsedtLength = loadingRequest.dataRequest.requestedLength; 658 | 659 | if (startOffset == requsedtLength) { 660 | return NO; 661 | } 662 | 663 | if (startOffset < 0) { 664 | TTLog(@"=====>>>>>>>"); 665 | startOffset = 0; 666 | } 667 | 668 | if (filled_endPoint <= startOffset) { 669 | if (startOffset == requsedtLength) { 670 | return YES; 671 | } 672 | return NO; 673 | } 674 | 675 | long long unReadLength = filled_endPoint - startOffset; 676 | long long needResponseDataLength = MIN(requsedtLength, unReadLength); 677 | 678 | if (responsedLength != 0) { 679 | needResponseDataLength = MIN(responsedLength, needResponseDataLength); 680 | } 681 | 682 | if (needResponseDataLength <= 0) { 683 | return NO; 684 | } 685 | NSData *respondData = [_data subdataWithRange:NSMakeRange((NSUInteger)startOffset, (NSUInteger)needResponseDataLength)]; 686 | [loadingRequest.dataRequest respondWithData:respondData]; 687 | // TTLog(@"填充数据长度%ld",respondData.length); 688 | respondData = nil; 689 | 690 | if (isContinue != NULL) { 691 | *isContinue = YES; 692 | } 693 | if (loadingRequest.dataRequest.currentOffset >= (loadingRequest.dataRequest.requestedOffset + loadingRequest.dataRequest.requestedLength)) { 694 | return YES; 695 | } 696 | return NO; 697 | } 698 | 699 | - (NSString *)transformCFArrayToNSObject { 700 | CFIndex count = CFArrayGetCount(_receivedDataPointArray); 701 | if (count == 0) { 702 | return @""; 703 | } 704 | NSMutableString *mString = [NSMutableString string]; 705 | for (CFIndex i = 0 ; i < count; i++) { 706 | TTReceivedDataPoint dataPoint = (TTReceivedDataPoint)CFArrayGetValueAtIndex(_receivedDataPointArray, i); 707 | [mString appendFormat:@"%lld-%lld;",dataPoint->begin,dataPoint->end]; 708 | } 709 | [mString deleteCharactersInRange:NSMakeRange(mString.length-1, 1)]; 710 | return mString; 711 | } 712 | 713 | /** 714 | 设置contentInformationRequest 的contentType 、contentLength(=视频数据总长度) 715 | */ 716 | - (void)setResourceLoadingContentInformationRequestInformation:(AVAssetResourceLoadingContentInformationRequest *)contentInformationRequest{ 717 | if (!contentInformationRequest) { 718 | return; 719 | } 720 | NSString *mimeType = _MIMEType; 721 | CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL); 722 | contentInformationRequest.byteRangeAccessSupported = YES; 723 | contentInformationRequest.contentType = CFBridgingRelease(contentType); 724 | contentInformationRequest.contentLength = _contentLength; 725 | } 726 | 727 | 728 | + (long long)caculateVideoResponseContentLength:(NSHTTPURLResponse *)response { 729 | long long videoContentLength = 0; 730 | NSString *content_range = response.allHeaderFields[@"Content-Range"]; 731 | NSString *contentLength = [[content_range componentsSeparatedByString:@"/"] lastObject]; 732 | videoContentLength = [contentLength longLongValue]; 733 | return videoContentLength; 734 | } 735 | 736 | @end 737 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/TTResourceLoaderDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // TTResourceLoaderDelegate.h 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/9. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | /** [resourceLoader setDelegate:self.resourceLoaderDelegate queue:TT_resourceLoader_delegate_queue()] */ 13 | extern dispatch_queue_t TT_resourceLoader_delegate_queue(void); 14 | 15 | @interface TTResourceLoaderDelegate : NSObject 16 | 17 | @end 18 | 19 | 20 | 21 | 22 | /** 下载任务 **/ 23 | @protocol TTResourceLoaderDownloadTaskDelegate; 24 | @interface TTResourceLoaderDownloadTask : NSObject 25 | 26 | @property (nonatomic, weak) id downloadDelegate; 27 | 28 | @end 29 | 30 | @protocol TTResourceLoaderDownloadTaskDelegate 31 | 32 | - (BOOL)TT_downloadTaskDataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response; 33 | - (void)TT_downloadTaskDataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data; 34 | - (void)TT_downloadTaskDataTask:(NSURLSessionDataTask *)dataTask didCompleteWithError:(NSError *)error; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Source/TTPlayerCache/TTResourceLoaderDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // TTResourceLoaderDelegate.m 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/9. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import "TTResourceLoaderDelegate.h" 10 | 11 | #import "TTPlayerCacheMacro.h" 12 | #import "TTResourceLoaderData.h" 13 | #import "TTReachabilityManager.h" 14 | #import "AVAssetResourceLoadingDataRequest+TTCategory.h" 15 | #import 16 | 17 | dispatch_queue_t TT_resourceLoader_delegate_queue(void) { 18 | static dispatch_queue_t resourceLoader_delegate_queue; 19 | static dispatch_once_t onceToken; 20 | dispatch_once(&onceToken, ^{ 21 | resourceLoader_delegate_queue = dispatch_queue_create("TT.resourceLoader.delegate.queue", DISPATCH_QUEUE_SERIAL); 22 | }); 23 | return resourceLoader_delegate_queue; 24 | } 25 | 26 | static dispatch_queue_t TT_resourceLoader_deal_queue(void) { 27 | static dispatch_queue_t resourceLoader_deal_queue; 28 | static dispatch_once_t onceToken; 29 | dispatch_once(&onceToken, ^{ 30 | resourceLoader_deal_queue = dispatch_queue_create("TT.resourceLoader.deal.queue", DISPATCH_QUEUE_SERIAL); 31 | }); 32 | return resourceLoader_deal_queue; 33 | } 34 | 35 | #ifndef NSFoundationVersionNumber_iOS_8_0 36 | #define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug 1140.11 37 | #else 38 | #define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0 39 | #endif 40 | 41 | static dispatch_queue_t url_session_manager_creation_queue() { 42 | static dispatch_queue_t af_url_session_manager_creation_queue; 43 | static dispatch_once_t onceToken; 44 | dispatch_once(&onceToken, ^{ 45 | af_url_session_manager_creation_queue = dispatch_queue_create("com.tt.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL); 46 | }); 47 | return af_url_session_manager_creation_queue; 48 | } 49 | static void url_session_manager_create_task_safely(dispatch_block_t block) { 50 | if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) { 51 | // Fix of bug 52 | // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8) 53 | // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093 54 | dispatch_sync(url_session_manager_creation_queue(), block); 55 | } else { 56 | block(); 57 | } 58 | } 59 | 60 | 61 | @interface TTResourceLoaderDelegate () 62 | { 63 | TTResourceLoaderData *_data; 64 | NSString *_url; 65 | 66 | dispatch_semaphore_t _sync_semaphore_t; 67 | dispatch_semaphore_t _cancel_semaphore_t; 68 | } 69 | 70 | @property (nonatomic, strong) NSURLSession *session; 71 | @property (nonatomic, strong) NSURLSessionDataTask *dataTask; 72 | 73 | @property (nonatomic, weak) AVAssetResourceLoadingRequest *currentLoadingRequest; 74 | 75 | @end 76 | 77 | /** 78 | * contentInformationRequest.contentLength = 视频数据总长度 79 | */ 80 | 81 | /** 82 | * mp4 -系统请求顺序 83 | * 1、先请求两个字节 range 0-2 84 | * 2、请求全部的数据 range 0- 85 | * 3、请求中间部分的数据 range XXXX-XXXX (多个) 86 | * (判断第三步,不继续请求,缓存获取) 87 | * 一次只能有一个网络任务 88 | */ 89 | 90 | @implementation TTResourceLoaderDelegate 91 | 92 | - (void)dealloc { 93 | TTLog(@"*****%@ ****dealloc***",self.class); 94 | _data = nil; 95 | [_session invalidateAndCancel]; 96 | [self hideNetworkActivityIndicator]; 97 | } 98 | 99 | - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest { 100 | TTLog(@"*****系统开始....******"); 101 | if ([loadingRequest.request.URL.scheme isEqualToString:TTPlayerCustomScheme]) { 102 | self.currentLoadingRequest = loadingRequest; 103 | if (_sync_semaphore_t != NULL) { 104 | dispatch_semaphore_signal(_sync_semaphore_t); 105 | } 106 | dispatch_async(TT_resourceLoader_deal_queue(), ^{ 107 | [self handleAssetResourceLoadingRequest:loadingRequest]; 108 | }); 109 | return YES; 110 | } 111 | return NO; 112 | } 113 | 114 | - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest { 115 | //系统会自动处理大视频,当前缓存到一定程度,系统会调用当前方法=>取消下载 116 | self.currentLoadingRequest = nil; 117 | [_data cancelLoadingRequestWithTaskId:self.dataTask.taskIdentifier]; 118 | TTLog(@"*****系统取消....******"); 119 | dispatch_async(TT_resourceLoader_deal_queue(), ^{ 120 | //信号量,等待currentLoadingRequest 赋值后继续执行 121 | _sync_semaphore_t = dispatch_semaphore_create(0); 122 | dispatch_semaphore_wait(_sync_semaphore_t, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC)); 123 | _sync_semaphore_t = NULL; 124 | if (!self.currentLoadingRequest) { 125 | TTLog(@"****取消中.....****"); 126 | [self cancelDataTask:NO]; 127 | } 128 | }); 129 | } 130 | 131 | - (void)cancelDataTask:(BOOL)isSync { 132 | if (self.dataTask && self.dataTask.state != NSURLSessionTaskStateCompleted) { 133 | TTLog(@"**cancelDataTask:**开始取消。。。。"); 134 | [self.dataTask cancel]; 135 | self.dataTask = nil; 136 | if (isSync) { 137 | //如果cacel request ,等待代理调用完,在需要线程继续 138 | _cancel_semaphore_t = dispatch_semaphore_create(0); 139 | dispatch_semaphore_wait(_cancel_semaphore_t, dispatch_time(DISPATCH_TIME_NOW, 2 *NSEC_PER_SEC)); 140 | _cancel_semaphore_t = NULL; 141 | } 142 | } 143 | } 144 | 145 | #pragma mark -处理AVAssetResourceLoadingRequest 146 | - (void)handleAssetResourceLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest { 147 | if (!_data) { 148 | //初始化 存储DATA 149 | _data = [[TTResourceLoaderData alloc] initWithURL:loadingRequest.request.URL.absoluteString]; 150 | __weak typeof(self) weakSelf = self; 151 | [TTReachabilityManager sharedReachabilityManager].reachableStatusChanged = ^(BOOL isReachable) { 152 | if (isReachable) { 153 | [weakSelf reloadLoadingRequestWhenHasNetError]; 154 | } 155 | }; 156 | } 157 | TTLog(@"\n\n ********************* 开始一次数据加载*********************\n"); 158 | 159 | //TT_requestsAllDataToEndOfResource == NO 时新建请求,快进时会创建改loadingRequest 160 | 161 | if (@available(iOS 9.0, *)) {//iOS 9.0 以上 162 | loadingRequest.dataRequest.TT_requestsAllDataToEndOfResource = loadingRequest.dataRequest.requestsAllDataToEndOfResource; 163 | }else { 164 | AVAssetResourceLoadingDataRequest *dataRequest = loadingRequest.dataRequest; 165 | long long r_length = dataRequest.requestedOffset + dataRequest.requestedLength; 166 | if (_data.contentLength == r_length) { 167 | dataRequest.TT_requestsAllDataToEndOfResource = YES; 168 | }else { 169 | dataRequest.TT_requestsAllDataToEndOfResource = NO; 170 | } 171 | } 172 | 173 | if (loadingRequest.dataRequest.TT_requestsAllDataToEndOfResource == NO && loadingRequest.dataRequest.requestedOffset != 0) { 174 | NSURLRequest *request = [self requestWithLoadingRequest:loadingRequest toEnd:NO]; 175 | __block NSURLSessionDataTask *dataTask = nil; 176 | url_session_manager_create_task_safely(^{ 177 | dataTask = [self.session dataTaskWithRequest:request]; 178 | }); 179 | [_data addLoadingRequest:loadingRequest dataTask:dataTask]; 180 | [dataTask resume]; 181 | TTLog(@"***TT_requestsAllDataToEndOfResource ==NO***-%@--",loadingRequest); 182 | return; 183 | } 184 | 185 | if (![_data needContinueNetworking:loadingRequest]) { 186 | TTLog(@"读取本地缓存==》%@",loadingRequest); 187 | return; 188 | } 189 | 190 | [_data cancelLoadingRequestWithTaskId:self.dataTask.taskIdentifier]; 191 | [_data cancelNoRequestsAllDataToEndOfResourceTask]; 192 | 193 | NSURLRequest *request = [self requestWithLoadingRequest:loadingRequest toEnd:YES]; 194 | 195 | [self cancelDataTask:YES]; 196 | 197 | __block NSURLSessionDataTask *dataTask = nil; 198 | url_session_manager_create_task_safely(^{ 199 | dataTask = [self.session dataTaskWithRequest:request]; 200 | }); 201 | self.dataTask = dataTask; 202 | 203 | [_data addLoadingRequest:loadingRequest dataTask:dataTask]; 204 | 205 | [dataTask resume]; 206 | 207 | TTLog(@"\n\n*******网络请求-loadingRequest:%@ \n",loadingRequest); 208 | } 209 | 210 | - (void)reloadLoadingRequestWhenHasNetError { 211 | dispatch_async(TT_resourceLoader_deal_queue(), ^{ 212 | [_data reloadLoadingRequestWhenHasNetError]; 213 | }); 214 | } 215 | 216 | /** 217 | 根据loadingRequest 组装新的NSURLRequest 218 | 219 | @param loadingRequest <#loadingRequest description#> 220 | @param toEnd YES 请求至结尾,NO组装 221 | @return <#return value description#> 222 | */ 223 | - (NSURLRequest *)requestWithLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest toEnd:(BOOL)toEnd{ 224 | long long requestOffset = loadingRequest.dataRequest.currentOffset; 225 | long long requsedtLength = loadingRequest.dataRequest.requestedLength; 226 | long long requestEnd = requestOffset + requsedtLength - 1; 227 | 228 | NSMutableURLRequest *mutableURLRequest = [loadingRequest.request mutableCopy]; 229 | //设置缓存策略 230 | mutableURLRequest.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; 231 | 232 | _url = [loadingRequest.request.URL.absoluteString stringByReplacingOccurrencesOfString:TTPlayerCustomProtocol withString:@""]; 233 | mutableURLRequest.URL = [NSURL URLWithString:_url]; 234 | 235 | TTLog(@">>>下载URL>>:%@",_url); 236 | 237 | ////计算组装 Range /////////////////// 238 | if (toEnd) { 239 | [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%lld-",requestOffset] forHTTPHeaderField:@"Range"]; 240 | }else { 241 | [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%lld-%lld",requestOffset,requestEnd] forHTTPHeaderField:@"Range"]; 242 | } 243 | ///////////////////////////////////////////// 244 | 245 | return mutableURLRequest; 246 | } 247 | 248 | #pragma mark -下载数据 249 | - (BOOL)TT_downloadTaskDataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response { 250 | [self showNetworkActivityIndicator]; 251 | __block BOOL allow = YES; 252 | dispatch_sync(TT_resourceLoader_deal_queue(), ^{ 253 | if (!_data.hasConfigured) { 254 | long long contentLength = [TTResourceLoaderData caculateVideoResponseContentLength:(NSHTTPURLResponse *)response]; 255 | if (contentLength == 0 || ![[response.MIMEType lowercaseString] containsString:@"video"]) { 256 | allow = NO; 257 | }else { 258 | [_data configContentLength:contentLength MIMEType:response.MIMEType]; 259 | } 260 | } 261 | }); 262 | return allow; 263 | } 264 | 265 | - (void)TT_downloadTaskDataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { 266 | [_data appendData:data taskId:dataTask.taskIdentifier]; 267 | } 268 | 269 | - (void)TT_downloadTaskDataTask:(NSURLSessionDataTask *)task didCompleteWithError:(NSError *)error { 270 | [_data taskCompleteWithError:error taskId:task.taskIdentifier]; 271 | if (error && error.code == NSURLErrorCancelled && _cancel_semaphore_t && self.dataTask == task) { 272 | dispatch_semaphore_signal(_cancel_semaphore_t); 273 | } 274 | [self hideNetworkActivityIndicator]; 275 | } 276 | 277 | 278 | #pragma mark ----------------------- 279 | #pragma mark -seesion 280 | - (NSURLSession *)session { 281 | if (!_session) { 282 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 283 | operationQueue.name = @"tt.opeationQueue.download"; 284 | operationQueue.maxConcurrentOperationCount = 1; 285 | TTResourceLoaderDownloadTask *downloadTask = [TTResourceLoaderDownloadTask new]; 286 | downloadTask.downloadDelegate = self; 287 | //NSURLSession delegate会retain 代理,故新建 288 | _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:downloadTask delegateQueue:operationQueue]; 289 | } 290 | return _session; 291 | } 292 | 293 | 294 | #pragma mark -help 295 | - (void)showNetworkActivityIndicator { 296 | dispatch_async(dispatch_get_main_queue(), ^{ 297 | [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; 298 | }); 299 | } 300 | 301 | - (void)hideNetworkActivityIndicator { 302 | if ([_data hasDataTaskRequesting]) { 303 | return; 304 | } 305 | dispatch_async(dispatch_get_main_queue(), ^{ 306 | [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; 307 | }); 308 | } 309 | 310 | @end 311 | 312 | @implementation TTResourceLoaderDownloadTask 313 | { 314 | NSUInteger _dowloadTotal; 315 | NSUInteger _speed; 316 | CFAbsoluteTime _time; 317 | NSNotificationCenter *_notificationCenter; 318 | } 319 | - (void)dealloc { 320 | _notificationCenter = nil; 321 | } 322 | 323 | - (instancetype)init { 324 | if (self = [super init]) { 325 | _notificationCenter = [NSNotificationCenter defaultCenter]; 326 | } 327 | return self; 328 | } 329 | 330 | /** 计算下载速度 */ 331 | - (void)caculateDownloadSpeed { 332 | CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent(); 333 | //计算一秒内的速度 334 | CFAbsoluteTime intervalTime = currentTime - _time; //间隔 335 | if (intervalTime >= 1) { 336 | _speed = _dowloadTotal/intervalTime; 337 | NSString *speedString ; 338 | if ([self respondsToSelector:@selector(transformBytesToString:)]) { 339 | speedString = [self transformBytesToString:_speed]; 340 | }else { 341 | speedString = @"0"; 342 | } 343 | dispatch_block_t block = ^() { 344 | [_notificationCenter postNotificationName:TTVideoDownloadSpeedNotification object:nil userInfo:@{TTDownloadSpeed:speedString}]; 345 | }; 346 | dispatch_async(dispatch_get_main_queue(), block); 347 | _dowloadTotal = 0; 348 | _time = currentTime; 349 | } 350 | } 351 | 352 | - (void)postDownloadFinishedNotificationWithError:(NSError *)error { 353 | dispatch_block_t block = ^() { 354 | [_notificationCenter postNotificationName:TTVideoDownloadSpeedNotification object:nil userInfo:@{TTDownloadFinished:@(YES)}]; 355 | if (error && error.code != NSURLErrorCancelled) { 356 | [_notificationCenter postNotificationName:TTVideoDownloadFailNotification object:nil userInfo:@{TTDownloadError:error}]; 357 | } 358 | }; 359 | dispatch_async(dispatch_get_main_queue(), block); 360 | } 361 | 362 | /** 转换速度成 B/s KB/s M/s */ 363 | - (NSString *)transformBytesToString:(NSUInteger)speed { 364 | static NSUInteger cell_KB = 1<<10; //1024 365 | static NSUInteger cell_M = 1 << 20;//1024 *1024 366 | NSString *speedString = nil; 367 | if (speed < cell_KB) { 368 | speedString = [NSString stringWithFormat:@"%luB/s",(unsigned long)speed]; 369 | }else if (speed < cell_M) { 370 | speedString = [NSString stringWithFormat:@"%luKB/s",(unsigned long)(speed/cell_KB)]; 371 | }else { 372 | speedString = [NSString stringWithFormat:@"%luM/s",(unsigned long)(speed/cell_M)]; 373 | } 374 | return speedString; 375 | } 376 | 377 | 378 | #pragma mark -NSURLSessionDownloadDelegate 379 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { 380 | if (_downloadDelegate && [_downloadDelegate respondsToSelector:@selector(TT_downloadTaskDataTask:didReceiveResponse:)]) { 381 | BOOL allow = [_downloadDelegate TT_downloadTaskDataTask:dataTask didReceiveResponse:response]; 382 | completionHandler(allow?NSURLSessionResponseAllow : NSURLSessionResponseCancel); 383 | if (!allow) { 384 | NSString *msg = [NSString stringWithFormat:@"\n[ url can'tb be connect...\n(%@) ]\n", response.URL]; 385 | NSError *error = [NSError errorWithDomain:msg code:NSURLErrorBadURL userInfo:@{@"url":response.URL}]; 386 | [self postDownloadFinishedNotificationWithError:error]; 387 | } 388 | }else { 389 | completionHandler(NSURLSessionResponseCancel); 390 | return; 391 | } 392 | } 393 | 394 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 395 | didCompleteWithError:(nullable NSError *)error { 396 | [_downloadDelegate TT_downloadTaskDataTask:(NSURLSessionDataTask *)task didCompleteWithError:error]; 397 | [self postDownloadFinishedNotificationWithError:error]; 398 | } 399 | 400 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 401 | didReceiveData:(NSData *)data { 402 | [_downloadDelegate TT_downloadTaskDataTask:dataTask didReceiveData:data]; 403 | 404 | _dowloadTotal += data.length; 405 | [self caculateDownloadSpeed]; 406 | } 407 | 408 | @end 409 | -------------------------------------------------------------------------------- /TTPlayerCache.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | 4 | s.name = "TTPlayerCache" 5 | s.version = "0.2.0" 6 | s.summary = "A cache for AVPlayer of TTPlayerCache." 7 | s.homepage = "https://github.com/sun8801/TTPlayerCache" 8 | s.license = "MIT" 9 | s.author = { "sun" => "sun8801@users.noreply.github.com" } 10 | 11 | s.platform = :ios, "8.0" 12 | s.ios.deployment_target = "8.0" 13 | 14 | 15 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 16 | # 17 | # Specify the location from where the source should be retrieved. 18 | # Supports git, hg, bzr, svn and HTTP. 19 | # 20 | 21 | s.source = { :git => 'https://github.com/sun8801/TTPlayerCache.git', 22 | :tag => s.version.to_s , 23 | :submodules => true} 24 | s.requires_arc = true 25 | s.frameworks = "UIKit","AVFoundation","SystemConfiguration","MobileCoreServices" 26 | 27 | s.source_files = 'Source/TTPlayerCache/TTPlayerCache.h' 28 | s.public_header_files = 'source/TTPlayerCache/TTPlayerCache.h' 29 | 30 | 31 | s.subspec 'Reachability' do |ss| 32 | ss.source_files = 'Source/TTPlayerCache/Reachability/*.{h,m}' 33 | ss.public_header_files = 'Source/TTPlayerCache/Reachability/*.h' 34 | end 35 | 36 | s.subspec 'Category' do |ss| 37 | ss.source_files = 'Source/TTPlayerCache/Category/*.{h,m}' 38 | ss.public_header_files = 'Source/TTPlayerCache/Category/*.h' 39 | end 40 | 41 | s.subspec 'PlayerCache' do |ss| 42 | ss.dependency 'TTPlayerCache/Category' 43 | ss.dependency 'TTPlayerCache/Reachability' 44 | 45 | ss.source_files = 'Source/TTPlayerCache/TTPlayerCacheMacro.h', 'Source/TTPlayerCache/TTResourceLoader{Delegate,Data,Cache}.{h,m}' 46 | ss.public_header_files = 'Source/TTPlayerCache/TTPlayerCacheMacro.h', 'Source/TTPlayerCache/TTResourceLoader{Delegate,Data,Cache}.h' 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /TTPlayerCache.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7D71BE331FDA84FC003069B3 /* AVAssetResourceLoadingDataRequest+TTCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D71BE251FDA84FB003069B3 /* AVAssetResourceLoadingDataRequest+TTCategory.m */; }; 11 | 7D71BE341FDA84FC003069B3 /* TTReachabilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D71BE281FDA84FB003069B3 /* TTReachabilityManager.m */; }; 12 | 7D71BE351FDA84FC003069B3 /* TTResourceLoaderCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D71BE291FDA84FB003069B3 /* TTResourceLoaderCache.m */; }; 13 | 7D71BE361FDA84FC003069B3 /* TTReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D71BE2C1FDA84FB003069B3 /* TTReachability.m */; }; 14 | 7D71BE371FDA84FC003069B3 /* TTResourceLoaderData.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D71BE311FDA84FB003069B3 /* TTResourceLoaderData.m */; }; 15 | 7D71BE381FDA84FC003069B3 /* TTResourceLoaderDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D71BE321FDA84FB003069B3 /* TTResourceLoaderDelegate.m */; }; 16 | 7DCA9F6A1FD643EF00644A65 /* TTPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DCA9F691FD643EE00644A65 /* TTPlayerView.m */; }; 17 | 7DE434391FCD356900D8085F /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DE434381FCD356900D8085F /* MainViewController.m */; }; 18 | 7DE8E1C91FB3FBA0002E7BA6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DE8E1C81FB3FBA0002E7BA6 /* AppDelegate.m */; }; 19 | 7DE8E1CC1FB3FBA0002E7BA6 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DE8E1CB1FB3FBA0002E7BA6 /* ViewController.m */; }; 20 | 7DE8E1CF1FB3FBA0002E7BA6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DE8E1CD1FB3FBA0002E7BA6 /* Main.storyboard */; }; 21 | 7DE8E1D11FB3FBA0002E7BA6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7DE8E1D01FB3FBA0002E7BA6 /* Assets.xcassets */; }; 22 | 7DE8E1D41FB3FBA0002E7BA6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7DE8E1D21FB3FBA0002E7BA6 /* LaunchScreen.storyboard */; }; 23 | 7DE8E1D71FB3FBA0002E7BA6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DE8E1D61FB3FBA0002E7BA6 /* main.m */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 7D71BE251FDA84FB003069B3 /* AVAssetResourceLoadingDataRequest+TTCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AVAssetResourceLoadingDataRequest+TTCategory.m"; sourceTree = ""; }; 28 | 7D71BE261FDA84FB003069B3 /* TTResourceLoaderDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TTResourceLoaderDelegate.h; sourceTree = ""; }; 29 | 7D71BE271FDA84FB003069B3 /* TTResourceLoaderData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TTResourceLoaderData.h; sourceTree = ""; }; 30 | 7D71BE281FDA84FB003069B3 /* TTReachabilityManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTReachabilityManager.m; sourceTree = ""; }; 31 | 7D71BE291FDA84FB003069B3 /* TTResourceLoaderCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTResourceLoaderCache.m; sourceTree = ""; }; 32 | 7D71BE2A1FDA84FB003069B3 /* TTPlayerCacheMacro.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TTPlayerCacheMacro.h; sourceTree = ""; }; 33 | 7D71BE2C1FDA84FB003069B3 /* TTReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTReachability.m; sourceTree = ""; }; 34 | 7D71BE2D1FDA84FB003069B3 /* TTReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TTReachability.h; sourceTree = ""; }; 35 | 7D71BE2E1FDA84FB003069B3 /* AVAssetResourceLoadingDataRequest+TTCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AVAssetResourceLoadingDataRequest+TTCategory.h"; sourceTree = ""; }; 36 | 7D71BE2F1FDA84FB003069B3 /* TTResourceLoaderCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TTResourceLoaderCache.h; sourceTree = ""; }; 37 | 7D71BE301FDA84FB003069B3 /* TTReachabilityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TTReachabilityManager.h; sourceTree = ""; }; 38 | 7D71BE311FDA84FB003069B3 /* TTResourceLoaderData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTResourceLoaderData.m; sourceTree = ""; }; 39 | 7D71BE321FDA84FB003069B3 /* TTResourceLoaderDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTResourceLoaderDelegate.m; sourceTree = ""; }; 40 | 7D71BE3A1FDE1788003069B3 /* TTPlayerCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TTPlayerCache.h; sourceTree = ""; }; 41 | 7DCA9F681FD643EE00644A65 /* TTPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TTPlayerView.h; sourceTree = ""; }; 42 | 7DCA9F691FD643EE00644A65 /* TTPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTPlayerView.m; sourceTree = ""; }; 43 | 7DE434371FCD356900D8085F /* MainViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = ""; }; 44 | 7DE434381FCD356900D8085F /* MainViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = ""; }; 45 | 7DE8E1C41FB3FBA0002E7BA6 /* TTPlayerCache.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TTPlayerCache.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 7DE8E1C71FB3FBA0002E7BA6 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 47 | 7DE8E1C81FB3FBA0002E7BA6 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 48 | 7DE8E1CA1FB3FBA0002E7BA6 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 49 | 7DE8E1CB1FB3FBA0002E7BA6 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 50 | 7DE8E1CE1FB3FBA0002E7BA6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 51 | 7DE8E1D01FB3FBA0002E7BA6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 7DE8E1D31FB3FBA0002E7BA6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 53 | 7DE8E1D51FB3FBA0002E7BA6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 7DE8E1D61FB3FBA0002E7BA6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | 7DE8E1C11FB3FBA0002E7BA6 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 6ADFF66844FE77F423D01DE8 /* Frameworks */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | ); 72 | name = Frameworks; 73 | sourceTree = ""; 74 | }; 75 | 7D71BE231FDA84FB003069B3 /* Source */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 7D71BE241FDA84FB003069B3 /* TTPlayerCache */, 79 | ); 80 | path = Source; 81 | sourceTree = SOURCE_ROOT; 82 | }; 83 | 7D71BE241FDA84FB003069B3 /* TTPlayerCache */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 7D71BE391FDA92BC003069B3 /* Category */, 87 | 7D71BE2B1FDA84FB003069B3 /* Reachability */, 88 | 7D71BE261FDA84FB003069B3 /* TTResourceLoaderDelegate.h */, 89 | 7D71BE321FDA84FB003069B3 /* TTResourceLoaderDelegate.m */, 90 | 7D71BE271FDA84FB003069B3 /* TTResourceLoaderData.h */, 91 | 7D71BE311FDA84FB003069B3 /* TTResourceLoaderData.m */, 92 | 7D71BE2F1FDA84FB003069B3 /* TTResourceLoaderCache.h */, 93 | 7D71BE291FDA84FB003069B3 /* TTResourceLoaderCache.m */, 94 | 7D71BE2A1FDA84FB003069B3 /* TTPlayerCacheMacro.h */, 95 | 7D71BE3A1FDE1788003069B3 /* TTPlayerCache.h */, 96 | ); 97 | path = TTPlayerCache; 98 | sourceTree = ""; 99 | }; 100 | 7D71BE2B1FDA84FB003069B3 /* Reachability */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 7D71BE2D1FDA84FB003069B3 /* TTReachability.h */, 104 | 7D71BE2C1FDA84FB003069B3 /* TTReachability.m */, 105 | 7D71BE301FDA84FB003069B3 /* TTReachabilityManager.h */, 106 | 7D71BE281FDA84FB003069B3 /* TTReachabilityManager.m */, 107 | ); 108 | path = Reachability; 109 | sourceTree = ""; 110 | }; 111 | 7D71BE391FDA92BC003069B3 /* Category */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 7D71BE2E1FDA84FB003069B3 /* AVAssetResourceLoadingDataRequest+TTCategory.h */, 115 | 7D71BE251FDA84FB003069B3 /* AVAssetResourceLoadingDataRequest+TTCategory.m */, 116 | ); 117 | path = Category; 118 | sourceTree = ""; 119 | }; 120 | 7DCA9F671FD643EE00644A65 /* TTPlayerView */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 7DCA9F681FD643EE00644A65 /* TTPlayerView.h */, 124 | 7DCA9F691FD643EE00644A65 /* TTPlayerView.m */, 125 | ); 126 | path = TTPlayerView; 127 | sourceTree = ""; 128 | }; 129 | 7DE8E1BB1FB3FB9F002E7BA6 = { 130 | isa = PBXGroup; 131 | children = ( 132 | 7DE8E1C61FB3FBA0002E7BA6 /* TTPlayerCache */, 133 | 7DE8E1C51FB3FBA0002E7BA6 /* Products */, 134 | 91D80A1DA54D12EF6BADA4BC /* Pods */, 135 | 6ADFF66844FE77F423D01DE8 /* Frameworks */, 136 | ); 137 | sourceTree = ""; 138 | }; 139 | 7DE8E1C51FB3FBA0002E7BA6 /* Products */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 7DE8E1C41FB3FBA0002E7BA6 /* TTPlayerCache.app */, 143 | ); 144 | name = Products; 145 | sourceTree = ""; 146 | }; 147 | 7DE8E1C61FB3FBA0002E7BA6 /* TTPlayerCache */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 7D71BE231FDA84FB003069B3 /* Source */, 151 | 7DCA9F671FD643EE00644A65 /* TTPlayerView */, 152 | 7DE8E1C71FB3FBA0002E7BA6 /* AppDelegate.h */, 153 | 7DE8E1C81FB3FBA0002E7BA6 /* AppDelegate.m */, 154 | 7DE434371FCD356900D8085F /* MainViewController.h */, 155 | 7DE434381FCD356900D8085F /* MainViewController.m */, 156 | 7DE8E1CA1FB3FBA0002E7BA6 /* ViewController.h */, 157 | 7DE8E1CB1FB3FBA0002E7BA6 /* ViewController.m */, 158 | 7DE8E1CD1FB3FBA0002E7BA6 /* Main.storyboard */, 159 | 7DE8E1D01FB3FBA0002E7BA6 /* Assets.xcassets */, 160 | 7DE8E1D21FB3FBA0002E7BA6 /* LaunchScreen.storyboard */, 161 | 7DE8E1D51FB3FBA0002E7BA6 /* Info.plist */, 162 | 7DE8E1D61FB3FBA0002E7BA6 /* main.m */, 163 | ); 164 | path = TTPlayerCache; 165 | sourceTree = ""; 166 | }; 167 | 91D80A1DA54D12EF6BADA4BC /* Pods */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | ); 171 | name = Pods; 172 | sourceTree = ""; 173 | }; 174 | /* End PBXGroup section */ 175 | 176 | /* Begin PBXNativeTarget section */ 177 | 7DE8E1C31FB3FBA0002E7BA6 /* TTPlayerCache */ = { 178 | isa = PBXNativeTarget; 179 | buildConfigurationList = 7DE8E1DA1FB3FBA0002E7BA6 /* Build configuration list for PBXNativeTarget "TTPlayerCache" */; 180 | buildPhases = ( 181 | 7DE8E1C01FB3FBA0002E7BA6 /* Sources */, 182 | 7DE8E1C11FB3FBA0002E7BA6 /* Frameworks */, 183 | 7DE8E1C21FB3FBA0002E7BA6 /* Resources */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | ); 189 | name = TTPlayerCache; 190 | productName = TTPlayerCache; 191 | productReference = 7DE8E1C41FB3FBA0002E7BA6 /* TTPlayerCache.app */; 192 | productType = "com.apple.product-type.application"; 193 | }; 194 | /* End PBXNativeTarget section */ 195 | 196 | /* Begin PBXProject section */ 197 | 7DE8E1BC1FB3FB9F002E7BA6 /* Project object */ = { 198 | isa = PBXProject; 199 | attributes = { 200 | LastUpgradeCheck = 0900; 201 | ORGANIZATIONNAME = szt; 202 | TargetAttributes = { 203 | 7DE8E1C31FB3FBA0002E7BA6 = { 204 | CreatedOnToolsVersion = 9.0.1; 205 | ProvisioningStyle = Automatic; 206 | }; 207 | }; 208 | }; 209 | buildConfigurationList = 7DE8E1BF1FB3FB9F002E7BA6 /* Build configuration list for PBXProject "TTPlayerCache" */; 210 | compatibilityVersion = "Xcode 8.0"; 211 | developmentRegion = en; 212 | hasScannedForEncodings = 0; 213 | knownRegions = ( 214 | en, 215 | Base, 216 | ); 217 | mainGroup = 7DE8E1BB1FB3FB9F002E7BA6; 218 | productRefGroup = 7DE8E1C51FB3FBA0002E7BA6 /* Products */; 219 | projectDirPath = ""; 220 | projectRoot = ""; 221 | targets = ( 222 | 7DE8E1C31FB3FBA0002E7BA6 /* TTPlayerCache */, 223 | ); 224 | }; 225 | /* End PBXProject section */ 226 | 227 | /* Begin PBXResourcesBuildPhase section */ 228 | 7DE8E1C21FB3FBA0002E7BA6 /* Resources */ = { 229 | isa = PBXResourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 7DE8E1D41FB3FBA0002E7BA6 /* LaunchScreen.storyboard in Resources */, 233 | 7DE8E1D11FB3FBA0002E7BA6 /* Assets.xcassets in Resources */, 234 | 7DE8E1CF1FB3FBA0002E7BA6 /* Main.storyboard in Resources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXResourcesBuildPhase section */ 239 | 240 | /* Begin PBXSourcesBuildPhase section */ 241 | 7DE8E1C01FB3FBA0002E7BA6 /* Sources */ = { 242 | isa = PBXSourcesBuildPhase; 243 | buildActionMask = 2147483647; 244 | files = ( 245 | 7D71BE331FDA84FC003069B3 /* AVAssetResourceLoadingDataRequest+TTCategory.m in Sources */, 246 | 7DE8E1CC1FB3FBA0002E7BA6 /* ViewController.m in Sources */, 247 | 7D71BE371FDA84FC003069B3 /* TTResourceLoaderData.m in Sources */, 248 | 7DE8E1D71FB3FBA0002E7BA6 /* main.m in Sources */, 249 | 7DE434391FCD356900D8085F /* MainViewController.m in Sources */, 250 | 7DE8E1C91FB3FBA0002E7BA6 /* AppDelegate.m in Sources */, 251 | 7D71BE351FDA84FC003069B3 /* TTResourceLoaderCache.m in Sources */, 252 | 7D71BE341FDA84FC003069B3 /* TTReachabilityManager.m in Sources */, 253 | 7D71BE361FDA84FC003069B3 /* TTReachability.m in Sources */, 254 | 7D71BE381FDA84FC003069B3 /* TTResourceLoaderDelegate.m in Sources */, 255 | 7DCA9F6A1FD643EF00644A65 /* TTPlayerView.m in Sources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | /* End PBXSourcesBuildPhase section */ 260 | 261 | /* Begin PBXVariantGroup section */ 262 | 7DE8E1CD1FB3FBA0002E7BA6 /* Main.storyboard */ = { 263 | isa = PBXVariantGroup; 264 | children = ( 265 | 7DE8E1CE1FB3FBA0002E7BA6 /* Base */, 266 | ); 267 | name = Main.storyboard; 268 | sourceTree = ""; 269 | }; 270 | 7DE8E1D21FB3FBA0002E7BA6 /* LaunchScreen.storyboard */ = { 271 | isa = PBXVariantGroup; 272 | children = ( 273 | 7DE8E1D31FB3FBA0002E7BA6 /* Base */, 274 | ); 275 | name = LaunchScreen.storyboard; 276 | sourceTree = ""; 277 | }; 278 | /* End PBXVariantGroup section */ 279 | 280 | /* Begin XCBuildConfiguration section */ 281 | 7DE8E1D81FB3FBA0002E7BA6 /* Debug */ = { 282 | isa = XCBuildConfiguration; 283 | buildSettings = { 284 | ALWAYS_SEARCH_USER_PATHS = NO; 285 | CLANG_ANALYZER_NONNULL = YES; 286 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 287 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 288 | CLANG_CXX_LIBRARY = "libc++"; 289 | CLANG_ENABLE_MODULES = YES; 290 | CLANG_ENABLE_OBJC_ARC = YES; 291 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 292 | CLANG_WARN_BOOL_CONVERSION = YES; 293 | CLANG_WARN_COMMA = YES; 294 | CLANG_WARN_CONSTANT_CONVERSION = YES; 295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 296 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 297 | CLANG_WARN_EMPTY_BODY = YES; 298 | CLANG_WARN_ENUM_CONVERSION = YES; 299 | CLANG_WARN_INFINITE_RECURSION = YES; 300 | CLANG_WARN_INT_CONVERSION = YES; 301 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 302 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 303 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 304 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 305 | CLANG_WARN_STRICT_PROTOTYPES = YES; 306 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 307 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 308 | CLANG_WARN_UNREACHABLE_CODE = YES; 309 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 310 | CODE_SIGN_IDENTITY = "iPhone Developer"; 311 | COPY_PHASE_STRIP = NO; 312 | DEBUG_INFORMATION_FORMAT = dwarf; 313 | ENABLE_STRICT_OBJC_MSGSEND = YES; 314 | ENABLE_TESTABILITY = YES; 315 | GCC_C_LANGUAGE_STANDARD = gnu11; 316 | GCC_DYNAMIC_NO_PIC = NO; 317 | GCC_NO_COMMON_BLOCKS = YES; 318 | GCC_OPTIMIZATION_LEVEL = 0; 319 | GCC_PREPROCESSOR_DEFINITIONS = ( 320 | "DEBUG=1", 321 | "$(inherited)", 322 | ); 323 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 324 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 325 | GCC_WARN_UNDECLARED_SELECTOR = YES; 326 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 327 | GCC_WARN_UNUSED_FUNCTION = YES; 328 | GCC_WARN_UNUSED_VARIABLE = YES; 329 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 330 | MTL_ENABLE_DEBUG_INFO = YES; 331 | ONLY_ACTIVE_ARCH = YES; 332 | SDKROOT = iphoneos; 333 | }; 334 | name = Debug; 335 | }; 336 | 7DE8E1D91FB3FBA0002E7BA6 /* Release */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | ALWAYS_SEARCH_USER_PATHS = NO; 340 | CLANG_ANALYZER_NONNULL = YES; 341 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 343 | CLANG_CXX_LIBRARY = "libc++"; 344 | CLANG_ENABLE_MODULES = YES; 345 | CLANG_ENABLE_OBJC_ARC = YES; 346 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 347 | CLANG_WARN_BOOL_CONVERSION = YES; 348 | CLANG_WARN_COMMA = YES; 349 | CLANG_WARN_CONSTANT_CONVERSION = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 352 | CLANG_WARN_EMPTY_BODY = YES; 353 | CLANG_WARN_ENUM_CONVERSION = YES; 354 | CLANG_WARN_INFINITE_RECURSION = YES; 355 | CLANG_WARN_INT_CONVERSION = YES; 356 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 359 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 360 | CLANG_WARN_STRICT_PROTOTYPES = YES; 361 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 362 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 363 | CLANG_WARN_UNREACHABLE_CODE = YES; 364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 365 | CODE_SIGN_IDENTITY = "iPhone Developer"; 366 | COPY_PHASE_STRIP = NO; 367 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 368 | ENABLE_NS_ASSERTIONS = NO; 369 | ENABLE_STRICT_OBJC_MSGSEND = YES; 370 | GCC_C_LANGUAGE_STANDARD = gnu11; 371 | GCC_NO_COMMON_BLOCKS = YES; 372 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 373 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 374 | GCC_WARN_UNDECLARED_SELECTOR = YES; 375 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 376 | GCC_WARN_UNUSED_FUNCTION = YES; 377 | GCC_WARN_UNUSED_VARIABLE = YES; 378 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 379 | MTL_ENABLE_DEBUG_INFO = NO; 380 | SDKROOT = iphoneos; 381 | VALIDATE_PRODUCT = YES; 382 | }; 383 | name = Release; 384 | }; 385 | 7DE8E1DB1FB3FBA0002E7BA6 /* Debug */ = { 386 | isa = XCBuildConfiguration; 387 | buildSettings = { 388 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 389 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 390 | CODE_SIGN_STYLE = Automatic; 391 | DEVELOPMENT_TEAM = 36WT396TPZ; 392 | INFOPLIST_FILE = TTPlayerCache/Info.plist; 393 | IPHONEOS_DEPLOYMENT_TARGET = 8; 394 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 395 | PRODUCT_BUNDLE_IDENTIFIER = com.sun.TTPlayerCache; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | PROVISIONING_PROFILE = ""; 398 | PROVISIONING_PROFILE_SPECIFIER = ""; 399 | TARGETED_DEVICE_FAMILY = "1,2"; 400 | }; 401 | name = Debug; 402 | }; 403 | 7DE8E1DC1FB3FBA0002E7BA6 /* Release */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 407 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 408 | CODE_SIGN_STYLE = Automatic; 409 | DEVELOPMENT_TEAM = 36WT396TPZ; 410 | INFOPLIST_FILE = TTPlayerCache/Info.plist; 411 | IPHONEOS_DEPLOYMENT_TARGET = 8; 412 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 413 | PRODUCT_BUNDLE_IDENTIFIER = com.sun.TTPlayerCache; 414 | PRODUCT_NAME = "$(TARGET_NAME)"; 415 | PROVISIONING_PROFILE = ""; 416 | PROVISIONING_PROFILE_SPECIFIER = ""; 417 | TARGETED_DEVICE_FAMILY = "1,2"; 418 | }; 419 | name = Release; 420 | }; 421 | /* End XCBuildConfiguration section */ 422 | 423 | /* Begin XCConfigurationList section */ 424 | 7DE8E1BF1FB3FB9F002E7BA6 /* Build configuration list for PBXProject "TTPlayerCache" */ = { 425 | isa = XCConfigurationList; 426 | buildConfigurations = ( 427 | 7DE8E1D81FB3FBA0002E7BA6 /* Debug */, 428 | 7DE8E1D91FB3FBA0002E7BA6 /* Release */, 429 | ); 430 | defaultConfigurationIsVisible = 0; 431 | defaultConfigurationName = Release; 432 | }; 433 | 7DE8E1DA1FB3FBA0002E7BA6 /* Build configuration list for PBXNativeTarget "TTPlayerCache" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | 7DE8E1DB1FB3FBA0002E7BA6 /* Debug */, 437 | 7DE8E1DC1FB3FBA0002E7BA6 /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | /* End XCConfigurationList section */ 443 | }; 444 | rootObject = 7DE8E1BC1FB3FB9F002E7BA6 /* Project object */; 445 | } 446 | -------------------------------------------------------------------------------- /TTPlayerCache.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TTPlayerCache.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TTPlayerCache.xcodeproj/project.xcworkspace/xcuserdata/szt.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun8801/TTPlayerCache/fe512d17ef8935190f530d7fc02fb5be29b7f4c2/TTPlayerCache.xcodeproj/project.xcworkspace/xcuserdata/szt.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TTPlayerCache.xcodeproj/project.xcworkspace/xcuserdata/szt.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | EnabledFullIndexStoreVisibility 12 | 13 | IssueFilterStyle 14 | ShowActiveSchemeOnly 15 | LiveSourceIssuesEnabled 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /TTPlayerCache.xcodeproj/xcuserdata/szt.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /TTPlayerCache.xcodeproj/xcuserdata/szt.xcuserdatad/xcschemes/TTPlayerCache.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 57 | 59 | 65 | 66 | 67 | 68 | 72 | 73 | 74 | 75 | 79 | 80 | 81 | 82 | 88 | 90 | 96 | 97 | 98 | 99 | 101 | 102 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /TTPlayerCache.xcodeproj/xcuserdata/szt.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TTPlayerCache.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 7DE8E1C31FB3FBA0002E7BA6 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /TTPlayerCache/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun8801/TTPlayerCache/fe512d17ef8935190f530d7fc02fb5be29b7f4c2/TTPlayerCache/.DS_Store -------------------------------------------------------------------------------- /TTPlayerCache/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/9. 6 | // Copyright © 2017年 szt. 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 | -------------------------------------------------------------------------------- /TTPlayerCache/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/9. 6 | // Copyright © 2017年 szt. 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 | -------------------------------------------------------------------------------- /TTPlayerCache/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /TTPlayerCache/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 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /TTPlayerCache/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 | 40 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 71 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 139 | 151 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /TTPlayerCache/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /TTPlayerCache/MainViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.h 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/28. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MainViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TTPlayerCache/MainViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.m 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/28. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import "MainViewController.h" 10 | #import "TTPlayerCache.h" 11 | 12 | @interface MainViewController () 13 | { 14 | NSString *_selectedUrl; 15 | } 16 | 17 | @property (weak, nonatomic) IBOutlet UITableView *tableView; 18 | @property (weak, nonatomic) IBOutlet UILabel *cacheSizeLabel; 19 | 20 | @property (nonatomic, strong) NSArray *dataSource; 21 | 22 | @end 23 | 24 | @implementation MainViewController 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | // Do any additional setup after loading the view. 29 | 30 | TTOpenLog = YES; 31 | 32 | [self showCacheSize]; 33 | 34 | NSLog(@"\n\n沙盒目录》》:%@\n\n",NSHomeDirectory()); 35 | } 36 | 37 | - (void)viewDidAppear:(BOOL)animated { 38 | [super viewDidAppear:animated]; 39 | 40 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 41 | [self showCacheSize]; 42 | }); 43 | } 44 | 45 | - (NSArray *)dataSource { 46 | if (!_dataSource) { 47 | ////10+M 48 | NSString *videoUrl0 = @"http://group.file.dachentech.com.cn/o_1bjrg7i8p40211ad4si1b94c4o4s"; 49 | videoUrl0 = TTResourceUrlFromOrigianllUrl(videoUrl0); 50 | 51 | ////20+M 52 | NSString *videoUrl1 = @"https://video.uning.tv/b8d3c6c8-26e3-11e6-96db-008cfae40bc8/film/6456e3cd-1954-47d3-b7f9-8ca15b1d0227.mp4?s=53760559"; 53 | videoUrl1 = TTResourceUrlFromOrigianllUrl(videoUrl1); 54 | 55 | ////7+M 56 | NSString *videoUrl2 = @"http://7xplva.com2.z0.glb.qiniucdn.com/%E5%BA%8F%E5%88%97%2002_1.mp4"; 57 | videoUrl2 = TTResourceUrlFromOrigianllUrl(videoUrl2); 58 | 59 | ////200+M 60 | NSString *videoUrl4 = @"http://mov.bn.netease.com/open-movie/nos/mp4/2015/03/25/SAKKKQR8I_sd.mp4"; 61 | videoUrl4 = TTResourceUrlFromOrigianllUrl(videoUrl4); 62 | 63 | NSString *videoUrl5 = @"http://cache.utovr.com/201508270528174780.m3u8"; 64 | videoUrl5 = TTResourceUrlFromOrigianllUrl(videoUrl5); 65 | 66 | 67 | _dataSource = @[videoUrl0,videoUrl1,videoUrl2,videoUrl4, videoUrl5]; 68 | } 69 | return _dataSource; 70 | } 71 | 72 | #pragma mark -UITableViewDataSource 73 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 74 | return self.dataSource.count; 75 | } 76 | 77 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 78 | 79 | static NSString *cellId = @"cellId"; 80 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId]; 81 | if (!cell) { 82 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId]; 83 | cell.textLabel.font = [UIFont systemFontOfSize:14]; 84 | cell.textLabel.numberOfLines = 0; 85 | cell.contentView.backgroundColor = [UIColor cyanColor]; 86 | } 87 | cell.textLabel.text = self.dataSource[indexPath.row]; 88 | return cell; 89 | } 90 | 91 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 92 | _selectedUrl = self.dataSource[indexPath.row]; 93 | } 94 | 95 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 96 | if (!_selectedUrl) { 97 | _selectedUrl = self.dataSource[0]; 98 | } 99 | [segue.destinationViewController performSelector:NSSelectorFromString(@"setUrl:") withObject:_selectedUrl]; 100 | [super prepareForSegue:segue sender:sender]; 101 | } 102 | 103 | - (IBAction)clearLocalCacheBtnAction:(UIButton *)sender { 104 | [TTResourceLoaderCache clearCache]; 105 | [self showCacheSize]; 106 | [[[UIAlertView alloc] initWithTitle:@"提示" message:@"清除缓存成功" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show]; 107 | } 108 | 109 | - (void)showCacheSize { 110 | self.cacheSizeLabel.text = [NSString stringWithFormat:@"size:%.2fM",[TTResourceLoaderCache getCacheSize]/1024.0/1024.0]; 111 | } 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /TTPlayerCache/TTPlayerView/TTPlayerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TTPlayerView.h 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/9. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TTPlayerView : UIView 12 | 13 | + (instancetype)new NS_UNAVAILABLE; 14 | - (instancetype)init NS_UNAVAILABLE; 15 | - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; 16 | 17 | + (instancetype)playerViewWith:(NSURL *)url; 18 | 19 | - (void)prepareToPlay; 20 | 21 | - (void)play; 22 | - (void)pause; 23 | 24 | 25 | - (void)seekNextTime; 26 | 27 | /** 28 | 播放到指定时间 29 | 30 | @param time <#time description#> 31 | */ 32 | - (void)seekToTime:(NSTimeInterval)time; 33 | 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /TTPlayerCache/TTPlayerView/TTPlayerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TTPlayerView.m 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/9. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import "TTPlayerView.h" 10 | #import "TTResourceLoaderDelegate.h" 11 | #import "TTPlayerCacheMacro.h" 12 | #import 13 | 14 | NSString *TTFilmLengthTransformToTimeString(id filmLength) { 15 | NSInteger filmTimeLength = 0; 16 | if ([filmLength isKindOfClass:[NSString class]]) { 17 | if (((NSString *)filmLength).length == 0) { 18 | return @"00:00"; 19 | } 20 | } 21 | if ([filmLength respondsToSelector:@selector(floatValue)]) { 22 | filmTimeLength = ceil([filmLength floatValue]); 23 | }else { 24 | if ([filmLength isKindOfClass:[NSNumber class]]) { 25 | filmTimeLength = ceil(((NSNumber *)filmLength).floatValue); 26 | } 27 | } 28 | if (filmTimeLength < 0) { 29 | filmTimeLength = 0; 30 | } 31 | 32 | NSInteger sec = filmTimeLength % 60; 33 | NSInteger min = filmTimeLength / 60; 34 | return [NSString stringWithFormat:@"%02ld:%02ld",min,sec]; 35 | } 36 | 37 | @interface TTPlayerView () 38 | 39 | @property (nonatomic, strong) NSURL *videoURL; 40 | 41 | /** 缓存代理*/ 42 | @property (nonatomic, strong) TTResourceLoaderDelegate *resourceLoaderDelegate; 43 | 44 | /** 播放属性 */ 45 | @property (nonatomic, strong) AVPlayer *player; 46 | @property (nonatomic, strong) AVPlayerItem *playerItem; 47 | @property (nonatomic, strong) AVURLAsset *urlAsset; 48 | @property (nonatomic, strong) AVAssetImageGenerator *imageGenerator; 49 | @property (nonatomic, strong) id timeObserve; 50 | /** playerLayer */ 51 | @property (nonatomic, weak) AVPlayerLayer *playerLayer; 52 | 53 | //控制 缓存进度 54 | @property (nonatomic, strong) UIProgressView *progressView; 55 | //视频时长label 56 | @property (nonatomic, strong) UILabel *timeLengthLabel; 57 | //当缓存为空时的自动暂停 58 | @property (nonatomic, assign) BOOL autoPauseByBufferEmpty; 59 | @property (nonatomic, assign) Float64 seekTime; 60 | 61 | @property (nonatomic, strong) UILabel *downloadSpeedLabel; 62 | 63 | @end 64 | 65 | @implementation TTPlayerView 66 | 67 | #pragma mark -override super method 68 | 69 | - (void)dealloc { 70 | [self pause]; 71 | self.resourceLoaderDelegate = nil; 72 | 73 | [self.player removeTimeObserver:self.timeObserve]; 74 | 75 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 76 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 77 | 78 | [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem]; 79 | [_playerItem removeObserver:self forKeyPath:@"status"]; 80 | [_playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"]; 81 | [_playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"]; 82 | [_playerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"]; 83 | } 84 | 85 | + (Class)layerClass { 86 | return [AVPlayerLayer class]; 87 | } 88 | - (AVPlayerLayer *)playerLayer { 89 | return (AVPlayerLayer *)self.layer; 90 | } 91 | 92 | - (instancetype)initWithFrame:(CGRect)frame { 93 | if (self = [super initWithFrame:frame]) { 94 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadSpeedChanged:) name:TTVideoDownloadSpeedNotification object:nil]; 95 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadFail:) name:TTVideoDownloadFailNotification object:nil]; 96 | } 97 | return self; 98 | } 99 | 100 | - (void)layoutSubviews { 101 | [super layoutSubviews]; 102 | [self layoutPlayerView]; 103 | } 104 | 105 | #pragma mark -public method 106 | + (instancetype)playerViewWith:(NSURL *)url { 107 | CGFloat width = CGRectGetWidth([UIScreen mainScreen].bounds); 108 | TTPlayerView *playerView = [[self alloc] initWithFrame:CGRectMake(0, 0,width , width *9.0/16)]; 109 | playerView.videoURL = url; 110 | return playerView; 111 | } 112 | 113 | - (void)prepareToPlay { 114 | [self configPlayer]; 115 | } 116 | 117 | - (void)play { 118 | if (self.autoPauseByBufferEmpty) { 119 | [self.player seekToTime:CMTimeMakeWithSeconds(CMTimeGetSeconds(self.player.currentTime)+0.1, self.player.currentTime.timescale)]; 120 | } 121 | [self.player play]; 122 | self.autoPauseByBufferEmpty = NO; 123 | } 124 | 125 | - (void)pause { 126 | [self.player pause]; 127 | } 128 | 129 | #pragma mark -快进 130 | - (void)seekNextTime { 131 | Float64 currentTime = CMTimeGetSeconds(self.playerItem.currentTime); 132 | self.seekTime = currentTime+10; 133 | [self.player seekToTime:CMTimeMakeWithSeconds(self.seekTime, self.playerItem.currentTime.timescale)]; 134 | } 135 | 136 | - (void)seekToTime:(NSTimeInterval)time { 137 | __weak typeof(self) weakSelf = self; 138 | self.seekTime = time; 139 | [self.player seekToTime:CMTimeMakeWithSeconds(self.seekTime, self.playerItem.currentTime.timescale) toleranceBefore:CMTimeMakeWithSeconds(1, 1) toleranceAfter:CMTimeMakeWithSeconds(1, 1) completionHandler:^(BOOL finished) { 140 | if (finished) { 141 | [weakSelf play]; 142 | } 143 | }]; 144 | } 145 | 146 | #pragma mark -private method 147 | 148 | - (void)configPlayer { 149 | self.resourceLoaderDelegate = [TTResourceLoaderDelegate new]; 150 | 151 | self.urlAsset = [AVURLAsset assetWithURL:self.videoURL]; 152 | // // 153 | [self.urlAsset.resourceLoader setDelegate:self.resourceLoaderDelegate queue:TT_resourceLoader_delegate_queue()]; 154 | // 初始化playerItem 155 | self.playerItem = [AVPlayerItem playerItemWithAsset:self.urlAsset]; 156 | // 每次都重新创建Player,替换replaceCurrentItemWithPlayerItem:,该方法阻塞线程 157 | self.player = [AVPlayer playerWithPlayerItem:self.playerItem]; 158 | 159 | // 初始化playerLayer 160 | self.playerLayer.player = self.player; 161 | 162 | self.backgroundColor = [UIColor blackColor]; 163 | 164 | // 添加播放进度计时器 165 | [self createTimer]; 166 | } 167 | 168 | - (void)createTimer { 169 | __weak typeof(self) weakSelf = self; 170 | self.timeObserve = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1, 1) queue:nil usingBlock:^(CMTime time){ 171 | AVPlayerItem *currentItem = weakSelf.playerItem; 172 | NSArray *loadedRanges = currentItem.seekableTimeRanges; 173 | if (loadedRanges.count > 0 && currentItem.duration.timescale != 0) { 174 | // NSInteger currentTime = (NSInteger)CMTimeGetSeconds([currentItem currentTime]); 175 | // CGFloat totalTime = (CGFloat)currentItem.duration.value / currentItem.duration.timescale; 176 | // CGFloat value = CMTimeGetSeconds([currentItem currentTime]) / totalTime; 177 | 178 | [weakSelf setPlayerTimeLabel]; 179 | } 180 | }]; 181 | } 182 | 183 | - (void)setPlayerTimeLabel { 184 | NSInteger currentTime = (NSInteger)CMTimeGetSeconds([self.playerItem currentTime]); 185 | Float64 length = CMTimeGetSeconds(self.player.currentItem.duration); 186 | NSString *time = [NSString stringWithFormat:@"%@ / %@",TTFilmLengthTransformToTimeString(@(currentTime)),TTFilmLengthTransformToTimeString(@(length))]; 187 | self.timeLengthLabel.text = time; 188 | } 189 | 190 | - (void)layoutPlayerView { 191 | CGFloat width = CGRectGetWidth(self.frame); 192 | CGFloat height = CGRectGetHeight(self.frame); 193 | 194 | self.progressView.frame = CGRectMake(20, height -15, width -20 *2, 15); 195 | self.timeLengthLabel.frame = CGRectMake((width -150)/2.0, CGRectGetMinY(self.progressView.frame)-30, 150, 25); 196 | self.downloadSpeedLabel.frame = CGRectMake(width -60 -10, 0, 60, 30); 197 | } 198 | 199 | 200 | #pragma mark -设置 201 | - (void)pauseByBufferEmpty { 202 | self.autoPauseByBufferEmpty = YES; 203 | [self pause]; 204 | self.seekTime = 0; 205 | } 206 | 207 | #pragma mark - KVO 208 | 209 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 210 | if (object == self.player.currentItem) { 211 | if ([keyPath isEqualToString:@"status"]) { 212 | if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) { 213 | NSLog(@"视频可以播放了。。。。"); 214 | [self setPlayerTimeLabel]; 215 | } 216 | NSLog(@"当前视频状态:%ld",(long)self.player.currentItem.status); 217 | } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) { 218 | 219 | // 计算缓冲进度 220 | Float64 timeInterval = [self availableDuration]; 221 | CMTime duration = self.playerItem.duration; 222 | Float64 totalDuration = CMTimeGetSeconds(duration); 223 | [self.progressView setProgress:timeInterval/totalDuration animated:YES]; 224 | 225 | // Float64 currentTime = CMTimeGetSeconds([self.player.currentItem currentTime]); 226 | 227 | if (self.seekTime != 0 && ABS(timeInterval - self.seekTime) <= 0.5) { 228 | [self pauseByBufferEmpty]; 229 | } 230 | 231 | } else if ([keyPath isEqualToString:@"playbackBufferEmpty"]) { 232 | 233 | // 当缓冲是空的时候 234 | if (self.playerItem.playbackBufferEmpty) { 235 | [self pauseByBufferEmpty]; 236 | } 237 | 238 | } else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) { 239 | 240 | // 当缓冲好的时候 241 | if (self.playerItem.playbackLikelyToKeepUp && self.autoPauseByBufferEmpty){ 242 | [self play]; 243 | } 244 | } 245 | } 246 | } 247 | 248 | #pragma mark -AVPlayerItemDidPlayToEndTimeNotification 播放完成 249 | - (void)moviePlayDidEnd:(NSNotification *)noti { 250 | NSLog(@"播放结束"); 251 | } 252 | 253 | #pragma mark - NSNotification 254 | #pragma mark - 下载速度改变。。。 255 | - (void)downloadSpeedChanged:(NSNotification *)noti { 256 | BOOL hidden = [noti.userInfo[TTDownloadFinished] boolValue]; 257 | if (hidden) { 258 | [self performSelector:@selector(hiddenDownloadSpeedLabel) withObject:nil afterDelay:2]; 259 | }else { 260 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hiddenDownloadSpeedLabel) object:nil]; 261 | self.downloadSpeedLabel.hidden = NO; 262 | NSString *speedString = noti.userInfo[TTDownloadSpeed]; 263 | self.downloadSpeedLabel.text = speedString; 264 | } 265 | } 266 | 267 | - (void)downloadFail:(NSNotification *)noti { 268 | NSError *error = noti.userInfo[TTDownloadError]; 269 | if (error) { 270 | [[[UIAlertView alloc] initWithTitle:@"提示" message:error.localizedDescription delegate:nil cancelButtonTitle:@"知道了" otherButtonTitles:nil] show]; 271 | } 272 | 273 | } 274 | 275 | - (void)hiddenDownloadSpeedLabel { 276 | self.downloadSpeedLabel.hidden = YES; 277 | } 278 | 279 | #pragma mark - 计算缓冲进度 280 | 281 | /** 282 | * 计算缓冲进度 283 | * 284 | * @return 缓冲进度 285 | */ 286 | - (Float64)availableDuration { 287 | NSArray *loadedTimeRanges = [[_player currentItem] loadedTimeRanges]; 288 | CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 获取缓冲区域 289 | Float64 startSeconds = CMTimeGetSeconds(timeRange.start); 290 | Float64 durationSeconds = CMTimeGetSeconds(timeRange.duration); 291 | Float64 result = startSeconds + durationSeconds;// 计算缓冲总进度 292 | return result; 293 | } 294 | 295 | #pragma mark -property method 296 | /** 297 | * 根据playerItem,来添加移除观察者 298 | * 299 | * @param playerItem playerItem 300 | */ 301 | - (void)setPlayerItem:(AVPlayerItem *)playerItem { 302 | if (_playerItem == playerItem) {return;} 303 | 304 | if (_playerItem) { 305 | [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem]; 306 | [_playerItem removeObserver:self forKeyPath:@"status"]; 307 | [_playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"]; 308 | [_playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"]; 309 | [_playerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"]; 310 | } 311 | _playerItem = playerItem; 312 | if (playerItem) { 313 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; 314 | [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; 315 | [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; 316 | // 缓冲区空了,需要等待数据 317 | [playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil]; 318 | // 缓冲区有足够数据可以播放了 319 | [playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil]; 320 | } 321 | } 322 | 323 | - (UIProgressView *)progressView { 324 | if (!_progressView) { 325 | _progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; 326 | _progressView.progressTintColor = [UIColor blueColor]; 327 | _progressView.trackTintColor = [UIColor whiteColor]; 328 | _progressView.progress = 0; 329 | [self addSubview:_progressView]; 330 | } 331 | return _progressView; 332 | } 333 | 334 | - (UILabel *)timeLengthLabel { 335 | if (!_timeLengthLabel) { 336 | _timeLengthLabel = [UILabel new]; 337 | _timeLengthLabel.backgroundColor = [UIColor clearColor]; 338 | _timeLengthLabel.textColor = [UIColor whiteColor]; 339 | _timeLengthLabel.font = [UIFont systemFontOfSize:13]; 340 | _timeLengthLabel.textAlignment = NSTextAlignmentCenter; 341 | [self addSubview:_timeLengthLabel]; 342 | } 343 | return _timeLengthLabel; 344 | } 345 | 346 | - (UILabel *)downloadSpeedLabel { 347 | if (!_downloadSpeedLabel) { 348 | _downloadSpeedLabel = [UILabel new]; 349 | _downloadSpeedLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.3]; 350 | _downloadSpeedLabel.textColor = [UIColor whiteColor]; 351 | _downloadSpeedLabel.font = [UIFont systemFontOfSize:13]; 352 | _downloadSpeedLabel.textAlignment = NSTextAlignmentRight; 353 | _downloadSpeedLabel.minimumScaleFactor = 0.5; 354 | _downloadSpeedLabel.adjustsFontSizeToFitWidth = YES; 355 | [self addSubview:_downloadSpeedLabel]; 356 | } 357 | return _downloadSpeedLabel; 358 | } 359 | 360 | @end 361 | -------------------------------------------------------------------------------- /TTPlayerCache/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/9. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @property (nonatomic, copy) NSString *url; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /TTPlayerCache/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/9. 6 | // Copyright © 2017年 szt. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #import "TTPlayerCacheMacro.h" 12 | #import "TTResourceLoaderDelegate.h" 13 | #import 14 | 15 | #import "TTPlayerView.h" 16 | 17 | @interface ViewController () 18 | 19 | @property (strong, nonatomic) IBOutlet UIView *playerBGView; 20 | @property (weak, nonatomic) IBOutlet UIButton *playBtn; 21 | @property (weak, nonatomic) IBOutlet UIButton *pauseBtn; 22 | 23 | @property (weak, nonatomic) IBOutlet UITextField *jumpTextField; 24 | 25 | 26 | @property (nonatomic, strong) TTPlayerView *playerView; 27 | 28 | @end 29 | 30 | @implementation ViewController 31 | 32 | - (void)viewDidLoad { 33 | [super viewDidLoad]; 34 | // Do any additional setup after loading the view, typically from a nib. 35 | 36 | [self setupPlayer]; 37 | } 38 | 39 | - (void)setupPlayer { 40 | 41 | self.playerView = [TTPlayerView playerViewWith:[NSURL URLWithString:self.url]]; 42 | [self.playerBGView addSubview:self.playerView]; 43 | 44 | [self.playerView prepareToPlay]; 45 | } 46 | 47 | #pragma mark -action method 48 | - (IBAction)playBtnAction:(UIButton *)sender { 49 | [self.playerView play]; 50 | } 51 | - (IBAction)pauseBtnAction:(UIButton *)sender { 52 | [self.playerView pause]; 53 | } 54 | - (IBAction)seekNext10BtnAction:(UIButton *)sender { 55 | [self.playerView seekNextTime]; 56 | } 57 | 58 | - (IBAction)jumpBtnAction:(UIButton *)sender { 59 | if (self.jumpTextField.text.length <= 0) { 60 | [self.playerView seekToTime:0]; 61 | return; 62 | } 63 | [self.playerView seekToTime:[self.jumpTextField.text floatValue]]; 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /TTPlayerCache/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TTPlayerCache 4 | // 5 | // Created by sunzongtang on 2017/11/9. 6 | // Copyright © 2017年 szt. 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 | --------------------------------------------------------------------------------