mirror of https://github.com/oxen-io/session-ios
parent
918e3f7dfe
commit
86d61eee30
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "VideoPlayer_Slider_Thumb_15x15_@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "VideoPlayer_Slider_Thumb_15x15_@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "VideoPlayer_Slider_Thumb_15x15_@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
@ -0,0 +1,200 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue