Last active
July 22, 2023 06:56
-
-
Save freshking/eccd41361edd9381a81bbeccce3a97fb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import AVKit | |
class VideoView: UIView { | |
// video layer that will stream the url | |
private let videoLayer = AVPlayerLayer() | |
// pan gesture used for scrubbing | |
private let panGesture = UIPanGestureRecognizer() | |
// time when scrubbing began | |
private var scrubbingBeginTime: CMTime? | |
// setup view | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
// self | |
backgroundColor = .black | |
// setup videoPlayer | |
videoLayer.videoGravity = .resizeAspect | |
layer.addSublayer(videoLayer) | |
// setup panGesture | |
panGesture.minimumNumberOfTouches = 1 | |
panGesture.maximumNumberOfTouches = 1 | |
panGesture.cancelsTouchesInView = true | |
panGesture.addTarget(self, action: #selector(didScrub(recognizer:))) | |
addGestureRecognizer(panGesture) | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func layoutSublayers(of layer: CALayer) { | |
super.layoutSublayers(of: layer) | |
// resize videoLayer frame to fit bounds | |
videoLayer.frame = bounds | |
} | |
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { | |
if gestureRecognizer == panGesture { | |
// only allow pan gesture to begin when panning horizontally | |
let translation = panGesture.translation(in: self) | |
return abs(translation.x) >= 0 && abs(translation.x) > abs(translation.y) | |
} | |
return super.gestureRecognizerShouldBegin(gestureRecognizer) | |
} | |
func beginStreaming() { | |
// setup player for videoLayer | |
let url = URL(string: "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8")! | |
let player = AVPlayer(url: url) | |
videoLayer.player = player | |
// begin playback | |
videoLayer.player?.play() | |
} | |
@objc private func didScrub(recognizer: UIPanGestureRecognizer) { | |
guard videoLayer.isReadyForDisplay == true, let player = videoLayer.player, let currentItem = player.currentItem else { | |
return | |
} | |
switch recognizer.state { | |
case .possible: | |
// nothing to do here | |
break | |
case .began: | |
// pause playback when user begins panning | |
videoLayer.player?.pause() | |
// set time scrubbing began | |
scrubbingBeginTime = currentItem.currentTime() | |
case .changed: | |
guard let scrubbingBeginTime = scrubbingBeginTime else { | |
return | |
} | |
let totalSeconds = currentItem.duration.seconds | |
// translate point of pan in view | |
let point = recognizer.translation(in: self) | |
let scrubbingBeginPercent = Double(scrubbingBeginTime.seconds/totalSeconds) | |
// calculate percentage of point in view | |
var percent = Double(point.x/bounds.width) | |
percent += scrubbingBeginPercent | |
if percent < 0 { | |
percent = 0 | |
} else if percent > 1.0 { | |
percent = 1.0 | |
} | |
// calculate time to seek to in video timeline | |
let seconds = Float64(percent * totalSeconds) | |
let time = CMTimeMakeWithSeconds(seconds, preferredTimescale: currentItem.duration.timescale) | |
player.seek(to: time, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) | |
case .ended, .cancelled, .failed: | |
// reset scrubbing begin time | |
scrubbingBeginTime = nil | |
// resume playback after user stops panning | |
videoLayer.player?.play() | |
@unknown default: | |
break | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment