[ios] How to play a local video with Swift?

gbk's solution in swift 3

PlayerView

import AVFoundation
import UIKit

class PlayerView: UIView {

override class var layerClass: AnyClass {
    return AVPlayerLayer.self
}

var player:AVPlayer? {
    set {
        if let layer = layer as? AVPlayerLayer {
            layer.player = player
        }
    }
    get {
        if let layer = layer as? AVPlayerLayer {
            return layer.player
        } else {
            return nil
        }
    }
}
}

VideoPlayer

import AVFoundation
import Foundation

protocol VideoPlayerDelegate {
    func downloadedProgress(progress:Double)
    func readyToPlay()
    func didUpdateProgress(progress:Double)
    func didFinishPlayItem()
    func didFailPlayToEnd()
}

let videoContext:UnsafeMutableRawPointer? = nil

class VideoPlayer : NSObject {



private var assetPlayer:AVPlayer?
private var playerItem:AVPlayerItem?
private var urlAsset:AVURLAsset?
private var videoOutput:AVPlayerItemVideoOutput?

private var assetDuration:Double = 0
private var playerView:PlayerView?

private var autoRepeatPlay:Bool = true
private var autoPlay:Bool = true

var delegate:VideoPlayerDelegate?

var playerRate:Float = 1 {
    didSet {
        if let player = assetPlayer {
            player.rate = playerRate > 0 ? playerRate : 0.0
        }
    }
}

var volume:Float = 1.0 {
    didSet {
        if let player = assetPlayer {
            player.volume = volume > 0 ? volume : 0.0
        }
    }
}

// MARK: - Init

convenience init(urlAsset: URL, view:PlayerView, startAutoPlay:Bool = true, repeatAfterEnd:Bool = true) {
    self.init()

    playerView = view
    autoPlay = startAutoPlay
    autoRepeatPlay = repeatAfterEnd

    if let playView = playerView, let playerLayer = playView.layer as? AVPlayerLayer {
        playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
    }
    initialSetupWithURL(url: urlAsset)
    prepareToPlay()
}

override init() {
    super.init()
}

// MARK: - Public

func isPlaying() -> Bool {
    if let player = assetPlayer {
        return player.rate > 0
    } else {
        return false
    }
}

func seekToPosition(seconds:Float64) {
    if let player = assetPlayer {
        pause()
        if let timeScale = player.currentItem?.asset.duration.timescale {
            player.seek(to: CMTimeMakeWithSeconds(seconds, timeScale), completionHandler: { (complete) in
                self.play()
            })
        }
    }
}

func pause() {
    if let player = assetPlayer {
        player.pause()
    }
}

func play() {
    if let player = assetPlayer {
        if (player.currentItem?.status == .readyToPlay) {
            player.play()
            player.rate = playerRate
        }
    }
}

func cleanUp() {
    if let item = playerItem {
        item.removeObserver(self, forKeyPath: "status")
        item.removeObserver(self, forKeyPath: "loadedTimeRanges")
    }
    NotificationCenter.default.removeObserver(self)
    assetPlayer = nil
    playerItem = nil
    urlAsset = nil
}

// MARK: - Private

private func prepareToPlay() {
    let keys = ["tracks"]
    if let asset = urlAsset {
        asset.loadValuesAsynchronously(forKeys: keys, completionHandler: {
            DispatchQueue.main.async {
                self.startLoading()
            }
        })
    }
}

private func startLoading(){
    var error:NSError?
    guard let asset = urlAsset else {return}
    let status:AVKeyValueStatus = asset.statusOfValue(forKey: "tracks", error: &error)

    if status == AVKeyValueStatus.loaded {
        assetDuration = CMTimeGetSeconds(asset.duration)

        let videoOutputOptions = [kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)]
        videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: videoOutputOptions)
        playerItem = AVPlayerItem(asset: asset)

