123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- //
- // TSVideoPlayerVC.swift
- // AIEmoji
- //
- // Created by 100Years on 2025/4/17.
- //
- import UIKit
- import AVKit
- import SnapKit
- class TSAIListVideoPlayerVC: UIViewController {
-
- // MARK: - Properties
- private var player: AVPlayer?
- private var playerLayer: AVPlayerLayer?
- private var timeObserverToken: Any?
- private var isPlaying = false{
- didSet{
- playPauseButton.setImage(isPlaying ? .videoPause : .videoPlay, for: .normal)
- }
- }
-
- private let videoURL: URL
-
-
- public var isRunloppPlay:Bool = false
-
- // MARK: - UI Components
- private lazy var playerContainerView: UIView = {
- let view = UIView()
- view.backgroundColor = .clear
- return view
- }()
-
- private lazy var playPauseButton: TSUIExpandedTouchButton = {
- let button = TSUIExpandedTouchButton()
- button.setImage(.videoPlay, for: .normal)
- button.tintColor = .white
- button.addTarget(self, action: #selector(playPauseTapped), for: .touchUpInside)
- return button
- }()
-
- private lazy var progressSlider: TSProgressSlider = {
- let slider = TSProgressSlider()
- slider.minimumTrackTintColor = UIColor.themeColor
- slider.maximumTrackTintColor = .white.withAlphaComponent(0.2)
- slider.setThumbImage(UIImage.circle(diameter: 10, color: .white), for: .normal)
- slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
- slider.addTarget(self, action: #selector(sliderTouchEnded(_:)), for: .touchUpInside)
- slider.addTarget(self, action: #selector(sliderTouchEnded(_:)), for: .touchUpOutside)
- return slider
- }()
-
- private lazy var currentTimeLabel: UILabel = {
- let label = UILabel.createLabel(text: "00:00",font:.font(size: 12),textColor: .white)
- return label
- }()
-
- private lazy var durationLabel: UILabel = {
- let label = UILabel.createLabel(text: "00:00",font:.font(size: 12),textColor: .white)
- return label
- }()
- private lazy var controlsContainerView: UIView = {
- let view = UIView()
- return view
- }()
-
- // MARK: - Initialization
- init(videoURL: URL) {
- self.videoURL = videoURL
- super.init(nibName: nil, bundle: nil)
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- // MARK: - Lifecycle
- override func viewDidLoad() {
- super.viewDidLoad()
- setupUI()
- setupPlayer()
-
-
- dealThings()
- }
-
- func dealThings() {
- // 监听应用生命周期事件
- NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { _ in
- self.playPause()
- self.setControlsView(isHidden: false)
- }
- NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { _ in }
- }
-
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- playerLayer?.frame = playerContainerView.bounds
- }
-
- deinit {
- removePeriodicTimeObserver()
- }
-
- @objc private func clickBgView() {
- setControlsView(isHidden: !controlsContainerView.isHidden)
- }
-
- func setControlsView(isHidden:Bool) {
- playPauseButton.isHidden = isHidden
- controlsContainerView.isHidden = isHidden
- }
-
- // MARK: - Setup
- private func setupUI() {
- view.backgroundColor = .clear
- playerContainerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(clickBgView)))
- view.addSubview(playerContainerView)
-
- playerContainerView.snp.makeConstraints { make in
- make.edges.equalToSuperview()
- }
-
-
- playerContainerView.addSubview(playPauseButton)
- playPauseButton.snp.makeConstraints { make in
- make.centerX.equalToSuperview()
- make.centerY.equalToSuperview()//.offset(-50)
- make.width.height.equalTo(56)
- }
-
- playerContainerView.addSubview(controlsContainerView)
- controlsContainerView.snp.makeConstraints { make in
- make.leading.trailing.equalTo(0)
- make.bottom.equalTo(-80-k_Height_safeAreaInsetsBottom())
- }
-
- controlsContainerView.addSubview(progressSlider)
- controlsContainerView.addSubview(currentTimeLabel)
- controlsContainerView.addSubview(durationLabel)
-
- progressSlider.snp.makeConstraints { make in
- make.left.equalTo(16)
- make.right.equalTo(-16)
- make.top.equalTo(0)
- make.height.equalTo(10)
- }
-
- let label = UILabel.createLabel(text: "/",font: .font(size: 11),textColor: .white)
- controlsContainerView.addSubview(label)
- label.snp.makeConstraints { make in
- make.top.equalTo(progressSlider.snp.bottom).offset(6)
- make.centerX.equalToSuperview()
- make.height.equalTo(13)
- make.bottom.equalToSuperview()
- }
-
- currentTimeLabel.snp.makeConstraints { make in
- make.height.equalTo(13)
- make.centerY.equalTo(label)
- make.right.equalTo(label.snp.left)
- }
-
- durationLabel.snp.makeConstraints { make in
- make.height.equalTo(13)
- make.centerY.equalTo(label)
- make.left.equalTo(label.snp.right)
- }
- }
- private func setupPlayer() {
- player = AVPlayer(url: videoURL)
- playerLayer = AVPlayerLayer(player: player)
- playerLayer?.videoGravity = .resizeAspect
-
- if let playerLayer = playerLayer {
- playerContainerView.layer.insertSublayer(playerLayer, at: 0)
- }
-
- // Add time observer to update progress
- addPeriodicTimeObserver()
-
- // Observe when the video ends
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(playerDidFinishPlaying),
- name: .AVPlayerItemDidPlayToEndTime,
- object: player?.currentItem
- )
-
- // Get video duration
- let duration = player?.currentItem?.asset.duration.seconds ?? 0
- durationLabel.text = formatTime(seconds: Float(duration))
- }
-
- func setControlsBottom(bottem:CGFloat){
- controlsContainerView.snp.updateConstraints { make in
- make.bottom.equalTo(bottem)
- }
- }
-
- func runloppPlay() {
- self.isRunloppPlay = true
- setControlsView(isHidden: true)
- playPlay()
- }
- // MARK: - Player Controls
- @objc private func playPauseTapped() {
- if isPlaying {
- playPause()
- } else {
- playPlay()
- }
- }
-
- @objc func playPlay() {
- player?.play()
- isPlaying = true
- }
-
- @objc func playPause() {
- player?.pause()
- isPlaying = false
- }
-
- @objc private func playerDidFinishPlaying() {
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1){
- self.playerDidFinish()
- }
- }
-
- func playerDidFinish() {
- player?.seek(to: CMTime.zero)
- isPlaying = false
- progressSlider.value = 0
- currentTimeLabel.text = "00:00"
-
- if isRunloppPlay {
- playPlay()
- }
- }
- // MARK: - Progress Slider
- @objc private func sliderValueChanged(_ sender: UISlider) {
- playPause()
- guard let duration = player?.currentItem?.duration else { return }
- let totalSeconds = CMTimeGetSeconds(duration)
- let value = Float64(sender.value) * totalSeconds
- let seekTime = CMTime(value: Int64(value), timescale: 1)
- currentTimeLabel.text = formatTime(seconds: Float(value))
- }
-
- @objc private func sliderTouchEnded(_ sender: UISlider) {
- guard let duration = player?.currentItem?.duration else { return }
- let totalSeconds = CMTimeGetSeconds(duration)
- let value = Float64(sender.value) * totalSeconds
- let seekTime = CMTime(value: Int64(value), timescale: 1)
- player?.seek(to: seekTime, toleranceBefore: .zero, toleranceAfter: .zero)
- playPlay()
- }
-
- // MARK: - Time Observer
- private func addPeriodicTimeObserver() {
- let interval = CMTime(seconds: 0.05, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
-
- timeObserverToken = player?.addPeriodicTimeObserver(
- forInterval: interval,
- queue: .main
- ) { [weak self] time in
- guard let self = self else { return }
- let timeElapsed = Float(time.seconds)
-
- if let duration = self.player?.currentItem?.duration {
- let durationSeconds = Float(CMTimeGetSeconds(duration))
- self.progressSlider.value = Float(timeElapsed / durationSeconds)
- self.currentTimeLabel.text = self.formatTime(seconds: timeElapsed)
- }
- }
- }
-
- private func removePeriodicTimeObserver() {
- if let token = timeObserverToken {
- player?.removeTimeObserver(token)
- timeObserverToken = nil
- }
- }
-
- // MARK: - Helper Methods
- private func formatTime(seconds: Float) -> String {
- let minutes = Int(seconds) / 60
- let seconds = Int(seconds) % 60
- return String(format: "%02d:%02d", minutes, seconds)
- }
- }
|