Browse Source

添加 设置封面 选择一张图界面

jsonwang 3 years ago
parent
commit
c7f042cfb9

BIN
BFFramework/Assets/Stuckpoint/icon_uploadVideo_more@2x.png


BIN
BFFramework/Assets/Stuckpoint/icon_upload_do@2x.png


+ 252 - 0
BFFramework/Classes/selectImage/PQImageCropVC.swift

@@ -0,0 +1,252 @@
+//
+//  PQImageCropVC.swift
+//  PQSpeed
+//
+//  Created by ak on 2020/8/3.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+// add by ak 主要功能 图片裁剪界面
+// e.g.
+// let image = UIImage(named:"img.jpg")!
+// let vc = PQImageCropVC(frame: (self.navigationController?.view.frame)!, image: image, aspectWidth:9, aspectHeight: 16)
+
+import UIKit
+
+class PQImageCropVC: PQBaseViewController, UIScrollViewDelegate {
+    var uploadData: PQUploadModel?
+    var updataVideoData: PQVideoListModel? // 如果updataVideoData不为空则为修改视频
+    var aspectW: CGFloat!
+    var aspectH: CGFloat!
+    var img: UIImage!
+
+    var imageView: UIImageView!
+    var scrollView: UIScrollView!
+ 
+    var holeRect: CGRect!
+    var gap: CGFloat = 100 // 距屏幕边缘距离
+
+    required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+    init(image: UIImage, aspectWidth: CGFloat, aspectHeight: CGFloat) {
+        super.init(nibName: nil, bundle: nil)
+        aspectW = aspectWidth
+        aspectH = aspectHeight
+        img = image
+        BFLog(message: "原图大小 \(img.size) aspectW:\(aspectW!) aspectH:\(aspectH!)")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        setupView()
+    }
+
+    func setupView() {
+        let holeWidth = view.frame.width - gap
+        BFLog(message: "aspectH :\(String(describing: aspectH))")
+        let holeHeight = holeWidth * aspectH / aspectW
+        if img.imageOrientation != .up {
+            UIGraphicsBeginImageContextWithOptions(img.size, false, img.scale)
+            var rect = CGRect.zero
+            rect.size = img.size
+            img.draw(in: rect)
+            img = UIGraphicsGetImageFromCurrentImageContext()
+            UIGraphicsEndImageContext()
+        }
+
+        holeRect = CGRect(x: 0, y: view.frame.height / 2 - holeHeight / 2, width: holeWidth, height: holeHeight)
+
+        imageView = UIImageView(image: img)
+        scrollView = UIScrollView(frame: CGRect(x: gap / 2.0, y: 0, width: holeWidth, height: holeHeight))
+        scrollView.center = view.center
+        scrollView.addSubview(imageView)
+        scrollView.showsVerticalScrollIndicator = false
+        scrollView.showsHorizontalScrollIndicator = false
+        scrollView.alwaysBounceHorizontal = true
+        scrollView.alwaysBounceVertical = true
+        scrollView.delegate = self
+        view.addSubview(scrollView)
+
+        // 设置最小 zoom 是防止在裁剪区出现黑边导致裁剪后的图不对
+        let minZoom = max(holeWidth / img.size.width, holeHeight / img.size.height)
+        BFLog(message: "minZoom is: \(minZoom)")
+
+        scrollView.minimumZoomScale = minZoom
+        scrollView.maximumZoomScale = minZoom * 4
+        // 后设置自动缩放
+        scrollView.setZoomScale(minZoom, animated: false)
+        scrollView.clipsToBounds = false
+        scrollView.contentOffset = CGPoint(x: (img.size.width * minZoom - holeWidth) / 2, y: (img.size.height * minZoom - holeHeight) / 2)
+
+        // 添加 mark layer
+        let cropView = UIView(frame: view.frame)
+        cropView.isUserInteractionEnabled = false
+        view.addSubview(cropView)
+        let layer = CAShapeLayer()
+        let clipWidth = holeWidth
+        let clipHeight = holeHeight
+
+        let cropAreaX = (cScreenWidth - clipWidth) / 2
+        let cropAreaY = (cScreenHeigth - clipHeight) / 2
+        let cropAreaWidth = clipWidth
+        let cropAreaHeight = clipHeight
+        let cropframe = CGRect(x: cropAreaX, y: cropAreaY, width: cropAreaWidth, height: cropAreaHeight)
+
+        let path = UIBezierPath(roundedRect: cropView.frame, cornerRadius: 0)
+        let cropPath = UIBezierPath(rect: cropframe)
+
+        path.append(cropPath)
+        layer.path = path.cgPath
+        layer.fillRule = CAShapeLayerFillRule.evenOdd
+        layer.fillColor = UIColor.black.cgColor
+        layer.opacity = 0.5
+        layer.frame = cropView.bounds
+        cropView.layer.addSublayer(layer)
+        view.bringSubviewToFront(cropView)
+
+        // 单独画线
+        let drawLine = DrawLine()
+        drawLine.setCropAreaLeft(cropAreaLeft: cropAreaX, cropAreaTop: cropAreaY, cropAreaRight: cropAreaX + cropAreaWidth, cropAreaBottom: cropAreaY + cropAreaHeight)
+        drawLine.frame = cropView.bounds
+        cropView.layer.addSublayer(drawLine)
+        view.bringSubviewToFront(cropView)
+
+        // 添加下边工具条
+        view.addSubview(bottomView)
+
+        // 显示引导
+        let haveShowGuid = getUserDefaults(key: cIsShowImageCropGuid)
+        if haveShowGuid == nil {
+//            let guid = PQGuidClipView(frame: view.frame)
+//            view.addSubview(guid)
+//            saveUserDefaults(key: cIsShowImageCropGuid, value: cIsShowImageCropGuid)
+        }
+    }
+
+    lazy var bottomView: UIView = {
+        let bottomView = UIView(frame: CGRect(x: 0, y: cScreenHeigth - cDevice_iPhoneTabBarHei, width: cScreenWidth, height: cDevice_iPhoneTabBarHei))
+        let deleteBtn = UIButton(frame: CGRect(x: cDefaultMargin, y: 0, width: cDevice_iPhoneTabBarHei, height: cDevice_iPhoneTabBarHei))
+        bottomView.backgroundColor = PQBFConfig.shared.styleBackGroundColor
+        deleteBtn.setImage(UIImage(named: "icon_detail_back"), for: .normal)
+        deleteBtn.addTarget(self, action: #selector(tappedClose), for: .touchUpInside)
+
+        bottomView.addSubview(deleteBtn)
+
+        let selecteBtn = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: cDevice_iPhoneTabBarHei))
+        selecteBtn.setTitle("裁剪封面", for: .normal)
+        selecteBtn.setTitleColor(.black, for: .normal)
+        selecteBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
+
+        bottomView.addSubview(selecteBtn)
+        selecteBtn.center.x = bottomView.center.x
+
+        let nextBtn = UIButton(frame: CGRect(x: bottomView.frame.width - 100, y: (cDevice_iPhoneTabBarHei - 40) / 2, width: 80, height: 40))
+        nextBtn.addCorner(corner: 20)
+        nextBtn.setTitle("确定", for: .normal)
+        nextBtn.addTarget(self, action: #selector(tappedCrop), for: .touchUpInside)
+        nextBtn.setTitleColor(UIColor.white, for: .normal)
+        nextBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
+        nextBtn.backgroundColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
+        bottomView.addSubview(nextBtn)
+
+        return bottomView
+    }()
+
+    // MARK: scrollView delegate
+
+    func viewForZooming(in _: UIScrollView) -> UIView? {
+        BFLog(message: "viewForZooming")
+        // 是imageView的容器,实现这个代理保证图片的放大拖动交互
+        return imageView
+    }
+
+    func scrollViewDidZoom(_ scrollView: UIScrollView) {
+        BFLog(message: "scrollViewDidZoom")
+        let gapToTheHole = scrollView.frame.height / 2 - holeRect.height / 2
+        scrollView.contentInset = UIEdgeInsets(top: gapToTheHole, left: 0, bottom: gapToTheHole, right: 0)
+    }
+
+    // 返回
+    @objc func tappedClose() {
+        BFLog(message: "返回")
+        // add by ak bugfix 图片超出 返回异常
+        navigationController?.popViewController(animated: true)
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        scrollView.isHidden = false
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        scrollView.isHidden = true
+    }
+
+    // 裁剪
+    @objc func tappedCrop() {
+        BFLog(message: "开始裁剪图片")
+
+        // 根据scrollView缩放系数和偏移量 对原图进行裁剪
+        var imgX: CGFloat = 0
+        if scrollView.contentOffset.x > 0 {
+            imgX = scrollView.contentOffset.x / scrollView.zoomScale
+        }
+
+        let gapToTheHole: CGFloat = 0
+        var imgY: CGFloat = 0
+        if scrollView.contentOffset.y + 0 > 0 {
+            imgY = (scrollView.contentOffset.y + gapToTheHole) / scrollView.zoomScale
+        }
+
+        let imgW = holeRect.width / scrollView.zoomScale
+        let imgH = holeRect.height / scrollView.zoomScale
+
+        print("IMG x: \(imgX) y: \(imgY) w: \(imgW) h: \(imgH)")
+
+        let cropRect = CGRect(x: imgX, y: imgY, width: imgW, height: imgH)
+        let imageRef = img.cgImage!.cropping(to: cropRect)
+        let croppedImage = UIImage(cgImage: imageRef!)
+        //  这个方法是使用view 截屏
+        //  let croppedImage : UIImage = scrollView.graphicsGetImage() ?? img
+        BFLog(message: "croppedImage\(croppedImage.size)")
+        
+        postNotification(name: cSelectedImageSuccessKey, userInfo: ["image": croppedImage])
+      
+        let publicVideoVC = navigationController?.viewControllers.first(where: { (vc) -> Bool in
+            vc is PQStuckPointPublicController
+        })
+ 
+        navigationController?.popToViewController(publicVideoVC!, animated: true)
+ 
+    }
+}
+
+class DrawLine: CAShapeLayer {
+    var mCropAreaLeft: CGFloat = 0
+    var mCropAreaTop: CGFloat = 0
+    var mCropAreaRight: CGFloat = cScreenWidth
+    var mCropAreaBottom: CGFloat = 0
+
+    let mLineWidth: CGFloat = 7
+
+    func setCropAreaLeft(cropAreaLeft: CGFloat, cropAreaTop: CGFloat, cropAreaRight: CGFloat, cropAreaBottom: CGFloat) {
+        mCropAreaLeft = cropAreaLeft
+        mCropAreaTop = cropAreaTop
+        mCropAreaRight = cropAreaRight
+        mCropAreaBottom = cropAreaBottom
+
+        setNeedsDisplay()
+    }
+
+    override func draw(in ctx: CGContext) {
+        UIGraphicsPushContext(ctx)
+
+        ctx.setStrokeColor(UIColor.white.cgColor)
+        ctx.setLineWidth(mLineWidth)
+        ctx.addRect(CGRect(x: mCropAreaLeft, y: mCropAreaTop, width: mCropAreaRight - mCropAreaLeft, height: mCropAreaBottom - mCropAreaTop))
+        // 必和路径
+        ctx.strokePath()
+
+        UIGraphicsPopContext()
+    }
+}

