mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
6.5 KiB
Swift
202 lines
6.5 KiB
Swift
//
|
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import AVFoundation
|
|
|
|
@available(iOS 9.0, *)
|
|
@objc
|
|
public class VideoPlayerView: UIView {
|
|
var player: AVPlayer? {
|
|
get {
|
|
return playerLayer.player
|
|
}
|
|
set {
|
|
playerLayer.player = newValue
|
|
}
|
|
}
|
|
|
|
var playerLayer: AVPlayerLayer {
|
|
return layer as! AVPlayerLayer
|
|
}
|
|
|
|
// Override UIView property
|
|
override public static var layerClass: AnyClass {
|
|
return AVPlayerLayer.self
|
|
}
|
|
}
|
|
|
|
@available(iOS 9.0, *)
|
|
@objc
|
|
public protocol PlayerProgressBarDelegate {
|
|
func playerProgressBarDidStartScrubbing(_ playerProgressBar: PlayerProgressBar)
|
|
func playerProgressBar(_ playerProgressBar: PlayerProgressBar, scrubbedToTime time: CMTime)
|
|
func playerProgressBar(_ playerProgressBar: PlayerProgressBar, didFinishScrubbingAtTime time: CMTime, shouldResumePlayback: Bool)
|
|
}
|
|
|
|
@available(iOS 9.0, *)
|
|
@objc
|
|
public class PlayerProgressBar: UIView {
|
|
public let TAG = "[PlayerProgressBar]"
|
|
|
|
@objc
|
|
public weak var delegate: PlayerProgressBarDelegate?
|
|
|
|
private lazy var formatter: DateComponentsFormatter = {
|
|
let formatter = DateComponentsFormatter()
|
|
formatter.unitsStyle = .positional
|
|
formatter.allowedUnits = [.minute, .second ]
|
|
formatter.zeroFormattingBehavior = [ .pad ]
|
|
|
|
return formatter
|
|
}()
|
|
|
|
// MARK: Subviews
|
|
private let positionLabel = UILabel()
|
|
private let remainingLabel = UILabel()
|
|
private let slider = UISlider()
|
|
private let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
|
weak private var progressObserver: AnyObject?
|
|
|
|
private let kPreferredTimeScale: CMTimeScale = 100
|
|
|
|
public var player: AVPlayer? {
|
|
didSet {
|
|
guard let item = player?.currentItem else {
|
|
owsFail("No player item")
|
|
return
|
|
}
|
|
|
|
slider.minimumValue = 0
|
|
|
|
let duration: CMTime = item.asset.duration
|
|
slider.maximumValue = Float(CMTimeGetSeconds(duration))
|
|
|
|
// OPTIMIZE We need a high frequency observer for smooth slider updates,
|
|
// but could use a much less frequent observer for label updates
|
|
progressObserver = player?.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.01, preferredTimescale: kPreferredTimeScale), queue: nil, using: { [weak self] (_) in
|
|
self?.updateState()
|
|
}) as AnyObject
|
|
updateState()
|
|
}
|
|
}
|
|
|
|
required public init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override public init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
|
|
// Background
|
|
backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
|
|
if !UIAccessibilityIsReduceTransparencyEnabled() {
|
|
addSubview(blurEffectView)
|
|
blurEffectView.autoPinToSuperviewEdges()
|
|
}
|
|
|
|
// Configure controls
|
|
|
|
let kLabelFont = UIFont.monospacedDigitSystemFont(ofSize: 12, weight: UIFontWeightRegular)
|
|
positionLabel.font = kLabelFont
|
|
remainingLabel.font = kLabelFont
|
|
|
|
// We use a smaller thumb for the progress slider.
|
|
slider.setThumbImage(#imageLiteral(resourceName: "sliderProgressThumb"), for: .normal)
|
|
slider.maximumTrackTintColor = UIColor.ows_black
|
|
slider.minimumTrackTintColor = UIColor.ows_black
|
|
|
|
slider.addTarget(self, action: #selector(handleSliderTouchDown), for: .touchDown)
|
|
slider.addTarget(self, action: #selector(handleSliderTouchUp), for: .touchUpInside)
|
|
slider.addTarget(self, action: #selector(handleSliderTouchUp), for: .touchUpOutside)
|
|
slider.addTarget(self, action: #selector(handleSliderValueChanged), for: .valueChanged)
|
|
|
|
// Layout Subviews
|
|
|
|
addSubview(positionLabel)
|
|
addSubview(remainingLabel)
|
|
addSubview(slider)
|
|
|
|
positionLabel.autoPinEdge(toSuperviewMargin: .leading)
|
|
positionLabel.autoVCenterInSuperview()
|
|
|
|
let kSliderMargin: CGFloat = 8
|
|
|
|
slider.autoPinEdge(.leading, to: .trailing, of: positionLabel, withOffset: kSliderMargin)
|
|
slider.autoVCenterInSuperview()
|
|
|
|
remainingLabel.autoPinEdge(.leading, to: .trailing, of: slider, withOffset: kSliderMargin)
|
|
remainingLabel.autoPinEdge(toSuperviewMargin: .trailing)
|
|
remainingLabel.autoVCenterInSuperview()
|
|
}
|
|
|
|
// MARK: Gesture handling
|
|
|
|
var wasPlayingWhenScrubbingStarted: Bool = false
|
|
|
|
@objc
|
|
private func handleSliderTouchDown(_ slider: UISlider) {
|
|
guard let player = self.player else {
|
|
owsFail("player was nil")
|
|
return
|
|
}
|
|
|
|
self.wasPlayingWhenScrubbingStarted = (player.rate != 0) && (player.error == nil)
|
|
|
|
self.delegate?.playerProgressBarDidStartScrubbing(self)
|
|
}
|
|
|
|
@objc
|
|
private func handleSliderTouchUp(_ slider: UISlider) {
|
|
let sliderTime = time(slider: slider)
|
|
self.delegate?.playerProgressBar(self, didFinishScrubbingAtTime: sliderTime, shouldResumePlayback:wasPlayingWhenScrubbingStarted)
|
|
}
|
|
|
|
@objc
|
|
private func handleSliderValueChanged(_ slider: UISlider) {
|
|
let sliderTime = time(slider: slider)
|
|
self.delegate?.playerProgressBar(self, scrubbedToTime: sliderTime)
|
|
}
|
|
|
|
// MARK: Render cycle
|
|
|
|
private func updateState() {
|
|
guard let player = player else {
|
|
owsFail("\(TAG) player isn't set.")
|
|
return
|
|
}
|
|
|
|
guard let item = player.currentItem else {
|
|
owsFail("\(TAG) player has no item.")
|
|
return
|
|
}
|
|
|
|
let position = player.currentTime()
|
|
let positionSeconds: Float64 = CMTimeGetSeconds(position)
|
|
positionLabel.text = formatter.string(from: positionSeconds)
|
|
|
|
let duration: CMTime = item.asset.duration
|
|
let remainingTime = duration - position
|
|
let remainingSeconds = CMTimeGetSeconds(remainingTime)
|
|
|
|
guard let remainingString = formatter.string(from: remainingSeconds) else {
|
|
owsFail("unable to format time remaining")
|
|
remainingLabel.text = "0:00"
|
|
return
|
|
}
|
|
|
|
// show remaining time as negative
|
|
remainingLabel.text = "-\(remainingString)"
|
|
|
|
slider.setValue(Float(positionSeconds), animated: false)
|
|
}
|
|
|
|
// MARK: Util
|
|
|
|
private func time(slider: UISlider) -> CMTime {
|
|
let seconds: Double = Double(slider.value)
|
|
return CMTime(seconds: seconds, preferredTimescale: kPreferredTimeScale)
|
|
}
|
|
}
|