        if let item = playerItem {
            item.addObserver(self, forKeyPath: "status", options: .initial, context: videoContext)
            item.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.new, .old], context: videoContext)

            NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
            NotificationCenter.default.addObserver(self, selector: #selector(didFailedToPlayToEnd), name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object: nil)

            if let output = videoOutput {
                item.add(output)

                item.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed
                assetPlayer = AVPlayer(playerItem: item)

                if let player = assetPlayer {
                    player.rate = playerRate
                }

                addPeriodicalObserver()
                if let playView = playerView, let layer = playView.layer as? AVPlayerLayer {
                    layer.player = assetPlayer
                    print("player created")
                }
            }
        }
    }
}

private func addPeriodicalObserver() {
    let timeInterval = CMTimeMake(1, 1)

    if let player = assetPlayer {
        player.addPeriodicTimeObserver(forInterval: timeInterval, queue: DispatchQueue.main, using: { (time) in
            self.playerDidChangeTime(time: time)
        })
    }
}

private func playerDidChangeTime(time:CMTime) {
    if let player = assetPlayer {
        let timeNow = CMTimeGetSeconds(player.currentTime())
        let progress = timeNow / assetDuration

        delegate?.didUpdateProgress(progress: progress)
    }
}

@objc private func playerItemDidReachEnd() {
    delegate?.didFinishPlayItem()

    if let player = assetPlayer {
        player.seek(to: kCMTimeZero)
        if autoRepeatPlay == true {
            play()
        }
    }
}

@objc private func didFailedToPlayToEnd() {
    delegate?.didFailPlayToEnd()
}

private func playerDidChangeStatus(status:AVPlayerStatus) {
    if status == .failed {
        print("Failed to load video")
    } else if status == .readyToPlay, let player = assetPlayer {
        volume = player.volume
        delegate?.readyToPlay()

        if autoPlay == true && player.rate == 0.0 {
            play()
        }
    }
}

private func moviewPlayerLoadedTimeRangeDidUpdated(ranges:Array<NSValue>) {
    var maximum:TimeInterval = 0
    for value in ranges {
        let range:CMTimeRange = value.timeRangeValue
        let currentLoadedTimeRange = CMTimeGetSeconds(range.start) + CMTimeGetSeconds(range.duration)
        if currentLoadedTimeRange > maximum {
            maximum = currentLoadedTimeRange
        }
    }
    let progress:Double = assetDuration == 0 ? 0.0 : Double(maximum) / assetDuration

    delegate?.downloadedProgress(progress: progress)
}

deinit {
    cleanUp()
}

private func initialSetupWithURL(url: URL) {
    let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true]
    urlAsset = AVURLAsset(url: url, options: options)
}

// MARK: - Observations

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if context == videoContext {
        if let key = keyPath {
            if key == "status", let player = assetPlayer {
                playerDidChangeStatus(status: player.status)
            } else if key == "loadedTimeRanges", let item = playerItem {
                moviewPlayerLoadedTimeRangeDidUpdated(ranges: item.loadedTimeRanges)
            }
        }
    }
}
}

Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to objective-c

Adding a UISegmentedControl to UITableView Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Warp \ bend effect on a UIView? Use NSInteger as array index Detect if the device is iPhone X Linker Command failed with exit code 1 (use -v to see invocation), Xcode 8, Swift 3 ITSAppUsesNonExemptEncryption export compliance while internal testing? How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem? Change status bar text color to light in iOS 9 with Objective-C

Examples related to swift

Make a VStack fill the width of the screen in SwiftUI Xcode 10.2.1 Command PhaseScriptExecution failed with a nonzero exit code Command CompileSwift failed with a nonzero exit code in Xcode 10 Convert Json string to Json object in Swift 4 iOS Swift - Get the Current Local Time and Date Timestamp Xcode 9 Swift Language Version (SWIFT_VERSION) How do I use Safe Area Layout programmatically? How can I use String substring in Swift 4? 'substring(to:)' is deprecated: Please use String slicing subscript with a 'partial range from' operator Safe Area of Xcode 9 The use of Swift 3 @objc inference in Swift 4 mode is deprecated?

Examples related to avplayer

How to play a local video with Swift? Check play state of AVPlayer

Examples related to avplayerviewcontroller

How to play video with AVPlayerViewController (AVKit) in Swift How to play a local video with Swift?