+ 50 - 0
BFFramework/Classes/selectImage/PQImageSelectedController.swift

@@ -0,0 +1,50 @@
+//
+//  PQImageSelectedController.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/8/14.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import UIKit
+
+class PQImageSelectedController: PQUploadController {
+    var updataVideoData: PQVideoListModel? // 如果updataVideoData不为空则为修改视频
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        emptyData?.title = "哦呜~ 你没有可上传的图片~"
+        emptyData?.emptyImage = "icon_authorError"
+        // Do any additional setup after loading the view.
+    }
+
+    @objc override func btnClick(sender: UIButton) {
+        switch sender.tag {
+        case 1: // 返回
+            navigationController?.popViewController(animated: true)
+        case 2: // 筛选
+            showCollects()
+        case 3: // 下一步
+            if selectedData == nil {
+                cShowHUB(superView: nil, msg: "请选择图片")
+                return
+            }
+            PQPHAssetVideoParaseUtil.requestAssetOringinImage(asset: (selectedData?.asset)!) { [weak self] _, _, image, _ in
+                if image != nil {
+                    self?.selectedData?.image = image
+                }
+                self?.selectedData?.videoBucketKey = self?.uploadData?.videoBucketKey
+                let vc = PQImageCropVC(image: (self?.selectedData?.image)!, aspectWidth: self?.videoWidth ?? 0.0, aspectHeight: self?.videoHeight ?? 0.0)
+                self?.selectedData?.duration = self?.uploadData?.duration ?? 0.0
+
+                self?.selectedData?.localPath = self?.uploadData?.localPath
+                vc.uploadData = self?.selectedData
+                vc.updataVideoData = self?.updataVideoData
+                self?.navigationController?.pushViewController(vc, animated: true)
+            }
+        case 4: // 播放
+            break
+        default:
+            break
+        }
+    }
+}

