├── README.md └── FSVideoView.swift /README.md: -------------------------------------------------------------------------------- 1 | # FSVideoView - UI element support easy video playback. 2 | 3 | ## Updated: I plan to not keep it as a file. I gave up. Will make it a Cocoapods and Carthage support framework 4 | 5 | Use a video as a background is more commonly 6 | on many app nowadays .There are many different solutions, 7 | some may use gif and some may use video.For video, 8 | some choose to play directly through AVPlayerlayer, 9 | but we chose GLKView because it's more flexible, 10 | both on functionality and performance. It cost 11 | a little bit more cpu than AVPlayer but it's fun 12 | to have many interesting effect by code. 13 | 14 | **We now support loop. You can add the videos to play, we will loop them as default. 15 | 16 | **We don't support sound. ( May be in the coming future we see a need)** 17 | 18 | 19 | Also we allow you to add simple filter to the 20 | video at real time.Because the video is finally 21 | render by an CIImage object, you just have to 22 | handle the CIImage as usually like adding an 23 | filter to it, chain them up... 24 | 25 | let videoView = FSVideoView(frame: view.bounds) 26 | var controlFlag = 0 27 | videoView.filter = { image -> CIImage in 28 | controlFlag++ 29 | if controlFlag % 10 > 5 { 30 | let filter = CIFilter(name: "CIColorInvert", withInputParameters: ["inputImage":image])! 31 | return filter.outputImage! 32 | } 33 | let filter = CIFilter(name: "CIColorClamp", withInputParameters: ["inputImage":image,"inputMinComponents":CIVector(CGRect: CGRect(x: 0.1, y: 0.1, width: 0.3, height: 0)),"inputMaxComponents":CIVector(CGRect: CGRectMake(0.5, 0.7, 0.9, 1))])! 34 | return filter.outputImage! 35 | } 36 | view.addSubview(videoView) 37 | view.sendSubviewToBack(videoView) 38 | do { 39 | try videoView.playVideos([path,path2],fps: 25,loop: true) 40 | videoView.play() 41 | }catch _ { 42 | 43 | } 44 | 45 | Have fun! 46 | 47 | ***Help us to improve this element if it will be fun for you.*** 48 | -------------------------------------------------------------------------------- /FSVideoView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FSVideoView.swift 3 | // 4 | // Copyright (c) <2015> 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the 11 | // Software 12 | // is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | // FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | import UIKit 28 | import AVFoundation 29 | import MobileCoreServices 30 | import GLKit 31 | 32 | /// FSVideoView using an underlay GLKView to draw each frame of the 33 | /// video by rendering of CIContext from an CIImage. We only aspect a simple 34 | /// video play back for special use, so we don't provide the audio play back 35 | /// right now. Because we render the video through CIImage, we also provide 36 | /// a filter parameter that let you to put some effect if you like. 37 | public class FSVideoView:UIView{ 38 | 39 | public var filter : (CIImage -> CIImage)? 40 | 41 | private let glContext = EAGLContext(API: EAGLRenderingAPI.OpenGLES2) 42 | private var completion : ((Bool)->())? 43 | /// Loop the videos from input sources 44 | private var loop = false 45 | private var pauseFlag = false 46 | private var videoUrls = [NSURL]() 47 | private var fps : Int64 = 25 48 | 49 | private lazy var glView : GLKView = { 50 | GLKView(frame: self.bounds, context: self.glContext) 51 | }() 52 | 53 | private lazy var rendererContext:CIContext = { 54 | CIContext(EAGLContext: self.glContext) 55 | }() 56 | 57 | override public func didMoveToSuperview() { 58 | glView.bindDrawable() 59 | addSubview(glView) 60 | } 61 | 62 | public func playVideos(urls:[NSURL],fps:Int64 = 24,loop:Bool = false, completion:((Bool)->())? = nil)throws{ 63 | self.loop = loop 64 | self.fps = fps 65 | if !loop { 66 | self.completion = completion 67 | } 68 | videoUrls.removeAll() 69 | videoUrls.appendContentsOf(urls) 70 | } 71 | 72 | private func playVideosInUrls(){ 73 | func playUrlAtIndex(index:Int){ 74 | if videoUrls.count <= 0 { 75 | return 76 | } 77 | if pauseFlag { 78 | return 79 | } 80 | if index <= videoUrls.count - 1{ 81 | do { 82 | try _playVideo(videoUrls[index], completion: { (f) -> () in 83 | if f { 84 | playUrlAtIndex(index + 1) 85 | } 86 | }) 87 | }catch _ { 88 | print("FSVideoBackgroundView: Error occur when try to play file at index:\(index)") 89 | } 90 | }else{ 91 | if loop { 92 | playUrlAtIndex(0) 93 | }else{ 94 | completion?(true) 95 | } 96 | } 97 | } 98 | playUrlAtIndex(0) 99 | } 100 | 101 | /// Don't set fps higher than 25 since it's meaningless and will take away 102 | /// the help of increase performance. 103 | /// completion will only execute when video mode is not loop. 104 | public func playVideo(of url:NSURL,fps:Int64 = 24,loop:Bool = false,completion:((Bool)->())? = nil)throws{ 105 | self.loop = loop 106 | self.fps = fps 107 | self.completion = completion 108 | videoUrls.removeAll() 109 | videoUrls.append(url) 110 | } 111 | 112 | public func play(){ 113 | playVideosInUrls() 114 | } 115 | 116 | public func pause(){ 117 | pauseFlag = true 118 | } 119 | 120 | public func stop(){ 121 | completion = nil 122 | dispatch_source = nil 123 | dispatch_block_cancel(renderBlock) 124 | reader.cancelReading() 125 | reader = nil 126 | } 127 | 128 | private var dispatch_source : dispatch_source_t! 129 | private var reader : AVAssetReader! 130 | private var renderBlock : dispatch_block_t! 131 | internal func _playVideo(url:NSURL,completion:((Bool)->())? = nil) throws{ 132 | //load an asset to play from url 133 | let asset = AVAsset(URL: url) 134 | // loading up the first track from an video file 135 | let track = asset.tracksWithMediaType(AVMediaTypeVideo)[0] 136 | 137 | do{ 138 | reader = try AVAssetReader(asset: asset) 139 | }catch let err{ 140 | print("Error Located in FSVideoBackgroundView") 141 | completion?(false) 142 | throw err 143 | } 144 | 145 | let setting = [kCVPixelBufferPixelFormatTypeKey as String: 146 | NSNumber(unsignedInt: kCVPixelFormatType_32BGRA)] 147 | let output = AVAssetReaderTrackOutput(track: track, 148 | outputSettings: setting) 149 | reader.addOutput(output) 150 | // If output catch sample buffer before start reading, will raise an 151 | // exception 152 | reader.startReading() 153 | 154 | //FIXME: add 5 fps to tempory fix the latency inssus, will be fix soon 155 | let deltaPerFrame = UInt64((1 / Double(fps)) * 1000000000) 156 | let drawBounds = CGRect(x: 0, y: 0, width: glView.drawableWidth, height: glView.drawableHeight) 157 | 158 | let renderQueue = dispatch_queue_create("com.FSVideoView.renderQueue", DISPATCH_QUEUE_SERIAL) 159 | let dispatch_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, renderQueue) 160 | dispatch_source_set_timer(dispatch_source, DISPATCH_TIME_NOW, deltaPerFrame, 0) 161 | // Prepare resources to reduce alloc in realtime 162 | let drawingWidth = glView.drawableWidth 163 | let drawingHeight = glView.drawableHeight 164 | let drawingBounds = CGRect(x: 0, y: 0, width: drawingWidth, height: drawingHeight) 165 | let viewAR = drawingBounds.width / drawingBounds.height 166 | renderBlock = dispatch_block_create(DISPATCH_BLOCK_BARRIER, { [unowned self, glView = self.glView] () -> Void in 167 | // Return flase only when output buffer is nil because an absense 168 | // of imageBuffer may due to the current frame is empty. 169 | if self.reader.canAddOutput(output){ 170 | self.reader.addOutput(output) 171 | } 172 | 173 | guard let buffer = output.copyNextSampleBuffer() else{ 174 | dispatch_source_cancel(dispatch_source) 175 | if self.reader.status == .Completed{ 176 | completion?(true) 177 | }else{ 178 | completion?(false) 179 | } 180 | return 181 | } 182 | 183 | guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else{ 184 | return 185 | } 186 | 187 | // For supporting iOS8 and it didn't support initial CIImage 188 | // with CVImageBuffer yet, we have to do it manually by converting 189 | // an CVImageBuffer to CVPixelBuffer. 190 | let opaque = Unmanaged.passUnretained(imageBuffer).toOpaque() 191 | let pixelBuffer = Unmanaged.fromOpaque(opaque).takeUnretainedValue() 192 | 193 | var image = CIImage(CVPixelBuffer: pixelBuffer) 194 | // Calculating Draw Rect 195 | if let filter = self.filter { 196 | image = filter(image) 197 | } 198 | 199 | var drawFrame = image.extent 200 | let imageAR = drawFrame.width / drawFrame.height 201 | 202 | if imageAR < viewAR { 203 | let finalHeight = imageAR / viewAR * drawFrame.height 204 | let finalY = (drawFrame.height - finalHeight) / 2 205 | drawFrame.size.height = finalHeight 206 | drawFrame.origin.y = finalY 207 | }else{ 208 | let finalWidth = imageAR / viewAR * drawFrame.width 209 | let finalX = (drawFrame.width - finalWidth) / 2 210 | drawFrame.size.width = finalWidth 211 | drawFrame.origin.x = finalX 212 | } 213 | 214 | if glView.context != EAGLContext.currentContext(){ 215 | EAGLContext.setCurrentContext(self.glView.context) 216 | } 217 | 218 | glView.bindDrawable() 219 | glClearColor(0.5, 0.5, 0.5, 1) 220 | glClear(0x00000000) // make it long so easy to see 221 | glEnable(UInt32(GL_BLEND)) // constant value of 0x0BE2 222 | glBlendFunc(0x1, UInt32(GL_ONE_MINUS_SRC_ALPHA)) 223 | self.rendererContext.drawImage(image, inRect: drawBounds, fromRect: drawFrame) 224 | glView.display() 225 | 226 | }) 227 | 228 | 229 | dispatch_source_set_event_handler(dispatch_source, renderBlock) 230 | dispatch_resume(dispatch_source) 231 | 232 | } 233 | 234 | } 235 | 236 | --------------------------------------------------------------------------------