In my iOS app to communicate with my external accessory to play video from stream

676 Views Asked by At

In my iOS application, I need to play video using external accessory framework. The framework will provide the stream over a delegate method. But i couldnot find a player which plays from byte stream. Please recommend the best way to approach this problem

  • (void)stream:(NSStream*)theStream handleEvent:(NSStreamEvent)streamEvent { }
1

There are 1 best solutions below

3
Eduardo Rodriguez On

There are two approaches to this, the first one is to save the byte stream in an local temporal file and then play it with AVAsset.

let url = URL(fileURLWithPath: filePath)
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: ["playable"])
player = AVPlayer(playerItem: playerItem)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
playerLayer.videoGravity = .resizeAspect
view.layer.addSublayer(playerLayer)

If you don't want to save the video in the device you can make a HTTPServer within your app and use AVURLAsset to play it with AVPlayer. Here is a example using GCD Web Server, please notice that the server needs to accept ranges requests in order to work perfectly with AVPlayer and .

self.webServer = GCDWebServer()
self.webServer!.addDefaultHandler(forMethod: "GET", request: GCDWebServerRequest.self, asyncProcessBlock: {(request,completionBlock) in
    let path = Bundle.main.path(forResource: "sample", ofType: "mp4")
    var attr:[FileAttributeKey : Any]
    do {
        attr = try FileManager.default.attributesOfItem(atPath: path!)
    }
    catch{
        completionBlock(GCDWebServerResponse(statusCode: 500))
        return
    }

    let fileSize = attr[FileAttributeKey.size] as! UInt64
    if (request.hasByteRange()){
        let handler = FileHandle.init(forReadingAtPath: path!)
        handler?.seek(toFileOffset: UInt64(request.byteRange.lowerBound))
        var response:GCDWebServerDataResponse
        if(request.byteRange.upperBound <= -1){
            response = GCDWebServerDataResponse(data: (handler?.readDataToEndOfFile())!, contentType: "video/mp4")
        }
        else{
            response = GCDWebServerDataResponse(data: (handler?.readData(ofLength: request.byteRange.length))!, contentType: "video/mp4")
        }

        response.statusCode = 206
        response.setValue("bytes \(request.byteRange.lowerBound)-\(request.byteRange.upperBound-1)/\(fileSize)", forAdditionalHeader: "Content-Range")
        response.setValue("bytes", forAdditionalHeader: "Accept-Ranges")
        response.setValue("\(request.byteRange.length)", forAdditionalHeader: "Content-Length")
        response.setValue("video/mp4", forAdditionalHeader: "Content-Type")
        response.setValue("keep-alive", forAdditionalHeader: "Connection")
        completionBlock(response)
        return
    }
    else {
        let handler = FileHandle.init(forReadingAtPath: path!)
        let response = GCDWebServerDataResponse(data: (handler?.readDataToEndOfFile())!, contentType: "video/mp4")

        response.setValue("bytes", forAdditionalHeader: "Accept-Ranges")
        response.setValue("keep-alive", forAdditionalHeader: "Connection")
        completionBlock(response)
        return
    }
})
self.webServer!.start(withPort: 8080, bonjourName: "")
GCDWebServer.setLogLevel(0)

Then you can set your AVPlayer to consume the local web server to play the video.

let asset = AVURLAsset(url: URL(string: "http://localhost:8080/")!)
let playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: ["playable"])
player = AVPlayer(playerItem: playerItem)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = CGRect(x: 0, y: 0, width: viewPlayer.frame.width, height: viewPlayer.frame.height)
playerLayer.videoGravity = .resizeAspect
viewPlayer.layer.addSublayer(playerLayer)
self.view.layoutIfNeeded()
player!.play()