+ 127 - 0
BFFramework/Classes/selectImage/PQSelecteVideoItemCell.swift

@@ -0,0 +1,127 @@
+//
+//  PQSelecteVideoItemCell.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/8/1.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import Photos
+import UIKit
+class PQSelecteVideoItemCell: UICollectionViewCell {
+    var representedAssetIdentifier: String!
+    lazy var videoImageView: UIImageView = {
+        let videoImageView = UIImageView()
+        videoImageView.contentMode = .scaleAspectFill
+        videoImageView.clipsToBounds = true
+        return videoImageView
+    }()
+
+    lazy var placeHoldeImageView: UIImageView = {
+        let placeHoldeImageView = UIImageView()
+        placeHoldeImageView.image = UIImage(named: "cut_place")
+        placeHoldeImageView.clipsToBounds = true
+        return placeHoldeImageView
+    }()
+
+    lazy var timeLab: UILabel = {
+        let timeLab = UILabel()
+        timeLab.font = UIFont.systemFont(ofSize: 11)
+        timeLab.textColor = UIColor.white
+        timeLab.text = "00:00"
+        return timeLab
+    }()
+ 
+    lazy var seleImage: UIImageView = {
+        let seleImage = UIImageView(image: UIImage().BF_Image(named:  "icon_upload_do"))
+        return seleImage
+    }()
+
+    lazy var bgView: UIView = {
+        let bgView = UIView()
+        bgView.backgroundColor = UIColor(red: 238 / 255.0, green: 0, blue: 81 / 255.0, alpha: 0.3)
+        return bgView
+    }()
+
+    @objc class func selecteVideoItemCell(collectionView: UICollectionView, indexPath: IndexPath) -> PQSelecteVideoItemCell {
+        let cell: PQSelecteVideoItemCell = collectionView.dequeueReusableCell(withReuseIdentifier: "PQSelecteVideoItemCell", for: indexPath) as! PQSelecteVideoItemCell
+        return cell
+    }
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        contentView.addSubview(videoImageView)
+        videoImageView.addSubview(timeLab)
+        videoImageView.addSubview(placeHoldeImageView)
+        contentView.addSubview(bgView)
+        bgView.addSubview(seleImage)
+        bgView.isHidden = !isSelected
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    var uploadData: PQUploadModel? {
+        didSet {
+            addData()
+            addLayout()
+        }
+    }
+
+    var thubImage: UIImage? {
+        didSet {
+            addData()
+            videoImageView.image = thubImage
+            addLayout()
+        }
+    }
+
+    func addData() {
+        videoImageView.contentMode = uploadData?.contentMode ?? .scaleAspectFill
+        if uploadData?.contentMode == .scaleAspectFit {
+            addCorner(corner: 6)
+        } else {
+            addCorner(corner: 0)
+        }
+        if uploadData?.image != nil {
+            videoImageView.image = uploadData?.image
+        } else if uploadData?.imageUrl != nil, (uploadData?.imageUrl.count ?? 0) > 0 {
+            netImage(url: uploadData?.imageUrl ?? "", mainView: videoImageView)
+//        }else if(uploadData?.asset != nil){
+//            PQPHAssetVideoParaseUtil.requestAssetImage(asset: (uploadData?.asset)!, itemSize: CGSize.init(width: 0, height: 0)) { [weak self](image, info) in
+//                self?.videoImageView.image = image
+//            }
+        } else {
+            videoImageView.image = nil
+        }
+        timeLab.text = (uploadData?.asset?.duration ?? 0).formatDurationToHMS()
+        timeLab.isHidden = (uploadData?.asset?.duration ?? 0) <= 0.0
+        bgView.isHidden = !(uploadData?.isSelected ?? false)
+        videoImageView.backgroundColor = (((uploadData?.videoWidth ?? 0) < (uploadData?.videoHeight ?? 0)) || (uploadData?.image == nil && (uploadData?.imageUrl == nil || (uploadData?.imageUrl.count ?? 0) <= 0))) ? UIColor.hexColor(hexadecimal: "#212223") : UIColor.black
+        placeHoldeImageView.isHidden = !(uploadData?.image == nil && (uploadData?.imageUrl == nil || (uploadData?.imageUrl.count ?? 0) <= 0))
+    }
+
+    func addLayout() {
+        placeHoldeImageView.snp.remakeConstraints { make in
+            make.width.equalTo(28)
+            make.height.equalTo(24)
+            make.center.equalToSuperview()
+        }
+        videoImageView.snp.remakeConstraints { make in
+            make.top.bottom.left.right.equalToSuperview()
+        }
+        timeLab.snp.makeConstraints { make in
+            make.bottom.equalTo(videoImageView).offset(-cDefaultMargin / 2)
+            make.right.equalTo(videoImageView).offset(-cDefaultMargin / 2)
+        }
+        bgView.snp.makeConstraints { make in
+            make.size.equalToSuperview()
+        }
+        seleImage.snp.makeConstraints { make in
+            make.width.height.equalTo(23)
+            make.right.equalToSuperview().offset(-cDefaultMargin)
+            make.top.equalToSuperview().offset(cDefaultMargin)
+        }
+    }
+}

+ 765 - 0
BFFramework/Classes/selectImage/PQUploadController.swift

