├── .gitignore ├── Example ├── LLVideoPlayer.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── LLVideoPlayer-Example.xcscheme ├── LLVideoPlayer.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── LLVideoPlayer │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── LLAppDelegate.h │ ├── LLAppDelegate.m │ ├── LLVideoPlayer-Info.plist │ ├── LLVideoPlayer-Prefix.pch │ ├── LLViewController.h │ ├── LLViewController.m │ ├── Main.storyboard │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m ├── Podfile └── Tests │ ├── Tests-Info.plist │ ├── Tests-Prefix.pch │ ├── Tests.m │ └── en.lproj │ └── InfoPlist.strings ├── LICENSE ├── LLVideoPlayer.podspec ├── LLVideoPlayer ├── AVPlayer+LLPlayer.h ├── AVPlayer+LLPlayer.m ├── CacheSupport │ ├── AVAssetResourceLoadingRequest+LLVideoPlayer.h │ ├── AVAssetResourceLoadingRequest+LLVideoPlayer.m │ ├── LLVideoPlayerCacheFile.h │ ├── LLVideoPlayerCacheFile.m │ ├── LLVideoPlayerCacheLoader.h │ ├── LLVideoPlayerCacheLoader.m │ ├── LLVideoPlayerCacheManager.h │ ├── LLVideoPlayerCacheManager.m │ ├── LLVideoPlayerCachePolicy.h │ ├── LLVideoPlayerCachePolicy.m │ ├── LLVideoPlayerCacheUtils.h │ ├── LLVideoPlayerCacheUtils.m │ ├── LLVideoPlayerDownloadRequest.h │ ├── LLVideoPlayerDownloadRequest.m │ ├── LLVideoPlayerDownloader.h │ ├── LLVideoPlayerDownloader.m │ ├── LLVideoPlayerLoadingRequest.h │ ├── LLVideoPlayerLoadingRequest.m │ ├── LLVideoPlayerLocalOperation.h │ ├── LLVideoPlayerLocalOperation.m │ ├── LLVideoPlayerOperationDelegate.h │ ├── LLVideoPlayerRemoteOperation.h │ ├── LLVideoPlayerRemoteOperation.m │ ├── NSString+LLVideoPlayer.h │ ├── NSString+LLVideoPlayer.m │ ├── NSURL+LLVideoPlayer.h │ ├── NSURL+LLVideoPlayer.m │ ├── NSURLResponse+LLVideoPlayer.h │ └── NSURLResponse+LLVideoPlayer.m ├── LLVideoPlayer.h ├── LLVideoPlayer.m ├── LLVideoPlayerDefines.h ├── LLVideoPlayerDelegate.h ├── LLVideoPlayerHelper.h ├── LLVideoPlayerHelper.m ├── LLVideoPlayerView.h ├── LLVideoPlayerView.m ├── LLVideoTrack.h └── LLVideoTrack.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | # Note: if you ignore the Pods directory, make sure to uncomment 31 | # `pod install` in .travis.yml 32 | # 33 | Example/Pods/ 34 | Example/Podfile.lock 35 | 36 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 11 | 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; }; 12 | 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 13 | 6003F598195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F596195388D20070C39A /* InfoPlist.strings */; }; 14 | 6003F59A195388D20070C39A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F599195388D20070C39A /* main.m */; }; 15 | 6003F59E195388D20070C39A /* LLAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F59D195388D20070C39A /* LLAppDelegate.m */; }; 16 | 6003F5A7195388D20070C39A /* LLViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5A6195388D20070C39A /* LLViewController.m */; }; 17 | 6003F5A9195388D20070C39A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5A8195388D20070C39A /* Images.xcassets */; }; 18 | 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; 19 | 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 20 | 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 21 | 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; }; 22 | 6003F5BC195388D20070C39A /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* Tests.m */; }; 23 | 64BFD03C61AC9C450C5F4EBD /* libPods-LLVideoPlayer_Example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BAA0623C28321117DBC40C1A /* libPods-LLVideoPlayer_Example.a */; }; 24 | 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; 25 | 9ABCB3314C1D1BF8C4AB79D2 /* libPods-LLVideoPlayer_Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 239C2CCCB5E562C8CD7CD625 /* libPods-LLVideoPlayer_Tests.a */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | 6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 6003F582195388D10070C39A /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 6003F589195388D20070C39A; 34 | remoteInfo = LLVideoPlayer; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 239C2CCCB5E562C8CD7CD625 /* libPods-LLVideoPlayer_Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LLVideoPlayer_Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 54F87A54032CDA079BD27E8C /* Pods-LLVideoPlayer_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LLVideoPlayer_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LLVideoPlayer_Tests/Pods-LLVideoPlayer_Tests.debug.xcconfig"; sourceTree = ""; }; 41 | 6003F58A195388D20070C39A /* LLVideoPlayer_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LLVideoPlayer_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43 | 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 44 | 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 45 | 6003F595195388D20070C39A /* LLVideoPlayer-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "LLVideoPlayer-Info.plist"; sourceTree = ""; }; 46 | 6003F597195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 47 | 6003F599195388D20070C39A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 48 | 6003F59B195388D20070C39A /* LLVideoPlayer-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LLVideoPlayer-Prefix.pch"; sourceTree = ""; }; 49 | 6003F59C195388D20070C39A /* LLAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LLAppDelegate.h; sourceTree = ""; }; 50 | 6003F59D195388D20070C39A /* LLAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LLAppDelegate.m; sourceTree = ""; }; 51 | 6003F5A5195388D20070C39A /* LLViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LLViewController.h; sourceTree = ""; }; 52 | 6003F5A6195388D20070C39A /* LLViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LLViewController.m; sourceTree = ""; }; 53 | 6003F5A8195388D20070C39A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 54 | 6003F5AE195388D20070C39A /* LLVideoPlayer_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LLVideoPlayer_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 56 | 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; 57 | 6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 58 | 6003F5BB195388D20070C39A /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = ""; }; 59 | 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; }; 60 | 60D7DDA8938ABDF34C2F4A9C /* Pods-LLVideoPlayer_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LLVideoPlayer_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-LLVideoPlayer_Tests/Pods-LLVideoPlayer_Tests.release.xcconfig"; sourceTree = ""; }; 61 | 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 62 | 953508CE19D6E2A746FD10B9 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 63 | AC3AE37EF4A7CF9693469274 /* LLVideoPlayer.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LLVideoPlayer.podspec; path = ../LLVideoPlayer.podspec; sourceTree = ""; }; 64 | BAA0623C28321117DBC40C1A /* libPods-LLVideoPlayer_Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LLVideoPlayer_Example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | D9A535C580A8E3E0229BF300 /* Pods-LLVideoPlayer_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LLVideoPlayer_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-LLVideoPlayer_Example/Pods-LLVideoPlayer_Example.release.xcconfig"; sourceTree = ""; }; 66 | DD4397F33CBCE9AFB3FE03C1 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 67 | F45AE028E8812195A3DAAB18 /* Pods-LLVideoPlayer_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LLVideoPlayer_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LLVideoPlayer_Example/Pods-LLVideoPlayer_Example.debug.xcconfig"; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 6003F587195388D20070C39A /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */, 76 | 6003F592195388D20070C39A /* UIKit.framework in Frameworks */, 77 | 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */, 78 | 64BFD03C61AC9C450C5F4EBD /* libPods-LLVideoPlayer_Example.a in Frameworks */, 79 | ); 80 | runOnlyForDeploymentPostprocessing = 0; 81 | }; 82 | 6003F5AB195388D20070C39A /* Frameworks */ = { 83 | isa = PBXFrameworksBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */, 87 | 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */, 88 | 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */, 89 | 9ABCB3314C1D1BF8C4AB79D2 /* libPods-LLVideoPlayer_Tests.a in Frameworks */, 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXFrameworksBuildPhase section */ 94 | 95 | /* Begin PBXGroup section */ 96 | 6003F581195388D10070C39A = { 97 | isa = PBXGroup; 98 | children = ( 99 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */, 100 | 6003F593195388D20070C39A /* Example for LLVideoPlayer */, 101 | 6003F5B5195388D20070C39A /* Tests */, 102 | 6003F58C195388D20070C39A /* Frameworks */, 103 | 6003F58B195388D20070C39A /* Products */, 104 | 7C682EC7D8682E296228BDE4 /* Pods */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | 6003F58B195388D20070C39A /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 6003F58A195388D20070C39A /* LLVideoPlayer_Example.app */, 112 | 6003F5AE195388D20070C39A /* LLVideoPlayer_Tests.xctest */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | 6003F58C195388D20070C39A /* Frameworks */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 6003F58D195388D20070C39A /* Foundation.framework */, 121 | 6003F58F195388D20070C39A /* CoreGraphics.framework */, 122 | 6003F591195388D20070C39A /* UIKit.framework */, 123 | 6003F5AF195388D20070C39A /* XCTest.framework */, 124 | BAA0623C28321117DBC40C1A /* libPods-LLVideoPlayer_Example.a */, 125 | 239C2CCCB5E562C8CD7CD625 /* libPods-LLVideoPlayer_Tests.a */, 126 | ); 127 | name = Frameworks; 128 | sourceTree = ""; 129 | }; 130 | 6003F593195388D20070C39A /* Example for LLVideoPlayer */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 6003F59C195388D20070C39A /* LLAppDelegate.h */, 134 | 6003F59D195388D20070C39A /* LLAppDelegate.m */, 135 | 6003F5A5195388D20070C39A /* LLViewController.h */, 136 | 6003F5A6195388D20070C39A /* LLViewController.m */, 137 | 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */, 138 | 6003F5A8195388D20070C39A /* Images.xcassets */, 139 | 6003F594195388D20070C39A /* Supporting Files */, 140 | ); 141 | name = "Example for LLVideoPlayer"; 142 | path = LLVideoPlayer; 143 | sourceTree = ""; 144 | }; 145 | 6003F594195388D20070C39A /* Supporting Files */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 6003F595195388D20070C39A /* LLVideoPlayer-Info.plist */, 149 | 6003F596195388D20070C39A /* InfoPlist.strings */, 150 | 6003F599195388D20070C39A /* main.m */, 151 | 6003F59B195388D20070C39A /* LLVideoPlayer-Prefix.pch */, 152 | ); 153 | name = "Supporting Files"; 154 | sourceTree = ""; 155 | }; 156 | 6003F5B5195388D20070C39A /* Tests */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 6003F5BB195388D20070C39A /* Tests.m */, 160 | 6003F5B6195388D20070C39A /* Supporting Files */, 161 | ); 162 | path = Tests; 163 | sourceTree = ""; 164 | }; 165 | 6003F5B6195388D20070C39A /* Supporting Files */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 6003F5B7195388D20070C39A /* Tests-Info.plist */, 169 | 6003F5B8195388D20070C39A /* InfoPlist.strings */, 170 | 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */, 171 | ); 172 | name = "Supporting Files"; 173 | sourceTree = ""; 174 | }; 175 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | AC3AE37EF4A7CF9693469274 /* LLVideoPlayer.podspec */, 179 | 953508CE19D6E2A746FD10B9 /* README.md */, 180 | DD4397F33CBCE9AFB3FE03C1 /* LICENSE */, 181 | ); 182 | name = "Podspec Metadata"; 183 | sourceTree = ""; 184 | }; 185 | 7C682EC7D8682E296228BDE4 /* Pods */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | F45AE028E8812195A3DAAB18 /* Pods-LLVideoPlayer_Example.debug.xcconfig */, 189 | D9A535C580A8E3E0229BF300 /* Pods-LLVideoPlayer_Example.release.xcconfig */, 190 | 54F87A54032CDA079BD27E8C /* Pods-LLVideoPlayer_Tests.debug.xcconfig */, 191 | 60D7DDA8938ABDF34C2F4A9C /* Pods-LLVideoPlayer_Tests.release.xcconfig */, 192 | ); 193 | name = Pods; 194 | sourceTree = ""; 195 | }; 196 | /* End PBXGroup section */ 197 | 198 | /* Begin PBXNativeTarget section */ 199 | 6003F589195388D20070C39A /* LLVideoPlayer_Example */ = { 200 | isa = PBXNativeTarget; 201 | buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "LLVideoPlayer_Example" */; 202 | buildPhases = ( 203 | 61B1050F3215201BE2709298 /* [CP] Check Pods Manifest.lock */, 204 | 6003F586195388D20070C39A /* Sources */, 205 | 6003F587195388D20070C39A /* Frameworks */, 206 | 6003F588195388D20070C39A /* Resources */, 207 | 0C114742D96713BD97FA90A0 /* [CP] Embed Pods Frameworks */, 208 | B5CA7DC81B587D1B97D717B9 /* [CP] Copy Pods Resources */, 209 | ); 210 | buildRules = ( 211 | ); 212 | dependencies = ( 213 | ); 214 | name = LLVideoPlayer_Example; 215 | productName = LLVideoPlayer; 216 | productReference = 6003F58A195388D20070C39A /* LLVideoPlayer_Example.app */; 217 | productType = "com.apple.product-type.application"; 218 | }; 219 | 6003F5AD195388D20070C39A /* LLVideoPlayer_Tests */ = { 220 | isa = PBXNativeTarget; 221 | buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "LLVideoPlayer_Tests" */; 222 | buildPhases = ( 223 | 0FF3A132128A1011C38D46BB /* [CP] Check Pods Manifest.lock */, 224 | 6003F5AA195388D20070C39A /* Sources */, 225 | 6003F5AB195388D20070C39A /* Frameworks */, 226 | 6003F5AC195388D20070C39A /* Resources */, 227 | FE557A45ABB3128303AF50F0 /* [CP] Embed Pods Frameworks */, 228 | 834983618138D0AF19DCAC13 /* [CP] Copy Pods Resources */, 229 | ); 230 | buildRules = ( 231 | ); 232 | dependencies = ( 233 | 6003F5B4195388D20070C39A /* PBXTargetDependency */, 234 | ); 235 | name = LLVideoPlayer_Tests; 236 | productName = LLVideoPlayerTests; 237 | productReference = 6003F5AE195388D20070C39A /* LLVideoPlayer_Tests.xctest */; 238 | productType = "com.apple.product-type.bundle.unit-test"; 239 | }; 240 | /* End PBXNativeTarget section */ 241 | 242 | /* Begin PBXProject section */ 243 | 6003F582195388D10070C39A /* Project object */ = { 244 | isa = PBXProject; 245 | attributes = { 246 | CLASSPREFIX = LL; 247 | LastUpgradeCheck = 0720; 248 | ORGANIZATIONNAME = mario; 249 | TargetAttributes = { 250 | 6003F589195388D20070C39A = { 251 | DevelopmentTeam = 588AWE66S7; 252 | }; 253 | 6003F5AD195388D20070C39A = { 254 | TestTargetID = 6003F589195388D20070C39A; 255 | }; 256 | }; 257 | }; 258 | buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "LLVideoPlayer" */; 259 | compatibilityVersion = "Xcode 3.2"; 260 | developmentRegion = English; 261 | hasScannedForEncodings = 0; 262 | knownRegions = ( 263 | en, 264 | Base, 265 | ); 266 | mainGroup = 6003F581195388D10070C39A; 267 | productRefGroup = 6003F58B195388D20070C39A /* Products */; 268 | projectDirPath = ""; 269 | projectRoot = ""; 270 | targets = ( 271 | 6003F589195388D20070C39A /* LLVideoPlayer_Example */, 272 | 6003F5AD195388D20070C39A /* LLVideoPlayer_Tests */, 273 | ); 274 | }; 275 | /* End PBXProject section */ 276 | 277 | /* Begin PBXResourcesBuildPhase section */ 278 | 6003F588195388D20070C39A /* Resources */ = { 279 | isa = PBXResourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */, 283 | 6003F5A9195388D20070C39A /* Images.xcassets in Resources */, 284 | 6003F598195388D20070C39A /* InfoPlist.strings in Resources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | 6003F5AC195388D20070C39A /* Resources */ = { 289 | isa = PBXResourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | /* End PBXResourcesBuildPhase section */ 297 | 298 | /* Begin PBXShellScriptBuildPhase section */ 299 | 0C114742D96713BD97FA90A0 /* [CP] Embed Pods Frameworks */ = { 300 | isa = PBXShellScriptBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | ); 304 | inputPaths = ( 305 | ); 306 | name = "[CP] Embed Pods Frameworks"; 307 | outputPaths = ( 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | shellPath = /bin/sh; 311 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LLVideoPlayer_Example/Pods-LLVideoPlayer_Example-frameworks.sh\"\n"; 312 | showEnvVarsInLog = 0; 313 | }; 314 | 0FF3A132128A1011C38D46BB /* [CP] Check Pods Manifest.lock */ = { 315 | isa = PBXShellScriptBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | ); 319 | inputPaths = ( 320 | ); 321 | name = "[CP] Check Pods Manifest.lock"; 322 | outputPaths = ( 323 | ); 324 | runOnlyForDeploymentPostprocessing = 0; 325 | shellPath = /bin/sh; 326 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 327 | showEnvVarsInLog = 0; 328 | }; 329 | 61B1050F3215201BE2709298 /* [CP] Check Pods Manifest.lock */ = { 330 | isa = PBXShellScriptBuildPhase; 331 | buildActionMask = 2147483647; 332 | files = ( 333 | ); 334 | inputPaths = ( 335 | ); 336 | name = "[CP] Check Pods Manifest.lock"; 337 | outputPaths = ( 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | shellPath = /bin/sh; 341 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 342 | showEnvVarsInLog = 0; 343 | }; 344 | 834983618138D0AF19DCAC13 /* [CP] Copy Pods Resources */ = { 345 | isa = PBXShellScriptBuildPhase; 346 | buildActionMask = 2147483647; 347 | files = ( 348 | ); 349 | inputPaths = ( 350 | ); 351 | name = "[CP] Copy Pods Resources"; 352 | outputPaths = ( 353 | ); 354 | runOnlyForDeploymentPostprocessing = 0; 355 | shellPath = /bin/sh; 356 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LLVideoPlayer_Tests/Pods-LLVideoPlayer_Tests-resources.sh\"\n"; 357 | showEnvVarsInLog = 0; 358 | }; 359 | B5CA7DC81B587D1B97D717B9 /* [CP] Copy Pods Resources */ = { 360 | isa = PBXShellScriptBuildPhase; 361 | buildActionMask = 2147483647; 362 | files = ( 363 | ); 364 | inputPaths = ( 365 | ); 366 | name = "[CP] Copy Pods Resources"; 367 | outputPaths = ( 368 | ); 369 | runOnlyForDeploymentPostprocessing = 0; 370 | shellPath = /bin/sh; 371 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LLVideoPlayer_Example/Pods-LLVideoPlayer_Example-resources.sh\"\n"; 372 | showEnvVarsInLog = 0; 373 | }; 374 | FE557A45ABB3128303AF50F0 /* [CP] Embed Pods Frameworks */ = { 375 | isa = PBXShellScriptBuildPhase; 376 | buildActionMask = 2147483647; 377 | files = ( 378 | ); 379 | inputPaths = ( 380 | ); 381 | name = "[CP] Embed Pods Frameworks"; 382 | outputPaths = ( 383 | ); 384 | runOnlyForDeploymentPostprocessing = 0; 385 | shellPath = /bin/sh; 386 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LLVideoPlayer_Tests/Pods-LLVideoPlayer_Tests-frameworks.sh\"\n"; 387 | showEnvVarsInLog = 0; 388 | }; 389 | /* End PBXShellScriptBuildPhase section */ 390 | 391 | /* Begin PBXSourcesBuildPhase section */ 392 | 6003F586195388D20070C39A /* Sources */ = { 393 | isa = PBXSourcesBuildPhase; 394 | buildActionMask = 2147483647; 395 | files = ( 396 | 6003F59E195388D20070C39A /* LLAppDelegate.m in Sources */, 397 | 6003F5A7195388D20070C39A /* LLViewController.m in Sources */, 398 | 6003F59A195388D20070C39A /* main.m in Sources */, 399 | ); 400 | runOnlyForDeploymentPostprocessing = 0; 401 | }; 402 | 6003F5AA195388D20070C39A /* Sources */ = { 403 | isa = PBXSourcesBuildPhase; 404 | buildActionMask = 2147483647; 405 | files = ( 406 | 6003F5BC195388D20070C39A /* Tests.m in Sources */, 407 | ); 408 | runOnlyForDeploymentPostprocessing = 0; 409 | }; 410 | /* End PBXSourcesBuildPhase section */ 411 | 412 | /* Begin PBXTargetDependency section */ 413 | 6003F5B4195388D20070C39A /* PBXTargetDependency */ = { 414 | isa = PBXTargetDependency; 415 | target = 6003F589195388D20070C39A /* LLVideoPlayer_Example */; 416 | targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */; 417 | }; 418 | /* End PBXTargetDependency section */ 419 | 420 | /* Begin PBXVariantGroup section */ 421 | 6003F596195388D20070C39A /* InfoPlist.strings */ = { 422 | isa = PBXVariantGroup; 423 | children = ( 424 | 6003F597195388D20070C39A /* en */, 425 | ); 426 | name = InfoPlist.strings; 427 | sourceTree = ""; 428 | }; 429 | 6003F5B8195388D20070C39A /* InfoPlist.strings */ = { 430 | isa = PBXVariantGroup; 431 | children = ( 432 | 6003F5B9195388D20070C39A /* en */, 433 | ); 434 | name = InfoPlist.strings; 435 | sourceTree = ""; 436 | }; 437 | /* End PBXVariantGroup section */ 438 | 439 | /* Begin XCBuildConfiguration section */ 440 | 6003F5BD195388D20070C39A /* Debug */ = { 441 | isa = XCBuildConfiguration; 442 | buildSettings = { 443 | ALWAYS_SEARCH_USER_PATHS = NO; 444 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 445 | CLANG_CXX_LIBRARY = "libc++"; 446 | CLANG_ENABLE_MODULES = YES; 447 | CLANG_ENABLE_OBJC_ARC = YES; 448 | CLANG_WARN_BOOL_CONVERSION = YES; 449 | CLANG_WARN_CONSTANT_CONVERSION = YES; 450 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 451 | CLANG_WARN_EMPTY_BODY = YES; 452 | CLANG_WARN_ENUM_CONVERSION = YES; 453 | CLANG_WARN_INT_CONVERSION = YES; 454 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 455 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 456 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 457 | COPY_PHASE_STRIP = NO; 458 | ENABLE_TESTABILITY = YES; 459 | GCC_C_LANGUAGE_STANDARD = gnu99; 460 | GCC_DYNAMIC_NO_PIC = NO; 461 | GCC_OPTIMIZATION_LEVEL = 0; 462 | GCC_PREPROCESSOR_DEFINITIONS = ( 463 | "DEBUG=1", 464 | "$(inherited)", 465 | ); 466 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 467 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 468 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 469 | GCC_WARN_UNDECLARED_SELECTOR = YES; 470 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 471 | GCC_WARN_UNUSED_FUNCTION = YES; 472 | GCC_WARN_UNUSED_VARIABLE = YES; 473 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 474 | ONLY_ACTIVE_ARCH = YES; 475 | SDKROOT = iphoneos; 476 | TARGETED_DEVICE_FAMILY = "1,2"; 477 | }; 478 | name = Debug; 479 | }; 480 | 6003F5BE195388D20070C39A /* Release */ = { 481 | isa = XCBuildConfiguration; 482 | buildSettings = { 483 | ALWAYS_SEARCH_USER_PATHS = NO; 484 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 485 | CLANG_CXX_LIBRARY = "libc++"; 486 | CLANG_ENABLE_MODULES = YES; 487 | CLANG_ENABLE_OBJC_ARC = YES; 488 | CLANG_WARN_BOOL_CONVERSION = YES; 489 | CLANG_WARN_CONSTANT_CONVERSION = YES; 490 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 491 | CLANG_WARN_EMPTY_BODY = YES; 492 | CLANG_WARN_ENUM_CONVERSION = YES; 493 | CLANG_WARN_INT_CONVERSION = YES; 494 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 495 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 496 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 497 | COPY_PHASE_STRIP = YES; 498 | ENABLE_NS_ASSERTIONS = NO; 499 | GCC_C_LANGUAGE_STANDARD = gnu99; 500 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 501 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 502 | GCC_WARN_UNDECLARED_SELECTOR = YES; 503 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 504 | GCC_WARN_UNUSED_FUNCTION = YES; 505 | GCC_WARN_UNUSED_VARIABLE = YES; 506 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 507 | SDKROOT = iphoneos; 508 | TARGETED_DEVICE_FAMILY = "1,2"; 509 | VALIDATE_PRODUCT = YES; 510 | }; 511 | name = Release; 512 | }; 513 | 6003F5C0195388D20070C39A /* Debug */ = { 514 | isa = XCBuildConfiguration; 515 | baseConfigurationReference = F45AE028E8812195A3DAAB18 /* Pods-LLVideoPlayer_Example.debug.xcconfig */; 516 | buildSettings = { 517 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 518 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 519 | DEVELOPMENT_TEAM = 588AWE66S7; 520 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 521 | GCC_PREFIX_HEADER = "LLVideoPlayer/LLVideoPlayer-Prefix.pch"; 522 | GCC_PREPROCESSOR_DEFINITIONS = ( 523 | "$(inherited)", 524 | "COCOAPODS=1", 525 | ); 526 | INFOPLIST_FILE = "LLVideoPlayer/LLVideoPlayer-Info.plist"; 527 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 528 | MODULE_NAME = ExampleApp; 529 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; 530 | PRODUCT_NAME = "$(TARGET_NAME)"; 531 | WRAPPER_EXTENSION = app; 532 | }; 533 | name = Debug; 534 | }; 535 | 6003F5C1195388D20070C39A /* Release */ = { 536 | isa = XCBuildConfiguration; 537 | baseConfigurationReference = D9A535C580A8E3E0229BF300 /* Pods-LLVideoPlayer_Example.release.xcconfig */; 538 | buildSettings = { 539 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 540 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 541 | DEVELOPMENT_TEAM = 588AWE66S7; 542 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 543 | GCC_PREFIX_HEADER = "LLVideoPlayer/LLVideoPlayer-Prefix.pch"; 544 | INFOPLIST_FILE = "LLVideoPlayer/LLVideoPlayer-Info.plist"; 545 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 546 | MODULE_NAME = ExampleApp; 547 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; 548 | PRODUCT_NAME = "$(TARGET_NAME)"; 549 | WRAPPER_EXTENSION = app; 550 | }; 551 | name = Release; 552 | }; 553 | 6003F5C3195388D20070C39A /* Debug */ = { 554 | isa = XCBuildConfiguration; 555 | baseConfigurationReference = 54F87A54032CDA079BD27E8C /* Pods-LLVideoPlayer_Tests.debug.xcconfig */; 556 | buildSettings = { 557 | BUNDLE_LOADER = "$(TEST_HOST)"; 558 | FRAMEWORK_SEARCH_PATHS = ( 559 | "$(SDKROOT)/Developer/Library/Frameworks", 560 | "$(inherited)", 561 | "$(DEVELOPER_FRAMEWORKS_DIR)", 562 | ); 563 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 564 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; 565 | GCC_PREPROCESSOR_DEFINITIONS = ( 566 | "DEBUG=1", 567 | "$(inherited)", 568 | ); 569 | INFOPLIST_FILE = "Tests/Tests-Info.plist"; 570 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; 571 | PRODUCT_NAME = "$(TARGET_NAME)"; 572 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LLVideoPlayer_Example.app/LLVideoPlayer_Example"; 573 | WRAPPER_EXTENSION = xctest; 574 | }; 575 | name = Debug; 576 | }; 577 | 6003F5C4195388D20070C39A /* Release */ = { 578 | isa = XCBuildConfiguration; 579 | baseConfigurationReference = 60D7DDA8938ABDF34C2F4A9C /* Pods-LLVideoPlayer_Tests.release.xcconfig */; 580 | buildSettings = { 581 | BUNDLE_LOADER = "$(TEST_HOST)"; 582 | FRAMEWORK_SEARCH_PATHS = ( 583 | "$(SDKROOT)/Developer/Library/Frameworks", 584 | "$(inherited)", 585 | "$(DEVELOPER_FRAMEWORKS_DIR)", 586 | ); 587 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 588 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; 589 | INFOPLIST_FILE = "Tests/Tests-Info.plist"; 590 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; 591 | PRODUCT_NAME = "$(TARGET_NAME)"; 592 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LLVideoPlayer_Example.app/LLVideoPlayer_Example"; 593 | WRAPPER_EXTENSION = xctest; 594 | }; 595 | name = Release; 596 | }; 597 | /* End XCBuildConfiguration section */ 598 | 599 | /* Begin XCConfigurationList section */ 600 | 6003F585195388D10070C39A /* Build configuration list for PBXProject "LLVideoPlayer" */ = { 601 | isa = XCConfigurationList; 602 | buildConfigurations = ( 603 | 6003F5BD195388D20070C39A /* Debug */, 604 | 6003F5BE195388D20070C39A /* Release */, 605 | ); 606 | defaultConfigurationIsVisible = 0; 607 | defaultConfigurationName = Release; 608 | }; 609 | 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "LLVideoPlayer_Example" */ = { 610 | isa = XCConfigurationList; 611 | buildConfigurations = ( 612 | 6003F5C0195388D20070C39A /* Debug */, 613 | 6003F5C1195388D20070C39A /* Release */, 614 | ); 615 | defaultConfigurationIsVisible = 0; 616 | defaultConfigurationName = Release; 617 | }; 618 | 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "LLVideoPlayer_Tests" */ = { 619 | isa = XCConfigurationList; 620 | buildConfigurations = ( 621 | 6003F5C3195388D20070C39A /* Debug */, 622 | 6003F5C4195388D20070C39A /* Release */, 623 | ); 624 | defaultConfigurationIsVisible = 0; 625 | defaultConfigurationName = Release; 626 | }; 627 | /* End XCConfigurationList section */ 628 | }; 629 | rootObject = 6003F582195388D10070C39A /* Project object */; 630 | } 631 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer.xcodeproj/xcshareddata/xcschemes/LLVideoPlayer-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 79 | 80 | 81 | 82 | 83 | 84 | 90 | 92 | 98 | 99 | 100 | 101 | 103 | 104 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/LLAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLAppDelegate.h 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 12/23/2016. 6 | // Copyright (c) 2016 mario. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface LLAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/LLAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLAppDelegate.m 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 12/23/2016. 6 | // Copyright (c) 2016 mario. All rights reserved. 7 | // 8 | 9 | #import "LLAppDelegate.h" 10 | 11 | @implementation LLAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // 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. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // 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. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/LLVideoPlayer-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | NSAppTransportSecurity 47 | 48 | NSAllowsArbitraryLoads 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/LLVideoPlayer-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | @import UIKit; 15 | @import Foundation; 16 | #endif 17 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/LLViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLViewController.h 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 12/23/2016. 6 | // Copyright (c) 2016 mario. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface LLViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/LLViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLViewController.m 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 12/23/2016. 6 | // Copyright (c) 2016 mario. All rights reserved. 7 | // 8 | 9 | #import "LLViewController.h" 10 | #import "LLVideoPlayer.h" 11 | #import "Masonry.h" 12 | 13 | #define kTestVideoURL [NSURL URLWithString:@"https://www.example.com/foo.mp4"] 14 | 15 | @interface LLViewController () 16 | 17 | @property (nonatomic, strong) LLVideoPlayer *player; 18 | @property (nonatomic, strong) UILabel *stateLabel; 19 | @property (nonatomic, strong) UILabel *currentTimeLabel; 20 | @property (nonatomic, strong) UILabel *totalTimeLabel; 21 | @property (nonatomic, strong) UISlider *slider; 22 | @property (nonatomic, strong) UISwitch *cacheSwitch; 23 | @property (nonatomic, assign) NSTimeInterval beginTime; 24 | @property (nonatomic, assign) NSTimeInterval endTime; 25 | 26 | @end 27 | 28 | @implementation LLViewController 29 | 30 | - (void)viewDidLoad 31 | { 32 | [super viewDidLoad]; 33 | // Do any additional setup after loading the view, typically from a nib. 34 | 35 | [self createPlayer]; 36 | { 37 | self.cacheSwitch = [UISwitch new]; 38 | [self.view addSubview:self.cacheSwitch]; 39 | self.cacheSwitch.frame = CGRectMake(10, 30, self.cacheSwitch.frame.size.width, self.cacheSwitch.frame.size.height); 40 | self.cacheSwitch.on = YES; 41 | } 42 | 43 | { 44 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 45 | [button setTitle:@"preload" forState:UIControlStateNormal]; 46 | [self.view addSubview:button]; 47 | button.frame = CGRectMake(100, 30, 60, 40); 48 | [button addTarget:self action:@selector(preloadAction:) forControlEvents:UIControlEventTouchUpInside]; 49 | } 50 | 51 | { 52 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 53 | [button setTitle:@"clear" forState:UIControlStateNormal]; 54 | [self.view addSubview:button]; 55 | button.frame = CGRectMake(160, 30, 60, 40); 56 | [button addTarget:self action:@selector(clearAction:) forControlEvents:UIControlEventTouchUpInside]; 57 | } 58 | 59 | { 60 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 61 | [button setTitle:@"destroy" forState:UIControlStateNormal]; 62 | [self.view addSubview:button]; 63 | button.frame = CGRectMake(220, 30, 60, 40); 64 | [button addTarget:self action:@selector(destroyAction:) forControlEvents:UIControlEventTouchUpInside]; 65 | } 66 | 67 | { 68 | self.stateLabel = [UILabel new]; 69 | self.stateLabel.backgroundColor = [UIColor clearColor]; 70 | self.stateLabel.font = [UIFont systemFontOfSize:14]; 71 | self.stateLabel.textColor = [UIColor redColor]; 72 | [self.view addSubview:self.stateLabel]; 73 | self.stateLabel.frame = CGRectMake(10, 300, 300, 20); 74 | 75 | self.stateLabel.text = [LLVideoPlayerHelper playerStateToString:self.player.state]; 76 | } 77 | 78 | { 79 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 80 | [button setTitle:@"load" forState:UIControlStateNormal]; 81 | [self.view addSubview:button]; 82 | button.frame = CGRectMake(10, 340, 50, 40); 83 | [button addTarget:self action:@selector(loadAction:) forControlEvents:UIControlEventTouchUpInside]; 84 | } 85 | 86 | { 87 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 88 | [button setTitle:@"play" forState:UIControlStateNormal]; 89 | [self.view addSubview:button]; 90 | button.frame = CGRectMake(60, 340, 50, 40); 91 | [button addTarget:self action:@selector(playAction:) forControlEvents:UIControlEventTouchUpInside]; 92 | } 93 | 94 | { 95 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 96 | [button setTitle:@"pause" forState:UIControlStateNormal]; 97 | [self.view addSubview:button]; 98 | button.frame = CGRectMake(110, 340, 50, 40); 99 | [button addTarget:self action:@selector(pauseAction:) forControlEvents:UIControlEventTouchUpInside]; 100 | } 101 | 102 | { 103 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 104 | [button setTitle:@"dismiss" forState:UIControlStateNormal]; 105 | [self.view addSubview:button]; 106 | button.frame = CGRectMake(160, 340, 60, 40); 107 | [button addTarget:self action:@selector(dismissAction:) forControlEvents:UIControlEventTouchUpInside]; 108 | } 109 | 110 | { 111 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 112 | [button setTitle:@"loadErr" forState:UIControlStateNormal]; 113 | [self.view addSubview:button]; 114 | button.frame = CGRectMake(220, 340, 60, 40); 115 | [button addTarget:self action:@selector(loadErrAction:) forControlEvents:UIControlEventTouchUpInside]; 116 | } 117 | 118 | { 119 | self.currentTimeLabel = [UILabel new]; 120 | self.currentTimeLabel.backgroundColor = [UIColor clearColor]; 121 | self.currentTimeLabel.font = [UIFont systemFontOfSize:14]; 122 | [self.view addSubview:self.currentTimeLabel]; 123 | self.currentTimeLabel.frame = CGRectMake(10, 400, 60, 30); 124 | 125 | self.slider = [[UISlider alloc] init]; 126 | [self.view addSubview:self.slider]; 127 | self.slider.frame = CGRectMake(60, 405, 200, 20); 128 | [self.slider addTarget:self action:@selector(sliderTouchUpInside:) forControlEvents:UIControlEventTouchUpInside]; 129 | 130 | self.totalTimeLabel = [UILabel new]; 131 | self.totalTimeLabel.backgroundColor = [UIColor clearColor]; 132 | self.totalTimeLabel.font = [UIFont systemFontOfSize:14]; 133 | [self.view addSubview:self.totalTimeLabel]; 134 | self.totalTimeLabel.frame = CGRectMake(270, 400, 60, 30); 135 | 136 | self.currentTimeLabel.text = [LLVideoPlayerHelper timeStringFromSecondsValue:0]; 137 | self.totalTimeLabel.text = [LLVideoPlayerHelper timeStringFromSecondsValue:0]; 138 | } 139 | } 140 | 141 | - (void)didReceiveMemoryWarning 142 | { 143 | [super didReceiveMemoryWarning]; 144 | // Dispose of any resources that can be recreated. 145 | } 146 | 147 | - (void)createPlayer 148 | { 149 | if (self.player) { 150 | self.player.delegate = nil; 151 | [self.player.view removeFromSuperview]; 152 | self.player = nil; 153 | } 154 | 155 | self.player = [[LLVideoPlayer alloc] init]; 156 | [self.view addSubview:self.player.view]; 157 | self.player.view.frame = CGRectMake(10, 80, 300, 200); 158 | self.player.delegate = self; 159 | self.player.cacheSupportEnabled = YES; 160 | LLVideoPlayerCachePolicy *policy = [LLVideoPlayerCachePolicy defaultPolicy]; 161 | self.player.cachePolicy = policy; 162 | } 163 | 164 | - (void)loadAction:(id)sender 165 | { 166 | NSLog(@"[PRESS] loadAction"); 167 | self.beginTime = CFAbsoluteTimeGetCurrent(); 168 | self.player.cacheSupportEnabled = self.cacheSwitch.on; 169 | 170 | [self.player loadVideoWithStreamURL:kTestVideoURL]; 171 | } 172 | 173 | - (void)playAction:(id)sender 174 | { 175 | [self.player playContent]; 176 | } 177 | 178 | - (void)pauseAction:(id)sender 179 | { 180 | [self.player pauseContent]; 181 | } 182 | 183 | - (void)dismissAction:(id)sender 184 | { 185 | [self.player dismissContent]; 186 | } 187 | 188 | - (void)loadErrAction:(id)sender 189 | { 190 | NSLog(@"[PRESS] loadErrAction"); 191 | self.player.cacheSupportEnabled = self.cacheSwitch.on; 192 | [self.player loadVideoWithStreamURL:kTestVideoURL]; 193 | 194 | // Testing dismiss right after loading 195 | [self.player dismissContent]; 196 | } 197 | 198 | - (void)destroyAction:(id)sender 199 | { 200 | NSLog(@"[PRESS] destroyAction"); 201 | self.player.delegate = nil; 202 | [self.player.view removeFromSuperview]; 203 | self.player = nil; 204 | } 205 | 206 | - (void)sliderTouchUpInside:(UISlider *)sender 207 | { 208 | float sec = [self.player.track.totalDuration floatValue] * sender.value; 209 | 210 | [self.player pauseContent:YES completionHandler:^{ 211 | [self.player seekToTimeInSecond:sec userAction:YES completionHandler:^(BOOL finished) { 212 | [self.player playContent]; 213 | }]; 214 | }]; 215 | } 216 | 217 | - (void)clearAction:(id)sender 218 | { 219 | [LLVideoPlayer clearAllCache]; 220 | NSLog(@"Clear Cache."); 221 | } 222 | 223 | - (void)preloadAction:(id)sender 224 | { 225 | NSLog(@"[PRESS] preloadAction"); 226 | [LLVideoPlayer preloadWithURL:kTestVideoURL]; 227 | } 228 | 229 | #pragma mark - LLVideoPlayerDelegate 230 | 231 | #pragma mark - State Changed 232 | 233 | - (BOOL)shouldVideoPlayer:(LLVideoPlayer *)videoPlayer changeStateTo:(LLVideoPlayerState)state 234 | { 235 | return YES; 236 | } 237 | 238 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer willChangeStateTo:(LLVideoPlayerState)state 239 | { 240 | } 241 | 242 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer didChangeStateFrom:(LLVideoPlayerState)state 243 | { 244 | if (state == LLVideoPlayerStateContentLoading && videoPlayer.state == LLVideoPlayerStateContentPlaying) { 245 | self.endTime =CFAbsoluteTimeGetCurrent(); 246 | NSLog(@"time: %f", self.endTime - self.beginTime); 247 | } 248 | self.stateLabel.text = [LLVideoPlayerHelper playerStateToString:self.player.state]; 249 | } 250 | 251 | #pragma mark - Play Control 252 | 253 | - (BOOL)shouldVideoPlayerStartVideo:(LLVideoPlayer *)videoPlayer 254 | { 255 | return YES; 256 | } 257 | 258 | - (void)videoPlayerWillStartVideo:(LLVideoPlayer *)videoPlayer 259 | { 260 | 261 | } 262 | 263 | - (void)videoPlayerDidStartVideo:(LLVideoPlayer *)videoPlayer 264 | { 265 | self.totalTimeLabel.text = [LLVideoPlayerHelper timeStringFromSecondsValue:[videoPlayer.track.totalDuration floatValue]]; 266 | } 267 | 268 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer durationDidLoad:(NSNumber *)duration 269 | { 270 | NSLog(@"durationDidLoad: %@", duration); 271 | } 272 | 273 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer didPlayFrame:(NSTimeInterval)time 274 | { 275 | NSLog(@"didPlayFrame: %f", time); 276 | self.currentTimeLabel.text = [LLVideoPlayerHelper timeStringFromSecondsValue:time]; 277 | self.totalTimeLabel.text = [LLVideoPlayerHelper timeStringFromSecondsValue:[videoPlayer.track.totalDuration floatValue]]; 278 | self.slider.value = time / [videoPlayer.track.totalDuration doubleValue]; 279 | } 280 | 281 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer loadedTimeRanges:(NSArray *)ranges 282 | { 283 | 284 | } 285 | 286 | - (void)videoPlayerDidPlayToEnd:(LLVideoPlayer *)videoPlayer 287 | { 288 | 289 | } 290 | 291 | #pragma mark - Error 292 | 293 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer didFailWithError:(NSError *)error 294 | { 295 | 296 | } 297 | 298 | @end 299 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/LLVideoPlayer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 12/23/2016. 6 | // Copyright (c) 2016 mario. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | #import "LLAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) 13 | { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([LLAppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios,8.0 2 | 3 | target 'LLVideoPlayer_Example' do 4 | pod 'Masonry' 5 | pod 'LLVideoPlayer', :path => '../' 6 | 7 | target 'LLVideoPlayer_Tests' do 8 | inherit! :search_paths 9 | 10 | 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // The contents of this file are implicitly included at the beginning of every test case source file. 2 | 3 | #ifdef __OBJC__ 4 | 5 | 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Example/Tests/Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerTests.m 3 | // LLVideoPlayerTests 4 | // 5 | // Created by mario on 12/23/2016. 6 | // Copyright (c) 2016 mario. All rights reserved. 7 | // 8 | 9 | @import XCTest; 10 | #import 11 | 12 | @interface Tests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation Tests 17 | 18 | - (void)setUp 19 | { 20 | [super setUp]; 21 | // Put setup code here. This method is called before the invocation of each test method in the class. 22 | } 23 | 24 | - (void)tearDown 25 | { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testExample 31 | { 32 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 33 | } 34 | 35 | - (void)testCustomSchemeURL 36 | { 37 | NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"]; 38 | NSURL *url2 = [url ll_customSchemeURL]; 39 | XCTAssert([url2.scheme isEqualToString:@"streaming"]); 40 | XCTAssert([url2.ll_originalSchemeURL isEqual:url]); 41 | } 42 | 43 | @end 44 | 45 | -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 mario 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /LLVideoPlayer.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'LLVideoPlayer' 3 | s.version = '1.2.6' 4 | s.summary = 'A low level, flexible video player based on AVPlayer for iOS.' 5 | 6 | s.description = <<-DESC 7 | LLVideoPlayer is a low level video player which is simple and easy to extend. 8 | DESC 9 | 10 | s.homepage = 'https://github.com/huangguiyang/LLVideoPlayer' 11 | s.license = { :type => 'MIT', :file => 'LICENSE' } 12 | s.author = { 'mario' => 'guiyang.huang@gmail.com' } 13 | s.source = { :git => 'https://github.com/huangguiyang/LLVideoPlayer.git', :tag => s.version.to_s } 14 | 15 | s.ios.deployment_target = '8.0' 16 | 17 | s.public_header_files = 'LLVideoPlayer/*.h' 18 | s.source_files = 'LLVideoPlayer/*.{m,h}' 19 | 20 | s.subspec 'CacheSupport' do |ss| 21 | ss.source_files = 'LLVideoPlayer/CacheSupport' 22 | end 23 | 24 | s.frameworks = 'QuartzCore', 'MediaPlayer', 'AVFoundation' 25 | end 26 | -------------------------------------------------------------------------------- /LLVideoPlayer/AVPlayer+LLPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // AVPlayer+LLPlayer.h 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/29. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AVPlayer (LLPlayer) 12 | 13 | - (void)ll_seekToTimeInSeconds:(float)time completionHandler:(void (^)(BOOL finished))completionHandler; 14 | 15 | - (void)ll_seekToTimeInSeconds:(float)time accurate:(BOOL)accurate completionHandler:(void (^)(BOOL finished))completionHandler; 16 | 17 | - (NSTimeInterval)ll_currentItemDuration; 18 | 19 | - (CMTime)ll_currentCMTime; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /LLVideoPlayer/AVPlayer+LLPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // AVPlayer+LLPlayer.m 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/29. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import "AVPlayer+LLPlayer.h" 10 | 11 | @implementation AVPlayer (LLPlayer) 12 | 13 | - (void)ll_seekToTimeInSeconds:(float)time completionHandler:(void (^)(BOOL))completionHandler 14 | { 15 | [self ll_seekToTimeInSeconds:time accurate:NO completionHandler:completionHandler]; 16 | } 17 | 18 | - (void)ll_seekToTimeInSeconds:(float)time accurate:(BOOL)accurate completionHandler:(void (^)(BOOL))completionHandler 19 | { 20 | if (accurate) { 21 | [self seekToTime:CMTimeMakeWithSeconds((NSInteger)time, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler]; 22 | } else { 23 | [self seekToTime:CMTimeMakeWithSeconds((NSInteger)time, 1) completionHandler:completionHandler]; 24 | } 25 | } 26 | 27 | - (NSTimeInterval)ll_currentItemDuration 28 | { 29 | return CMTimeGetSeconds([self.currentItem duration]); 30 | } 31 | 32 | - (CMTime)ll_currentCMTime 33 | { 34 | return [self currentTime]; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/AVAssetResourceLoadingRequest+LLVideoPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // AVAssetResourceLoadingRequest+LLVideoPlayer.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface AVAssetResourceLoadingRequest (LLVideoPlayer) 12 | 13 | - (void)ll_fillContentInformation:(NSURLResponse *)response; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/AVAssetResourceLoadingRequest+LLVideoPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // AVAssetResourceLoadingRequest+LLVideoPlayer.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import "AVAssetResourceLoadingRequest+LLVideoPlayer.h" 10 | #import 11 | #import "NSURLResponse+LLVideoPlayer.h" 12 | 13 | @implementation AVAssetResourceLoadingRequest (LLVideoPlayer) 14 | 15 | - (void)ll_fillContentInformation:(NSURLResponse *)response 16 | { 17 | if (nil == response) { 18 | return; 19 | } 20 | 21 | self.response = response; 22 | 23 | if (nil == self.contentInformationRequest) { 24 | return; 25 | } 26 | 27 | NSString *mimeType = [response MIMEType]; 28 | CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL); 29 | self.contentInformationRequest.contentType = CFBridgingRelease(contentType); 30 | 31 | if (NO == [response isKindOfClass:[NSHTTPURLResponse class]]) { 32 | return; 33 | } 34 | 35 | self.contentInformationRequest.byteRangeAccessSupported = [(NSHTTPURLResponse *)response ll_supportRange]; 36 | self.contentInformationRequest.contentLength = [(NSHTTPURLResponse *)response ll_totalLength]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerCacheFile.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerCacheFile.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import 10 | 11 | FOUNDATION_EXTERN NSString * const kLLVideoCacheFileExtensionIndex; 12 | 13 | @interface LLVideoPlayerCacheFile : NSObject 14 | 15 | - (instancetype)initWithURL:(NSURL *)url; 16 | 17 | #pragma mark - Read/Write 18 | 19 | - (NSData *)dataWithRange:(NSRange)range; 20 | - (void)writeData:(NSData *)data atOffset:(NSUInteger)offset; 21 | - (void)receiveResponse:(NSURLResponse *)response; 22 | 23 | - (NSURLResponse *)constructURLResponseForURL:(NSURL *)url andRange:(NSRange)range; 24 | - (void)enumerateRangesWithRequestRange:(NSRange)requestRange usingBlock:(void (^)(NSRange range, BOOL cached))block; 25 | 26 | - (void)synchronize; 27 | 28 | - (BOOL)isComplete; 29 | 30 | + (NSString *)cacheDirectory; 31 | + (NSString *)cacheFilePathWithURL:(NSURL *)url; 32 | 33 | #pragma mark - Property 34 | 35 | @property (nonatomic, assign, readonly) NSUInteger fileLength; 36 | @property (nonatomic, strong, readonly) NSString *cacheFilePath; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerCacheFile.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerCacheFile.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import "LLVideoPlayerCacheFile.h" 10 | #import "LLVideoPlayerCacheUtils.h" 11 | #import "NSURLResponse+LLVideoPlayer.h" 12 | #import "AVAssetResourceLoadingRequest+LLVideoPlayer.h" 13 | #import "NSString+LLVideoPlayer.h" 14 | #import 15 | #import 16 | 17 | NSString * const kLLVideoCacheFileExtensionIndex = @"idx"; 18 | 19 | static int mapfile(const char *filename, void **out_data_ptr, size_t *out_data_length) 20 | { 21 | int fd; 22 | int error = 0; 23 | struct stat statInfo; 24 | *out_data_ptr = NULL; 25 | *out_data_length = 0; 26 | 27 | fd = open(filename, O_RDWR, 0); 28 | if (fd < 0) { 29 | error = errno; 30 | } else { 31 | if (fstat(fd, &statInfo) < 0) { 32 | error = errno; 33 | } else { 34 | *out_data_ptr = mmap(NULL, 35 | statInfo.st_size, 36 | PROT_READ | PROT_WRITE, 37 | MAP_FILE | MAP_SHARED, fd, 0); 38 | if (*out_data_ptr == MAP_FAILED) { 39 | error = errno; 40 | } else { 41 | *out_data_length = statInfo.st_size; 42 | } 43 | } 44 | 45 | close(fd); 46 | } 47 | 48 | return error; 49 | } 50 | 51 | @interface LLVideoPlayerCacheFile () { 52 | NSUInteger _fileLength; 53 | void *_fileDataPtr; 54 | BOOL _complete; 55 | } 56 | 57 | @property (nonatomic, strong) NSString *cacheFilePath; 58 | @property (nonatomic, strong) NSString *indexFilePath; 59 | @property (nonatomic, strong) NSDictionary *allHeaderFields; 60 | @property (nonatomic, strong) NSLock *lock; 61 | @property (nonatomic, strong) NSURLResponse *response; 62 | @property (nonatomic, strong) NSMutableArray *ranges; 63 | 64 | @end 65 | 66 | @implementation LLVideoPlayerCacheFile 67 | 68 | - (void)dealloc 69 | { 70 | if (_fileDataPtr && _fileLength > 0) { 71 | munmap(_fileDataPtr, _fileLength); 72 | } 73 | } 74 | 75 | - (instancetype)initWithURL:(NSURL *)url 76 | { 77 | self = [super init]; 78 | if (self) { 79 | _lock = [[NSLock alloc] init]; 80 | _ranges = [NSMutableArray array]; 81 | _cacheFilePath = [LLVideoPlayerCacheFile cacheFilePathWithURL:url]; 82 | _indexFilePath = [NSString stringWithFormat:@"%@.%@", _cacheFilePath, kLLVideoCacheFileExtensionIndex]; 83 | 84 | NSFileManager *fileManager = [NSFileManager defaultManager]; 85 | NSString *dir = [_cacheFilePath stringByDeletingLastPathComponent]; 86 | if (NO == [fileManager fileExistsAtPath:dir] && 87 | NO == [fileManager createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:nil]) { 88 | return nil; 89 | } 90 | 91 | [self loadCacheFile]; 92 | [self checkComplete]; 93 | } 94 | return self; 95 | } 96 | 97 | #pragma mark - Private 98 | 99 | - (void)checkComplete 100 | { 101 | if (_ranges.count == 1) { 102 | NSRange range = [_ranges[0] rangeValue]; 103 | _complete = range.location == 0 && range.length == _fileLength; 104 | } else { 105 | _complete = NO; 106 | } 107 | } 108 | 109 | - (void)loadCacheFile 110 | { 111 | if (NO == [self tryloadCacheFile]) { 112 | // reset index 113 | _fileLength = 0; 114 | _fileDataPtr = NULL; 115 | _allHeaderFields = nil; 116 | [_ranges removeAllObjects]; 117 | [[NSFileManager defaultManager] removeItemAtPath:_indexFilePath error:nil]; 118 | [[NSFileManager defaultManager] removeItemAtPath:_cacheFilePath error:nil]; 119 | } 120 | } 121 | 122 | - (BOOL)tryloadCacheFile 123 | { 124 | NSData *indexData = [NSData dataWithContentsOfFile:_indexFilePath]; 125 | if (nil == indexData) { 126 | return NO; 127 | } 128 | 129 | NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:indexData options:NSJSONReadingAllowFragments | NSJSONReadingMutableContainers error:nil]; 130 | if (nil == dict || NO == [dict isKindOfClass:[NSDictionary class]]) { 131 | return NO; 132 | } 133 | 134 | _allHeaderFields = dict[@"allHeaderFields"]; 135 | NSString *contentRange = LLValueForHTTPHeaderField(_allHeaderFields, @"Content-Range"); 136 | _fileLength = [[contentRange ll_decodeLengthFromContentRange] integerValue]; 137 | if (_allHeaderFields.count == 0 || _fileLength == 0) { 138 | return NO; 139 | } 140 | 141 | NSMutableArray *ranges = dict[@"ranges"]; 142 | [_ranges removeAllObjects]; 143 | for (NSString *rangeString in ranges) { 144 | NSRange range = NSRangeFromString(rangeString); 145 | [_ranges addObject:[NSValue valueWithRange:range]]; 146 | } 147 | 148 | struct stat statInfo; 149 | const char *filename = [_cacheFilePath UTF8String]; 150 | int r = stat(filename, &statInfo); 151 | if (r == 0) { 152 | if (statInfo.st_size > 0 && statInfo.st_size != _fileLength) { 153 | return NO; 154 | } 155 | if (statInfo.st_size == 0) { 156 | if (truncate([_cacheFilePath UTF8String], _fileLength) != 0) { 157 | return NO; 158 | } 159 | } 160 | if (mapfile(filename, &_fileDataPtr, &_fileLength) != 0) { 161 | return NO; 162 | } 163 | } else if (r == ENOENT) { 164 | if (NO == [[NSFileManager defaultManager] createFileAtPath:_cacheFilePath contents:nil attributes:nil]) { 165 | return NO; 166 | } 167 | if (truncate(filename, _fileLength) != 0) { 168 | return NO; 169 | } 170 | if (mapfile(filename, &_fileDataPtr, &_fileLength) != 0) { 171 | return NO; 172 | } 173 | } else { 174 | return NO; 175 | } 176 | 177 | return YES; 178 | } 179 | 180 | - (BOOL)saveIndexFile 181 | { 182 | NSMutableArray *ranges = [NSMutableArray array]; 183 | 184 | for (NSValue *rangeValue in self.ranges) { 185 | [ranges addObject:NSStringFromRange([rangeValue rangeValue])]; 186 | } 187 | 188 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 189 | dict[@"ranges"] = ranges; 190 | dict[@"allHeaderFields"] = self.allHeaderFields; 191 | 192 | NSData *indexData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil]; 193 | if (nil == indexData) { 194 | return NO; 195 | } 196 | 197 | return [indexData writeToFile:self.indexFilePath atomically:YES]; 198 | } 199 | 200 | - (void)addRange:(NSRange)range 201 | { 202 | NSInteger index = NSNotFound; 203 | for (NSInteger i = 0; i < self.ranges.count; i++) { 204 | NSRange r = [self.ranges[i] rangeValue]; 205 | if (r.location >= range.location) { 206 | index = i; 207 | break; 208 | } 209 | } 210 | 211 | if (index == NSNotFound) { 212 | [self.ranges addObject:[NSValue valueWithRange:range]]; 213 | } else { 214 | [self.ranges insertObject:[NSValue valueWithRange:range] atIndex:index]; 215 | } 216 | 217 | // merge ranges if possible 218 | for (NSInteger i = 0; i < self.ranges.count; i++) { 219 | if ((i + 1) < self.ranges.count) { 220 | NSRange currentRange = [self.ranges[i] rangeValue]; 221 | NSRange nextRange = [self.ranges[i+1] rangeValue]; 222 | if (LLRangeCanMerge(currentRange, nextRange)) { 223 | [self.ranges removeObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, 2)]]; 224 | [self.ranges insertObject:[NSValue valueWithRange:NSUnionRange(currentRange, nextRange)] atIndex:i]; 225 | i -= 1; 226 | } 227 | } 228 | } 229 | 230 | [self checkComplete]; 231 | } 232 | 233 | - (NSRange)cachedRangeForRange:(NSRange)requestRange 234 | { 235 | if (requestRange.location >= _fileLength) { 236 | return LLInvalidRange; 237 | } 238 | 239 | NSRange foundRange = LLInvalidRange; 240 | for (NSValue *rangeValue in self.ranges) { 241 | NSRange range = [rangeValue rangeValue]; 242 | if (NSLocationInRange(requestRange.location, range)) { 243 | foundRange = range; 244 | break; 245 | } 246 | } 247 | 248 | if (NSEqualRanges(foundRange, LLInvalidRange)) { 249 | return LLInvalidRange; 250 | } 251 | 252 | NSRange resultRange = NSIntersectionRange(foundRange, requestRange); 253 | if (NSEqualRanges(resultRange, requestRange)) { 254 | return resultRange; 255 | } else { 256 | return LLInvalidRange; 257 | } 258 | } 259 | 260 | #pragma mark - Read/Write 261 | 262 | - (NSData *)dataWithRange:(NSRange)range 263 | { 264 | if (NO == LLValidFileRange(range)) { 265 | return nil; 266 | } 267 | 268 | if (NULL == _fileDataPtr) { 269 | return nil; 270 | } 271 | 272 | NSData *data = nil; 273 | 274 | [self.lock lock]; 275 | NSRange resultRange = [self cachedRangeForRange:range]; 276 | if (LLValidFileRange(resultRange)) { 277 | data = [[NSData alloc] initWithBytes:(char *)_fileDataPtr + range.location length:range.length]; 278 | } 279 | [self.lock unlock]; 280 | 281 | return data; 282 | } 283 | 284 | - (void)writeData:(NSData *)data atOffset:(NSUInteger)offset 285 | { 286 | if (nil == data || data.length == 0 || NULL == _fileDataPtr) { 287 | return; 288 | } 289 | if (offset > _fileLength || offset + data.length > _fileLength) { 290 | return; 291 | } 292 | 293 | [self.lock lock]; 294 | memcpy((char *)_fileDataPtr + offset, [data bytes], data.length); 295 | // Add Range 296 | [self addRange:NSMakeRange(offset, data.length)]; 297 | [self.lock unlock]; 298 | } 299 | 300 | - (BOOL)gotFileLength:(NSUInteger)length 301 | { 302 | NSFileManager *fileManager = [NSFileManager defaultManager]; 303 | 304 | if ([fileManager fileExistsAtPath:_cacheFilePath]) { 305 | [fileManager removeItemAtPath:_cacheFilePath error:nil]; 306 | } 307 | 308 | if (NO == [fileManager createFileAtPath:_cacheFilePath contents:nil attributes:nil]) { 309 | return NO; 310 | } 311 | 312 | const char *filename = [_cacheFilePath UTF8String]; 313 | 314 | if (truncate(filename, length) != 0) { 315 | return NO; 316 | } 317 | 318 | void *dataPtr = NULL; 319 | size_t dataLength = 0; 320 | 321 | if (mapfile(filename, &dataPtr, &dataLength) != 0) { 322 | return NO; 323 | } 324 | 325 | _fileDataPtr = dataPtr; 326 | _fileLength = dataLength; 327 | 328 | return YES; 329 | } 330 | 331 | - (void)receiveResponse:(NSURLResponse *)response 332 | { 333 | if (nil == response || NO == [response isKindOfClass:[NSHTTPURLResponse class]]) { 334 | return; 335 | } 336 | [self.lock lock]; 337 | if (nil == _response) { 338 | // save local 339 | _response = response; 340 | if (_fileLength == 0) { 341 | [self gotFileLength:[response ll_totalLength]]; 342 | } 343 | if (nil == _allHeaderFields) { 344 | _allHeaderFields = [(NSHTTPURLResponse *)response allHeaderFields]; 345 | [self saveIndexFile]; 346 | } 347 | } 348 | [self.lock unlock]; 349 | } 350 | 351 | - (NSURLResponse *)constructURLResponseForURL:(NSURL *)url andRange:(NSRange)range 352 | { 353 | NSHTTPURLResponse *response = nil; 354 | 355 | [self.lock lock]; 356 | if (_fileLength > 0 && _allHeaderFields.count > 0) { 357 | if (range.length == NSIntegerMax) { 358 | range.length = _fileLength - range.location; 359 | } 360 | 361 | NSMutableDictionary *responseHeaders = [self.allHeaderFields mutableCopy]; 362 | NSString *contentRangeKey = @"Content-Range"; 363 | BOOL supportRange = LLValueForHTTPHeaderField(responseHeaders, contentRangeKey) != nil; 364 | 365 | if (supportRange && LLValidByteRange(range)) { 366 | responseHeaders[contentRangeKey] = LLRangeToHTTPRangeResponseHeader(range, _fileLength); 367 | } else { 368 | [responseHeaders removeObjectForKey:contentRangeKey]; 369 | } 370 | 371 | responseHeaders[@"Content-Length"] = [NSString stringWithFormat:@"%tu", range.length]; 372 | NSInteger statusCode = supportRange ? 206 : 200; 373 | NSString *httpVersion = @"HTTP/1.1"; 374 | 375 | response = [[NSHTTPURLResponse alloc] initWithURL:url 376 | statusCode:statusCode 377 | HTTPVersion:httpVersion 378 | headerFields:responseHeaders]; 379 | } 380 | [self.lock unlock]; 381 | return response; 382 | } 383 | 384 | - (void)enumerateRangesWithRequestRange:(NSRange)requestRange usingBlock:(void (^)(NSRange range, BOOL cached))block 385 | { 386 | NSParameterAssert(block); 387 | NSInteger start = requestRange.location; 388 | NSInteger end = requestRange.length == NSIntegerMax ? NSIntegerMax : NSMaxRange(requestRange); 389 | 390 | NSArray *ranges = [self cachedRanges]; 391 | for (NSValue *value in ranges) { 392 | NSRange range = [value rangeValue]; 393 | 394 | if (start >= NSMaxRange(range)) { 395 | continue; 396 | } 397 | 398 | if (start < range.location) { 399 | NSInteger cacheEnd = MIN(range.location, end); 400 | block(NSMakeRange(start, cacheEnd - start), NO); 401 | start = cacheEnd; 402 | if (start == end) { 403 | break; 404 | } 405 | } 406 | 407 | // in range 408 | NSAssert(NSLocationInRange(start, range), @"Oops!!!"); 409 | 410 | if (end <= NSMaxRange(range)) { 411 | block(NSMakeRange(start, end - start), YES); 412 | start = end; 413 | break; 414 | } 415 | 416 | block(NSMakeRange(start, NSMaxRange(range) - start), YES); 417 | start = NSMaxRange(range); 418 | } 419 | 420 | if (end > start && (self.fileLength == 0 || start < self.fileLength)) { 421 | if (end == NSIntegerMax) { 422 | block(NSMakeRange(start, NSIntegerMax), NO); 423 | } else { 424 | block(NSMakeRange(start, end - start), NO); 425 | } 426 | } 427 | } 428 | 429 | - (void)synchronize 430 | { 431 | [self.lock lock]; 432 | [self saveIndexFile]; 433 | [self.lock unlock]; 434 | } 435 | 436 | - (BOOL)isComplete 437 | { 438 | return _complete; 439 | } 440 | 441 | - (NSArray *)cachedRanges 442 | { 443 | [self.lock lock]; 444 | NSArray *ranges = [NSArray arrayWithArray:_ranges]; 445 | [self.lock unlock]; 446 | return ranges; 447 | } 448 | 449 | + (NSString *)cacheDirectory 450 | { 451 | NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; 452 | return [cache stringByAppendingPathComponent:@"LLVideoPlayer"]; 453 | } 454 | 455 | + (NSString *)cacheFilePathWithURL:(NSURL *)url 456 | { 457 | NSString *name = [url.absoluteString ll_md5]; 458 | NSString *dir = [self cacheDirectory]; 459 | return [dir stringByAppendingPathComponent:name]; 460 | } 461 | 462 | @end 463 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerCacheLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerCacheLoader.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface LLVideoPlayerCacheLoader : NSObject 13 | 14 | - (instancetype)initWithURL:(NSURL *)streamURL; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerCacheLoader.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerCacheLoader.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import "LLVideoPlayerCacheLoader.h" 10 | #import "LLVideoPlayerLoadingRequest.h" 11 | #import "LLVideoPlayerCacheFile.h" 12 | #import "LLVideoPlayerCacheManager.h" 13 | #import "NSURLResponse+LLVideoPlayer.h" 14 | 15 | static NSString * const kLLVideoPlayerCacheLoaderBusy = @"LLVideoPlayerCacheLoaderBusy"; 16 | static NSString * const kLLVideoPlayerCacheLoaderIdle = @"LLVideoPlayerCacheLoaderIdle"; 17 | 18 | @interface LLVideoPlayerCacheLoader () 19 | 20 | @property (nonatomic, strong) NSURL *streamURL; 21 | @property (nonatomic, strong) LLVideoPlayerCacheFile *cacheFile; 22 | @property (nonatomic, strong) NSMutableArray *operationQueue; 23 | @property (nonatomic, assign) long long totalLength; 24 | @property (nonatomic, assign) long long loadedLength; 25 | @property (nonatomic, assign) BOOL notifyStart; 26 | @property (nonatomic, assign) BOOL notifyEnough; 27 | 28 | @end 29 | 30 | @implementation LLVideoPlayerCacheLoader 31 | 32 | #pragma mark - Initialize 33 | 34 | - (void)dealloc 35 | { 36 | for (LLVideoPlayerLoadingRequest *operation in _operationQueue) { 37 | [operation cancel]; 38 | } 39 | [[LLVideoPlayerCacheManager defaultManager] releaseCacheFileForURL:_streamURL]; 40 | if (NO == _notifyEnough) { 41 | [[NSNotificationCenter defaultCenter] postNotificationName:kLLVideoPlayerCacheLoaderIdle object:nil]; 42 | } 43 | } 44 | 45 | - (instancetype)initWithURL:(NSURL *)streamURL 46 | { 47 | self = [super init]; 48 | if (self) { 49 | _streamURL = streamURL; 50 | _operationQueue = [[NSMutableArray alloc] initWithCapacity:4]; 51 | _cacheFile = [[LLVideoPlayerCacheManager defaultManager] createCacheFileForURL:streamURL]; 52 | } 53 | return self; 54 | } 55 | 56 | #pragma mark - LLVideoPlayerLoadingRequestDelegate 57 | 58 | - (void)request:(LLVideoPlayerLoadingRequest *)operation didComepleteWithError:(NSError *)error 59 | { 60 | if (nil == error) { 61 | [operation.loadingRequest finishLoading]; 62 | /* 63 | NOTE: The loading ranges may be overlapped, 64 | so `loadedLength` may be greater than `totalLength`. 65 | */ 66 | self.loadedLength += operation.loadingRequest.dataRequest.requestedLength; 67 | if (self.totalLength == 0) { 68 | self.totalLength = [operation.loadingRequest.response ll_totalLength]; 69 | } 70 | } else { 71 | [operation.loadingRequest finishLoadingWithError:error]; 72 | } 73 | [self.operationQueue removeObject:operation]; 74 | 75 | if (NO == self.notifyEnough && self.totalLength > 0 && self.loadedLength * 4 >= self.totalLength * 3) { 76 | self.notifyEnough = YES; 77 | [[NSNotificationCenter defaultCenter] postNotificationName:kLLVideoPlayerCacheLoaderIdle object:nil]; 78 | } 79 | } 80 | 81 | #pragma mark - Private 82 | 83 | - (void)startLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest 84 | { 85 | LLVideoPlayerLoadingRequest *operation = [[LLVideoPlayerLoadingRequest alloc] initWithLoadingRequest:loadingRequest cacheFile:self.cacheFile]; 86 | operation.delegate = self; 87 | [self.operationQueue addObject:operation]; 88 | [operation resume]; 89 | } 90 | 91 | - (void)cancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest 92 | { 93 | for (NSInteger i = 0; i < self.operationQueue.count; i++) { 94 | LLVideoPlayerLoadingRequest *operation = self.operationQueue[i]; 95 | if (operation.loadingRequest == loadingRequest) { 96 | [operation cancel]; 97 | [self.operationQueue removeObjectAtIndex:i]; 98 | i--; 99 | } 100 | } 101 | } 102 | 103 | #pragma mark - AVAssetResourceLoaderDelegate 104 | 105 | - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest 106 | { 107 | if (NO == self.notifyStart) { 108 | self.notifyStart = YES; 109 | [[NSNotificationCenter defaultCenter] postNotificationName:kLLVideoPlayerCacheLoaderBusy object:nil]; 110 | } 111 | [self startLoadingRequest:loadingRequest]; 112 | return YES; 113 | } 114 | 115 | - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest 116 | { 117 | [self cancelLoadingRequest:loadingRequest]; 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerCacheManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerCacheManager.h 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 2018/10/26. 6 | // 7 | 8 | #import "LLVideoPlayerCacheFile.h" 9 | #import "LLVideoPlayerCachePolicy.h" 10 | #import 11 | 12 | @interface LLVideoPlayerCacheManager : NSObject 13 | 14 | @property (nonatomic, strong, readonly) NSURLSession *session; 15 | 16 | + (instancetype)defaultManager; 17 | 18 | - (LLVideoPlayerCacheFile *)createCacheFileForURL:(NSURL *)url; 19 | 20 | - (LLVideoPlayerCacheFile *)getCacheFileForURL:(NSURL *)url; 21 | 22 | - (void)releaseCacheFileForURL:(NSURL *)url; 23 | 24 | - (void)clearAllCache; 25 | - (void)removeCacheForURL:(NSURL *)url; 26 | - (void)cleanCacheWithPolicy:(LLVideoPlayerCachePolicy *)cachePolicy; 27 | 28 | #pragma mark - task 29 | - (NSURLSessionDataTask *)createDataTaskWithRequest:(NSURLRequest *)request delegate:(id)delegate; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerCacheManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerCacheManager.m 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 2018/10/26. 6 | // 7 | 8 | #import "LLVideoPlayerCacheManager.h" 9 | #import 10 | 11 | #define kMinFreeSpaceLimit (1ULL << 30) 12 | 13 | #ifndef NSFoundationVersionNumber_iOS_8_0 14 | #define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug 1140.11 15 | #else 16 | #define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0 17 | #endif 18 | 19 | static uint64_t disk_free_capacity(void) 20 | { 21 | NSError *error = nil; 22 | NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:&error]; 23 | if (error) { 24 | return -1; 25 | } 26 | int64_t space = [attrs[NSFileSystemFreeSize] longLongValue]; 27 | if (space < 0) { 28 | space = -1; 29 | } 30 | return space; 31 | } 32 | 33 | static dispatch_queue_t url_session_creation_queue() 34 | { 35 | static dispatch_queue_t queue; 36 | static dispatch_once_t onceToken; 37 | dispatch_once(&onceToken, ^{ 38 | queue = dispatch_queue_create("LLVideoPlayer.session.creation", DISPATCH_QUEUE_SERIAL); 39 | }); 40 | 41 | return queue; 42 | } 43 | 44 | static void create_url_session_task_safely(dispatch_block_t block) 45 | { 46 | if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) { 47 | dispatch_sync(url_session_creation_queue(), block); 48 | } else { 49 | block(); 50 | } 51 | } 52 | 53 | static char LLVideoPlayerCacheFileRefCountKey; 54 | 55 | @implementation LLVideoPlayerCacheFile (LLVideoPlayerCacheManager) 56 | 57 | - (NSInteger)_priv_refcount 58 | { 59 | NSNumber *num = objc_getAssociatedObject(self, &LLVideoPlayerCacheFileRefCountKey); 60 | return [num integerValue]; 61 | } 62 | 63 | - (void)set_priv_refcount:(NSInteger)_priv_refcount 64 | { 65 | NSNumber *num = [NSNumber numberWithInteger:_priv_refcount]; 66 | objc_setAssociatedObject(self, &LLVideoPlayerCacheFileRefCountKey, num, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 67 | } 68 | 69 | - (NSInteger)_inc_priv_refcount:(NSInteger)inc 70 | { 71 | NSInteger num = [self _priv_refcount] + inc; 72 | [self set_priv_refcount:num]; 73 | return num; 74 | } 75 | 76 | @end 77 | 78 | @interface LLVideoPlayerCacheManager () 79 | 80 | @property (nonatomic, strong) NSLock *lock; 81 | @property (nonatomic, strong) NSMutableDictionary *fileMap; 82 | @property (nonatomic, strong) NSURLSession *session; 83 | @property (nonatomic, strong) NSMutableDictionary *delegateMap; 84 | 85 | @end 86 | 87 | @implementation LLVideoPlayerCacheManager 88 | 89 | + (instancetype)defaultManager { 90 | static id manager; 91 | static dispatch_once_t onceToken; 92 | dispatch_once(&onceToken, ^{ 93 | manager = [[self alloc] init]; 94 | }); 95 | return manager; 96 | } 97 | 98 | - (void)dealloc 99 | { 100 | [_session invalidateAndCancel]; 101 | } 102 | 103 | - (instancetype)init { 104 | self = [super init]; 105 | if (self) { 106 | _lock = [[NSLock alloc] init]; 107 | _fileMap = [NSMutableDictionary dictionary]; 108 | NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 109 | _session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil]; 110 | _delegateMap = [NSMutableDictionary dictionary]; 111 | } 112 | return self; 113 | } 114 | 115 | - (LLVideoPlayerCacheFile *)createCacheFileForURL:(NSURL *)url { 116 | if (nil == url) { 117 | return nil; 118 | } 119 | [self.lock lock]; 120 | LLVideoPlayerCacheFile *cacheFile = [self.fileMap objectForKey:url]; 121 | if (nil == cacheFile) { 122 | cacheFile = [[LLVideoPlayerCacheFile alloc] initWithURL:url]; 123 | [self.fileMap setObject:cacheFile forKey:url]; 124 | } 125 | [cacheFile _inc_priv_refcount:1]; 126 | [self.lock unlock]; 127 | return cacheFile; 128 | } 129 | 130 | - (LLVideoPlayerCacheFile *)getCacheFileForURL:(NSURL *)url { 131 | if (nil == url) { 132 | return nil; 133 | } 134 | [self.lock lock]; 135 | LLVideoPlayerCacheFile *cacheFile = [self.fileMap objectForKey:url]; 136 | [self.lock unlock]; 137 | return cacheFile; 138 | } 139 | 140 | - (void)releaseCacheFileForURL:(NSURL *)url { 141 | if (nil == url) { 142 | return; 143 | } 144 | [self.lock lock]; 145 | LLVideoPlayerCacheFile *cacheFile = [self.fileMap objectForKey:url]; 146 | if (cacheFile) { 147 | NSInteger refCount = [cacheFile _inc_priv_refcount:-1]; 148 | if (refCount == 0) { 149 | [self.fileMap removeObjectForKey:url]; 150 | } else if (refCount < 0) { 151 | // WARNING: should never go here 152 | [self.fileMap removeObjectForKey:url]; 153 | } 154 | } 155 | [self.lock unlock]; 156 | } 157 | 158 | - (id)delegateForTask:(NSURLSessionTask *)task 159 | { 160 | NSParameterAssert(task); 161 | 162 | id delegate = nil; 163 | [self.lock lock]; 164 | delegate = self.delegateMap[@(task.taskIdentifier)]; 165 | [self.lock unlock]; 166 | 167 | return delegate; 168 | } 169 | 170 | - (void)removeDelegateForTask:(NSURLSessionTask *)task 171 | { 172 | NSParameterAssert(task); 173 | 174 | [self.lock lock]; 175 | [self.delegateMap removeObjectForKey:@(task.taskIdentifier)]; 176 | [self.lock unlock]; 177 | } 178 | 179 | - (void)setDelegate:(id)delegate forTask:(NSURLSessionTask *)task 180 | { 181 | NSParameterAssert(delegate); 182 | NSParameterAssert(task); 183 | 184 | [self.lock lock]; 185 | self.delegateMap[@(task.taskIdentifier)] = delegate; 186 | [self.lock unlock]; 187 | } 188 | 189 | - (NSURLSessionDataTask *)createDataTaskWithRequest:(NSURLRequest *)request delegate:(id)delegate 190 | { 191 | __block NSURLSessionDataTask *dataTask = nil; 192 | create_url_session_task_safely(^{ 193 | dataTask = [self.session dataTaskWithRequest:request]; 194 | }); 195 | 196 | [self setDelegate:delegate forTask:dataTask]; 197 | 198 | return dataTask; 199 | } 200 | 201 | #pragma mark - NSURLSessionDataDelegate 202 | 203 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error 204 | { 205 | id delegate = [self delegateForTask:task]; 206 | 207 | if (delegate) { 208 | [delegate URLSession:session task:task didCompleteWithError:error]; 209 | [self removeDelegateForTask:task]; 210 | } 211 | } 212 | 213 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler 214 | { 215 | id delegate = [self delegateForTask:dataTask]; 216 | [delegate URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler]; 217 | } 218 | 219 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data 220 | { 221 | id delegate = [self delegateForTask:dataTask]; 222 | [delegate URLSession:session dataTask:dataTask didReceiveData:data]; 223 | } 224 | 225 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler 226 | { 227 | id delegate = [self delegateForTask:task]; 228 | [delegate URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; 229 | } 230 | 231 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask 232 | { 233 | id delegate = [self delegateForTask:dataTask]; 234 | 235 | if (delegate) { 236 | [self removeDelegateForTask:dataTask]; 237 | [self setDelegate:delegate forTask:downloadTask]; 238 | } 239 | } 240 | 241 | #pragma mark - Clean 242 | 243 | + (void)enumerateDirectory:(NSString *)directory usingBlock:(void (^)(NSString *path, NSDictionary *attr))block 244 | { 245 | NSParameterAssert(block); 246 | NSError *error = nil; 247 | NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directory error:&error]; 248 | if (error) { 249 | return; 250 | } 251 | 252 | for (NSString *name in contents) { 253 | if ([name pathExtension].length > 0) { 254 | continue; 255 | } 256 | 257 | NSString *path = [directory stringByAppendingPathComponent:name]; 258 | error = nil; 259 | NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; 260 | if (error) { 261 | continue; 262 | } 263 | if (NO == [[attr fileType] isEqualToString:NSFileTypeRegular]) { 264 | continue; 265 | } 266 | 267 | block(path, attr); 268 | } 269 | } 270 | 271 | - (void)clearAllCache 272 | { 273 | NSString *dir = [LLVideoPlayerCacheFile cacheDirectory]; 274 | [self.lock lock]; 275 | [LLVideoPlayerCacheManager enumerateDirectory:dir usingBlock:^(NSString *path, NSDictionary *attr) { 276 | [self removeCacheAtPathNoLock:path]; 277 | }]; 278 | [self.lock unlock]; 279 | } 280 | 281 | - (void)removeCacheForURL:(NSURL *)url 282 | { 283 | [self removeCacheAtPath:[LLVideoPlayerCacheFile cacheFilePathWithURL:url]]; 284 | } 285 | 286 | - (void)removeCacheAtPathNoLock:(NSString *)path 287 | { 288 | BOOL found = NO; 289 | for (LLVideoPlayerCacheFile *cacheFile in self.fileMap.allValues) { 290 | if ([cacheFile.cacheFilePath isEqualToString:path]) { 291 | found = YES; 292 | break; 293 | } 294 | } 295 | if (NO == found) { 296 | NSFileManager *fileManager = [NSFileManager defaultManager]; 297 | 298 | [fileManager removeItemAtPath:path error:nil]; 299 | 300 | NSString *index = [NSString stringWithFormat:@"%@.%@", path, kLLVideoCacheFileExtensionIndex]; 301 | [fileManager removeItemAtPath:index error:nil]; 302 | } 303 | } 304 | 305 | - (void)removeCacheAtPath:(NSString *)path 306 | { 307 | [self.lock lock]; 308 | [self removeCacheAtPathNoLock:path]; 309 | [self.lock unlock]; 310 | } 311 | 312 | - (void)cleanCacheWithPolicy:(LLVideoPlayerCachePolicy *)cachePolicy 313 | { 314 | NSString *directory = [LLVideoPlayerCacheFile cacheDirectory]; 315 | if (nil == cachePolicy) { 316 | cachePolicy = [LLVideoPlayerCachePolicy defaultPolicy]; 317 | } 318 | 319 | __block NSUInteger totalSize = 0; 320 | NSDate *now = [NSDate date]; 321 | NSMutableArray *paths = [NSMutableArray array]; 322 | 323 | [LLVideoPlayerCacheManager enumerateDirectory:directory usingBlock:^(NSString *path, NSDictionary *attr) { 324 | NSDate *date = [attr fileCreationDate]; 325 | NSInteger hours = [now timeIntervalSinceDate:date] / 3600; 326 | if (hours >= cachePolicy.outdatedHours) { 327 | [self removeCacheAtPath:path]; 328 | return; 329 | } 330 | 331 | [paths addObject:path]; 332 | totalSize += [attr fileSize]; 333 | }]; 334 | 335 | const uint64_t minFreeSpaceLimit = kMinFreeSpaceLimit; 336 | int64_t diskSpaceFreeSize = disk_free_capacity(); 337 | 338 | for (NSString *path in paths) { 339 | if (totalSize < cachePolicy.diskCapacity && diskSpaceFreeSize >= minFreeSpaceLimit) { 340 | break; 341 | } 342 | NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; 343 | [self removeCacheAtPath:path]; 344 | totalSize -= [attr fileSize]; 345 | diskSpaceFreeSize += [attr fileSize]; 346 | } 347 | } 348 | 349 | @end 350 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerCachePolicy.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerCachePolicy.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface LLVideoPlayerCachePolicy : NSObject 12 | 13 | + (instancetype)defaultPolicy; 14 | 15 | @property (nonatomic, assign) NSUInteger diskCapacity; // in bytes 16 | @property (nonatomic, assign) NSUInteger outdatedHours; // in hours 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerCachePolicy.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerCachePolicy.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import "LLVideoPlayerCachePolicy.h" 10 | 11 | @implementation LLVideoPlayerCachePolicy 12 | 13 | + (instancetype)defaultPolicy 14 | { 15 | return [[self alloc] init]; 16 | } 17 | 18 | - (instancetype)init 19 | { 20 | self = [super init]; 21 | if (self) { 22 | // default 23 | self.diskCapacity = 500ULL << 20; 24 | self.outdatedHours = 7 * 24; 25 | } 26 | return self; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerCacheUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerCacheUtils.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | FOUNDATION_EXTERN const NSRange LLInvalidRange; 13 | 14 | NS_INLINE BOOL LLValidByteRange(NSRange range) 15 | { 16 | return range.location != NSNotFound || range.length > 0; 17 | } 18 | 19 | NS_INLINE BOOL LLValidFileRange(NSRange range) 20 | { 21 | return range.location != NSNotFound && range.length > 0 && range.length != NSIntegerMax; 22 | } 23 | 24 | NS_INLINE BOOL LLRangeCanMerge(NSRange range1, NSRange range2) 25 | { 26 | return NSMaxRange(range1) == range2.location || NSMaxRange(range2) == range1.location || NSIntersectionRange(range1, range2).length > 0; 27 | } 28 | 29 | FOUNDATION_EXTERN NSString *LLRangeToHTTPRangeHeader(NSRange range); 30 | FOUNDATION_EXTERN NSRange LLHTTPRangeHeaderToRange(NSString *rangeStr); 31 | FOUNDATION_EXTERN NSString *LLRangeToHTTPRangeResponseHeader(NSRange range, NSUInteger length); 32 | 33 | FOUNDATION_EXTERN NSString *LLLoadingRequestToString(AVAssetResourceLoadingRequest *loadingRequest); 34 | 35 | // case insensitive 36 | FOUNDATION_EXTERN NSString *LLValueForHTTPHeaderField(NSDictionary *headers, NSString *key); 37 | 38 | void ll_run_on_non_ui_thread(dispatch_block_t); 39 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerCacheUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerCacheUtils.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import "LLVideoPlayerCacheUtils.h" 10 | 11 | const NSRange LLInvalidRange = { NSNotFound, 0 }; 12 | 13 | NSString *LLRangeToHTTPRangeHeader(NSRange range) 14 | { 15 | if (LLValidByteRange(range)) { 16 | if (range.location == NSNotFound) { 17 | return [NSString stringWithFormat:@"bytes=-%tu", range.length]; 18 | } else if (range.length == NSIntegerMax) { 19 | return [NSString stringWithFormat:@"bytes=%tu-", range.location]; 20 | } else { 21 | return [NSString stringWithFormat:@"bytes=%tu-%tu", range.location, NSMaxRange(range) - 1]; 22 | } 23 | } else { 24 | return nil; 25 | } 26 | } 27 | 28 | NSRange LLHTTPRangeHeaderToRange(NSString *rangeStr) 29 | { 30 | if (NO == [rangeStr hasPrefix:@"bytes="]) { 31 | return NSMakeRange(0, 0); 32 | } 33 | 34 | NSString *sub = [rangeStr substringFromIndex:6]; 35 | if ([sub hasPrefix:@"-"]) { 36 | NSInteger length = [[sub substringFromIndex:1] integerValue]; 37 | return NSMakeRange(NSNotFound, length); 38 | } else if ([sub hasSuffix:@"-"]) { 39 | NSInteger loc = [[sub substringToIndex:sub.length-1] integerValue]; 40 | return NSMakeRange(loc, NSIntegerMax); 41 | } else { 42 | NSArray *components = [sub componentsSeparatedByString:@"-"]; 43 | if (components.count != 2) { 44 | return NSMakeRange(0, 0); 45 | } 46 | NSInteger start = [components[0] integerValue]; 47 | NSInteger end = [components[1] integerValue]; 48 | 49 | return NSMakeRange(start, end - start + 1); 50 | } 51 | } 52 | 53 | NSString *LLRangeToHTTPRangeResponseHeader(NSRange range, NSUInteger length) 54 | { 55 | if (LLValidByteRange(range)) { 56 | NSUInteger start = range.location; 57 | NSUInteger end = NSMaxRange(range) - 1; 58 | if (range.location == NSNotFound) { 59 | start = range.location; 60 | } else if (range.length == NSIntegerMax) { 61 | start = length - range.length; 62 | end = start + range.length - 1; 63 | } 64 | 65 | return [NSString stringWithFormat:@"bytes %tu-%tu/%tu", start, end, length]; 66 | } else { 67 | return nil; 68 | } 69 | } 70 | 71 | NSString *LLLoadingRequestToString(AVAssetResourceLoadingRequest *loadingRequest) 72 | { 73 | if ([loadingRequest.dataRequest respondsToSelector:@selector(requestsAllDataToEndOfResource)]) { 74 | return [NSString stringWithFormat:@"<%p: %@, ToEnd: %@>", 75 | loadingRequest, 76 | NSStringFromRange(NSMakeRange(loadingRequest.dataRequest.requestedOffset, 77 | loadingRequest.dataRequest.requestedLength)), 78 | [loadingRequest.dataRequest requestsAllDataToEndOfResource] ? @"YES" : @"NO"]; 79 | } else { 80 | return [NSString stringWithFormat:@"<%p: %@>", 81 | loadingRequest, 82 | NSStringFromRange(NSMakeRange(loadingRequest.dataRequest.requestedOffset, 83 | loadingRequest.dataRequest.requestedLength))]; 84 | } 85 | } 86 | 87 | NSString *LLValueForHTTPHeaderField(NSDictionary *headers, NSString *key) 88 | { 89 | NSString *value; 90 | 91 | if (nil == headers || nil == key) return nil; 92 | value = [headers objectForKey:key]; 93 | if (value) return value; 94 | value = [headers objectForKey:[key lowercaseString]]; 95 | if (value) return value; 96 | value = [headers objectForKey:[key capitalizedString]]; 97 | if (value) return value; 98 | 99 | return nil; 100 | } 101 | 102 | void ll_run_on_non_ui_thread(dispatch_block_t block) 103 | { 104 | if ([NSThread isMainThread]) { 105 | dispatch_async(dispatch_get_global_queue(0, 0), block); 106 | } else { 107 | block(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerDownloadRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerDownloadRequest.h 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 2018/10/26. 6 | // 7 | 8 | #import "LLVideoPlayerCacheFile.h" 9 | #import 10 | 11 | @interface LLVideoPlayerDownloadRequest : NSObject 12 | 13 | @property (nonatomic, strong, readonly) NSURLRequest *request; 14 | @property (nonatomic, copy) void (^completedBlock)(NSError *error); 15 | 16 | - (instancetype)initWithRequest:(NSURLRequest *)request cacheFile:(LLVideoPlayerCacheFile *)cacheFile; 17 | 18 | - (void)resume; 19 | - (void)cancel; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerDownloadRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerDownloadRequest.m 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 2018/10/26. 6 | // 7 | 8 | #import "LLVideoPlayerDownloadRequest.h" 9 | #import "LLVideoPlayerRemoteOperation.h" 10 | #import "LLVideoPlayerCacheUtils.h" 11 | 12 | @interface LLVideoPlayerDownloadRequest () 13 | 14 | @property (nonatomic, strong) NSURLRequest *request; 15 | @property (nonatomic, strong) LLVideoPlayerCacheFile *cacheFile; 16 | @property (nonatomic, strong) NSOperationQueue *operationQueue; 17 | @property (nonatomic, strong) NSMutableArray *taskQueue; 18 | @property (nonatomic, strong) NSRecursiveLock *lock; 19 | @property (nonatomic, strong) NSError *error; 20 | 21 | @end 22 | 23 | @implementation LLVideoPlayerDownloadRequest 24 | 25 | - (instancetype)initWithRequest:(NSURLRequest *)request cacheFile:(LLVideoPlayerCacheFile *)cacheFile 26 | { 27 | self = [super init]; 28 | if (self) { 29 | _request = request; 30 | _cacheFile = cacheFile; 31 | _operationQueue = [[NSOperationQueue alloc] init]; 32 | _lock = [[NSRecursiveLock alloc] init]; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)resume 38 | { 39 | [self.lock lock]; 40 | [self startOperation]; 41 | [self.lock unlock]; 42 | } 43 | 44 | - (void)cancel 45 | { 46 | [self.lock lock]; 47 | NSArray *copyQueue = [NSArray arrayWithArray:self.taskQueue]; 48 | for (NSOperation *operation in copyQueue) { 49 | [operation cancel]; 50 | } 51 | [self.lock unlock]; 52 | } 53 | 54 | - (void)startOperation 55 | { 56 | NSString *rangeStr = [self.request valueForHTTPHeaderField:@"Range"]; 57 | NSRange requestRange = LLHTTPRangeHeaderToRange(rangeStr); 58 | if (requestRange.length == 0) { 59 | requestRange = NSMakeRange(0, NSIntegerMax); 60 | } 61 | 62 | self.taskQueue = [NSMutableArray arrayWithCapacity:4]; 63 | 64 | [self.cacheFile enumerateRangesWithRequestRange:requestRange usingBlock:^(NSRange range, BOOL cached) { 65 | if (NO == cached) { 66 | [self addTaskWithRange:range]; 67 | } 68 | }]; 69 | 70 | // resume task 71 | if (self.taskQueue.count > 0) { 72 | for (NSOperation *operation in self.taskQueue) { 73 | [self.operationQueue addOperation:operation]; 74 | } 75 | } else { 76 | dispatch_async(dispatch_get_main_queue(), ^{ 77 | if (self.completedBlock) { 78 | self.completedBlock(nil); 79 | } 80 | }); 81 | } 82 | } 83 | 84 | - (void)addTaskWithRange:(NSRange)range 85 | { 86 | NSMutableURLRequest *request = [self.request mutableCopy]; 87 | NSString *rangeStr = LLRangeToHTTPRangeHeader(range); 88 | if (rangeStr) { 89 | [request setValue:rangeStr forHTTPHeaderField:@"Range"]; 90 | } 91 | LLVideoPlayerRemoteOperation *task = [[LLVideoPlayerRemoteOperation alloc] initWithRequest:request cacheFile:self.cacheFile]; 92 | [task setDelegate:self]; 93 | 94 | [self.taskQueue addObject:task]; 95 | } 96 | 97 | #pragma mark - LLVideoPlayerOperationDelegate 98 | 99 | - (void)operation:(NSOperation *)operation didCompleteWithError:(NSError *)error 100 | { 101 | [self.lock lock]; 102 | [self.taskQueue removeObject:operation]; 103 | if (error && nil == self.error) { 104 | self.error = error; 105 | } 106 | if (self.taskQueue.count == 0) { 107 | dispatch_async(dispatch_get_main_queue(), ^{ 108 | if (self.completedBlock) { 109 | self.completedBlock(self.error); 110 | } 111 | }); 112 | } 113 | [self.lock unlock]; 114 | } 115 | 116 | - (void)operation:(NSOperation *)operation didReceiveData:(NSData *)data 117 | { 118 | 119 | } 120 | 121 | - (void)operation:(NSOperation *)operation didReceiveResponse:(NSURLResponse *)response 122 | { 123 | 124 | } 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerDownloader.h 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 2018/10/26. 6 | // 7 | 8 | #import 9 | 10 | @interface LLVideoPlayerDownloader : NSObject 11 | 12 | @property (nonatomic, assign) NSInteger maxConcurrentCount; 13 | 14 | + (instancetype)defaultDownloader; 15 | 16 | - (void)preloadWithURL:(NSURL *)url; 17 | - (void)preloadWithURL:(NSURL *)url bytes:(NSUInteger)bytes; 18 | - (void)cancelPreloadWithURL:(NSURL *)url; 19 | - (void)cancelAllPreloads; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerDownloader.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerDownloader.m 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 2018/10/26. 6 | // 7 | 8 | #import "LLVideoPlayerDownloader.h" 9 | #import "LLVideoPlayerCacheManager.h" 10 | #import "LLVideoPlayerDownloadRequest.h" 11 | #import "LLVideoPlayerCacheUtils.h" 12 | 13 | #define kDefaultBytesLimit (1 << 20) 14 | #define kDefaultConcurrentCount 2 15 | 16 | typedef NS_ENUM(NSInteger, LLVideoPlayerDownloaderState) { 17 | LLVideoPlayerDownloaderStateIdle, 18 | LLVideoPlayerDownloaderStatePaused 19 | }; 20 | 21 | static char kQueueSpecificKey[1]; 22 | 23 | @interface LLVideoPlayerDownloader () { 24 | dispatch_queue_t _queue; 25 | } 26 | 27 | @property (nonatomic, weak) LLVideoPlayerCacheManager *manager; 28 | @property (nonatomic, strong) NSMutableArray *runningTasks; 29 | @property (nonatomic, strong) NSMutableArray *pendingRequests; 30 | @property (nonatomic, assign) LLVideoPlayerDownloaderState state; 31 | 32 | @end 33 | 34 | @implementation LLVideoPlayerDownloader 35 | 36 | + (instancetype)defaultDownloader 37 | { 38 | static id instance; 39 | static dispatch_once_t onceToken; 40 | dispatch_once(&onceToken, ^{ 41 | instance = [[self alloc] init]; 42 | }); 43 | return instance; 44 | } 45 | 46 | - (void)dealloc 47 | { 48 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 49 | } 50 | 51 | - (instancetype)init 52 | { 53 | self = [super init]; 54 | if (self) { 55 | _queue = dispatch_queue_create("LLVideoPlayerDownloader", DISPATCH_QUEUE_SERIAL); 56 | dispatch_queue_set_specific(_queue, kQueueSpecificKey, (__bridge void *)_queue, NULL); 57 | _manager = [LLVideoPlayerCacheManager defaultManager]; 58 | _maxConcurrentCount = kDefaultConcurrentCount; 59 | _runningTasks = [NSMutableArray array]; 60 | _pendingRequests = [NSMutableArray array]; 61 | [[NSNotificationCenter defaultCenter] addObserver:self 62 | selector:@selector(cacheLoaderBusy) 63 | name:@"LLVideoPlayerCacheLoaderBusy" 64 | object:nil]; 65 | [[NSNotificationCenter defaultCenter] addObserver:self 66 | selector:@selector(cacheLoaderIdle) 67 | name:@"LLVideoPlayerCacheLoaderIdle" 68 | object:nil]; 69 | } 70 | return self; 71 | } 72 | 73 | - (void)scheduleWithBlock:(dispatch_block_t)block { 74 | if (dispatch_get_specific(kQueueSpecificKey) == dispatch_queue_get_specific(_queue, kQueueSpecificKey)) { 75 | @autoreleasepool { 76 | if (block) block(); 77 | } 78 | } else { 79 | dispatch_async(_queue, ^{ 80 | @autoreleasepool { 81 | if (block) block(); 82 | } 83 | }); 84 | } 85 | } 86 | 87 | - (void)preloadWithURL:(NSURL *)url 88 | { 89 | [self preloadWithURL:url bytes:kDefaultBytesLimit]; 90 | } 91 | 92 | - (void)preloadWithURL:(NSURL *)url bytes:(NSUInteger)bytes 93 | { 94 | if (nil == url || [url isFileURL]) { 95 | return; 96 | } 97 | if (bytes == 0 || bytes > NSIntegerMax) { 98 | bytes = NSIntegerMax; 99 | } 100 | 101 | NSRange range = NSMakeRange(0, bytes); 102 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 103 | NSString *rangeStr = LLRangeToHTTPRangeHeader(range); 104 | if (rangeStr) { 105 | [request setValue:rangeStr forHTTPHeaderField:@"Range"]; 106 | } 107 | 108 | [self scheduleWithBlock:^{ 109 | [self.pendingRequests addObject:request]; 110 | [self processNextNoLock]; 111 | }]; 112 | } 113 | 114 | - (void)cancelPreloadWithURL:(NSURL *)url 115 | { 116 | if (nil == url || [url isFileURL]) { 117 | return; 118 | } 119 | 120 | [self scheduleWithBlock:^{ 121 | for (int i = 0; i < self.pendingRequests.count; i++) { 122 | NSURLRequest *request = self.pendingRequests[i]; 123 | if ([request.URL isEqual:url]) { 124 | [self.pendingRequests removeObjectAtIndex:i]; 125 | i--; 126 | } 127 | } 128 | for (LLVideoPlayerDownloadRequest *task in self.runningTasks) { 129 | if ([task.request.URL isEqual:url]) { 130 | [task cancel]; 131 | } 132 | } 133 | }]; 134 | } 135 | 136 | - (void)cancelAllPreloads 137 | { 138 | [self scheduleWithBlock:^{ 139 | [self.pendingRequests removeAllObjects]; 140 | for (LLVideoPlayerDownloadRequest *task in self.runningTasks) { 141 | [task cancel]; 142 | } 143 | }]; 144 | } 145 | 146 | #pragma mark - Private 147 | 148 | - (BOOL)preloadWithRequest:(NSURLRequest *)request 149 | { 150 | NSURL *url = request.URL; 151 | 152 | // exist? 153 | for (LLVideoPlayerDownloadRequest *task in self.runningTasks) { 154 | if ([task.request.URL isEqual:url]) { 155 | NSString *range1 = [task.request valueForHTTPHeaderField:@"Range"]; 156 | NSString *range2 = [request valueForHTTPHeaderField:@"Range"]; 157 | if ([range1 isEqualToString:range2]) { 158 | return NO; 159 | } 160 | } 161 | } 162 | 163 | // complete? 164 | LLVideoPlayerCacheFile *cacheFile = [self.manager getCacheFileForURL:url]; 165 | if (cacheFile && [cacheFile isComplete]) { 166 | return NO; 167 | } 168 | 169 | cacheFile = [self.manager createCacheFileForURL:url]; 170 | LLVideoPlayerDownloadRequest *task = [[LLVideoPlayerDownloadRequest alloc] initWithRequest:request cacheFile:cacheFile]; 171 | __weak typeof(self) wself = self; 172 | __weak typeof(task) wtask = task; 173 | [task setCompletedBlock:^(NSError *error) { 174 | __strong typeof(wself) self = wself; 175 | __strong typeof(wtask) task = wtask; 176 | [self scheduleWithBlock:^{ 177 | [self.runningTasks removeObject:task]; 178 | [self.manager releaseCacheFileForURL:url]; 179 | [self processNextNoLock]; 180 | }]; 181 | }]; 182 | [self.runningTasks addObject:task]; 183 | [task resume]; 184 | 185 | return YES; 186 | } 187 | 188 | - (void)processNextNoLock 189 | { 190 | if (self.state == LLVideoPlayerDownloaderStateIdle) { 191 | NSInteger count = self.runningTasks.count; 192 | while (count < self.maxConcurrentCount && self.pendingRequests.count > 0) { 193 | NSURLRequest *request = [self.pendingRequests firstObject]; 194 | [self.pendingRequests removeObject:request]; 195 | if ([self preloadWithRequest:request]) { 196 | count++; 197 | } 198 | } 199 | } 200 | } 201 | 202 | - (void)setState:(LLVideoPlayerDownloaderState)state 203 | { 204 | if (_state == state) { 205 | return; 206 | } 207 | 208 | [self scheduleWithBlock:^{ 209 | _state = state; 210 | if (state == LLVideoPlayerDownloaderStateIdle) { 211 | [self processNextNoLock]; 212 | } else if (state == LLVideoPlayerDownloaderStatePaused) { 213 | for (LLVideoPlayerDownloadRequest *task in self.runningTasks) { 214 | [self.pendingRequests addObject:task.request]; 215 | [task cancel]; 216 | } 217 | } 218 | }]; 219 | } 220 | 221 | #pragma mark - Cache Loader Notification 222 | 223 | - (void)cacheLoaderBusy 224 | { 225 | self.state = LLVideoPlayerDownloaderStatePaused; 226 | } 227 | 228 | - (void)cacheLoaderIdle 229 | { 230 | self.state = LLVideoPlayerDownloaderStateIdle; 231 | } 232 | 233 | @end 234 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerLoadingRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerLoadingRequest.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/8/21. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | #import "LLVideoPlayerCacheFile.h" 12 | 13 | @class LLVideoPlayerLoadingRequest; 14 | @protocol LLVideoPlayerLoadingRequestDelegate 15 | 16 | - (void)request:(LLVideoPlayerLoadingRequest *)operation didComepleteWithError:(NSError *)error; 17 | 18 | @end 19 | 20 | @interface LLVideoPlayerLoadingRequest : NSObject 21 | 22 | - (instancetype)initWithLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest 23 | cacheFile:(LLVideoPlayerCacheFile *)cacheFile; 24 | 25 | @property (nonatomic, strong, readonly) AVAssetResourceLoadingRequest *loadingRequest; 26 | @property (nonatomic, weak) id delegate; 27 | 28 | - (void)resume; 29 | - (void)cancel; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerLoadingRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerLoadingRequest.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/8/21. 6 | // 7 | // 8 | 9 | #import "LLVideoPlayerLoadingRequest.h" 10 | #import "LLVideoPlayerLocalOperation.h" 11 | #import "LLVideoPlayerRemoteOperation.h" 12 | #import "NSURL+LLVideoPlayer.h" 13 | #import "LLVideoPlayerCacheUtils.h" 14 | #import "AVAssetResourceLoadingRequest+LLVideoPlayer.h" 15 | 16 | @interface LLVideoPlayerLoadingRequest () 17 | 18 | @property (nonatomic, strong) AVAssetResourceLoadingRequest *loadingRequest; 19 | @property (nonatomic, strong) LLVideoPlayerCacheFile *cacheFile; 20 | @property (nonatomic, strong) NSMutableArray *taskQueue; 21 | @property (nonatomic, strong) NSOperationQueue *operationQueue; 22 | @property (nonatomic, strong) NSRecursiveLock *lock; 23 | @property (nonatomic, strong) NSURLResponse *response; 24 | @property (nonatomic, assign) NSInteger cancels; 25 | 26 | @end 27 | 28 | @implementation LLVideoPlayerLoadingRequest 29 | 30 | - (instancetype)initWithLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest 31 | cacheFile:(LLVideoPlayerCacheFile *)cacheFile 32 | { 33 | self = [super init]; 34 | if (self) { 35 | _loadingRequest = loadingRequest; 36 | _cacheFile = cacheFile; 37 | _lock = [[NSRecursiveLock alloc] init]; 38 | _operationQueue = [NSOperationQueue new]; 39 | _operationQueue.maxConcurrentOperationCount = 1; // serial queue 40 | } 41 | return self; 42 | } 43 | 44 | - (void)resume 45 | { 46 | [self.lock lock]; 47 | [self startOperation]; 48 | [self.lock unlock]; 49 | } 50 | 51 | - (void)cancel 52 | { 53 | [self.lock lock]; 54 | NSArray *copyQueue = [NSArray arrayWithArray:self.taskQueue]; 55 | for (NSOperation *operation in copyQueue) { 56 | [operation cancel]; 57 | } 58 | [self.lock unlock]; 59 | } 60 | 61 | #pragma mark - LLVideoPlayerOperationDelegate 62 | 63 | - (void)operation:(NSOperation *)operation didCompleteWithError:(NSError *)error 64 | { 65 | [self.lock lock]; 66 | [self.taskQueue removeObject:operation]; 67 | // must cancel all request if error occurs, otherwise `respondWithData:` would corrupt. 68 | if (nil == error) { 69 | if (self.taskQueue.count == 0 && self.cancels == 0) { 70 | dispatch_async(dispatch_get_main_queue(), ^{ 71 | if ([self.delegate respondsToSelector:@selector(request:didComepleteWithError:)]) { 72 | [self.delegate request:self didComepleteWithError:nil]; 73 | } 74 | }); 75 | } 76 | } else { 77 | if ([operation isCancelled] || error.code == NSURLErrorCancelled) { 78 | self.cancels++; 79 | } else { 80 | [self cancel]; 81 | dispatch_async(dispatch_get_main_queue(), ^{ 82 | if ([self.delegate respondsToSelector:@selector(request:didComepleteWithError:)]) { 83 | [self.delegate request:self didComepleteWithError:error]; 84 | } 85 | }); 86 | } 87 | } 88 | [self.lock unlock]; 89 | } 90 | 91 | - (void)operation:(NSOperation *)operation didReceiveResponse:(NSURLResponse *)response 92 | { 93 | [self tryResponse:response]; 94 | } 95 | 96 | - (void)operation:(NSOperation *)operation didReceiveData:(NSData *)data 97 | { 98 | [self.loadingRequest.dataRequest respondWithData:data]; 99 | } 100 | 101 | #pragma mark - Private 102 | 103 | - (void)tryResponse:(NSURLResponse *)response 104 | { 105 | if (nil == _response && response) { 106 | _response = response; 107 | [self.loadingRequest ll_fillContentInformation:response]; 108 | } 109 | } 110 | 111 | - (void)startOperation 112 | { 113 | // range 114 | NSRange range; 115 | if ([self.loadingRequest.dataRequest respondsToSelector:@selector(requestsAllDataToEndOfResource)] && 116 | self.loadingRequest.dataRequest.requestsAllDataToEndOfResource) { 117 | range = NSMakeRange(self.loadingRequest.dataRequest.requestedOffset, NSIntegerMax); 118 | } else { 119 | range = NSMakeRange(self.loadingRequest.dataRequest.requestedOffset, 120 | self.loadingRequest.dataRequest.requestedLength); 121 | } 122 | 123 | self.taskQueue = [NSMutableArray arrayWithCapacity:4]; 124 | 125 | [self mapOperationToTasksWithRange:range]; 126 | NSURLResponse *response = [self.cacheFile constructURLResponseForURL:self.loadingRequest.request.URL andRange:range]; 127 | [self tryResponse:response]; 128 | 129 | // resume task 130 | for (NSOperation *operation in self.taskQueue) { 131 | [self.operationQueue addOperation:operation]; 132 | } 133 | } 134 | 135 | - (void)addTaskWithRange:(NSRange)range fromCache:(BOOL)fromCache 136 | { 137 | NSOperation *task; 138 | 139 | NSMutableURLRequest *request = [self.loadingRequest.request mutableCopy]; 140 | request.URL = [self.loadingRequest.request.URL ll_originalSchemeURL]; 141 | request.cachePolicy = NSURLRequestReloadIgnoringCacheData; // very important 142 | NSString *rangeStr = LLRangeToHTTPRangeHeader(range); 143 | if (rangeStr) { 144 | [request setValue:rangeStr forHTTPHeaderField:@"Range"]; 145 | } 146 | 147 | if (fromCache) { 148 | task = [[LLVideoPlayerLocalOperation alloc] initWithRequest:request cacheFile:self.cacheFile]; 149 | [(LLVideoPlayerLocalOperation *)task setDelegate:self]; 150 | } else { 151 | task = [[LLVideoPlayerRemoteOperation alloc] initWithRequest:request cacheFile:self.cacheFile]; 152 | [(LLVideoPlayerRemoteOperation *)task setDelegate:self]; 153 | } 154 | 155 | [self.taskQueue addObject:task]; 156 | } 157 | 158 | - (void)mapOperationToTasksWithRange:(NSRange)requestRange 159 | { 160 | [self.cacheFile enumerateRangesWithRequestRange:requestRange usingBlock:^(NSRange range, BOOL cached) { 161 | [self addTaskWithRange:range fromCache:cached]; 162 | }]; 163 | } 164 | 165 | @end 166 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerLocalOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerLocalOperation.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/8/21. 6 | // 7 | // 8 | 9 | #import "LLVideoPlayerCacheFile.h" 10 | #import "LLVideoPlayerOperationDelegate.h" 11 | #import 12 | 13 | @interface LLVideoPlayerLocalOperation : NSOperation 14 | 15 | - (instancetype)initWithRequest:(NSURLRequest *)request cacheFile:(LLVideoPlayerCacheFile *)cacheFile; 16 | 17 | @property (nonatomic, weak) id delegate; 18 | @property (nonatomic, strong) NSURLRequest *request; 19 | @property (nonatomic, strong) LLVideoPlayerCacheFile *cacheFile; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerLocalOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerLocalOperation.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/8/21. 6 | // 7 | // 8 | 9 | #import "LLVideoPlayerLocalOperation.h" 10 | #import "LLVideoPlayerCacheUtils.h" 11 | 12 | @interface LLVideoPlayerLocalOperation () 13 | 14 | @property (assign, nonatomic, getter = isExecuting) BOOL executing; 15 | @property (assign, nonatomic, getter = isFinished) BOOL finished; 16 | 17 | @end 18 | 19 | @implementation LLVideoPlayerLocalOperation 20 | @synthesize executing = _executing; 21 | @synthesize finished = _finished; 22 | 23 | - (NSString *)description 24 | { 25 | return [NSString stringWithFormat:@"<%@: %p> %@", NSStringFromClass(self.class), self, [self.request valueForHTTPHeaderField:@"Range"]]; 26 | } 27 | 28 | - (instancetype)initWithRequest:(NSURLRequest *)request cacheFile:(LLVideoPlayerCacheFile *)cacheFile 29 | { 30 | self = [super init]; 31 | if (self) { 32 | _request = request; 33 | _cacheFile = cacheFile; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)main 39 | { 40 | @autoreleasepool { 41 | NSError *error = nil; 42 | NSData *data = nil; 43 | 44 | @synchronized (self) { 45 | if ([self isCancelled]) { 46 | return; 47 | } 48 | 49 | self.executing = YES; 50 | 51 | NSString *rangeStr = [self.request valueForHTTPHeaderField:@"Range"]; 52 | NSRange range = LLHTTPRangeHeaderToRange(rangeStr); 53 | data = [self.cacheFile dataWithRange:range]; 54 | [self finish]; 55 | } 56 | 57 | if (data) { 58 | if ([self.delegate respondsToSelector:@selector(operation:didReceiveData:)]) { 59 | [self.delegate operation:self didReceiveData:data]; 60 | } 61 | } else { 62 | // data == nil 63 | error = [NSError errorWithDomain:NSURLErrorDomain code:404 userInfo:nil]; 64 | } 65 | 66 | if ([self.delegate respondsToSelector:@selector(operation:didCompleteWithError:)]) { 67 | [self.delegate operation:self didCompleteWithError:error]; 68 | } 69 | } 70 | } 71 | 72 | - (void)cancel 73 | { 74 | @synchronized (self) { 75 | if ([self isFinished] || [self isCancelled]) { 76 | return; 77 | } 78 | [super cancel]; 79 | if ([self isExecuting]) { 80 | [self finish]; 81 | } 82 | } 83 | 84 | if ([self.delegate respondsToSelector:@selector(operation:didCompleteWithError:)]) { 85 | [self.delegate operation:self didCompleteWithError: 86 | [NSError errorWithDomain:@"LLVideoPlayerCacheTask" code:NSURLErrorCancelled userInfo:nil]]; 87 | } 88 | } 89 | 90 | - (void)finish 91 | { 92 | self.executing = NO; 93 | self.finished = YES; 94 | } 95 | 96 | - (void)setFinished:(BOOL)finished 97 | { 98 | if (_finished != finished) { 99 | [self willChangeValueForKey:@"isFinished"]; 100 | _finished = finished; 101 | [self didChangeValueForKey:@"isFinished"]; 102 | } 103 | } 104 | 105 | - (void)setExecuting:(BOOL)executing 106 | { 107 | if (_executing != executing) { 108 | [self willChangeValueForKey:@"isExecuting"]; 109 | _executing = executing; 110 | [self didChangeValueForKey:@"isExecuting"]; 111 | } 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerOperationDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerOperationDelegate.h 3 | // Pods 4 | // 5 | // Created by mario on 2018/10/26. 6 | // 7 | 8 | #ifndef LLVideoPlayerOperationDelegate_h 9 | #define LLVideoPlayerOperationDelegate_h 10 | 11 | #import 12 | 13 | @protocol LLVideoPlayerOperationDelegate 14 | 15 | @optional 16 | 17 | - (void)operation:(NSOperation *)operation didCompleteWithError:(NSError *)error; 18 | - (void)operation:(NSOperation *)operation didReceiveData:(NSData *)data; 19 | - (void)operation:(NSOperation *)operation didReceiveResponse:(NSURLResponse *)response; 20 | 21 | @end 22 | 23 | #endif /* LLVideoPlayerOperationDelegate_h */ 24 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerRemoteOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerRemoteOperation.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/8/21. 6 | // 7 | // 8 | 9 | #import "LLVideoPlayerCacheFile.h" 10 | #import "LLVideoPlayerOperationDelegate.h" 11 | #import 12 | 13 | typedef NS_OPTIONS(NSUInteger, LLVideoPlayerRemoteOptions) { 14 | LLVideoPlayerAllowInvalidSSLCertificates = 1 << 0, 15 | }; 16 | 17 | @interface LLVideoPlayerRemoteOperation : NSOperation 18 | 19 | - (instancetype)initWithRequest:(NSURLRequest *)request cacheFile:(LLVideoPlayerCacheFile *)cacheFile; 20 | 21 | @property (nonatomic, weak) id delegate; 22 | @property (nonatomic, strong) NSURLRequest *request; 23 | @property (nonatomic, strong) LLVideoPlayerCacheFile *cacheFile; 24 | @property (nonatomic, strong) NSURLCredential *credential; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/LLVideoPlayerRemoteOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerRemoteOperation.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/8/21. 6 | // 7 | // 8 | 9 | #import "LLVideoPlayerRemoteOperation.h" 10 | #import "NSURL+LLVideoPlayer.h" 11 | #import "LLVideoPlayerCacheUtils.h" 12 | #import "NSURLResponse+LLVideoPlayer.h" 13 | #import "LLVideoPlayerCacheManager.h" 14 | 15 | @interface LLVideoPlayerRemoteOperation () 16 | 17 | @property (assign, nonatomic, getter=isExecuting) BOOL executing; 18 | @property (assign, nonatomic, getter=isFinished) BOOL finished; 19 | 20 | @property (nonatomic, weak) NSURLSession *unownedSession; 21 | @property (nonatomic, strong) NSURLSession *ownedSession; 22 | @property (nonatomic, strong) NSURLSessionDataTask *dataTask; 23 | @property (nonatomic, assign) LLVideoPlayerRemoteOptions options; 24 | @property (nonatomic, assign) NSUInteger offset; 25 | 26 | @end 27 | 28 | @implementation LLVideoPlayerRemoteOperation 29 | @synthesize executing = _executing; 30 | @synthesize finished = _finished; 31 | 32 | - (NSString *)description 33 | { 34 | return [NSString stringWithFormat:@"<%@: %p> %@", NSStringFromClass(self.class), self, [self.request valueForHTTPHeaderField:@"Range"]]; 35 | } 36 | 37 | - (instancetype)initWithRequest:(NSURLRequest *)request cacheFile:(LLVideoPlayerCacheFile *)cacheFile 38 | { 39 | self = [super init]; 40 | if (self) { 41 | _request = request; 42 | _cacheFile = cacheFile; 43 | _unownedSession = [LLVideoPlayerCacheManager defaultManager].session; 44 | _options = 0; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)reset 50 | { 51 | self.delegate = nil; 52 | self.dataTask = nil; 53 | if (self.ownedSession) { 54 | [self.ownedSession invalidateAndCancel]; 55 | self.ownedSession = nil; 56 | } 57 | } 58 | 59 | - (void)finish 60 | { 61 | self.finished = YES; 62 | self.executing = NO; 63 | [self reset]; 64 | } 65 | 66 | - (void)start 67 | { 68 | @synchronized (self) { 69 | if ([self isCancelled]) { 70 | return; 71 | } 72 | 73 | self.executing = YES; 74 | 75 | NSURLSession *session = self.unownedSession; 76 | if (nil == self.unownedSession) { 77 | NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 78 | self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig 79 | delegate:self 80 | delegateQueue:nil]; 81 | session = self.ownedSession; 82 | } 83 | 84 | NSRange range = LLHTTPRangeHeaderToRange([self.request valueForHTTPHeaderField:@"Range"]); 85 | self.offset = range.location; 86 | 87 | if (session == [LLVideoPlayerCacheManager defaultManager].session) { 88 | self.dataTask = [[LLVideoPlayerCacheManager defaultManager] createDataTaskWithRequest:self.request delegate:self]; 89 | } else { 90 | self.dataTask = [session dataTaskWithRequest:self.request]; 91 | } 92 | } 93 | 94 | [self.dataTask resume]; 95 | } 96 | 97 | - (void)cancel 98 | { 99 | @synchronized (self) { 100 | if ([self isFinished] || [self isCancelled]) { 101 | return; 102 | } 103 | 104 | [super cancel]; 105 | 106 | [self.cacheFile synchronize]; 107 | 108 | if ([self.delegate respondsToSelector:@selector(operation:didCompleteWithError:)]) { 109 | [self.delegate operation:self didCompleteWithError: 110 | [NSError errorWithDomain:@"LLVideoPlayerCacheTask" code:NSURLErrorCancelled userInfo:nil]]; 111 | } 112 | 113 | if (self.dataTask) { 114 | [self.dataTask cancel]; 115 | 116 | if (self.isExecuting) { 117 | self.executing = NO; 118 | } 119 | if (NO == self.isFinished) { 120 | self.finished = YES; 121 | } 122 | } 123 | 124 | [self reset]; 125 | } 126 | } 127 | 128 | #pragma mark - NSURLSessionDataDelegate 129 | 130 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error 131 | { 132 | [self.cacheFile synchronize]; 133 | 134 | if ([self.delegate respondsToSelector:@selector(operation:didCompleteWithError:)]) { 135 | [self.delegate operation:self didCompleteWithError:error]; 136 | } 137 | 138 | [self finish]; 139 | } 140 | 141 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler 142 | { 143 | if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) { 144 | [self.cacheFile receiveResponse:response]; 145 | if ([self.delegate respondsToSelector:@selector(operation:didReceiveResponse:)]) { 146 | [self.delegate operation:self didReceiveResponse:response]; 147 | } 148 | } else { 149 | NSUInteger code = [((NSHTTPURLResponse *)response) statusCode]; 150 | NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:code userInfo:nil]; 151 | 152 | [self.dataTask cancel]; 153 | 154 | if ([self.delegate respondsToSelector:@selector(operation:didCompleteWithError:)]) { 155 | [self.delegate operation:self didCompleteWithError:error]; 156 | } 157 | 158 | [self finish]; 159 | } 160 | 161 | if (completionHandler) { 162 | completionHandler(NSURLSessionResponseAllow); 163 | } 164 | } 165 | 166 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data 167 | { 168 | [self.cacheFile writeData:data atOffset:self.offset]; 169 | self.offset += data.length; 170 | if ([self.delegate respondsToSelector:@selector(operation:didReceiveData:)]) { 171 | [self.delegate operation:self didReceiveData:data]; 172 | } 173 | } 174 | 175 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler 176 | { 177 | NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; 178 | __block NSURLCredential *credential = nil; 179 | 180 | if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { 181 | if (!(self.options & LLVideoPlayerAllowInvalidSSLCertificates)) { 182 | disposition = NSURLSessionAuthChallengePerformDefaultHandling; 183 | } else { 184 | credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; 185 | disposition = NSURLSessionAuthChallengeUseCredential; 186 | } 187 | } else { 188 | if ([challenge previousFailureCount] == 0) { 189 | if (self.credential) { 190 | credential = self.credential; 191 | disposition = NSURLSessionAuthChallengeUseCredential; 192 | } else { 193 | disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; 194 | } 195 | } else { 196 | disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; 197 | } 198 | } 199 | 200 | if (completionHandler) { 201 | completionHandler(disposition, credential); 202 | } 203 | } 204 | 205 | - (void)setExecuting:(BOOL)executing { 206 | if (executing != _executing) { 207 | [self willChangeValueForKey:@"isExecuting"]; 208 | _executing = executing; 209 | [self didChangeValueForKey:@"isExecuting"]; 210 | } 211 | } 212 | 213 | - (void)setFinished:(BOOL)finished { 214 | if (finished != _finished) { 215 | [self willChangeValueForKey:@"isFinished"]; 216 | _finished = finished; 217 | [self didChangeValueForKey:@"isFinished"]; 218 | } 219 | } 220 | 221 | - (BOOL)isConcurrent { 222 | return YES; 223 | } 224 | 225 | - (BOOL)isAsynchronous { 226 | return YES; 227 | } 228 | 229 | @end 230 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/NSString+LLVideoPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+LLVideoPlayer.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface NSString (LLVideoPlayer) 12 | 13 | - (NSString *)ll_md5; 14 | 15 | - (NSString *)ll_decodeLengthFromContentRange; 16 | 17 | - (NSRange)ll_decodeRangeFromContentRange; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/NSString+LLVideoPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+LLVideoPlayer.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/23. 6 | // 7 | // 8 | 9 | #import "NSString+LLVideoPlayer.h" 10 | #import 11 | 12 | @implementation NSString (LLVideoPlayer) 13 | 14 | - (NSString *)ll_md5 15 | { 16 | const char *cStr = [self UTF8String]; 17 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 18 | CC_MD5( cStr, (int)strlen(cStr), result ); 19 | return [NSString stringWithFormat: 20 | @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 21 | result[0], result[1], result[2], result[3], 22 | result[4], result[5], result[6], result[7], 23 | result[8], result[9], result[10], result[11], 24 | result[12], result[13], result[14], result[15] 25 | ]; 26 | } 27 | 28 | - (NSString *)ll_decodeLengthFromContentRange 29 | { 30 | // For example: "Content-Range" = "bytes 57933824-57999359/65904318" 31 | 32 | NSArray *ranges = [self componentsSeparatedByString:@"/"]; 33 | if (ranges.count > 0) { 34 | NSString *result = [[ranges lastObject] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 35 | if (result.length == 0) { 36 | return nil; 37 | } 38 | 39 | return result; 40 | } 41 | return nil; 42 | } 43 | 44 | - (NSRange)ll_decodeRangeFromContentRange 45 | { 46 | NSArray *ranges = [self componentsSeparatedByString:@"/"]; 47 | if (ranges.count == 0) { 48 | return NSMakeRange(NSNotFound, 0); 49 | } 50 | ranges = [ranges[0] componentsSeparatedByString:@" "]; 51 | if (ranges.count < 2) { 52 | return NSMakeRange(NSNotFound, 0); 53 | } 54 | NSString *rangeString = [ranges[1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 55 | ranges = [rangeString componentsSeparatedByString:@"-"]; 56 | if (ranges.count != 2) { 57 | return NSMakeRange(NSNotFound, 0); 58 | } 59 | NSInteger start = [ranges[0] integerValue]; 60 | NSInteger end = [ranges[1] integerValue]; 61 | 62 | return NSMakeRange(start, end - start + 1); 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/NSURL+LLVideoPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+LLVideoPlayer.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/22. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface NSURL (LLVideoPlayer) 12 | 13 | - (NSURL *)ll_customSchemeURL; 14 | 15 | - (NSURL *)ll_originalSchemeURL; 16 | 17 | - (BOOL)ll_m3u8; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/NSURL+LLVideoPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+LLVideoPlayer.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/2/22. 6 | // 7 | // 8 | 9 | #import "NSURL+LLVideoPlayer.h" 10 | #import 11 | #import 12 | 13 | static NSString *const kCustomSchemePrefix = @"ll-"; 14 | 15 | @implementation NSURL (LLVideoPlayer) 16 | 17 | - (NSURL *)ll_customSchemeURL 18 | { 19 | NSURLComponents *components = [[NSURLComponents alloc] initWithURL:self resolvingAgainstBaseURL:NO]; 20 | if (NO == [components.scheme hasPrefix:kCustomSchemePrefix]) { 21 | components.scheme = [NSString stringWithFormat:@"%@%@", kCustomSchemePrefix, components.scheme]; 22 | } 23 | return [components URL]; 24 | } 25 | 26 | - (NSURL *)ll_originalSchemeURL 27 | { 28 | NSURLComponents *components = [[NSURLComponents alloc] initWithURL:self resolvingAgainstBaseURL:NO]; 29 | if ([components.scheme hasPrefix:kCustomSchemePrefix]) { 30 | components.scheme = [components.scheme substringFromIndex:kCustomSchemePrefix.length]; 31 | } 32 | return [components URL]; 33 | } 34 | 35 | - (BOOL)ll_m3u8 36 | { 37 | return [[[self pathExtension] lowercaseString] isEqualToString:@"m3u8"]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/NSURLResponse+LLVideoPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLResponse+LLVideoPlayer.h 3 | // Pods 4 | // 5 | // Created by mario on 2017/8/22. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface NSURLResponse (LLVideoPlayer) 12 | 13 | - (BOOL)ll_supportRange; 14 | 15 | - (long long)ll_totalLength; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /LLVideoPlayer/CacheSupport/NSURLResponse+LLVideoPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLResponse+LLVideoPlayer.m 3 | // Pods 4 | // 5 | // Created by mario on 2017/8/22. 6 | // 7 | // 8 | 9 | #import "NSURLResponse+LLVideoPlayer.h" 10 | #import "NSString+LLVideoPlayer.h" 11 | 12 | @implementation NSURLResponse (LLVideoPlayer) 13 | 14 | - (BOOL)ll_supportRange 15 | { 16 | if (NO == [self isKindOfClass:[NSHTTPURLResponse class]]) { 17 | return NO; 18 | } 19 | return ((NSHTTPURLResponse *)self).allHeaderFields[@"Content-Range"] != nil; 20 | } 21 | 22 | - (long long)ll_totalLength 23 | { 24 | // Get total content length 25 | 26 | if (NO == [self isKindOfClass:[NSHTTPURLResponse class]]) { 27 | return [self expectedContentLength]; 28 | } 29 | 30 | // For example: "Content-Range" = "bytes 57933824-57999359/65904318" 31 | NSString *contentRange = ((NSHTTPURLResponse *)self).allHeaderFields[@"Content-Range"]; 32 | NSString *lengthString = [contentRange ll_decodeLengthFromContentRange]; 33 | if (lengthString) { 34 | return [lengthString longLongValue]; 35 | } 36 | 37 | return [self expectedContentLength]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /LLVideoPlayer/LLVideoPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayer.h 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/24. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "LLVideoPlayerDefines.h" 12 | #import "LLVideoPlayerView.h" 13 | #import "LLVideoTrack.h" 14 | #import "LLVideoPlayerHelper.h" 15 | #import "LLVideoPlayerDelegate.h" 16 | #import "LLVideoPlayerCachePolicy.h" 17 | 18 | /// LLVideoPlayer: Low Level Video Player 19 | 20 | @interface LLVideoPlayer : NSObject 21 | 22 | @property (nonatomic, weak) id delegate; 23 | @property (nonatomic, assign) LLVideoPlayerState state; 24 | @property (nonatomic, strong) LLVideoPlayerView *view; 25 | @property (nonatomic, strong) LLVideoTrack *track; 26 | @property (nonatomic, strong) AVPlayer *avPlayer; 27 | @property (nonatomic, strong) AVPlayerItem *avPlayerItem; 28 | @property (nonatomic, strong) NSString *videoGravity; 29 | @property (nonatomic, assign) BOOL cacheSupportEnabled; 30 | @property (nonatomic, strong) LLVideoPlayerCachePolicy *cachePolicy; 31 | @property (nonatomic, assign) BOOL accurateSeek; 32 | 33 | - (instancetype)initWithVideoPlayerView:(LLVideoPlayerView *)videoPlayerView; 34 | 35 | #pragma mark - Load 36 | - (void)loadVideoWithTrack:(LLVideoTrack *)track; 37 | - (void)loadVideoWithStreamURL:(NSURL *)streamURL; 38 | - (void)reloadCurrentVideoTrack; 39 | 40 | #pragma mark - Control 41 | - (void)playContent; 42 | - (void)pauseContent; 43 | - (void)pauseContentWithCompletionHandler:(void (^)(void))completionHandler; 44 | - (void)pauseContent:(BOOL)isUserAction completionHandler:(void (^)(void))completionHandler; 45 | - (void)dismissContent; 46 | - (void)seekToTimeInSecond:(float)sec userAction:(BOOL)isUserAction completionHandler:(void (^)(BOOL finished))completionHandler; 47 | - (void)seekToLastWatchedDuration; 48 | 49 | #pragma mark - Data 50 | - (double)currentBitRateInKbps; 51 | - (NSTimeInterval)currentTime; 52 | - (BOOL)stalling; 53 | 54 | #pragma mark - Cache Support 55 | + (void)clearAllCache; 56 | + (void)removeCacheForURL:(NSURL *)url; 57 | 58 | + (NSString *)cachePathForURL:(NSURL *)url; 59 | + (BOOL)isCacheComplete:(NSURL *)url; 60 | 61 | + (void)preloadWithURL:(NSURL *)url; 62 | + (void)preloadWithURL:(NSURL *)url bytes:(NSUInteger)bytes; 63 | + (void)cancelPreloadWithURL:(NSURL *)url; 64 | + (void)cancelAllPreloads; 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /LLVideoPlayer/LLVideoPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerImpl.m 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/24. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import "LLVideoPlayer.h" 10 | #import "LLVideoTrack.h" 11 | #import 12 | #import "AVPlayer+LLPlayer.h" 13 | #import "LLVideoPlayerCacheLoader.h" 14 | #import "NSURL+LLVideoPlayer.h" 15 | #import "LLVideoPlayerCacheFile.h" 16 | #import "NSString+LLVideoPlayer.h" 17 | #import "LLVideoPlayerCacheUtils.h" 18 | #import "LLVideoPlayerDownloader.h" 19 | #import "LLVideoPlayerCacheManager.h" 20 | 21 | #if defined DEBUG 22 | #define NSLog(...) NSLog(__VA_ARGS__) 23 | #else 24 | #define NSLog(...) 25 | #endif 26 | 27 | typedef void (^VoidBlock) (void); 28 | 29 | @interface LLVideoPlayer () 30 | 31 | @property (nonatomic, strong) id avTimeObserver; 32 | @property (nonatomic, strong) LLVideoPlayerCacheLoader *resourceLoader; 33 | @property (nonatomic, strong) NSMutableSet *failingURLs; 34 | @property (nonatomic, assign) NSInteger lastFrameTime; 35 | 36 | @end 37 | 38 | @implementation LLVideoPlayer 39 | 40 | - (instancetype)init 41 | { 42 | return [self initWithVideoPlayerView:[LLVideoPlayerView new]]; 43 | } 44 | 45 | - (instancetype)initWithVideoPlayerView:(LLVideoPlayerView *)videoPlayerView 46 | { 47 | self = [super init]; 48 | if (self) { 49 | self.view = videoPlayerView; 50 | self.state = LLVideoPlayerStateUnknown; 51 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; 52 | [self.view.layer addObserver:self forKeyPath:@"readyForDisplay" options:NSKeyValueObservingOptionNew context:nil]; 53 | } 54 | return self; 55 | } 56 | 57 | - (void)dealloc 58 | { 59 | [self.view.layer removeObserver:self forKeyPath:@"readyForDisplay" context:nil]; 60 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; 61 | [self clearPlayer]; 62 | } 63 | 64 | #pragma mark - Load 65 | 66 | - (void)loadVideoWithTrack:(LLVideoTrack *)track 67 | { 68 | self.track = track; 69 | self.state = LLVideoPlayerStateContentLoading; 70 | 71 | VoidBlock completionHandler = ^{ 72 | [self playVideoTrack:self.track]; 73 | }; 74 | switch (self.state) { 75 | case LLVideoPlayerStateError: 76 | case LLVideoPlayerStateContentPaused: 77 | case LLVideoPlayerStateContentLoading: 78 | completionHandler(); 79 | break; 80 | case LLVideoPlayerStateContentPlaying: 81 | [self pauseContent:NO completionHandler:completionHandler]; 82 | break; 83 | case LLVideoPlayerStateDismissed: 84 | case LLVideoPlayerStateUnknown: 85 | default: 86 | break; 87 | } 88 | } 89 | 90 | - (void)loadVideoWithStreamURL:(NSURL *)streamURL 91 | { 92 | [self loadVideoWithTrack:[[LLVideoTrack alloc] initWithStreamURL:streamURL]]; 93 | } 94 | 95 | - (void)reloadCurrentVideoTrack 96 | { 97 | ll_run_on_ui_thread(^{ 98 | VoidBlock completionHandler = ^{ 99 | self.track.lastWatchedDuration = nil; 100 | self.track.isPlayedToEnd = NO; 101 | self.state = LLVideoPlayerStateContentLoading; 102 | [self playVideoTrack:self.track]; 103 | }; 104 | 105 | switch (self.state) { 106 | case LLVideoPlayerStateUnknown: 107 | case LLVideoPlayerStateError: 108 | case LLVideoPlayerStateDismissed: 109 | case LLVideoPlayerStateContentLoading: 110 | case LLVideoPlayerStateContentPaused: 111 | completionHandler(); 112 | break; 113 | case LLVideoPlayerStateContentPlaying: 114 | [self pauseContent:NO completionHandler:completionHandler]; 115 | break; 116 | default: 117 | break; 118 | } 119 | }); 120 | } 121 | 122 | #pragma mark - Control 123 | 124 | - (void)startContent 125 | { 126 | ll_run_on_ui_thread(^{ 127 | if (self.state == LLVideoPlayerStateContentLoading) { 128 | self.state = LLVideoPlayerStateContentPlaying; 129 | } 130 | }); 131 | } 132 | 133 | - (void)playContent 134 | { 135 | ll_run_on_ui_thread(^{ 136 | if (self.state == LLVideoPlayerStateContentPaused) { 137 | self.state = LLVideoPlayerStateContentPlaying; 138 | } 139 | }); 140 | } 141 | 142 | - (void)pauseContent 143 | { 144 | [self pauseContent:NO completionHandler:nil]; 145 | } 146 | 147 | - (void)pauseContentWithCompletionHandler:(void (^)(void))completionHandler 148 | { 149 | [self pauseContent:NO completionHandler:completionHandler]; 150 | } 151 | 152 | - (void)pauseContent:(BOOL)isUserAction completionHandler:(void (^)(void))completionHandler 153 | { 154 | ll_run_on_ui_thread(^{ 155 | switch (self.avPlayerItem.status) { 156 | case AVPlayerItemStatusFailed: 157 | NSLog(@"Trying to pause content but AVPlayerItemStatusFailed"); 158 | self.state = LLVideoPlayerStateError; 159 | return; 160 | break; 161 | case AVPlayerItemStatusUnknown: 162 | NSLog(@"Trying to pause content but AVPlayerItemStatusUnknown"); 163 | self.state = LLVideoPlayerStateContentLoading; 164 | return; 165 | break; 166 | default: 167 | break; 168 | } 169 | 170 | switch (self.avPlayer.status) { 171 | case AVPlayerStatusFailed: 172 | NSLog(@"Trying to pause content but AVPlayerStatusFailed"); 173 | self.state = LLVideoPlayerStateError; 174 | return; 175 | break; 176 | case AVPlayerStatusUnknown: 177 | NSLog(@"Trying to pause content but AVPlayerStatusUnknown"); 178 | self.state = LLVideoPlayerStateContentLoading; 179 | return; 180 | break; 181 | default: 182 | break; 183 | } 184 | 185 | switch (self.state) { 186 | case LLVideoPlayerStateError: 187 | NSLog(@"Trying to pause content but LLVideoPlayerStateError"); 188 | // fall through 189 | case LLVideoPlayerStateContentPlaying: 190 | case LLVideoPlayerStateContentPaused: 191 | self.state = LLVideoPlayerStateContentPaused; 192 | if (completionHandler) { 193 | completionHandler(); 194 | } 195 | break; 196 | 197 | case LLVideoPlayerStateContentLoading: 198 | NSLog(@"Trying to pause content but LLVideoPlayerStateContentLoading"); 199 | break; 200 | 201 | case LLVideoPlayerStateDismissed: 202 | case LLVideoPlayerStateUnknown: 203 | default: 204 | break; 205 | } 206 | }); 207 | } 208 | 209 | - (void)dismissContent 210 | { 211 | ll_run_on_ui_thread(^{ 212 | switch (self.avPlayerItem.status) { 213 | case AVPlayerItemStatusFailed: 214 | NSLog(@"Trying to dismiss content at AVPlayerItemStatusFailed"); 215 | break; 216 | case AVPlayerItemStatusUnknown: 217 | NSLog(@"Trying to dismiss content at AVPlayerItemStatusUnknown"); 218 | break; 219 | default: 220 | break; 221 | } 222 | 223 | switch (self.avPlayer.status) { 224 | case AVPlayerStatusFailed: 225 | NSLog(@"Trying to dismiss content at AVPlayerStatusFailed"); 226 | break; 227 | case AVPlayerStatusUnknown: 228 | NSLog(@"Trying to dismiss content at AVPlayerStatusUnknown"); 229 | break; 230 | default: 231 | break; 232 | } 233 | 234 | switch (self.state) { 235 | case LLVideoPlayerStateContentPlaying: 236 | NSLog(@"Trying to dismiss content at LLVideoPlayerStateContentPlaying"); 237 | [self pauseContent:NO completionHandler:nil]; 238 | break; 239 | case LLVideoPlayerStateContentLoading: 240 | NSLog(@"Trying to dismiss content at LLVideoPlayerStateContentLoading"); 241 | break; 242 | case LLVideoPlayerStateContentPaused: 243 | break; 244 | case LLVideoPlayerStateError: 245 | case LLVideoPlayerStateDismissed: 246 | case LLVideoPlayerStateUnknown: 247 | default: 248 | break; 249 | } 250 | 251 | self.state = LLVideoPlayerStateDismissed; 252 | }); 253 | } 254 | 255 | - (void)seekToTimeInSecond:(float)sec userAction:(BOOL)isUserAction completionHandler:(void (^)(BOOL finished))completionHandler 256 | { 257 | NSLog(@"seekToTimeInSecond: %f, userAction: %@", sec, isUserAction ? @"YES" : @"NO"); 258 | [self.avPlayer ll_seekToTimeInSeconds:sec accurate:self.accurateSeek completionHandler:completionHandler]; 259 | } 260 | 261 | - (void)seekToLastWatchedDuration 262 | { 263 | [self seekToLastWatchedDuration:nil]; 264 | } 265 | 266 | - (void)seekToLastWatchedDuration:(void (^)(BOOL finished))completionHandler; 267 | { 268 | ll_run_on_ui_thread(^{ 269 | float lastWatchedTime = [self.track.lastWatchedDuration floatValue]; 270 | if (lastWatchedTime > 0) { 271 | if ([self.delegate respondsToSelector:@selector(videoPlayerWillContinuePlaying:)]) { 272 | [self.delegate videoPlayerWillContinuePlaying:self]; 273 | } 274 | } 275 | 276 | NSLog(@"Seeking to last watched duration: %f", lastWatchedTime); 277 | 278 | [self.avPlayer ll_seekToTimeInSeconds:lastWatchedTime accurate:self.accurateSeek completionHandler:completionHandler]; 279 | }); 280 | } 281 | 282 | #pragma mark - AVPlayer 283 | 284 | - (void)clearPlayer 285 | { 286 | self.avTimeObserver = nil; 287 | [self.avPlayerItem.asset cancelLoading]; 288 | self.avPlayerItem = nil; 289 | self.resourceLoader = nil; 290 | self.avPlayer = nil; 291 | [[self activePlayerView] setPlayer:nil]; 292 | self.lastFrameTime = 0; 293 | } 294 | 295 | - (void)playVideoTrack:(LLVideoTrack *)track 296 | { 297 | if ([self.delegate respondsToSelector:@selector(shouldVideoPlayerStartVideo:)]) { 298 | if (NO == [self.delegate shouldVideoPlayerStartVideo:self]) { 299 | return; 300 | } 301 | } 302 | [self clearPlayer]; 303 | 304 | NSURL *streamURL = [track streamURL]; 305 | if (nil == streamURL) { 306 | return; 307 | } 308 | 309 | [self playOnAVPlayer:streamURL playerLayerView:[self activePlayerView] track:track]; 310 | } 311 | 312 | - (LLVideoPlayerView *)activePlayerView 313 | { 314 | return self.view; 315 | } 316 | 317 | - (BOOL)sessionCacheEnabled 318 | { 319 | if (NO == self.cacheSupportEnabled) { 320 | return NO; 321 | } 322 | if ([self.track.streamURL isFileURL]) { 323 | return NO; 324 | } 325 | if ([self.track.streamURL ll_m3u8]) { 326 | return NO; 327 | } 328 | if ([self.failingURLs containsObject:self.track.streamURL]) { 329 | return NO; 330 | } 331 | return YES; 332 | } 333 | 334 | - (void)playOnAVPlayer:(NSURL *)streamURL playerLayerView:(LLVideoPlayerView *)playerLayerView track:(LLVideoTrack *)track 335 | { 336 | static NSString *kPlayableKey = @"playable"; 337 | static NSString *kTracks = @"tracks"; 338 | 339 | AVURLAsset *asset; 340 | 341 | if ([self sessionCacheEnabled]) { 342 | asset = [[AVURLAsset alloc] initWithURL:[streamURL ll_customSchemeURL] options:nil]; 343 | self.resourceLoader = [[LLVideoPlayerCacheLoader alloc] initWithURL:streamURL]; 344 | [asset.resourceLoader setDelegate:self.resourceLoader queue:dispatch_get_main_queue()]; 345 | } else { 346 | asset = [[AVURLAsset alloc] initWithURL:streamURL options:nil]; 347 | } 348 | self.avPlayerItem = [AVPlayerItem playerItemWithAsset:asset]; 349 | 350 | [asset loadValuesAsynchronouslyForKeys:@[kPlayableKey, kTracks] completionHandler:^{ 351 | ll_run_on_ui_thread(^{ 352 | if (NO == [streamURL isEqual:self.track.streamURL]) { 353 | NSLog(@"URL dismatch: %@ loaded, but cuurent is %@",streamURL, self.track.streamURL); 354 | return; 355 | } 356 | if (self.state == LLVideoPlayerStateDismissed) { 357 | NSLog(@"Asset was dismissed while status %ld", (long)[asset statusOfValueForKey:kPlayableKey error:nil]); 358 | return; 359 | } 360 | NSError *error = nil; 361 | AVKeyValueStatus status = [asset statusOfValueForKey:kPlayableKey error:&error]; 362 | if (status == AVKeyValueStatusLoaded) { 363 | NSLog(@"AVURLAsset loaded. [OK]"); 364 | self.avPlayer = [self playerWithPlayerItem:self.avPlayerItem]; 365 | [playerLayerView setPlayer:self.avPlayer]; 366 | } else { 367 | NSLog(@"The asset's tracks were not loaded: %@", error); 368 | [self handleErrorCode:LLVideoPlayerErrorAssetLoadError track:track error:error]; 369 | } 370 | }); 371 | }]; 372 | } 373 | 374 | - (AVPlayer*)playerWithPlayerItem:(AVPlayerItem *)playerItem 375 | { 376 | AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem]; 377 | if ([player respondsToSelector:@selector(setAllowsExternalPlayback:)]) { 378 | player.allowsExternalPlayback = NO; 379 | } 380 | if ([player respondsToSelector:@selector(setAutomaticallyWaitsToMinimizeStalling:)]) { 381 | if (@available(iOS 10.0, *)) { 382 | [player setAutomaticallyWaitsToMinimizeStalling:NO]; 383 | } 384 | } 385 | return player; 386 | } 387 | 388 | - (void)setAvPlayer:(AVPlayer *)avPlayer 389 | { 390 | if (_avPlayer != avPlayer) { 391 | self.avTimeObserver = nil; 392 | [_avPlayer removeObserver:self forKeyPath:@"status"]; 393 | _avPlayer = avPlayer; 394 | if (avPlayer) { 395 | __weak __typeof(self) weakSelf = self; 396 | avPlayer.volume = [AVAudioSession sharedInstance].outputVolume; 397 | [avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil]; 398 | self.avTimeObserver = [avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:NULL usingBlock:^(CMTime time) { 399 | [weakSelf periodicTimeObserver:time]; 400 | }]; 401 | } 402 | } 403 | } 404 | 405 | - (void)setAvPlayerItem:(AVPlayerItem *)avPlayerItem 406 | { 407 | if (_avPlayerItem != avPlayerItem) { 408 | [_avPlayerItem removeObserver:self forKeyPath:@"status"]; 409 | [_avPlayerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"]; 410 | [_avPlayerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"]; 411 | [_avPlayerItem removeObserver:self forKeyPath:@"loadedTimeRanges"]; 412 | [[NSNotificationCenter defaultCenter] removeObserver:self 413 | name:AVPlayerItemDidPlayToEndTimeNotification 414 | object:nil]; 415 | [[NSNotificationCenter defaultCenter] removeObserver:self 416 | name:AVPlayerItemPlaybackStalledNotification 417 | object:nil]; 418 | _avPlayerItem = avPlayerItem; 419 | if (avPlayerItem) { 420 | [avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil]; 421 | [avPlayerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil]; 422 | [avPlayerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil]; 423 | [avPlayerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; 424 | [[NSNotificationCenter defaultCenter] addObserver:self 425 | selector:@selector(playerDidPlayToEnd:) 426 | name:AVPlayerItemDidPlayToEndTimeNotification 427 | object:nil]; 428 | [[NSNotificationCenter defaultCenter] addObserver:self 429 | selector:@selector(playItemPlaybackStall:) 430 | name:AVPlayerItemPlaybackStalledNotification 431 | object:nil]; 432 | } 433 | } 434 | } 435 | 436 | - (void)setAvTimeObserver:(id)avTimeObserver 437 | { 438 | if (_avTimeObserver) { 439 | [self.avPlayer removeTimeObserver:_avTimeObserver]; 440 | } 441 | _avTimeObserver = avTimeObserver; 442 | } 443 | 444 | - (void)setVideoGravity:(NSString *)videoGravity 445 | { 446 | [(AVPlayerLayer *)[self activePlayerView].layer setVideoGravity:videoGravity]; 447 | } 448 | 449 | - (NSString *)videoGravity 450 | { 451 | return [(AVPlayerLayer *)[self activePlayerView].layer videoGravity]; 452 | } 453 | 454 | #pragma mark - KVO 455 | 456 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 457 | { 458 | if (object == self.avPlayer) { 459 | /// AVPlayer 460 | 461 | /// status 462 | 463 | if ([keyPath isEqualToString:@"status"]) { 464 | switch (self.avPlayer.status) { 465 | case AVPlayerStatusReadyToPlay: 466 | NSLog(@"AVPlayerStatusReadyToPlay"); 467 | if (self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay) { 468 | [self handlePlayerItemReadyToPlay]; 469 | } 470 | break; 471 | case AVPlayerStatusFailed: 472 | NSLog(@"AVPlayerStatusFailed"); 473 | [self handleErrorCode:LLVideoPlayerErrorAVPlayerFail track:self.track error:nil]; 474 | break; 475 | default: 476 | break; 477 | } 478 | } 479 | 480 | } else if (object == self.avPlayerItem) { 481 | /// AVPlayerItem 482 | 483 | /// status 484 | 485 | if ([keyPath isEqualToString:@"status"]) { 486 | switch (self.avPlayerItem.status) { 487 | case AVPlayerItemStatusReadyToPlay: 488 | NSLog(@"AVPlayerItemStatusReadyToPlay"); 489 | if (self.avPlayer.status == AVPlayerStatusReadyToPlay) { 490 | [self handlePlayerItemReadyToPlay]; 491 | } 492 | break; 493 | case AVPlayerItemStatusFailed: 494 | NSLog(@"AVPlayerStAVPlayerItemStatusFailedatusFailed"); 495 | [self handleErrorCode:LLVideoPlayerErrorAVPlayerItemFail track:self.track error:nil]; 496 | break; 497 | default: 498 | break; 499 | } 500 | } 501 | 502 | /// playbackBufferEmpty 503 | 504 | if ([keyPath isEqualToString:@"playbackBufferEmpty"]) { 505 | NSLog(@"playbackBufferEmpty: %@", self.avPlayerItem.playbackBufferEmpty ? @"YES" : @"NO"); 506 | if (self.state == LLVideoPlayerStateContentPlaying && 507 | [self currentTime] > 0 && 508 | [self currentTime] < [self.avPlayer ll_currentItemDuration] - 1) { 509 | if ([self.delegate respondsToSelector:@selector(videoPlayer:playbackBufferEmpty:)]) { 510 | [self.delegate videoPlayer:self playbackBufferEmpty:self.avPlayerItem.playbackBufferEmpty]; 511 | } 512 | } 513 | } 514 | 515 | /// playbackLikelyToKeepUp 516 | 517 | if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) { 518 | NSLog(@"playbackLikelyToKeepUp: %@", self.avPlayerItem.playbackLikelyToKeepUp ? @"YES" : @"NO"); 519 | if (self.state == LLVideoPlayerStateContentPlaying) { 520 | if ([self.delegate respondsToSelector:@selector(videoPlayer:playbackLikelyToKeepUp:)]) { 521 | [self.delegate videoPlayer:self playbackLikelyToKeepUp:self.avPlayerItem.playbackLikelyToKeepUp]; 522 | } 523 | if (self.avPlayerItem.playbackLikelyToKeepUp && NO == [self isPlayingVideo]) { 524 | [self.avPlayer play]; 525 | } 526 | } 527 | } 528 | 529 | /// loadedTimeRanges 530 | if ([keyPath isEqualToString:@"loadedTimeRanges"]) { 531 | if ([self.delegate respondsToSelector:@selector(videoPlayer:loadedTimeRanges:)]) { 532 | [self.delegate videoPlayer:self loadedTimeRanges:self.avPlayerItem.loadedTimeRanges]; 533 | } 534 | } 535 | } else if (object == self.view.layer) { 536 | /// LLVideoPlayerView 537 | 538 | /// readyToDisplay 539 | if ([keyPath isEqualToString:@"readyForDisplay"]) { 540 | AVPlayerLayer *layer = (AVPlayerLayer *)self.view.layer; 541 | NSLog(@"playerReadyForDisplay: %@", layer.readyForDisplay ? @"YES" : @"NO"); 542 | if ([self.delegate respondsToSelector:@selector(videoPlayer:readyForDisplay:)]) { 543 | [self.delegate videoPlayer:self readyForDisplay:layer.readyForDisplay]; 544 | } 545 | } 546 | } 547 | } 548 | 549 | #pragma mark - Events 550 | 551 | - (void)handlePlayerItemReadyToPlay 552 | { 553 | ll_run_on_ui_thread(^{ 554 | switch (self.state) { 555 | case LLVideoPlayerStateContentLoading: 556 | case LLVideoPlayerStateError: { 557 | if ([self.delegate respondsToSelector:@selector(videoPlayerWillStartVideo:)]) { 558 | [self.delegate videoPlayerWillStartVideo:self]; 559 | } 560 | 561 | [self seekToLastWatchedDuration:^(BOOL finished) { 562 | if (finished) { 563 | [self startContent]; 564 | 565 | if ([self.delegate respondsToSelector:@selector(videoPlayerDidStartVideo:)]) { 566 | [self.delegate videoPlayerDidStartVideo:self]; 567 | } 568 | } 569 | }]; 570 | } 571 | break; 572 | 573 | case LLVideoPlayerStateContentPaused: 574 | break; 575 | 576 | case LLVideoPlayerStateContentPlaying: 577 | case LLVideoPlayerStateDismissed: 578 | case LLVideoPlayerStateUnknown: 579 | default: 580 | break; 581 | } 582 | }); 583 | } 584 | 585 | #pragma mark - Notifications 586 | 587 | - (void)periodicTimeObserver:(CMTime)time 588 | { 589 | NSTimeInterval timeInSeconds = CMTimeGetSeconds(time); 590 | 591 | if (timeInSeconds <= 0) { 592 | return; 593 | } 594 | 595 | NSTimeInterval duration = [self.avPlayer ll_currentItemDuration]; 596 | if (nil == self.track.totalDuration || (NSInteger)[self.track.totalDuration floatValue] != (NSInteger)duration) { 597 | self.track.totalDuration = [NSNumber numberWithFloat:duration]; 598 | if ([self.delegate respondsToSelector:@selector(videoPlayer:durationDidLoad:)]) { 599 | [self.delegate videoPlayer:self durationDidLoad:self.track.totalDuration]; 600 | } 601 | } 602 | 603 | if (self.state != LLVideoPlayerStateContentPlaying) { 604 | return; 605 | } 606 | 607 | NSInteger thisSecond = (NSInteger)(timeInSeconds + 0.5f); 608 | if (thisSecond == self.lastFrameTime) { 609 | return; 610 | } 611 | self.lastFrameTime = thisSecond; 612 | 613 | if ([self.delegate respondsToSelector:@selector(videoPlayer:didPlayFrame:)]) { 614 | [self.delegate videoPlayer:self didPlayFrame:timeInSeconds]; 615 | } 616 | } 617 | 618 | - (void)playerDidPlayToEnd:(NSNotification *)note 619 | { 620 | NSLog(@"playerDidPlayToEnd: %@", note.object); 621 | AVPlayerItem *finishedItem = note.object; 622 | if (NO == [finishedItem isEqual:self.avPlayerItem]) { 623 | NSLog(@"[WRN] finished playerItem of another AVPlayer"); 624 | return; 625 | } 626 | 627 | // is current player's item 628 | ll_run_on_ui_thread(^{ 629 | [self clearPlayer]; 630 | self.track.isPlayedToEnd = YES; 631 | self.state = LLVideoPlayerStateUnknown; 632 | if ([self.delegate respondsToSelector:@selector(videoPlayerDidPlayToEnd:)]) { 633 | [self.delegate videoPlayerDidPlayToEnd:self]; 634 | } 635 | }); 636 | } 637 | 638 | - (void)playItemPlaybackStall:(NSNotification *)note 639 | { 640 | NSLog(@"playItemPlaybackStall: %@", note.object); 641 | AVPlayerItem *stallItem = note.object; 642 | if (NO == [stallItem isEqual:self.avPlayerItem]) { 643 | NSLog(@"[WRN] stall playerItem of another AVPlayer"); 644 | return; 645 | } 646 | 647 | ll_run_on_ui_thread(^{ 648 | if ([self.delegate respondsToSelector:@selector(videoPlayerPlaybackStalled:)]) { 649 | [self.delegate videoPlayerPlaybackStalled:self]; 650 | } 651 | }); 652 | } 653 | 654 | - (void)handleWillResignActive:(NSNotification *)note 655 | { 656 | if (self.cacheSupportEnabled) { 657 | [LLVideoPlayer cleanCacheWithPolicy:self.cachePolicy]; 658 | } 659 | } 660 | 661 | #pragma mark - State Changed 662 | 663 | - (void)setState:(LLVideoPlayerState)newState 664 | { 665 | if (self.state == newState) { 666 | return; 667 | } 668 | 669 | if ([self.delegate respondsToSelector:@selector(shouldVideoPlayer:changeStateTo:)]) { 670 | if (NO == [self.delegate shouldVideoPlayer:self changeStateTo:newState]) { 671 | return; 672 | } 673 | } 674 | 675 | ll_run_on_ui_thread(^{ 676 | if ([self.delegate respondsToSelector:@selector(videoPlayer:willChangeStateTo:)]) { 677 | [self.delegate videoPlayer:self willChangeStateTo:newState]; 678 | } 679 | 680 | LLVideoPlayerState oldState = self.state; 681 | switch (oldState) { 682 | case LLVideoPlayerStateContentLoading: 683 | break; 684 | case LLVideoPlayerStateContentPlaying: 685 | break; 686 | case LLVideoPlayerStateContentPaused: 687 | break; 688 | case LLVideoPlayerStateError: 689 | break; 690 | case LLVideoPlayerStateDismissed: 691 | break; 692 | case LLVideoPlayerStateUnknown: 693 | default: 694 | break; 695 | } 696 | 697 | NSLog(@"Player State: %@ -> %@", 698 | [LLVideoPlayerHelper playerStateToString:oldState], 699 | [LLVideoPlayerHelper playerStateToString:newState]); 700 | 701 | _state = newState; 702 | switch (newState) { 703 | case LLVideoPlayerStateContentLoading: 704 | break; 705 | case LLVideoPlayerStateContentPlaying: 706 | [self.avPlayer play]; 707 | break; 708 | case LLVideoPlayerStateContentPaused: 709 | self.track.lastWatchedDuration = [NSNumber numberWithFloat:[self currentTime]]; 710 | [self.avPlayer pause]; 711 | break; 712 | case LLVideoPlayerStateError: 713 | [self.avPlayer pause]; 714 | break; 715 | case LLVideoPlayerStateDismissed: 716 | [self clearPlayer]; 717 | break; 718 | case LLVideoPlayerStateUnknown: 719 | default: 720 | break; 721 | } 722 | 723 | if ([self.delegate respondsToSelector:@selector(videoPlayer:didChangeStateFrom:)]) { 724 | [self.delegate videoPlayer:self didChangeStateFrom:oldState]; 725 | } 726 | }); 727 | } 728 | 729 | #pragma mark - Error 730 | 731 | - (void)handleErrorCode:(LLVideoPlayerError)errorCode track:(LLVideoTrack *)track error:(NSError *)error 732 | { 733 | NSLog(@"[ERROR] %@: %@", [LLVideoPlayerHelper errorCodeToString:errorCode], track); 734 | [self addFailingURL:track.streamURL]; 735 | if (errorCode == LLVideoPlayerErrorAssetLoadError) { 736 | [LLVideoPlayer removeCacheForURL:track.streamURL]; 737 | } 738 | if ([self.delegate respondsToSelector:@selector(videoPlayer:didFailWithError:)]) { 739 | [self.delegate videoPlayer:self didFailWithError:[NSError errorWithDomain:@"LLVideoPlayer" code:errorCode userInfo:error ? @{@"track":track, @"error": error} : @{@"track":track}]]; 740 | } 741 | } 742 | 743 | - (void)addFailingURL:(NSURL *)url { 744 | if (nil == url) { 745 | return; 746 | } 747 | if (nil == self.failingURLs) { 748 | self.failingURLs = [NSMutableSet set]; 749 | } 750 | if (self.failingURLs.count > 64) { 751 | [self.failingURLs removeAllObjects]; 752 | } 753 | [self.failingURLs addObject:url]; 754 | } 755 | 756 | #pragma mark - Misc 757 | 758 | - (double)currentBitRateInKbps 759 | { 760 | return [self.avPlayerItem.accessLog.events.lastObject observedBitrate]/1000; 761 | } 762 | 763 | - (NSTimeInterval)currentTime 764 | { 765 | return CMTimeGetSeconds([self.avPlayer ll_currentCMTime]); 766 | } 767 | 768 | - (BOOL)stalling 769 | { 770 | return self.avPlayerItem.playbackBufferEmpty || NO == self.avPlayerItem.playbackLikelyToKeepUp; 771 | } 772 | 773 | - (BOOL)isPlayingVideo 774 | { 775 | return self.avPlayer && self.avPlayer.rate != 0.0; 776 | } 777 | 778 | #pragma mark - Cache Support 779 | 780 | + (void)cleanCacheWithPolicy:(LLVideoPlayerCachePolicy *)cachePolicy 781 | { 782 | [[LLVideoPlayerCacheManager defaultManager] cleanCacheWithPolicy:cachePolicy]; 783 | } 784 | 785 | + (void)clearAllCache 786 | { 787 | [[LLVideoPlayerCacheManager defaultManager] clearAllCache]; 788 | } 789 | 790 | + (void)removeCacheForURL:(NSURL *)url 791 | { 792 | [[LLVideoPlayerCacheManager defaultManager] removeCacheForURL:url]; 793 | } 794 | 795 | + (NSString *)cachePathForURL:(NSURL *)url 796 | { 797 | if (nil == url || [url ll_m3u8]) { 798 | return nil; 799 | } 800 | if ([url isFileURL]) { 801 | return [url absoluteString]; 802 | } 803 | LLVideoPlayerCacheFile *cacheFile = [[LLVideoPlayerCacheManager defaultManager] createCacheFileForURL:url]; 804 | NSString *path = [cacheFile isComplete] ? cacheFile.cacheFilePath : nil; 805 | [[LLVideoPlayerCacheManager defaultManager] releaseCacheFileForURL:url]; 806 | return path; 807 | } 808 | 809 | + (BOOL)isCacheComplete:(NSURL *)url 810 | { 811 | return [self cachePathForURL:url] != nil; 812 | } 813 | 814 | + (void)preloadWithURL:(NSURL *)url 815 | { 816 | [self preloadWithURL:url bytes:(1 << 20)]; 817 | } 818 | 819 | + (void)preloadWithURL:(NSURL *)url bytes:(NSUInteger)bytes 820 | { 821 | if (nil == url || [url isFileURL] || [url ll_m3u8]) { 822 | return; 823 | } 824 | 825 | [[LLVideoPlayerDownloader defaultDownloader] preloadWithURL:url bytes:bytes]; 826 | } 827 | 828 | + (void)cancelPreloadWithURL:(NSURL *)url 829 | { 830 | if (nil == url || [url isFileURL] || [url ll_m3u8]) { 831 | return; 832 | } 833 | 834 | [[LLVideoPlayerDownloader defaultDownloader] cancelPreloadWithURL:url]; 835 | } 836 | 837 | + (void)cancelAllPreloads 838 | { 839 | [[LLVideoPlayerDownloader defaultDownloader] cancelAllPreloads]; 840 | } 841 | 842 | @end 843 | -------------------------------------------------------------------------------- /LLVideoPlayer/LLVideoPlayerDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerDefines.h 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/29. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #ifndef LLVideoPlayerDefines_h 10 | #define LLVideoPlayerDefines_h 11 | 12 | #import 13 | 14 | #pragma mark - Error Code 15 | typedef NS_ENUM(NSInteger, LLVideoPlayerError) { 16 | LLVideoPlayerErrorNone, 17 | LLVideoPlayerErrorAssetLoadError, 18 | LLVideoPlayerErrorStreamNotFound, 19 | LLVideoPlayerErrorAVPlayerFail, 20 | LLVideoPlayerErrorAVPlayerItemFail, 21 | LLVideoPlayerErrorUnknown 22 | }; 23 | 24 | #pragma mark - Player State 25 | typedef NS_ENUM(NSInteger, LLVideoPlayerState) { 26 | LLVideoPlayerStateUnknown, 27 | LLVideoPlayerStateContentLoading, 28 | LLVideoPlayerStateContentPlaying, 29 | LLVideoPlayerStateContentPaused, 30 | LLVideoPlayerStateDismissed, 31 | LLVideoPlayerStateError 32 | }; 33 | 34 | #endif /* LLVideoPlayerDefines_h */ 35 | -------------------------------------------------------------------------------- /LLVideoPlayer/LLVideoPlayerDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerDelegate.h 3 | // LLVideoPlayer 4 | // 5 | // Created by mario on 2016/12/8. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LLVideoPlayerDefines.h" 11 | 12 | @class LLVideoPlayer; 13 | 14 | /// LLVideoPlayerDelegate 15 | 16 | @protocol LLVideoPlayerDelegate 17 | 18 | @optional 19 | 20 | #pragma mark - State Changed 21 | - (BOOL)shouldVideoPlayer:(LLVideoPlayer *)videoPlayer changeStateTo:(LLVideoPlayerState)state; 22 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer willChangeStateTo:(LLVideoPlayerState)state; 23 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer didChangeStateFrom:(LLVideoPlayerState)state; 24 | 25 | #pragma mark - Play Control 26 | - (BOOL)shouldVideoPlayerStartVideo:(LLVideoPlayer *)videoPlayer; 27 | - (void)videoPlayerWillStartVideo:(LLVideoPlayer *)videoPlayer; 28 | - (void)videoPlayerDidStartVideo:(LLVideoPlayer *)videoPlayer; 29 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer didPlayFrame:(NSTimeInterval)time; 30 | - (void)videoPlayerDidPlayToEnd:(LLVideoPlayer *)videoPlayer; 31 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer loadedTimeRanges:(NSArray *)ranges; 32 | - (void)videoPlayerWillContinuePlaying:(LLVideoPlayer *)videoPlayer; 33 | 34 | #pragma mark - Error 35 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer didFailWithError:(NSError *)error; 36 | 37 | #pragma mark - 38 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer durationDidLoad:(NSNumber *)duration; 39 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer playbackBufferEmpty:(BOOL)bufferEmpty; 40 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer playbackLikelyToKeepUp:(BOOL)likelyToKeepUp; 41 | - (void)videoPlayerPlaybackStalled:(LLVideoPlayer *)videoPlayer; 42 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer readyForDisplay:(BOOL)readyForDisplay; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /LLVideoPlayer/LLVideoPlayerHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerHelper.h 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/29. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LLVideoPlayerDefines.h" 11 | 12 | @interface LLVideoPlayerHelper : NSObject 13 | 14 | + (NSString *)errorCodeToString:(LLVideoPlayerError)errorCode; 15 | 16 | + (NSString *)playerStateToString:(LLVideoPlayerState)state; 17 | 18 | + (NSString *)timeStringFromSecondsValue:(int)seconds; 19 | 20 | @end 21 | 22 | 23 | void ll_run_on_ui_thread(dispatch_block_t); 24 | -------------------------------------------------------------------------------- /LLVideoPlayer/LLVideoPlayerHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerHelper.m 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/29. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import "LLVideoPlayerHelper.h" 10 | 11 | @implementation LLVideoPlayerHelper 12 | 13 | + (NSString *)errorCodeToString:(LLVideoPlayerError)errorCode 14 | { 15 | switch (errorCode) { 16 | case LLVideoPlayerErrorNone: 17 | return @"LLVideoPlayerErrorNone"; 18 | 19 | case LLVideoPlayerErrorStreamNotFound: 20 | return @"LLVideoPlayerErrorStreamNotFound"; 21 | 22 | case LLVideoPlayerErrorUnknown: 23 | default: 24 | return @"LLVideoPlayerErrorUnknown"; 25 | } 26 | } 27 | 28 | + (NSString *)playerStateToString:(LLVideoPlayerState)state 29 | { 30 | switch (state) { 31 | case LLVideoPlayerStateContentLoading: 32 | return @"LLVideoPlayerStateContentLoading"; 33 | 34 | case LLVideoPlayerStateContentPlaying: 35 | return @"LLVideoPlayerStateContentPlaying"; 36 | 37 | case LLVideoPlayerStateContentPaused: 38 | return @"LLVideoPlayerStateContentPaused"; 39 | 40 | case LLVideoPlayerStateError: 41 | return @"LLVideoPlayerStateError"; 42 | 43 | case LLVideoPlayerStateDismissed: 44 | return @"LLVideoPlayerStateDismissed"; 45 | 46 | case LLVideoPlayerStateUnknown: 47 | default: 48 | return @"LLVideoPlayerStateUnknown"; 49 | } 50 | } 51 | 52 | + (NSString *)timeStringFromSecondsValue:(int)seconds 53 | { 54 | NSString *retVal; 55 | int hours = seconds / 3600; 56 | int minutes = (seconds / 60) % 60; 57 | int secs = seconds % 60; 58 | if (hours > 0) { 59 | retVal = [NSString stringWithFormat:@"%01d:%02d:%02d", hours, minutes, secs]; 60 | } else { 61 | retVal = [NSString stringWithFormat:@"%02d:%02d", minutes, secs]; 62 | } 63 | return retVal; 64 | } 65 | 66 | @end 67 | 68 | void ll_run_on_ui_thread(dispatch_block_t block) 69 | { 70 | if ([NSThread isMainThread]) { 71 | block(); 72 | } else { 73 | dispatch_sync(dispatch_get_main_queue(), block); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /LLVideoPlayer/LLVideoPlayerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerView.h 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/24. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "LLVideoPlayerDefines.h" 12 | 13 | /// LLVideoPlayerView: Low Level Video View 14 | 15 | @interface LLVideoPlayerView : UIView 16 | 17 | - (void)setPlayer:(AVPlayer *)player; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /LLVideoPlayer/LLVideoPlayerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoPlayerView.m 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/24. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import "LLVideoPlayerView.h" 10 | 11 | @implementation LLVideoPlayerView 12 | 13 | #pragma mark - Initialize 14 | 15 | - (instancetype)initWithFrame:(CGRect)frame 16 | { 17 | self = [super initWithFrame:frame]; 18 | if (self) { 19 | self.backgroundColor = [UIColor blackColor]; 20 | } 21 | return self; 22 | } 23 | 24 | + (Class)layerClass 25 | { 26 | return [AVPlayerLayer class]; 27 | } 28 | 29 | - (void)setPlayer:(AVPlayer *)player 30 | { 31 | [(AVPlayerLayer *)[self layer] setPlayer:player]; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /LLVideoPlayer/LLVideoTrack.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoTrack.h 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/29. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LLVideoTrack : NSObject 12 | 13 | - (instancetype)initWithStreamURL:(NSURL *)streamURL; 14 | 15 | @property (nonatomic, assign) BOOL isPlayedToEnd; 16 | @property (nonatomic, strong) NSNumber *totalDuration; 17 | @property (nonatomic, strong) NSNumber *lastWatchedDuration; 18 | @property (nonatomic, strong, readonly) NSURL *streamURL; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /LLVideoPlayer/LLVideoTrack.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLVideoTrack.m 3 | // IMYVideoPlayer 4 | // 5 | // Created by mario on 2016/11/29. 6 | // Copyright © 2016 mario. All rights reserved. 7 | // 8 | 9 | #import "LLVideoTrack.h" 10 | 11 | @interface LLVideoTrack () 12 | 13 | @property (nonatomic, strong) NSURL *streamURL; 14 | 15 | @end 16 | 17 | @implementation LLVideoTrack 18 | 19 | - (NSString *)description 20 | { 21 | return [NSString stringWithFormat:@"<%@: %p> streamURL: %@, totalDuration: %@, lastWatchedDuration: %@, isPlayedToEnd: %@", 22 | NSStringFromClass([self class]), self, 23 | self.streamURL, self.totalDuration, self.lastWatchedDuration, 24 | self.isPlayedToEnd ? @"YES" : @"NO"]; 25 | } 26 | 27 | - (instancetype)init 28 | { 29 | return [self initWithStreamURL:nil]; 30 | } 31 | 32 | - (instancetype)initWithStreamURL:(NSURL *)streamURL 33 | { 34 | self = [super init]; 35 | if (self) { 36 | self.streamURL = streamURL; 37 | } 38 | return self; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LLVideoPlayer 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/LLVideoPlayer.svg?style=flat)](http://cocoapods.org/pods/LLVideoPlayer) 4 | [![License](https://img.shields.io/cocoapods/l/LLVideoPlayer.svg?style=flat)](http://cocoapods.org/pods/LLVideoPlayer) 5 | [![Platform](https://img.shields.io/cocoapods/p/LLVideoPlayer.svg?style=flat)](http://cocoapods.org/pods/LLVideoPlayer) 6 | 7 | A low level video player based on AVPlayer with cache and preload support. 8 | 9 | 10 | 11 | - [x] Simple and flexible 12 | - [x] Cache support 13 | - [x] Preload support 14 | - [x] Memory Mapped IO 15 | 16 | 17 | ## Example 18 | 19 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 20 | 21 | ``` 22 | // create 23 | self.player = [[LLVideoPlayer alloc] init]; 24 | [self.view addSubview:self.player.view]; 25 | self.player.view.frame = CGRectMake(10, 80, 300, 200); 26 | self.player.delegate = self; 27 | 28 | 29 | // load 30 | NSURL *url = [NSURL URLWithString:@""]; 31 | [self.player loadVideoWithStreamURL:url]; 32 | 33 | 34 | // pause 35 | [self.player pauseContent]; 36 | 37 | // play 38 | [self.player playContent]; 39 | 40 | // dismiss 41 | [self.player dismissContent]; 42 | 43 | // delegate 44 | // see the header file for details. 45 | ``` 46 | 47 | ## Delegate 48 | 49 | There are some significant delegate methods you may be interested in: 50 | 51 | ``` 52 | // The first frame of the video is ready to display. 53 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer readyForDisplay:(BOOL)readyForDisplay; 54 | 55 | // The duration is available. 56 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer durationDidLoad:(NSNumber *)duration; 57 | 58 | // The buffer is empty or not. 59 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer playbackBufferEmpty:(BOOL)bufferEmpty; 60 | 61 | // The video is likely to keepup or not. 62 | - (void)videoPlayer:(LLVideoPlayer *)videoPlayer playbackLikelyToKeepUp:(BOOL)likelyToKeepUp; 63 | 64 | // The playback is stalled. 65 | - (void)videoPlayerPlaybackStalled:(LLVideoPlayer *)videoPlayer; 66 | 67 | /// more... 68 | ``` 69 | 70 | See `LLVideoPlayerDelegate` for more details. 71 | 72 | ## Customize UI Controls 73 | 74 | LLVideoPlayer comes without any UI controls for flexibility. Your can add your custom contols to the container view `LLVideoPlayerView`. 75 | 76 | ## Cache Support 77 | 78 | LLVideoPlayer supports customize cache policy. To enable the cache support (default is disable): 79 | 80 | ``` 81 | player.cacheSupportEnabled = YES; // That's all, so simple... 82 | ``` 83 | 84 | To set your customize cache policy: 85 | 86 | ``` 87 | LLVideoPlayerCachePolicy *policy = [LLVideoPlayerCachePolicy new]; 88 | policy.diskCapacity = 500ULL << 20; // max disk capacity in bytes, for example 500MiB 89 | policy.outdatedHours = 7 *24; // outdated hours, for example 7 days 90 | 91 | player.cachePolicy = policy; 92 | ``` 93 | 94 | See `LLVideoPlayerCachePolicy` for more details. 95 | 96 | To clear cache manually: 97 | 98 | ``` 99 | [LLVideoPlayer clearAllCache]; 100 | ``` 101 | 102 | ## Preload Support 103 | 104 | ``` 105 | // start a preload request 106 | [LLVideoPlayer preloadWithURL:url]; 107 | 108 | // start a preload request with specified bytes 109 | [LLVideoPlayer preloadWithURL:url bytes:(1 << 20)]; 110 | 111 | // cancel a preload request 112 | [LLVideoPlayer cancelPreloadWithURL:url]; 113 | 114 | // cancel all preload requests 115 | [LLVideoPlayer cancelAllPreloads]; 116 | ``` 117 | 118 | ## Requirements 119 | 120 | iOS 7 or above 121 | 122 | ## Installation 123 | 124 | LLVideoPlayer is available through [CocoaPods](http://cocoapods.org). To install 125 | it, simply add the following line to your Podfile: 126 | 127 | ```ruby 128 | pod "LLVideoPlayer" 129 | ``` 130 | 131 | ## License 132 | 133 | LLVideoPlayer is available under the MIT license. See the LICENSE file for more info. 134 | --------------------------------------------------------------------------------