Projects/VideoLooper/VideoLooper/PlayerLooper.swift
/*  | 
Copyright (C) 2016 Apple Inc. All Rights Reserved.  | 
See LICENSE.txt for this sample’s licensing information  | 
Abstract:  | 
An object that uses AVPlayerLooper to loop a video.  | 
*/  | 
import UIKit  | 
import AVFoundation  | 
class PlayerLooper: NSObject, Looper { | 
// MARK: Types  | 
    private struct ObserverContexts { | 
static var isLooping = 0  | 
static var isLoopingKey = "isLooping"  | 
static var loopCount = 0  | 
static var loopCountKey = "loopCount"  | 
static var playerItemDurationKey = "duration"  | 
}  | 
// MARK: Properties  | 
private var player: AVQueuePlayer?  | 
private var playerLayer: AVPlayerLayer?  | 
private var playerLooper: AVPlayerLooper?  | 
private var isObserving = false  | 
private let numberOfTimesToPlay: Int  | 
private let videoURL: URL  | 
// MARK: Looper  | 
    required init(videoURL: URL, loopCount: Int) { | 
self.videoURL = videoURL  | 
self.numberOfTimesToPlay = loopCount  | 
super.init()  | 
}  | 
    func start(in parentLayer: CALayer) { | 
player = AVQueuePlayer()  | 
playerLayer = AVPlayerLayer(player: player)  | 
        guard let playerLayer = playerLayer else { fatalError("Error creating player layer") } | 
playerLayer.frame = parentLayer.bounds  | 
parentLayer.addSublayer(playerLayer)  | 
let playerItem = AVPlayerItem(url: videoURL)  | 
        playerItem.asset.loadValuesAsynchronously(forKeys: [ObserverContexts.playerItemDurationKey], completionHandler: {()->Void in | 
/*  | 
The asset invokes its completion handler on an arbitrary queue when  | 
loading is complete. Because we want to access our AVPlayerLooper  | 
in our ensuing set-up, we must dispatch our handler to the main queue.  | 
*/  | 
            DispatchQueue.main.async(execute: { | 
                guard let player = self.player else { return } | 
var durationError: NSError? = nil  | 
let durationStatus = playerItem.asset.statusOfValue(forKey: ObserverContexts.playerItemDurationKey, error: &durationError)  | 
                guard durationStatus == .loaded else { fatalError("Failed to load duration property with error: \(durationError)") } | 
self.playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)  | 
self.startObserving()  | 
player.play()  | 
})  | 
})  | 
}  | 
    func stop() { | 
player?.pause()  | 
stopObserving()  | 
playerLooper?.disableLooping()  | 
playerLooper = nil  | 
playerLayer?.removeFromSuperlayer()  | 
playerLayer = nil  | 
player = nil  | 
}  | 
// MARK: Convenience  | 
    private func startObserving() { | 
        guard let playerLooper = playerLooper, !isObserving else { return } | 
playerLooper.addObserver(self, forKeyPath: ObserverContexts.isLoopingKey, options: .new, context: &ObserverContexts.isLooping)  | 
playerLooper.addObserver(self, forKeyPath: ObserverContexts.loopCountKey, options: .new, context: &ObserverContexts.loopCount)  | 
isObserving = true  | 
}  | 
    private func stopObserving() { | 
        guard let playerLooper = playerLooper, isObserving else { return } | 
playerLooper.removeObserver(self, forKeyPath: ObserverContexts.isLoopingKey, context: &ObserverContexts.isLooping)  | 
playerLooper.removeObserver(self, forKeyPath: ObserverContexts.loopCountKey, context: &ObserverContexts.loopCount)  | 
isObserving = false  | 
}  | 
// MARK: KVO  | 
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { | 
        if context == &ObserverContexts.isLooping { | 
            if let loopingStatus = change?[.newKey] as? Bool, !loopingStatus { | 
                print("Looping ended due to an error") | 
}  | 
}  | 
        else if context == &ObserverContexts.loopCount { | 
            guard let playerLooper = playerLooper else { return } | 
            if numberOfTimesToPlay > 0 && playerLooper.loopCount >= numberOfTimesToPlay - 1 { | 
                print("Exceeded loop limit of \(numberOfTimesToPlay) and disabling looping"); | 
stopObserving()  | 
playerLooper.disableLooping()  | 
}  | 
}  | 
        else { | 
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)  | 
}  | 
}  | 
}  | 
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13