├── LICENSE ├── README.md ├── videoplayer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── ashishsingh.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── ashishsingh.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── videoplayer ├── AppDelegate.swift ├── AppImageView.swift ├── AppUtilities.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── comment.imageset │ │ ├── Contents.json │ │ └── comment@2x.png │ ├── heart.imageset │ │ ├── Contents.json │ │ └── heart@2x.png │ └── viewed.imageset │ │ ├── Contents.json │ │ └── viewed@2x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ShotTableViewCell.swift ├── ShotTableViewCell.xib ├── VideoPlayLibrary │ ├── ASVideoContainer.swift │ ├── ASVideoLayerObject.swift │ └── ASVideoPlayerController.swift └── ViewController.swift ├── videoplayerTests ├── Info.plist └── videoplayerTests.swift └── videoplayerUITests ├── Info.plist └── videoplayerUITests.swift /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ashish Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AutoVideoPlayer 2 | Play/pause videos automatically in UITableview when an UITableViewCell is in focus, videos can be easily embedded in any UITableViewCell subclass. 3 | Can be easily extended to support UICollectionView 4 | 5 | * Easily implement video player in any UITableView subclass 6 | * Automatic video play when video view is visible and option to easily pause/play any video 7 | * Mute/Unmute videos 8 | * Videos are cached in memory and will be removed when there is memory pressure 9 | * The scroll of UITableView is super smooth since video assets are downloaded on background thread and played only when assets are completely downloaded ensuring the main thead is never blocked 10 | * Option to provide different bit rate for videos 11 | * Works when the app comes again from background 12 | 13 | It can also be used to play videos in any subclass of UIView. 14 | 15 | ## Demo 16 | ![](https://i.imgur.com/Q4ElIJt.gif) 17 | 18 | 19 | ## Download 20 | Drag and drop the VideoPlayLibrary folder in your project 21 | ## Usage 22 | 23 | #### Adopt ASAutoPlayVideoLayerContainer protocol in your UITableviewCell subclass like below. 24 | 25 | ``` 26 | var videoLayer: AVPlayerLayer = AVPlayerLayer() 27 | 28 | var videoURL: String? { 29 | didSet { 30 | if let videoURL = videoURL { 31 | ASVideoPlayerController.sharedVideoPlayer.setupVideoFor(url: videoURL) 32 | } 33 | videoLayer.isHidden = videoURL == nil 34 | } 35 | } 36 | ``` 37 | Implement following method to return the visible height of the UITableViewCell 38 | ``` 39 | func visibleVideoHeight() -> CGFloat { 40 | //return visible height of the Video Player layer 41 | } 42 | ``` 43 | 44 | #### ViewController Code 45 | 46 | Put following code in viewDidLoad 47 | ``` 48 | NotificationCenter.default.addObserver(self, 49 | selector: #selector(self.appEnteredFromBackground), 50 | name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) 51 | ``` 52 | 53 | Add following code to play/pause when view appears/disappears 54 | ``` 55 | override func viewDidAppear(_ animated: Bool) { 56 | super.viewDidAppear(animated) 57 | pausePlayeVideos() 58 | } 59 | ``` 60 | Add following methods 61 | 62 | ``` 63 | @objc func appEnteredFromBackground() { 64 | ASVideoPlayerController.sharedVideoPlayer.pausePlayeVideosFor(tableView: tableView, appEnteredFromBackground: true) 65 | } 66 | 67 | func pausePlayeVideos(){ 68 | ASVideoPlayerController.sharedVideoPlayer.pausePlayeVideosFor(tableView: tableView) 69 | } 70 | ``` 71 | 72 | Add following code in UITableView delegate and datasource methods 73 | ``` 74 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 75 | //if cell adopts ASAutoPlayVideoLayerContainer protocol then 76 | //set videoURL if you want to show video or else nil 77 | } 78 | 79 | func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { 80 | if let videoCell = cell as? ASAutoPlayVideoLayerContainer, videoCell.videoURL != nil { 81 | ASVideoPlayerController.sharedVideoPlayer.removeLayerFor(cell: videoCell) 82 | } 83 | } 84 | ``` 85 | Add following code to pause/play videos when scroll stops 86 | ``` 87 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 88 | if !decelerate { 89 | pausePlayeVideos() 90 | } 91 | } 92 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 93 | pausePlayeVideos() 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /videoplayer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 60B0B93F205BC45600E0BA36 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60B0B93E205BC45600E0BA36 /* ViewController.swift */; }; 11 | 60E2A903205106AD00FFF0F8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E2A902205106AD00FFF0F8 /* AppDelegate.swift */; }; 12 | 60E2A908205106AD00FFF0F8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 60E2A906205106AD00FFF0F8 /* Main.storyboard */; }; 13 | 60E2A90A205106AD00FFF0F8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 60E2A909205106AD00FFF0F8 /* Assets.xcassets */; }; 14 | 60E2A90D205106AD00FFF0F8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 60E2A90B205106AD00FFF0F8 /* LaunchScreen.storyboard */; }; 15 | 60E2A918205106AE00FFF0F8 /* videoplayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E2A917205106AE00FFF0F8 /* videoplayerTests.swift */; }; 16 | 60E2A923205106AE00FFF0F8 /* videoplayerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E2A922205106AE00FFF0F8 /* videoplayerUITests.swift */; }; 17 | 60E2A93520510A3B00FFF0F8 /* AppUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E2A93120510A3A00FFF0F8 /* AppUtilities.swift */; }; 18 | 60E2A93720510A3B00FFF0F8 /* AppImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E2A93320510A3B00FFF0F8 /* AppImageView.swift */; }; 19 | 60E2A94720510A4F00FFF0F8 /* ShotTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 60E2A93B20510A4E00FFF0F8 /* ShotTableViewCell.xib */; }; 20 | 60E2A94C20510A4F00FFF0F8 /* ShotTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E2A94020510A4E00FFF0F8 /* ShotTableViewCell.swift */; }; 21 | 60E2A94D20510A4F00FFF0F8 /* ASVideoLayerObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E2A94120510A4F00FFF0F8 /* ASVideoLayerObject.swift */; }; 22 | 60E2A94E20510A4F00FFF0F8 /* ASVideoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E2A94220510A4F00FFF0F8 /* ASVideoContainer.swift */; }; 23 | 60E2A94F20510A4F00FFF0F8 /* ASVideoPlayerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E2A94320510A4F00FFF0F8 /* ASVideoPlayerController.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | 60E2A914205106AE00FFF0F8 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 60E2A8F7205106AC00FFF0F8 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 60E2A8FE205106AD00FFF0F8; 32 | remoteInfo = videoplayer; 33 | }; 34 | 60E2A91F205106AE00FFF0F8 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 60E2A8F7205106AC00FFF0F8 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 60E2A8FE205106AD00FFF0F8; 39 | remoteInfo = videoplayer; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 60B0B93E205BC45600E0BA36 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 45 | 60E2A8FF205106AD00FFF0F8 /* videoplayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = videoplayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 60E2A902205106AD00FFF0F8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 47 | 60E2A907205106AD00FFF0F8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 48 | 60E2A909205106AD00FFF0F8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 49 | 60E2A90C205106AD00FFF0F8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 50 | 60E2A90E205106AD00FFF0F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | 60E2A913205106AE00FFF0F8 /* videoplayerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = videoplayerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 60E2A917205106AE00FFF0F8 /* videoplayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = videoplayerTests.swift; sourceTree = ""; }; 53 | 60E2A919205106AE00FFF0F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 60E2A91E205106AE00FFF0F8 /* videoplayerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = videoplayerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 60E2A922205106AE00FFF0F8 /* videoplayerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = videoplayerUITests.swift; sourceTree = ""; }; 56 | 60E2A924205106AE00FFF0F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 60E2A93120510A3A00FFF0F8 /* AppUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUtilities.swift; sourceTree = ""; }; 58 | 60E2A93320510A3B00FFF0F8 /* AppImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppImageView.swift; sourceTree = ""; }; 59 | 60E2A93B20510A4E00FFF0F8 /* ShotTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShotTableViewCell.xib; sourceTree = ""; }; 60 | 60E2A94020510A4E00FFF0F8 /* ShotTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShotTableViewCell.swift; sourceTree = ""; }; 61 | 60E2A94120510A4F00FFF0F8 /* ASVideoLayerObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASVideoLayerObject.swift; sourceTree = ""; }; 62 | 60E2A94220510A4F00FFF0F8 /* ASVideoContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASVideoContainer.swift; sourceTree = ""; }; 63 | 60E2A94320510A4F00FFF0F8 /* ASVideoPlayerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASVideoPlayerController.swift; sourceTree = ""; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | 60E2A8FC205106AD00FFF0F8 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | 60E2A910205106AE00FFF0F8 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 60E2A91B205106AE00FFF0F8 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | 60E2A8F6205106AC00FFF0F8 = { 92 | isa = PBXGroup; 93 | children = ( 94 | 60E2A901205106AD00FFF0F8 /* videoplayer */, 95 | 60E2A916205106AE00FFF0F8 /* videoplayerTests */, 96 | 60E2A921205106AE00FFF0F8 /* videoplayerUITests */, 97 | 60E2A900205106AD00FFF0F8 /* Products */, 98 | ); 99 | sourceTree = ""; 100 | }; 101 | 60E2A900205106AD00FFF0F8 /* Products */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 60E2A8FF205106AD00FFF0F8 /* videoplayer.app */, 105 | 60E2A913205106AE00FFF0F8 /* videoplayerTests.xctest */, 106 | 60E2A91E205106AE00FFF0F8 /* videoplayerUITests.xctest */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | 60E2A901205106AD00FFF0F8 /* videoplayer */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 60E2A9502051A04D00FFF0F8 /* VideoPlayLibrary */, 115 | 60E2A902205106AD00FFF0F8 /* AppDelegate.swift */, 116 | 60E2A93320510A3B00FFF0F8 /* AppImageView.swift */, 117 | 60B0B93E205BC45600E0BA36 /* ViewController.swift */, 118 | 60E2A94020510A4E00FFF0F8 /* ShotTableViewCell.swift */, 119 | 60E2A93B20510A4E00FFF0F8 /* ShotTableViewCell.xib */, 120 | 60E2A93120510A3A00FFF0F8 /* AppUtilities.swift */, 121 | 60E2A906205106AD00FFF0F8 /* Main.storyboard */, 122 | 60E2A909205106AD00FFF0F8 /* Assets.xcassets */, 123 | 60E2A90B205106AD00FFF0F8 /* LaunchScreen.storyboard */, 124 | 60E2A90E205106AD00FFF0F8 /* Info.plist */, 125 | ); 126 | path = videoplayer; 127 | sourceTree = ""; 128 | }; 129 | 60E2A916205106AE00FFF0F8 /* videoplayerTests */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 60E2A917205106AE00FFF0F8 /* videoplayerTests.swift */, 133 | 60E2A919205106AE00FFF0F8 /* Info.plist */, 134 | ); 135 | path = videoplayerTests; 136 | sourceTree = ""; 137 | }; 138 | 60E2A921205106AE00FFF0F8 /* videoplayerUITests */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 60E2A922205106AE00FFF0F8 /* videoplayerUITests.swift */, 142 | 60E2A924205106AE00FFF0F8 /* Info.plist */, 143 | ); 144 | path = videoplayerUITests; 145 | sourceTree = ""; 146 | }; 147 | 60E2A9502051A04D00FFF0F8 /* VideoPlayLibrary */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 60E2A94220510A4F00FFF0F8 /* ASVideoContainer.swift */, 151 | 60E2A94320510A4F00FFF0F8 /* ASVideoPlayerController.swift */, 152 | 60E2A94120510A4F00FFF0F8 /* ASVideoLayerObject.swift */, 153 | ); 154 | path = VideoPlayLibrary; 155 | sourceTree = ""; 156 | }; 157 | /* End PBXGroup section */ 158 | 159 | /* Begin PBXNativeTarget section */ 160 | 60E2A8FE205106AD00FFF0F8 /* videoplayer */ = { 161 | isa = PBXNativeTarget; 162 | buildConfigurationList = 60E2A927205106AE00FFF0F8 /* Build configuration list for PBXNativeTarget "videoplayer" */; 163 | buildPhases = ( 164 | 60E2A8FB205106AD00FFF0F8 /* Sources */, 165 | 60E2A8FC205106AD00FFF0F8 /* Frameworks */, 166 | 60E2A8FD205106AD00FFF0F8 /* Resources */, 167 | ); 168 | buildRules = ( 169 | ); 170 | dependencies = ( 171 | ); 172 | name = videoplayer; 173 | productName = videoplayer; 174 | productReference = 60E2A8FF205106AD00FFF0F8 /* videoplayer.app */; 175 | productType = "com.apple.product-type.application"; 176 | }; 177 | 60E2A912205106AE00FFF0F8 /* videoplayerTests */ = { 178 | isa = PBXNativeTarget; 179 | buildConfigurationList = 60E2A92A205106AE00FFF0F8 /* Build configuration list for PBXNativeTarget "videoplayerTests" */; 180 | buildPhases = ( 181 | 60E2A90F205106AE00FFF0F8 /* Sources */, 182 | 60E2A910205106AE00FFF0F8 /* Frameworks */, 183 | 60E2A911205106AE00FFF0F8 /* Resources */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | 60E2A915205106AE00FFF0F8 /* PBXTargetDependency */, 189 | ); 190 | name = videoplayerTests; 191 | productName = videoplayerTests; 192 | productReference = 60E2A913205106AE00FFF0F8 /* videoplayerTests.xctest */; 193 | productType = "com.apple.product-type.bundle.unit-test"; 194 | }; 195 | 60E2A91D205106AE00FFF0F8 /* videoplayerUITests */ = { 196 | isa = PBXNativeTarget; 197 | buildConfigurationList = 60E2A92D205106AE00FFF0F8 /* Build configuration list for PBXNativeTarget "videoplayerUITests" */; 198 | buildPhases = ( 199 | 60E2A91A205106AE00FFF0F8 /* Sources */, 200 | 60E2A91B205106AE00FFF0F8 /* Frameworks */, 201 | 60E2A91C205106AE00FFF0F8 /* Resources */, 202 | ); 203 | buildRules = ( 204 | ); 205 | dependencies = ( 206 | 60E2A920205106AE00FFF0F8 /* PBXTargetDependency */, 207 | ); 208 | name = videoplayerUITests; 209 | productName = videoplayerUITests; 210 | productReference = 60E2A91E205106AE00FFF0F8 /* videoplayerUITests.xctest */; 211 | productType = "com.apple.product-type.bundle.ui-testing"; 212 | }; 213 | /* End PBXNativeTarget section */ 214 | 215 | /* Begin PBXProject section */ 216 | 60E2A8F7205106AC00FFF0F8 /* Project object */ = { 217 | isa = PBXProject; 218 | attributes = { 219 | LastSwiftUpdateCheck = 0920; 220 | LastUpgradeCheck = 0920; 221 | ORGANIZATIONNAME = ashish; 222 | TargetAttributes = { 223 | 60E2A8FE205106AD00FFF0F8 = { 224 | CreatedOnToolsVersion = 9.2; 225 | ProvisioningStyle = Automatic; 226 | }; 227 | 60E2A912205106AE00FFF0F8 = { 228 | CreatedOnToolsVersion = 9.2; 229 | ProvisioningStyle = Automatic; 230 | TestTargetID = 60E2A8FE205106AD00FFF0F8; 231 | }; 232 | 60E2A91D205106AE00FFF0F8 = { 233 | CreatedOnToolsVersion = 9.2; 234 | ProvisioningStyle = Automatic; 235 | TestTargetID = 60E2A8FE205106AD00FFF0F8; 236 | }; 237 | }; 238 | }; 239 | buildConfigurationList = 60E2A8FA205106AC00FFF0F8 /* Build configuration list for PBXProject "videoplayer" */; 240 | compatibilityVersion = "Xcode 8.0"; 241 | developmentRegion = en; 242 | hasScannedForEncodings = 0; 243 | knownRegions = ( 244 | en, 245 | Base, 246 | ); 247 | mainGroup = 60E2A8F6205106AC00FFF0F8; 248 | productRefGroup = 60E2A900205106AD00FFF0F8 /* Products */; 249 | projectDirPath = ""; 250 | projectRoot = ""; 251 | targets = ( 252 | 60E2A8FE205106AD00FFF0F8 /* videoplayer */, 253 | 60E2A912205106AE00FFF0F8 /* videoplayerTests */, 254 | 60E2A91D205106AE00FFF0F8 /* videoplayerUITests */, 255 | ); 256 | }; 257 | /* End PBXProject section */ 258 | 259 | /* Begin PBXResourcesBuildPhase section */ 260 | 60E2A8FD205106AD00FFF0F8 /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | 60E2A90D205106AD00FFF0F8 /* LaunchScreen.storyboard in Resources */, 265 | 60E2A90A205106AD00FFF0F8 /* Assets.xcassets in Resources */, 266 | 60E2A94720510A4F00FFF0F8 /* ShotTableViewCell.xib in Resources */, 267 | 60E2A908205106AD00FFF0F8 /* Main.storyboard in Resources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | 60E2A911205106AE00FFF0F8 /* Resources */ = { 272 | isa = PBXResourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | ); 276 | runOnlyForDeploymentPostprocessing = 0; 277 | }; 278 | 60E2A91C205106AE00FFF0F8 /* Resources */ = { 279 | isa = PBXResourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | /* End PBXResourcesBuildPhase section */ 286 | 287 | /* Begin PBXSourcesBuildPhase section */ 288 | 60E2A8FB205106AD00FFF0F8 /* Sources */ = { 289 | isa = PBXSourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | 60E2A93720510A3B00FFF0F8 /* AppImageView.swift in Sources */, 293 | 60E2A903205106AD00FFF0F8 /* AppDelegate.swift in Sources */, 294 | 60E2A93520510A3B00FFF0F8 /* AppUtilities.swift in Sources */, 295 | 60E2A94D20510A4F00FFF0F8 /* ASVideoLayerObject.swift in Sources */, 296 | 60E2A94E20510A4F00FFF0F8 /* ASVideoContainer.swift in Sources */, 297 | 60E2A94F20510A4F00FFF0F8 /* ASVideoPlayerController.swift in Sources */, 298 | 60B0B93F205BC45600E0BA36 /* ViewController.swift in Sources */, 299 | 60E2A94C20510A4F00FFF0F8 /* ShotTableViewCell.swift in Sources */, 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | }; 303 | 60E2A90F205106AE00FFF0F8 /* Sources */ = { 304 | isa = PBXSourcesBuildPhase; 305 | buildActionMask = 2147483647; 306 | files = ( 307 | 60E2A918205106AE00FFF0F8 /* videoplayerTests.swift in Sources */, 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | 60E2A91A205106AE00FFF0F8 /* Sources */ = { 312 | isa = PBXSourcesBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | 60E2A923205106AE00FFF0F8 /* videoplayerUITests.swift in Sources */, 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | }; 319 | /* End PBXSourcesBuildPhase section */ 320 | 321 | /* Begin PBXTargetDependency section */ 322 | 60E2A915205106AE00FFF0F8 /* PBXTargetDependency */ = { 323 | isa = PBXTargetDependency; 324 | target = 60E2A8FE205106AD00FFF0F8 /* videoplayer */; 325 | targetProxy = 60E2A914205106AE00FFF0F8 /* PBXContainerItemProxy */; 326 | }; 327 | 60E2A920205106AE00FFF0F8 /* PBXTargetDependency */ = { 328 | isa = PBXTargetDependency; 329 | target = 60E2A8FE205106AD00FFF0F8 /* videoplayer */; 330 | targetProxy = 60E2A91F205106AE00FFF0F8 /* PBXContainerItemProxy */; 331 | }; 332 | /* End PBXTargetDependency section */ 333 | 334 | /* Begin PBXVariantGroup section */ 335 | 60E2A906205106AD00FFF0F8 /* Main.storyboard */ = { 336 | isa = PBXVariantGroup; 337 | children = ( 338 | 60E2A907205106AD00FFF0F8 /* Base */, 339 | ); 340 | name = Main.storyboard; 341 | sourceTree = ""; 342 | }; 343 | 60E2A90B205106AD00FFF0F8 /* LaunchScreen.storyboard */ = { 344 | isa = PBXVariantGroup; 345 | children = ( 346 | 60E2A90C205106AD00FFF0F8 /* Base */, 347 | ); 348 | name = LaunchScreen.storyboard; 349 | sourceTree = ""; 350 | }; 351 | /* End PBXVariantGroup section */ 352 | 353 | /* Begin XCBuildConfiguration section */ 354 | 60E2A925205106AE00FFF0F8 /* Debug */ = { 355 | isa = XCBuildConfiguration; 356 | buildSettings = { 357 | ALWAYS_SEARCH_USER_PATHS = NO; 358 | CLANG_ANALYZER_NONNULL = YES; 359 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 360 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 361 | CLANG_CXX_LIBRARY = "libc++"; 362 | CLANG_ENABLE_MODULES = YES; 363 | CLANG_ENABLE_OBJC_ARC = YES; 364 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 365 | CLANG_WARN_BOOL_CONVERSION = YES; 366 | CLANG_WARN_COMMA = YES; 367 | CLANG_WARN_CONSTANT_CONVERSION = YES; 368 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 369 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 370 | CLANG_WARN_EMPTY_BODY = YES; 371 | CLANG_WARN_ENUM_CONVERSION = YES; 372 | CLANG_WARN_INFINITE_RECURSION = YES; 373 | CLANG_WARN_INT_CONVERSION = YES; 374 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 375 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 376 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 377 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 378 | CLANG_WARN_STRICT_PROTOTYPES = YES; 379 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 380 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 381 | CLANG_WARN_UNREACHABLE_CODE = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | CODE_SIGN_IDENTITY = "iPhone Developer"; 384 | COPY_PHASE_STRIP = NO; 385 | DEBUG_INFORMATION_FORMAT = dwarf; 386 | ENABLE_STRICT_OBJC_MSGSEND = YES; 387 | ENABLE_TESTABILITY = YES; 388 | GCC_C_LANGUAGE_STANDARD = gnu11; 389 | GCC_DYNAMIC_NO_PIC = NO; 390 | GCC_NO_COMMON_BLOCKS = YES; 391 | GCC_OPTIMIZATION_LEVEL = 0; 392 | GCC_PREPROCESSOR_DEFINITIONS = ( 393 | "DEBUG=1", 394 | "$(inherited)", 395 | ); 396 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 397 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 398 | GCC_WARN_UNDECLARED_SELECTOR = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 400 | GCC_WARN_UNUSED_FUNCTION = YES; 401 | GCC_WARN_UNUSED_VARIABLE = YES; 402 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 403 | MTL_ENABLE_DEBUG_INFO = YES; 404 | ONLY_ACTIVE_ARCH = YES; 405 | SDKROOT = iphoneos; 406 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 407 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 408 | }; 409 | name = Debug; 410 | }; 411 | 60E2A926205106AE00FFF0F8 /* Release */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | ALWAYS_SEARCH_USER_PATHS = NO; 415 | CLANG_ANALYZER_NONNULL = YES; 416 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 417 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 418 | CLANG_CXX_LIBRARY = "libc++"; 419 | CLANG_ENABLE_MODULES = YES; 420 | CLANG_ENABLE_OBJC_ARC = YES; 421 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 422 | CLANG_WARN_BOOL_CONVERSION = YES; 423 | CLANG_WARN_COMMA = YES; 424 | CLANG_WARN_CONSTANT_CONVERSION = YES; 425 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 426 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 427 | CLANG_WARN_EMPTY_BODY = YES; 428 | CLANG_WARN_ENUM_CONVERSION = YES; 429 | CLANG_WARN_INFINITE_RECURSION = YES; 430 | CLANG_WARN_INT_CONVERSION = YES; 431 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 432 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 433 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 434 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 435 | CLANG_WARN_STRICT_PROTOTYPES = YES; 436 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 437 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 438 | CLANG_WARN_UNREACHABLE_CODE = YES; 439 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 440 | CODE_SIGN_IDENTITY = "iPhone Developer"; 441 | COPY_PHASE_STRIP = NO; 442 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 443 | ENABLE_NS_ASSERTIONS = NO; 444 | ENABLE_STRICT_OBJC_MSGSEND = YES; 445 | GCC_C_LANGUAGE_STANDARD = gnu11; 446 | GCC_NO_COMMON_BLOCKS = YES; 447 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 448 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 449 | GCC_WARN_UNDECLARED_SELECTOR = YES; 450 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 451 | GCC_WARN_UNUSED_FUNCTION = YES; 452 | GCC_WARN_UNUSED_VARIABLE = YES; 453 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 454 | MTL_ENABLE_DEBUG_INFO = NO; 455 | SDKROOT = iphoneos; 456 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 457 | VALIDATE_PRODUCT = YES; 458 | }; 459 | name = Release; 460 | }; 461 | 60E2A928205106AE00FFF0F8 /* Debug */ = { 462 | isa = XCBuildConfiguration; 463 | buildSettings = { 464 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 465 | CODE_SIGN_STYLE = Automatic; 466 | DEVELOPMENT_TEAM = R7CDRD8S2K; 467 | INFOPLIST_FILE = videoplayer/Info.plist; 468 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 469 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 470 | PRODUCT_BUNDLE_IDENTIFIER = com.ashishsingh.asvideoplayer; 471 | PRODUCT_NAME = "$(TARGET_NAME)"; 472 | SWIFT_VERSION = 4.0; 473 | TARGETED_DEVICE_FAMILY = 1; 474 | }; 475 | name = Debug; 476 | }; 477 | 60E2A929205106AE00FFF0F8 /* Release */ = { 478 | isa = XCBuildConfiguration; 479 | buildSettings = { 480 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 481 | CODE_SIGN_STYLE = Automatic; 482 | DEVELOPMENT_TEAM = R7CDRD8S2K; 483 | INFOPLIST_FILE = videoplayer/Info.plist; 484 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 485 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 486 | PRODUCT_BUNDLE_IDENTIFIER = com.ashishsingh.asvideoplayer; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | SWIFT_VERSION = 4.0; 489 | TARGETED_DEVICE_FAMILY = 1; 490 | }; 491 | name = Release; 492 | }; 493 | 60E2A92B205106AE00FFF0F8 /* Debug */ = { 494 | isa = XCBuildConfiguration; 495 | buildSettings = { 496 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 497 | BUNDLE_LOADER = "$(TEST_HOST)"; 498 | CODE_SIGN_STYLE = Automatic; 499 | DEVELOPMENT_TEAM = R7CDRD8S2K; 500 | INFOPLIST_FILE = videoplayerTests/Info.plist; 501 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 502 | PRODUCT_BUNDLE_IDENTIFIER = com.ashishsingh.videoplayerTests; 503 | PRODUCT_NAME = "$(TARGET_NAME)"; 504 | SWIFT_VERSION = 4.0; 505 | TARGETED_DEVICE_FAMILY = "1,2"; 506 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/videoplayer.app/videoplayer"; 507 | }; 508 | name = Debug; 509 | }; 510 | 60E2A92C205106AE00FFF0F8 /* Release */ = { 511 | isa = XCBuildConfiguration; 512 | buildSettings = { 513 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 514 | BUNDLE_LOADER = "$(TEST_HOST)"; 515 | CODE_SIGN_STYLE = Automatic; 516 | DEVELOPMENT_TEAM = R7CDRD8S2K; 517 | INFOPLIST_FILE = videoplayerTests/Info.plist; 518 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 519 | PRODUCT_BUNDLE_IDENTIFIER = com.ashishsingh.videoplayerTests; 520 | PRODUCT_NAME = "$(TARGET_NAME)"; 521 | SWIFT_VERSION = 4.0; 522 | TARGETED_DEVICE_FAMILY = "1,2"; 523 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/videoplayer.app/videoplayer"; 524 | }; 525 | name = Release; 526 | }; 527 | 60E2A92E205106AE00FFF0F8 /* Debug */ = { 528 | isa = XCBuildConfiguration; 529 | buildSettings = { 530 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 531 | CODE_SIGN_STYLE = Automatic; 532 | DEVELOPMENT_TEAM = R7CDRD8S2K; 533 | INFOPLIST_FILE = videoplayerUITests/Info.plist; 534 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 535 | PRODUCT_BUNDLE_IDENTIFIER = com.ashishsingh.videoplayerUITests; 536 | PRODUCT_NAME = "$(TARGET_NAME)"; 537 | SWIFT_VERSION = 4.0; 538 | TARGETED_DEVICE_FAMILY = "1,2"; 539 | TEST_TARGET_NAME = videoplayer; 540 | }; 541 | name = Debug; 542 | }; 543 | 60E2A92F205106AE00FFF0F8 /* Release */ = { 544 | isa = XCBuildConfiguration; 545 | buildSettings = { 546 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 547 | CODE_SIGN_STYLE = Automatic; 548 | DEVELOPMENT_TEAM = R7CDRD8S2K; 549 | INFOPLIST_FILE = videoplayerUITests/Info.plist; 550 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 551 | PRODUCT_BUNDLE_IDENTIFIER = com.ashishsingh.videoplayerUITests; 552 | PRODUCT_NAME = "$(TARGET_NAME)"; 553 | SWIFT_VERSION = 4.0; 554 | TARGETED_DEVICE_FAMILY = "1,2"; 555 | TEST_TARGET_NAME = videoplayer; 556 | }; 557 | name = Release; 558 | }; 559 | /* End XCBuildConfiguration section */ 560 | 561 | /* Begin XCConfigurationList section */ 562 | 60E2A8FA205106AC00FFF0F8 /* Build configuration list for PBXProject "videoplayer" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | 60E2A925205106AE00FFF0F8 /* Debug */, 566 | 60E2A926205106AE00FFF0F8 /* Release */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | 60E2A927205106AE00FFF0F8 /* Build configuration list for PBXNativeTarget "videoplayer" */ = { 572 | isa = XCConfigurationList; 573 | buildConfigurations = ( 574 | 60E2A928205106AE00FFF0F8 /* Debug */, 575 | 60E2A929205106AE00FFF0F8 /* Release */, 576 | ); 577 | defaultConfigurationIsVisible = 0; 578 | defaultConfigurationName = Release; 579 | }; 580 | 60E2A92A205106AE00FFF0F8 /* Build configuration list for PBXNativeTarget "videoplayerTests" */ = { 581 | isa = XCConfigurationList; 582 | buildConfigurations = ( 583 | 60E2A92B205106AE00FFF0F8 /* Debug */, 584 | 60E2A92C205106AE00FFF0F8 /* Release */, 585 | ); 586 | defaultConfigurationIsVisible = 0; 587 | defaultConfigurationName = Release; 588 | }; 589 | 60E2A92D205106AE00FFF0F8 /* Build configuration list for PBXNativeTarget "videoplayerUITests" */ = { 590 | isa = XCConfigurationList; 591 | buildConfigurations = ( 592 | 60E2A92E205106AE00FFF0F8 /* Debug */, 593 | 60E2A92F205106AE00FFF0F8 /* Release */, 594 | ); 595 | defaultConfigurationIsVisible = 0; 596 | defaultConfigurationName = Release; 597 | }; 598 | /* End XCConfigurationList section */ 599 | }; 600 | rootObject = 60E2A8F7205106AC00FFF0F8 /* Project object */; 601 | } 602 | -------------------------------------------------------------------------------- /videoplayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /videoplayer.xcodeproj/project.xcworkspace/xcuserdata/ashishsingh.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashish0309/AutoVideoPlayer/85462d6f5c2e9e0af06e6e06776c6f420855c780/videoplayer.xcodeproj/project.xcworkspace/xcuserdata/ashishsingh.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /videoplayer.xcodeproj/xcuserdata/ashishsingh.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 24 | 36 | 37 | 38 | 40 | 52 | 53 | 54 | 56 | 68 | 69 | 70 | 72 | 84 | 85 | 86 | 88 | 100 | 101 | 102 | 104 | 116 | 117 | 118 | 120 | 132 | 133 | 134 | 136 | 148 | 149 | 150 | 152 | 164 | 165 | 166 | 168 | 180 | 181 | 182 | 184 | 196 | 197 | 198 | 200 | 212 | 213 | 227 | 228 | 242 | 243 | 257 | 258 | 259 | 260 | 261 | 263 | 275 | 276 | 277 | 279 | 291 | 292 | 293 | 295 | 307 | 308 | 309 | 311 | 323 | 324 | 325 | 327 | 339 | 340 | 341 | 343 | 355 | 356 | 357 | 359 | 371 | 372 | 373 | 375 | 387 | 388 | 389 | 391 | 403 | 404 | 405 | 407 | 419 | 420 | 421 | 423 | 435 | 436 | 437 | 439 | 451 | 452 | 453 | 455 | 467 | 468 | 469 | 471 | 483 | 484 | 485 | 487 | 499 | 500 | 501 | 503 | 515 | 516 | 517 | 519 | 531 | 532 | 533 | 535 | 547 | 548 | 549 | 551 | 563 | 564 | 565 | 567 | 579 | 580 | 581 | 583 | 595 | 596 | 597 | 599 | 611 | 612 | 613 | 615 | 627 | 628 | 629 | 631 | 643 | 644 | 645 | 647 | 659 | 660 | 661 | 663 | 675 | 676 | 677 | 679 | 691 | 692 | 693 | 695 | 707 | 708 | 709 | 710 | 711 | -------------------------------------------------------------------------------- /videoplayer.xcodeproj/xcuserdata/ashishsingh.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | videoplayer.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /videoplayer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // videoplayer 4 | // 5 | // Created by ashish singh on 3/8/18. 6 | // Copyright © 2018 ashish. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | 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 | func applicationWillTerminate(_ application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /videoplayer/AppImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppImageView.swift 3 | // AutoPlayVideo 4 | // 5 | // Created by Ashish Singh on 7/22/17. 6 | // Copyright © 2017 Ashish. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | private var xoAssociationKey: UInt8 = 0 13 | 14 | extension UIImageView { 15 | @nonobjc static var imageCache = NSCache() 16 | var imageURL: String? { 17 | get { 18 | return objc_getAssociatedObject(self, &xoAssociationKey) as? String 19 | } 20 | set(newValue) { 21 | guard let urlString = newValue else { 22 | objc_setAssociatedObject(self,&xoAssociationKey ,newValue ,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 23 | image = nil 24 | return 25 | } 26 | objc_setAssociatedObject(self,&xoAssociationKey ,newValue ,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 27 | if let image = UIImageView.imageCache.object(forKey: "\((urlString as NSString).hash)" as NSString) as? UIImage { 28 | self.image = image 29 | return 30 | } 31 | DispatchQueue.global().async { [weak self] in 32 | guard let url = URL(string: urlString as String) else { 33 | return 34 | } 35 | guard let data = try? Data(contentsOf: url) else { 36 | return 37 | } 38 | let image = UIImage(data: data) 39 | guard let fetchedImage = image else { 40 | return 41 | } 42 | DispatchQueue.main.async { 43 | UIImageView.imageCache.setObject(fetchedImage, forKey: "\(urlString.hash)" as NSString) 44 | guard let pastImageUrl = self?.imageURL, 45 | url.absoluteString == pastImageUrl else { 46 | self?.image = nil 47 | return 48 | } 49 | let animation = CATransition() 50 | animation.type = kCATransitionFade 51 | animation.duration = 0.3 52 | self?.layer.add(animation, forKey: "transition") 53 | self?.image = fetchedImage 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /videoplayer/AppUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppUtilities.swift 3 | // AutoPlayVideo 4 | // 5 | // Created by Ashish Singh on 7/21/17. 6 | // Copyright © 2017 Ashish. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | class AppUtilities: NSObject { 13 | class func sizeOfString (string: String, 14 | constrainedToWidth width: Double, 15 | forFont font: UIFont) -> CGSize { 16 | return NSString(string: string).boundingRect(with: CGSize(width: width, 17 | height: Double.greatestFiniteMagnitude), 18 | options: NSStringDrawingOptions.usesLineFragmentOrigin, 19 | attributes: [NSAttributedStringKey.font: font], 20 | context: nil).size 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /videoplayer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /videoplayer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /videoplayer/Assets.xcassets/comment.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "comment@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } -------------------------------------------------------------------------------- /videoplayer/Assets.xcassets/comment.imageset/comment@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashish0309/AutoVideoPlayer/85462d6f5c2e9e0af06e6e06776c6f420855c780/videoplayer/Assets.xcassets/comment.imageset/comment@2x.png -------------------------------------------------------------------------------- /videoplayer/Assets.xcassets/heart.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "heart@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } -------------------------------------------------------------------------------- /videoplayer/Assets.xcassets/heart.imageset/heart@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashish0309/AutoVideoPlayer/85462d6f5c2e9e0af06e6e06776c6f420855c780/videoplayer/Assets.xcassets/heart.imageset/heart@2x.png -------------------------------------------------------------------------------- /videoplayer/Assets.xcassets/viewed.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "viewed@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } -------------------------------------------------------------------------------- /videoplayer/Assets.xcassets/viewed.imageset/viewed@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashish0309/AutoVideoPlayer/85462d6f5c2e9e0af06e6e06776c6f420855c780/videoplayer/Assets.xcassets/viewed.imageset/viewed@2x.png -------------------------------------------------------------------------------- /videoplayer/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /videoplayer/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /videoplayer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | ASVideoPlayer 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 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /videoplayer/ShotTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShotTableViewCell.swift 3 | // AutoPlayVideo 4 | // 5 | // Created by Ashish Singh on 7/21/17. 6 | // Copyright © 2017 Ashish. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | class ShotTableViewCell: UITableViewCell, ASAutoPlayVideoLayerContainer { 12 | @IBOutlet var descriptionLabel: UILabel! 13 | @IBOutlet var shotImageView: UIImageView! 14 | var playerController: ASVideoPlayerController? 15 | var videoLayer: AVPlayerLayer = AVPlayerLayer() 16 | var videoURL: String? { 17 | didSet { 18 | if let videoURL = videoURL { 19 | ASVideoPlayerController.sharedVideoPlayer.setupVideoFor(url: videoURL) 20 | } 21 | videoLayer.isHidden = videoURL == nil 22 | } 23 | } 24 | 25 | override func awakeFromNib() { 26 | super.awakeFromNib() 27 | shotImageView.layer.cornerRadius = 5 28 | shotImageView.backgroundColor = UIColor.gray.withAlphaComponent(0.7) 29 | shotImageView.clipsToBounds = true 30 | shotImageView.layer.borderColor = UIColor.gray.withAlphaComponent(0.3).cgColor 31 | shotImageView.layer.borderWidth = 0.5 32 | videoLayer.backgroundColor = UIColor.clear.cgColor 33 | videoLayer.videoGravity = AVLayerVideoGravity.resize 34 | shotImageView.layer.addSublayer(videoLayer) 35 | selectionStyle = .none 36 | } 37 | 38 | func configureCell(imageUrl: String?, 39 | description: String, 40 | videoUrl: String?) { 41 | self.descriptionLabel.text = description 42 | self.shotImageView.imageURL = imageUrl 43 | self.videoURL = videoUrl 44 | } 45 | 46 | override func prepareForReuse() { 47 | shotImageView.imageURL = nil 48 | super.prepareForReuse() 49 | } 50 | 51 | override func layoutSubviews() { 52 | super.layoutSubviews() 53 | let horizontalMargin: CGFloat = 20 54 | let width: CGFloat = bounds.size.width - horizontalMargin * 2 55 | let height: CGFloat = (width * 0.9).rounded(.up) 56 | videoLayer.frame = CGRect(x: 0, y: 0, width: width, height: height) 57 | } 58 | 59 | func visibleVideoHeight() -> CGFloat { 60 | let videoFrameInParentSuperView: CGRect? = self.superview?.superview?.convert(shotImageView.frame, from: shotImageView) 61 | guard let videoFrame = videoFrameInParentSuperView, 62 | let superViewFrame = superview?.frame else { 63 | return 0 64 | } 65 | let visibleVideoFrame = videoFrame.intersection(superViewFrame) 66 | return visibleVideoFrame.size.height 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /videoplayer/ShotTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /videoplayer/VideoPlayLibrary/ASVideoContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoObject.swift 3 | // AutoPlayVideo 4 | // 5 | // Created by Ashish Singh on 12/4/17. 6 | // Copyright © 2017 Ashish. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | class ASVideoContainer { 12 | var url: String 13 | var playOn: Bool { 14 | didSet { 15 | player.isMuted = ASVideoPlayerController.sharedVideoPlayer.mute 16 | playerItem.preferredPeakBitRate = ASVideoPlayerController.sharedVideoPlayer.preferredPeakBitRate 17 | if playOn && playerItem.status == .readyToPlay { 18 | player.play() 19 | } 20 | else{ 21 | player.pause() 22 | } 23 | } 24 | } 25 | 26 | let player: AVPlayer 27 | let playerItem: AVPlayerItem 28 | 29 | init(player: AVPlayer, item: AVPlayerItem, url: String) { 30 | self.player = player 31 | self.playerItem = item 32 | self.url = url 33 | playOn = false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /videoplayer/VideoPlayLibrary/ASVideoLayerObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoLayerObject.swift 3 | // AutoPlayVideo 4 | // 5 | // Created by Ashish Singh on 12/7/17. 6 | // Copyright © 2017 Ashish. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | class ASVideoLayerObject: NSObject { 12 | var layer = AVPlayerLayer() 13 | var used = false 14 | override init() { 15 | layer.backgroundColor = UIColor.clear.cgColor 16 | layer.videoGravity = AVLayerVideoGravity.resize 17 | } 18 | } 19 | 20 | struct VideoLayers { 21 | var layers = Array() 22 | init() { 23 | for _ in 0..<1{ 24 | layers.append(ASVideoLayerObject()) 25 | } 26 | } 27 | 28 | func getLayerForParentLayer(parentLayer: CALayer) -> AVPlayerLayer { 29 | for videoObject in layers { 30 | if videoObject.layer.superlayer == parentLayer { 31 | return videoObject.layer 32 | } 33 | } 34 | return getFreeVideoLayer() 35 | } 36 | 37 | func getFreeVideoLayer() -> AVPlayerLayer { 38 | for videoObject in layers { 39 | if videoObject.used == false { 40 | videoObject.used = true 41 | return videoObject.layer 42 | } 43 | } 44 | return layers[0].layer 45 | } 46 | 47 | func freeLayer(layerToFree: AVPlayerLayer) { 48 | for videoObject in layers { 49 | if videoObject.layer == layerToFree { 50 | videoObject.used = false 51 | videoObject.layer.player = nil 52 | if videoObject.layer.superlayer != nil { 53 | videoObject.layer.removeFromSuperlayer() 54 | } 55 | break 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /videoplayer/VideoPlayLibrary/ASVideoPlayerController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoPlayerController.swift 3 | // AutoPlayVideo 4 | // 5 | // Created by Ashish Singh on 12/3/17. 6 | // Copyright © 2017 Ashish. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | /** 13 | Protocol that needs to be adopted by subclass of any UIView 14 | that wants to play video. 15 | */ 16 | protocol ASAutoPlayVideoLayerContainer { 17 | var videoURL: String? { get set } 18 | var videoLayer: AVPlayerLayer { get set } 19 | func visibleVideoHeight() -> CGFloat 20 | } 21 | 22 | class ASVideoPlayerController: NSObject, NSCacheDelegate { 23 | var minimumLayerHeightToPlay: CGFloat = 60 24 | // Mute unmute video 25 | var mute = false 26 | var preferredPeakBitRate: Double = 1000000 27 | static private var playerViewControllerKVOContext = 0 28 | static let sharedVideoPlayer = ASVideoPlayerController() 29 | //video url for currently playing video 30 | private var videoURL: String? 31 | /** 32 | Stores video url as key and true as value when player item associated to the url 33 | is being observed for its status change. 34 | Helps in removing observers for player items that are not being played. 35 | */ 36 | private var observingURLs = Dictionary() 37 | // Cache of player and player item 38 | private var videoCache = NSCache() 39 | private var videoLayers = VideoLayers() 40 | // Current AVPlapyerLayer that is playing video 41 | private var currentLayer: AVPlayerLayer? 42 | 43 | override init() { 44 | super.init() 45 | videoCache.delegate = self 46 | } 47 | 48 | /** 49 | Download of an asset of url if corresponding videocontainer 50 | is not present. 51 | Uses the asset to create new playeritem. 52 | */ 53 | func setupVideoFor(url: String) { 54 | if self.videoCache.object(forKey: url as NSString) != nil { 55 | return 56 | } 57 | guard let URL = URL(string: url) else { 58 | return 59 | } 60 | let asset = AVURLAsset(url: URL) 61 | let requestedKeys = ["playable"] 62 | asset.loadValuesAsynchronously(forKeys: requestedKeys) { [weak self] in 63 | guard let strongSelf = self else { 64 | return 65 | } 66 | /** 67 | Need to check whether asset loaded successfully, if not successful then don't create 68 | AVPlayer and AVPlayerItem and return without caching the videocontainer, 69 | so that, the assets can be tried to be downloaded again when need be. 70 | */ 71 | var error: NSError? = nil 72 | let status = asset.statusOfValue(forKey: "playable", error: &error) 73 | switch status { 74 | case .loaded: 75 | break 76 | case .failed, .cancelled: 77 | print("Failed to load asset successfully") 78 | return 79 | default: 80 | print("Unkown state of asset") 81 | return 82 | } 83 | let player = AVPlayer() 84 | let item = AVPlayerItem(asset: asset) 85 | DispatchQueue.main.async { 86 | let videoContainer = ASVideoContainer(player: player, item: item, url: url) 87 | strongSelf.videoCache.setObject(videoContainer, forKey: url as NSString) 88 | videoContainer.player.replaceCurrentItem(with: videoContainer.playerItem) 89 | /** 90 | Try to play video again in case when playvideo method was called and 91 | asset was not obtained, so, earlier video must have not run 92 | */ 93 | if strongSelf.videoURL == url, let layer = strongSelf.currentLayer { 94 | strongSelf.playVideo(withLayer: layer, url: url) 95 | } 96 | } 97 | } 98 | } 99 | // Play video with the AVPlayerLayer provided 100 | func playVideo(withLayer layer: AVPlayerLayer, url: String) { 101 | videoURL = url 102 | currentLayer = layer 103 | if let videoContainer = self.videoCache.object(forKey: url as NSString) { 104 | layer.player = videoContainer.player 105 | videoContainer.playOn = true 106 | addObservers(url: url, videoContainer: videoContainer) 107 | } 108 | // Give chance for current video player to be ready to play 109 | DispatchQueue.main.async { 110 | if let videoContainer = self.videoCache.object(forKey: url as NSString), 111 | videoContainer.player.currentItem?.status == .readyToPlay { 112 | videoContainer.playOn = true 113 | } 114 | } 115 | } 116 | 117 | private func pauseVideo(forLayer layer: AVPlayerLayer, url: String) { 118 | videoURL = nil 119 | currentLayer = nil 120 | if let videoContainer = self.videoCache.object(forKey: url as NSString) { 121 | videoContainer.playOn = false 122 | //videoContainer.play = false 123 | removeObserverFor(url: url) 124 | } 125 | } 126 | 127 | func removeLayerFor(cell: ASAutoPlayVideoLayerContainer) { 128 | if let url = cell.videoURL { 129 | removeFromSuperLayer(layer: cell.videoLayer, url: url) 130 | } 131 | } 132 | 133 | private func removeFromSuperLayer(layer: AVPlayerLayer, url: String) { 134 | videoURL = nil 135 | currentLayer = nil 136 | if let videoContainer = self.videoCache.object(forKey: url as NSString) { 137 | videoContainer.playOn = false 138 | removeObserverFor(url: url) 139 | } 140 | layer.player = nil 141 | } 142 | 143 | private func pauseRemoveLayer(layer: AVPlayerLayer,url: String, layerHeight: CGFloat) { 144 | pauseVideo(forLayer: layer, url: url) 145 | } 146 | 147 | // Play video again in case the current player has finished playing 148 | @objc func playerDidFinishPlaying(note: NSNotification) { 149 | guard let playerItem = note.object as? AVPlayerItem, 150 | let currentPlayer = currentVideoContainer()?.player else { 151 | return 152 | } 153 | if let currentItem = currentPlayer.currentItem, currentItem == playerItem { 154 | currentPlayer.seek(to: kCMTimeZero) 155 | currentPlayer.play() 156 | } 157 | } 158 | 159 | private func currentVideoContainer() -> ASVideoContainer? { 160 | if let currentVideoUrl = videoURL { 161 | if let videoContainer = videoCache.object(forKey: currentVideoUrl as NSString) { 162 | return videoContainer 163 | } 164 | } 165 | return nil 166 | } 167 | 168 | private func addObservers(url: String, videoContainer: ASVideoContainer) { 169 | if self.observingURLs[url] == false || self.observingURLs[url] == nil { 170 | videoContainer.player.currentItem?.addObserver(self, 171 | forKeyPath: "status", 172 | options: [.new, .initial], 173 | context: &ASVideoPlayerController.playerViewControllerKVOContext) 174 | NotificationCenter.default.addObserver(self, 175 | selector: #selector(self.playerDidFinishPlaying(note:)), 176 | name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, 177 | object: videoContainer.player.currentItem) 178 | self.observingURLs[url] = true 179 | } 180 | } 181 | 182 | private func removeObserverFor(url: String) { 183 | if let videoContainer = self.videoCache.object(forKey: url as NSString) { 184 | if let currentItem = videoContainer.player.currentItem, observingURLs[url] == true { 185 | currentItem.removeObserver(self, 186 | forKeyPath: "status", 187 | context: &ASVideoPlayerController.playerViewControllerKVOContext) 188 | NotificationCenter.default.removeObserver(self, 189 | name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, 190 | object: currentItem) 191 | observingURLs[url] = false 192 | } 193 | } 194 | } 195 | 196 | /** 197 | Play UITableViewCell's videoplayer that has max visible video layer height 198 | when the scroll stops. 199 | */ 200 | func pausePlayeVideosFor(tableView: UITableView, appEnteredFromBackground: Bool = false) { 201 | let visisbleCells = tableView.visibleCells 202 | var videoCellContainer: ASAutoPlayVideoLayerContainer? 203 | var maxHeight: CGFloat = 0.0 204 | for cellView in visisbleCells { 205 | guard let containerCell = cellView as? ASAutoPlayVideoLayerContainer, 206 | let videoCellURL = containerCell.videoURL else { 207 | continue 208 | } 209 | let height = containerCell.visibleVideoHeight() 210 | if maxHeight < height { 211 | maxHeight = height 212 | videoCellContainer = containerCell 213 | } 214 | pauseRemoveLayer(layer: containerCell.videoLayer, url: videoCellURL, layerHeight: height) 215 | } 216 | guard let videoCell = videoCellContainer, 217 | let videoCellURL = videoCell.videoURL else { 218 | return 219 | } 220 | let minCellLayerHeight = videoCell.videoLayer.bounds.size.height * 0.5 221 | /** 222 | Visible video layer height should be at least more than max of predefined minimum height and 223 | cell's videolayer's 50% height to play video. 224 | */ 225 | let minimumVideoLayerVisibleHeight = max(minCellLayerHeight, minimumLayerHeightToPlay) 226 | if maxHeight > minimumVideoLayerVisibleHeight { 227 | if appEnteredFromBackground { 228 | setupVideoFor(url: videoCellURL) 229 | } 230 | playVideo(withLayer: videoCell.videoLayer, url: videoCellURL) 231 | } 232 | } 233 | 234 | // Set observing urls false when objects are removed from cache 235 | func cache(_ cache: NSCache, willEvictObject obj: Any) { 236 | if let videoObject = obj as? ASVideoContainer { 237 | observingURLs[videoObject.url] = false 238 | } 239 | } 240 | 241 | // Play video only when current videourl's player is ready to play 242 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, 243 | change: [NSKeyValueChangeKey: Any]?, 244 | context: UnsafeMutableRawPointer?) { 245 | // Make sure the this KVO callback was intended for this view controller. 246 | guard context == &ASVideoPlayerController.playerViewControllerKVOContext else { 247 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 248 | return 249 | } 250 | if keyPath == "status" { 251 | /** 252 | Handle `NSNull` value for `NSKeyValueChangeNewKey`, i.e. when 253 | `player.currentItem` is nil. 254 | */ 255 | let newStatus: AVPlayerItemStatus 256 | if let newStatusAsNumber = change?[NSKeyValueChangeKey.newKey] as? NSNumber { 257 | newStatus = AVPlayerItemStatus(rawValue: newStatusAsNumber.intValue)! 258 | if newStatus == .readyToPlay { 259 | guard let item = object as? AVPlayerItem, 260 | let currentItem = currentVideoContainer()?.player.currentItem else { 261 | return 262 | } 263 | if item == currentItem && currentVideoContainer()?.playOn == true { 264 | currentVideoContainer()?.playOn = true 265 | } 266 | } 267 | } 268 | else { 269 | newStatus = .unknown 270 | } 271 | if newStatus == .failed { 272 | 273 | } 274 | } 275 | } 276 | 277 | deinit { 278 | 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /videoplayer/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AutoPlayVideo 4 | // 5 | // Created by Ashish Singh on 7/21/17. 6 | // Copyright © 2017 Ashish. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 12 | let shotTableViewCellIdentifier = "ShotTableViewCell" 13 | let loadingCellTableViewCellCellIdentifier = "LoadingCellTableViewCell" 14 | var refreshControl: UIRefreshControl! 15 | let videos = [ 16 | "https://i.pinimg.com/564x/ba/a3/07/baa307a7de3030f0073c56fa95ab2a3c.jpg", 17 | "https://v.pinimg.com/videos/720p/77/4f/21/774f219598dde62c33389469f5c1b5d1.mp4", 18 | "https://i.pinimg.com/564x/ef/17/51/ef17519f5e473adc01dfd64c35cf44d4.jpg", 19 | "https://i.pinimg.com/564x/6f/5f/fb/6f5ffb82a1f9a9f7e478b8a2486831f5.jpg", 20 | "https://v.pinimg.com/videos/720p/75/40/9a/75409a62e9fb61a10b706d8f0c94de9a.mp4", 21 | "https://v.pinimg.com/videos/720p/0d/29/18/0d2918323789eabdd7a12cdd658eda04.mp4", 22 | "https://i.pinimg.com/564x/97/a5/51/97a5513d3c512eb382e564ba542d917b.jpg", 23 | "https://v.pinimg.com/videos/720p/dd/24/bb/dd24bb9cd68e9e25d1def88cad0a9ea7.mp4", 24 | "https://i.pinimg.com/564x/72/c1/a8/72c1a8aabbfe782643c4a5e739ec0ed2.jpg", 25 | "https://v.pinimg.com/videos/720p/d5/15/78/d51578c69d36c93c6e20144e9f887c73.mp4", 26 | "https://v.pinimg.com/videos/720p/c2/6d/2b/c26d2bacb4a9f6402d2aa0721193e06e.mp4", 27 | "https://i.pinimg.com/564x/e2/fc/bc/e2fcbc98ceeb7d9316f8b4c889440bf7.jpg", 28 | "https://v.pinimg.com/videos/720p/62/81/60/628160e025f9d61b826ecc921b9132cd.mp4", 29 | "https://v.pinimg.com/videos/720p/5f/aa/3d/5faa3d057eb31dd05876f622ea2e7502.mp4", 30 | "https://v.pinimg.com/videos/720p/65/b0/54/65b05496c385c89f79635738adc3b15d.mp4", 31 | "https://i.pinimg.com/564x/3c/52/d3/3c52d31a1b388ea584175f7859fb23e7.jpg", 32 | "https://i.pinimg.com/564x/4c/32/ee/4c32ee62af42bacec8c50ddfd10ade63.jpg", 33 | "https://i.pinimg.com/564x/94/cb/29/94cb29d0279e376c6d89fe9a31191f94.jpg", 34 | "https://v.pinimg.com/videos/720p/86/a1/c6/86a1c63fc58b2e1ef18878b7428912dc.mp4" 35 | ] 36 | 37 | let videoImages = [ 38 | "https://i.pinimg.com/videos/thumbnails/originals/77/4f/21/774f219598dde62c33389469f5c1b5d1-00001.jpg", 39 | "https://i.pinimg.com/videos/thumbnails/originals/75/40/9a/75409a62e9fb61a10b706d8f0c94de9a-00001.jpg", 40 | "https://i.pinimg.com/videos/thumbnails/originals/0d/29/18/0d2918323789eabdd7a12cdd658eda04-00001.jpg", 41 | "https://i.pinimg.com/videos/thumbnails/originals/dd/24/bb/dd24bb9cd68e9e25d1def88cad0a9ea7-00001.jpg", 42 | "https://i.pinimg.com/videos/thumbnails/originals/d5/15/78/d51578c69d36c93c6e20144e9f887c73-00001.jpg", 43 | "https://i.pinimg.com/videos/thumbnails/originals/c2/6d/2b/c26d2bacb4a9f6402d2aa0721193e06e-00001.jpg", 44 | "https://i.pinimg.com/videos/thumbnails/originals/62/81/60/628160e025f9d61b826ecc921b9132cd-00001.jpg", 45 | "https://i.pinimg.com/videos/thumbnails/originals/5f/aa/3d/5faa3d057eb31dd05876f622ea2e7502-00001.jpg", 46 | "https://i.pinimg.com/videos/thumbnails/originals/65/b0/54/65b05496c385c89f79635738adc3b15d-00001.jpg", 47 | "https://i.pinimg.com/videos/thumbnails/originals/86/a1/c6/86a1c63fc58b2e1ef18878b7428912dc-00001.jpg" 48 | ] 49 | let videoIndexes = [1:0, 4:1, 5:2, 7:3, 9:4, 10:5, 12:6, 13:7, 14:8, 18:9] 50 | @IBOutlet var tableView: UITableView! 51 | override func viewDidLoad() { 52 | super.viewDidLoad() 53 | tableView.dataSource = self 54 | tableView.delegate = self 55 | tableView.estimatedRowHeight = 100 56 | tableView.rowHeight = UITableViewAutomaticDimension 57 | var cellNib = UINib(nibName:shotTableViewCellIdentifier, bundle: nil) 58 | self.tableView.register(cellNib, forCellReuseIdentifier: shotTableViewCellIdentifier) 59 | cellNib = UINib(nibName:loadingCellTableViewCellCellIdentifier, bundle: nil) 60 | tableView.register(cellNib, forCellReuseIdentifier: loadingCellTableViewCellCellIdentifier) 61 | tableView.separatorStyle = .none 62 | NotificationCenter.default.addObserver(self, 63 | selector: #selector(self.appEnteredFromBackground), 64 | name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) 65 | 66 | } 67 | 68 | override func viewWillDisappear(_ animated: Bool) { 69 | super.viewWillDisappear(animated) 70 | } 71 | 72 | override func viewWillAppear(_ animated: Bool) { 73 | super.viewWillAppear(animated) 74 | 75 | } 76 | 77 | override func viewDidAppear(_ animated: Bool) { 78 | super.viewDidAppear(animated) 79 | pausePlayeVideos() 80 | } 81 | 82 | func numberOfSections(in tableView: UITableView) -> Int { 83 | return 1 84 | } 85 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 86 | return videos.count 87 | } 88 | 89 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 90 | let cell = tableView.dequeueReusableCell(withIdentifier: shotTableViewCellIdentifier, for: indexPath) as! ShotTableViewCell 91 | if let videoIndex = videoIndexes[indexPath.row] { 92 | cell.configureCell(imageUrl: videoImages[videoIndex], description: "Video", videoUrl: videos[indexPath.row]) 93 | } 94 | else{ 95 | cell.configureCell(imageUrl: videos[indexPath.row], description: "Image", videoUrl: nil) 96 | } 97 | return cell 98 | } 99 | 100 | func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { 101 | if let videoCell = cell as? ASAutoPlayVideoLayerContainer, let _ = videoCell.videoURL { 102 | ASVideoPlayerController.sharedVideoPlayer.removeLayerFor(cell: videoCell) 103 | } 104 | } 105 | 106 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 107 | pausePlayeVideos() 108 | } 109 | 110 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 111 | if !decelerate { 112 | pausePlayeVideos() 113 | } 114 | } 115 | 116 | func pausePlayeVideos(){ 117 | ASVideoPlayerController.sharedVideoPlayer.pausePlayeVideosFor(tableView: tableView) 118 | } 119 | 120 | @objc func appEnteredFromBackground() { 121 | ASVideoPlayerController.sharedVideoPlayer.pausePlayeVideosFor(tableView: tableView, appEnteredFromBackground: true) 122 | } 123 | } 124 | 125 | -------------------------------------------------------------------------------- /videoplayerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /videoplayerTests/videoplayerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // videoplayerTests.swift 3 | // videoplayerTests 4 | // 5 | // Created by ashish singh on 3/8/18. 6 | // Copyright © 2018 ashish. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import videoplayer 11 | 12 | class videoplayerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /videoplayerUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /videoplayerUITests/videoplayerUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // videoplayerUITests.swift 3 | // videoplayerUITests 4 | // 5 | // Created by ashish singh on 3/8/18. 6 | // Copyright © 2018 ashish. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class videoplayerUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | --------------------------------------------------------------------------------