@@ -0,0 +1,765 @@
+//
+//  PQUploadViewController.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/8/1.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import MobileCoreServices
+import Photos
+import UIKit
+
+let playerHeaderH: CGFloat = cScreenWidth * (250 / 375)
+
+class PQUploadController: PQBaseViewController {
+    // 最大的宽度
+    var maxWidth: CGFloat = cScreenWidth
+    // 最大的高度
+    var maxHeight: CGFloat = adapterWidth(width: 300)
+    // 画面比例
+    var aspectRatio: aspectRatio?
+    var preViewSize: CGSize {
+        if aspectRatio == nil, (isMember(of: PQUploadController.self) && selectedData == nil) || (!isMember(of: PQUploadController.self) && uploadData == nil) {
+            return CGSize(width: maxHeight, height: maxHeight)
+        }
+        if selectedData != nil {
+            aspectRatio = .origin(width: CGFloat(videoData[lastSeletedIndex?.item ?? 0].videoWidth), height: CGFloat(videoData[lastSeletedIndex?.item ?? 0].videoHeight))
+        } else if aspectRatio == nil {
+            aspectRatio = .origin(width: CGFloat(uploadData?.videoWidth ?? maxHeight), height: CGFloat(uploadData?.videoHeight ?? maxHeight))
+        }
+        switch aspectRatio {
+        case let .origin(width, height):
+            var tempHeight: CGFloat = 0
+            var tempWidth: CGFloat = 0
+            if width > height {
+                tempHeight = (maxWidth * height / width)
+                tempWidth = maxWidth
+            } else {
+                tempHeight = maxHeight
+                tempWidth = (maxHeight * width / height)
+            }
+            if tempHeight.isNaN || tempWidth.isNaN {
+                return CGSize.zero
+            } else {
+                return CGSize(width: tempWidth, height: tempHeight)
+            }
+        case .oneToOne:
+            if maxWidth > maxHeight {
+                return CGSize(width: maxHeight, height: maxHeight)
+            } else {
+                return CGSize(width: maxWidth, height: maxWidth)
+            }
+        case .sixteenToNine:
+            return CGSize(width: maxWidth, height: maxWidth * 9.0 / 16.0)
+        case .nineToSixteen:
+            return CGSize(width: maxHeight * 9.0 / 16.0, height: maxHeight)
+        default:
+            break
+        }
+        return CGSize(width: maxHeight, height: maxHeight)
+    }
+
+    // 上传入口类型
+    var sourceType: videoUploadSourceType = .videoUpload
+    // 制作视频项目Id
+    var makeVideoProjectId: String?
+    // 再创作数据
+    var reCreateData: PQReCreateModel?
+    // 视频创作埋点数据
+    var eventTrackData: PQVideoMakeEventTrackModel?
+    // 制作视频草稿Id
+    var makeVideoDraftboxId: String?
+    // 制作视频草稿结构化数据
+    var makeVideoSdata: String?
+    var isAssetImage: Bool = false // 是否请求的是图片
+    var videoWidth: CGFloat = 0 // 视频宽
+    var videoHeight: CGFloat = 0 // 视频高
+    var isLoop: Bool = true // 是否循环播放
+    var playerItem: AVPlayerItem? // 当前播放item
+    var videoOutput: AVPlayerItemVideoOutput?
+    let itemSize = CGSize(width: ((cScreenWidth - cDefaultMargin) / 3) * UIScreen.main.scale, height: ((cScreenWidth - cDefaultMargin) / 3) * UIScreen.main.scale)
+    var categoryH: CGFloat = cDefaultMargin * 40 // 相簿高度
+
+    var videoData: [PQUploadModel] = Array<PQUploadModel>.init()
+
+    var categoryData: [PQUploadModel] = Array<PQUploadModel>.init()
+
+    // 当前选中的数据
+    var selectedData: PQUploadModel?
+    // 确定上传的数据
+    var uploadData: PQUploadModel?
+    var catagerySelectedIndex: IndexPath = IndexPath(item: 0, section: 0) // 更多图库选择
+
+    var isJumpToAuthorization: Bool = false // 是否跳到授权
+    var lastSeletedIndex: IndexPath? // 上次选中的index
+    lazy var imageManager: PHCachingImageManager = {
+        (PHCachingImageManager.default() as? PHCachingImageManager) ?? PHCachingImageManager()
+    }()
+
+    var allPhotos: PHFetchResult<PHAsset>!
+    var previousPreheatRect = CGRect.zero
+
+    var smartAlbums: PHFetchResult<PHAssetCollection>!
+    var userCollections: PHFetchResult<PHCollection>!
+    let sectionLocalizedTitles = ["", NSLocalizedString("Smart Albums", comment: ""), NSLocalizedString("Albums", comment: "")]
+    lazy var fetchOptions: PHFetchOptions = {
+        let fetchOptions = PHFetchOptions()
+        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
+        fetchOptions.predicate = NSPredicate(format: "mediaType = %d", !isAssetImage ? PHAssetMediaType.video.rawValue : PHAssetMediaType.image.rawValue)
+        return fetchOptions
+    }()
+
+    lazy var imagesOptions: PHImageRequestOptions = {
+        let imagesOptions = PHImageRequestOptions()
+        imagesOptions.isSynchronous = true
+        imagesOptions.isNetworkAccessAllowed = false
+        imagesOptions.deliveryMode = .opportunistic
+        imagesOptions.resizeMode = .fast
+        imagesOptions.progressHandler = { _, _, _, info in
+            BFLog(message: "progressHandler = \(info)")
+        }
+        return imagesOptions
+    }()
+
+    lazy var singleImageOptions: PHImageRequestOptions = {
+        let singleImageOptions = PHImageRequestOptions()
+        singleImageOptions.isSynchronous = true
+        singleImageOptions.isNetworkAccessAllowed = false
+        singleImageOptions.deliveryMode = .highQualityFormat
+        return singleImageOptions
+    }()
+
+    lazy var backBtn: UIButton = {
+        let backBtn = UIButton(type: .custom)
+        backBtn.frame = CGRect(x: 0, y: cDevice_iPhoneStatusBarHei, width: cDefaultMargin * 4, height: cDefaultMargin * 4)
+        backBtn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: -5, right: 0)
+        backBtn.backgroundColor = .yellow
+       
+        backBtn.setImage(UIImage().BF_Image(named:  "icon_blanc_back"), for: .normal)
+        backBtn.addTarget(self, action: #selector(backBtnClick), for: .touchUpInside)
+        return backBtn
+    }()
+
+    var anthorEmptyData: PQEmptyModel? = {
+        let anthorEmptyData = PQEmptyModel()
+        anthorEmptyData.title = "开始上传"
+        anthorEmptyData.summary = "要开始上传视频,请先授予相册使用权限"
+        anthorEmptyData.emptyImage = "icon_authorError"
+        return anthorEmptyData
+    }()
+
+    var emptyData: PQEmptyModel? = {
+        let emptyData = PQEmptyModel()
+        emptyData.title = "哦呜~ 你没有可上传的视频~"
+        emptyData.emptyImage = "video_empty"
+        return emptyData
+    }()
+
+    lazy var emptyRemindView: PQEmptyRemindView = {
+        let emptyRemindView = PQEmptyRemindView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei))
+        emptyRemindView.isHidden = true
+        emptyRemindView.emptyData = anthorEmptyData
+        view.addSubview(emptyRemindView)
+        emptyRemindView.fullRefreshBloc = { [weak self] _, _ in
+            self?.isJumpToAuthorization = true
+            if emptyRemindView.refreshBtn.currentTitle == "授予权限" {
+                openAppSetting()
+            }
+        }
+        return emptyRemindView
+    }()
+
+    lazy var collectionView: UICollectionView = {
+        let layout = UICollectionViewFlowLayout()
+        layout.sectionInset = UIEdgeInsets.zero
+        let collectionView = UICollectionView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei), collectionViewLayout: layout)
+        collectionView.showsVerticalScrollIndicator = false
+        collectionView.register(PQSelecteVideoItemCell.self, forCellWithReuseIdentifier: "PQSelecteVideoItemCell")
+        collectionView.delegate = self
+        collectionView.dataSource = self
+        if #available(iOS 11.0, *) {
+            collectionView.contentInsetAdjustmentBehavior = .never
+        } else {
+            automaticallyAdjustsScrollViewInsets = false
+        }
+        collectionView.backgroundColor = .clear
+        return collectionView
+    }()
+
+    lazy var categoryCollectionView: UICollectionView = {
+        let layout = UICollectionViewFlowLayout()
+        layout.sectionInset = UIEdgeInsets.zero
+        let categoryCollectionView = UICollectionView(frame: CGRect(x: 0, y: -categoryH, width: cScreenWidth, height: categoryH), collectionViewLayout: layout)
+        categoryCollectionView.showsVerticalScrollIndicator = false
+        categoryCollectionView.register(PQAssetCategoryCell.self, forCellWithReuseIdentifier: "PQAssetCategoryCell")
+        categoryCollectionView.delegate = self
+        categoryCollectionView.dataSource = self
+        if #available(iOS 11.0, *) {
+            categoryCollectionView.contentInsetAdjustmentBehavior = .never
+        } else {
+            automaticallyAdjustsScrollViewInsets = false
+        }
+        categoryCollectionView.backgroundColor = .white
+        return categoryCollectionView
+    }()
+
+    lazy var categoryView: UIView = {
+        let categoryView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei))
+        categoryView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
+        let bgView = UIView(frame: CGRect(x: 0, y: categoryH, width: categoryView.frame.width, height: categoryView.frame.height - categoryH))
+        let ges = UITapGestureRecognizer(target: self, action: #selector(cancelClick))
+        bgView.addGestureRecognizer(ges)
+        categoryView.addSubview(bgView)
+        categoryView.isHidden = true
+        return categoryView
+    }()
+
+ 
+
+    lazy var playBtn: UIButton = {
+        let playBtn = UIButton(type: .custom)
+        playBtn.frame = CGRect(x: (preViewSize.width - cDefaultMargin * 5) / 2, y: (preViewSize.height - cDefaultMargin * 5) / 2, width: cDefaultMargin * 5, height: cDefaultMargin * 5)
+        playBtn.setImage(UIImage(named: "icon_video_play_big"), for: .normal)
+        playBtn.tag = 4
+        playBtn.isHidden = true
+        playBtn.isUserInteractionEnabled = false
+        // playBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
+        return playBtn
+    }()
+
+    var playerHeaderView: UIImageView = {
+        let playerHeaderView = UIImageView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: 0))
+        playerHeaderView.isUserInteractionEnabled = true
+        playerHeaderView.contentMode = .scaleAspectFit
+        playerHeaderView.clipsToBounds = true
+        playerHeaderView.backgroundColor = UIColor.yellow
+        return playerHeaderView
+    }()
+
+    lazy var selecteBtn: UIButton = {
+        let selecteBtn = UIButton(frame: CGRect(x: deleteBtn.frame.maxX + cDefaultMargin, y: 0, width: cScreenWidth - nextBtn.frame.width - deleteBtn.frame.maxX - cDefaultMargin * 5, height: cDevice_iPhoneTabBarHei))
+        selecteBtn.titleLabel?.lineBreakMode = .byTruncatingTail
+        selecteBtn.setTitle("全部", for: .normal)
+        
+        selecteBtn.setImage(UIImage().BF_Image(named: "icon_uploadVideo_more"), for: .normal)
+        selecteBtn.setTitleColor(UIColor.black, for: .normal)
+        selecteBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
+        selecteBtn.tag = 2
+        selecteBtn.imagePosition(at: .right, space: cDefaultMargin / 2)
+        selecteBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
+        return selecteBtn
+    }()
+
+    lazy var deleteBtn: UIButton = {
+        let deleteBtn = UIButton(frame: CGRect(x: cDefaultMargin, y: 0, width: cDefaultMargin * 4, height: cDevice_iPhoneTabBarHei))
+        deleteBtn.setImage( UIImage().BF_Image(named:  "icon_blanc_back"), for: .normal)
+        deleteBtn.tag = 1
+        deleteBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
+        return deleteBtn
+    }()
+
+    lazy var nextBtn: UIButton = {
+        let nextBtn = UIButton(frame: CGRect(x: 0, y: (cDevice_iPhoneTabBarHei - cDefaultMargin * 3) / 2, width: cDefaultMargin * 6, height: cDefaultMargin * 3))
+        nextBtn.addCorner(corner: 3)
+        nextBtn.setTitle("下一步", for: .normal)
+        nextBtn.titleLabel?.font = UIFont.systemFont(ofSize: 13, weight: .medium)
+        nextBtn.tag = 3
+        nextBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
+        nextBtn.backgroundColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
+        nextBtn.setTitleColor(.white, for: .normal)
+        return nextBtn
+    }()
+
+    lazy var bottomView: UIView = {
+        let bottomView = UIView(frame: CGRect(x: 0, y: cDefaultMargin * 2, width: cScreenWidth, height: cDevice_iPhoneNavBarHei))
+        bottomView.addSubview(selecteBtn)
+        bottomView.backgroundColor = PQBFConfig.shared.styleBackGroundColor
+        selecteBtn.center.y = nextBtn.center.y
+        bottomView.addSubview(deleteBtn)
+        bottomView.addSubview(nextBtn)
+        nextBtn.frame.origin.x = bottomView.frame.width - nextBtn.frame.width - cDefaultMargin * 2
+        return bottomView
+    }()
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        view.backgroundColor = .white
+        navHeadImageView?.backgroundColor = .white
+        lineView?.backgroundColor = .white
+        addSubViews()
+        loadLocalData()
+    }
+
+    deinit {
+        //        PHPhotoLibrary.shared().unregisterChangeObserver(self)
+    }
+
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+  
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+   
+        addPlayerItemObserver()
+    }
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        removePlayerItemObserver()
+    }
+    func addSubViews() {
+        view.addSubview(collectionView)
+        navHeadImageView?.addSubview(bottomView)
+    
+        PQNotification.addObserver(self, selector: #selector(didBecomeActiveNotification), name: UIApplication.didBecomeActiveNotification, object: nil)
+    }
+
+    func loadLocalData() {
+        let authStatus = PHPhotoLibrary.authorizationStatus()
+        if authStatus == .notDetermined {
+            // 第一次触发授权 alert
+            PHPhotoLibrary.requestAuthorization { [weak self] (status: PHAuthorizationStatus) -> Void in
+                if status == .authorized {
+                    if (self?.allPhotos == nil) || (self?.allPhotos.count ?? 0) <= 0 {
+                        self?.loadPhotoData()
+                    }
+                    if self?.backBtn != nil {
+                        self?.backBtn.removeFromSuperview()
+                    }
+                } else {
+                    DispatchQueue.main.async { [weak self] in
+                        self?.emptyRemindView.isHidden = false
+                        self?.emptyRemindView.refreshBtn.isHidden = false
+                        self?.emptyRemindView.refreshBtn.setTitle("授予权限", for: .normal)
+                        if self?.backBtn.superview == nil {
+                            self?.view.addSubview((self?.backBtn)!)
+                        } else {
+                            self?.backBtn.isHidden = false
+                        }
+                    }
+                }
+            }
+        } else if authStatus == .authorized {
+            if allPhotos == nil || allPhotos.count <= 0 {
+                loadPhotoData()
+            }
+            if backBtn.superview != nil {
+                backBtn.removeFromSuperview()
+            }
+
+        } else {
+            emptyRemindView.isHidden = false
+            emptyRemindView.refreshBtn.isHidden = false
+            emptyRemindView.refreshBtn.setTitle("授予权限", for: .normal)
+            if backBtn.superview == nil {
+                view.addSubview(backBtn)
+            } else {
+                backBtn.isHidden = false
+            }
+        }
+    }
+
+    func loadPhotoData() {
+        DispatchQueue.main.async { [weak self] in
+            PQLoadingHUB.shared.showHUB(superView: self!.view, isVerticality: false)
+        }
+        DispatchQueue.global().async { [weak self] in
+            self?.allPhotos = PHAsset.fetchAssets(with: self?.fetchOptions)
+            DispatchQueue.main.async { [weak self] in
+                if self?.view != nil {
+                    PQLoadingHUB.shared.dismissHUB(superView: self!.view)
+                }
+                self?.collectionView.reloadData()
+                if (self?.allPhotos.count ?? 0) <= 0 {
+                    self?.emptyRemindView.frame = CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei - cDevice_iPhoneTabBarHei)
+                    self?.emptyRemindView.emptyData = self?.emptyData
+                    self?.emptyRemindView.isHidden = false
+                } else {
+                    self?.emptyRemindView.isHidden = true
+                }
+                if (self?.allPhotos.count ?? 0) > 0 {
+                    let tempData = PQUploadModel()
+                    tempData.title = "全部"
+                    tempData.categoryList = self!.allPhotos
+                    self?.categoryData.insert(tempData, at: 0)
+                }
+                self?.updateCachedAssets()
+            }
+        }
+        PHPhotoLibrary.shared().register(self)
+
+        DispatchQueue.global().async { [weak self] in
+            self?.smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil)
+            self?.smartAlbums?.enumerateObjects { [weak self] assCollection, _, _ in
+                if assCollection.localizedTitle != "最近删除" {
+                    self?.convertCollection(collection: assCollection)
+                }
+            }
+            self?.userCollections = PHCollectionList.fetchTopLevelUserCollections(with: nil)
+            self?.userCollections.enumerateObjects { assCollection, index, point in
+                BFLog(message: "userCollections == \(assCollection),index = \(index),point = \(point)")
+                if assCollection is PHAssetCollection {
+                    if assCollection.localizedTitle != "最近删除" {
+                        self?.convertCollection(collection: assCollection as? PHAssetCollection)
+                    }
+                }
+            }
+        }
+  
+    }
+
+    // 转化处理获取到的相簿
+    func convertCollection(collection: PHAssetCollection?) {
+        if collection == nil {
+            return
+        }
+        DispatchQueue.global().async { [weak self] in
+            let assetsFetchResult = PHAsset.fetchAssets(in: collection!, options: self?.fetchOptions)
+            if assetsFetchResult.count > 0 {
+                let tempData = PQUploadModel()
+                tempData.title = collection?.localizedTitle
+                tempData.categoryList = assetsFetchResult
+                if tempData.categoryList.count > 0 {
+                    self?.categoryData.append(tempData)
+                }
+                BFLog(message: "assetsFetchResult = \(assetsFetchResult)")
+            }
+        }
+    }
+
+    @objc func btnClick(sender: UIButton) {
+        switch sender.tag {
+        case 1: // 返回
+            navigationController?.popViewController(animated: true)
+        
+        case 2: // 筛选
+            showCollects()
+        case 3: // 下一步
+            if selectedData == nil {
+                cShowHUB(superView: nil, msg: isAssetImage ? "请选择图片" : "请选择视频")
+                return
+            }
+ 
+            imageManager.requestImage(for: (selectedData?.asset)!, targetSize: itemSize, contentMode: .aspectFill, options: nil) { [weak self] image, _ in
+                self?.selectedData?.image = image
+                let vc = PQImageCropVC(image: (self?.selectedData?.image)!, aspectWidth: self?.videoWidth ?? 0.0, aspectHeight: self?.videoHeight ?? 0.0)
+                vc.uploadData = self?.selectedData
+                self?.navigationController?.pushViewController(vc, animated: true)
+            }
+        default:
+            break
+        }
+    }
+
+    @objc func showCollects() {
+        if categoryData.count <= 0 {
+            return
+        }
+        categoryCollectionView.reloadData()
+        if categoryView.superview == nil {
+            view.insertSubview(categoryView, belowSubview: navHeadImageView!)
+            categoryView.addSubview(categoryCollectionView)
+            showCategoryView()
+            return
+        }
+        if categoryView.isHidden {
+            showCategoryView()
+        } else {
+            cancelClick()
+        }
+    }
+
+    @objc func showCategoryView() {
+        categoryView.isHidden = false
+        categoryView.alpha = 0
+        view.bringSubviewToFront(categoryView)
+        view.bringSubviewToFront(bottomView)
+        UIView.animate(withDuration: 0.3, animations: {
+            self.categoryCollectionView.frame = CGRect(x: 0, y: 0, width: cScreenWidth, height: self.categoryH)
+            self.categoryView.alpha = 1
+        }) { _ in
+        }
+    }
+
+    @objc func cancelClick() {
+        UIView.animate(withDuration: 0.3, animations: {
+            self.categoryCollectionView.frame = CGRect(x: 0, y: -self.categoryH, width: cScreenWidth, height: self.categoryH)
+            self.categoryView.alpha = 0
+        }) { _ in
+            self.categoryView.isHidden = true
+        }
+    }
+
+ 
+ 
+
+    @objc func didBecomeActiveNotification() {
+        if isJumpToAuthorization {
+            loadLocalData()
+            isJumpToAuthorization = false
+        }
+    }
+ 
+}
+
+extension PQUploadController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate {
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection _: Int) -> Int {
+        if collectionView == self.collectionView {
+            return allPhotos == nil ? videoData.count : allPhotos.count
+        }
+        return categoryData.count
+    }
+
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        if collectionView == self.collectionView {
+            let cell = PQSelecteVideoItemCell.selecteVideoItemCell(collectionView: collectionView, indexPath: indexPath)
+            if videoData.count <= indexPath.item, allPhotos != nil {
+                let itemData = PQUploadModel()
+                // itemData.image = UIImage.init(named: "cut_place")
+                let asset = allPhotos.object(at: indexPath.item)
+                itemData.asset = asset
+                itemData.duration = asset.duration
+                videoData.append(itemData)
+            }
+            if videoData.count > indexPath.item {
+                let itemData = videoData[indexPath.item]
+                cell.uploadData = itemData
+                if itemData.image == nil, itemData.asset != nil {
+                    cell.representedAssetIdentifier = itemData.asset?.localIdentifier
+                    imageManager.requestImage(for: itemData.asset!, targetSize: itemSize, contentMode: .aspectFill, options: nil) { image, info in
+                        if info?.keys.contains("PHImageResultIsDegradedKey") ?? false, "\(info?["PHImageResultIsDegradedKey"] ?? "0")" == "0", cell.representedAssetIdentifier == itemData.asset?.localIdentifier {
+                            if image != nil {
+                                itemData.image = image
+                                cell.videoImageView.image = image
+                            } else if image == nil, info?.keys.contains("PHImageResultIsInCloudKey") ?? false {
+                                let option = PHImageRequestOptions()
+                                option.isNetworkAccessAllowed = true
+                                option.resizeMode = .fast
+                                self.imageManager.requestImageData(for: itemData.asset!, options: option) { data, _, _, _ in
+                                    if data != nil {
+                                        let image = UIImage(data: data!)
+                                        itemData.image = image
+                                        cell.videoImageView.image = image
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            } else {
+                cell.uploadData = PQUploadModel()
+            }
+            return cell
+        } else {
+            let cell = PQAssetCategoryCell.assetCategoryCell(collectionView: collectionView, indexPath: indexPath)
+            let itemData = categoryData[indexPath.item]
+            let asset = itemData.categoryList.object(at: 0)
+            if itemData.image == nil {
+                cell.representedAssetIdentifier = asset.localIdentifier
+                imageManager.requestImage(for: asset, targetSize: itemSize, contentMode: .aspectFill, options: nil) { image, info in
+                    if info?.keys.contains("PHImageResultIsDegradedKey") ?? false, "\(info?["PHImageResultIsDegradedKey"] ?? "0")" == "0", cell.representedAssetIdentifier == asset.localIdentifier {
+                        itemData.image = image
+                        cell.uploadData = itemData
+                    }
+                }
+            } else {
+                cell.uploadData = itemData
+            }
+            cell.isSelected = indexPath == catagerySelectedIndex
+            return cell
+        }
+    }
+
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        if collectionView == self.collectionView {
+            if videoData.count <= indexPath.item {
+                return
+            }
+            let itemData = videoData[indexPath.item]
+      
+            let ratio = Float(itemData.asset?.pixelWidth ?? 0) / Float(itemData.asset?.pixelHeight ?? 1)
+            if ratio < 0.4 || ratio > 4.2 {
+                cShowHUB(superView: nil, msg: "暂不支持该比例的素材")
+                return
+            }
+            if lastSeletedIndex != nil, lastSeletedIndex != indexPath {
+                let lastData = videoData[lastSeletedIndex!.item]
+                lastData.isSelected = false
+                collectionView.reloadItems(at: [lastSeletedIndex!])
+            }
+            lastSeletedIndex = indexPath
+            if itemData.isSelected {
+                itemData.isSelected = false
+                selectedData = nil
+             
+            } else {
+                itemData.isSelected = true
+                let itemData = videoData[indexPath.item]
+              
+                selectedData = itemData
+                
+            }
+         
+            collectionView.reloadItems(at: [indexPath])
+        } else {
+            videoData.removeAll()
+            self.collectionView.setContentOffset(CGPoint.zero, animated: true)
+            self.collectionView.reloadData()
+            allPhotos = categoryData[indexPath.item].categoryList
+            catagerySelectedIndex = indexPath
+            selecteBtn.setTitle(categoryData[indexPath.item].title, for: .normal)
+            selecteBtn.imagePosition(at: .right, space: cDefaultMargin / 2)
+           
+            lastSeletedIndex = nil
+    
+            if allPhotos.count <= 0 {
+                emptyRemindView.frame = CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei - cDevice_iPhoneTabBarHei)
+                emptyRemindView.emptyData = emptyData
+                emptyRemindView.isHidden = false
+            } else {
+                emptyRemindView.isHidden = true
+            }
+            self.collectionView.reloadData()
+            cancelClick()
+        }
+    }
+
+    func collectionView(_ collectionView: UICollectionView, layout _: UICollectionViewLayout, sizeForItemAt _: IndexPath) -> CGSize {
+        if collectionView == self.collectionView {
+            return CGSize(width: (cScreenWidth - cDefaultMargin) / 3, height: (cScreenWidth - cDefaultMargin) / 3)
+        }
+        return CGSize(width: collectionView.frame.width, height: cDefaultMargin * 8)
+    }
+
+    func collectionView(_ collectionView: UICollectionView, layout _: UICollectionViewLayout, minimumLineSpacingForSectionAt _: Int) -> CGFloat {
+        if collectionView == self.collectionView {
+            return cDefaultMargin / 2
+        }
+        return 0
+    }
+
+    func collectionView(_ collectionView: UICollectionView, layout _: UICollectionViewLayout, minimumInteritemSpacingForSectionAt _: Int) -> CGFloat {
+        if collectionView == self.collectionView {
+            return cDefaultMargin / 2
+        }
+        return 0
+    }
+
+    func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        //这里是不是有用的?
+//        if currentController() is PQUploadController || currentController() is PQImageSelectedController {
+//            if scrollView == collectionView {
+//                updateCachedAssets()
+//            } else {
+//                BFLog(message: "contentOffset = \(scrollView.contentOffset),contentSize = \(scrollView.contentSize)")
+//                if scrollView.contentOffset.y > ((scrollView.contentSize.height - scrollView.frame.height) + cDefaultMargin * 10) {
+//                    cancelClick()
+//                }
+//            }
+//        }
+    }
+}
+
+extension PQUploadController: PHPhotoLibraryChangeObserver {
+    func photoLibraryDidChange(_ changeInstance: PHChange) {
+        // Change notifications may be made on a background queue. Re-dispatch to the
+        // main queue before acting on the change as we'll be updating the UI.
+        DispatchQueue.main.sync { [weak self] in
+            // Check each of the three top-level fetches for changes.
+            if allPhotos != nil, changeInstance.changeDetails(for: allPhotos) != nil {
+                self?.categoryData.removeAll()
+                self?.loadPhotoData()
+            }
+        }
+    }
+}
+
+extension PQUploadController {
+    /// 添加监听
+    /// - Parameter playerItem: <#playerItem description#>
+    /// - Returns: <#description#>
+    func addPlayerItemObserver() {
+        playerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil)
+    }
+
+    /// 移除监听
+    /// - Returns: <#description#>
+    func removePlayerItemObserver() {
+        playerItem?.removeObserver(self, forKeyPath: "status")
+    }
+
+    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) {
+        if object is AVPlayerItem, keyPath == "status" {
+            BFLog(message: "(object as! AVPlayerItem).status = \((object as! AVPlayerItem).status.rawValue)")
+            PQLoadingHUB.shared.dismissHUB(superView: playerHeaderView)
+            switch (object as! AVPlayerItem).status {
+            case .unknown:
+                break
+            case .readyToPlay:
+                break
+            case .failed:
+                break
+            default:
+                break
+            }
+        }
+    }
+
+    private func resetCachedAssets() {
+        imageManager.stopCachingImagesForAllAssets()
+        previousPreheatRect = .zero
+    }
+
+    private func updateCachedAssets() {
+        if allPhotos != nil && allPhotos.count <= 0 {
+            return
+        }
+        guard isViewLoaded, view.window != nil else { return }
+        let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
+        let preheatRect = visibleRect.insetBy(dx: 0, dy: -0.5 * visibleRect.height)
+        let delta = abs(preheatRect.midY - previousPreheatRect.midY)
+        guard delta > view.bounds.height / 3 else { return }
+        let (addedRects, removedRects) = differencesBetweenRects(previousPreheatRect, preheatRect)
+        let addedAssets = addedRects
+            .flatMap { rect in collectionView.indexPathsForElements(in: rect) }
+            .map { indexPath in allPhotos.object(at: indexPath.item) }
+        let removedAssets = removedRects
+            .flatMap { rect in collectionView.indexPathsForElements(in: rect) }
+            .map { indexPath in allPhotos.object(at: indexPath.item) }
+        imageManager.startCachingImages(for: addedAssets,
+                                        targetSize: itemSize, contentMode: .aspectFill, options: nil)
+        imageManager.stopCachingImages(for: removedAssets,
+                                       targetSize: itemSize, contentMode: .aspectFill, options: nil)
+
+        previousPreheatRect = preheatRect
+    }
+
+    private func differencesBetweenRects(_ old: CGRect, _ new: CGRect) -> (added: [CGRect], removed: [CGRect]) {
+        if old.intersects(new) {
+            var added = [CGRect]()
+            if new.maxY > old.maxY {
+                added += [CGRect(x: new.origin.x, y: old.maxY,
+                                 width: new.width, height: new.maxY - old.maxY)]
+            }
+            if old.minY > new.minY {
+                added += [CGRect(x: new.origin.x, y: new.minY,
+                                 width: new.width, height: old.minY - new.minY)]
+            }
+            var removed = [CGRect]()
+            if new.maxY < old.maxY {
+                removed += [CGRect(x: new.origin.x, y: new.maxY,
+                                   width: new.width, height: old.maxY - new.maxY)]
+            }
+            if old.minY < new.minY {
+                removed += [CGRect(x: new.origin.x, y: old.minY,
+                                   width: new.width, height: new.minY - old.minY)]
+            }
+            return (added, removed)
+        } else {
+            return ([new], [old])
+        }
+    }
+}
+