wenweiwei 3 年 前
コミット
785d9e8015
54 ファイル変更3290 行追加64 行削除
  1. 10 0
      BFStuckPointKit.podspec
  2. 21 0
      BFStuckPointKit/Assets/BFStuckPointKit.xcassets/user_avatar_normal.imageset/Contents.json
  3. 0 0
      BFStuckPointKit/Assets/BFStuckPointKit.xcassets/user_avatar_normal.imageset/user_avatar_normal.png
  4. BIN
      BFStuckPointKit/Assets/Resources/msg_video_tag.png
  5. BIN
      BFStuckPointKit/Assets/Resources/watermark.png
  6. 13 0
      BFStuckPointKit/Classes/BFConfig/BFStuckPointKitConfig.swift
  7. 539 0
      BFStuckPointKit/Classes/BFUtils/PQAliOssUtil.swift
  8. 144 0
      BFStuckPointKit/Classes/BFUtils/PQSingletoMemoryUtil.swift
  9. 483 0
      BFStuckPointKit/Classes/BFUtils/PQSingletoRealmUtil.swift
  10. 75 0
      BFStuckPointKit/Classes/BFUtils/PQSingletoSourcesFileUtil.swift
  11. 475 0
      BFStuckPointKit/Classes/BFUtils/PQSingletoVideoPlayer.swift
  12. 129 0
      BFStuckPointKit/Classes/BFUtils/PQSingletonEnvUtil.swift
  13. 327 0
      BFStuckPointKit/Classes/BFUtils/PQThirdPlatformUtil.swift
  14. 4 2
      BFStuckPointKit/Classes/Controller/PQEditMusicSearchController.swift
  15. 12 11
      BFStuckPointKit/Classes/Controller/PQStuckPointEditerController.swift
  16. 5 3
      BFStuckPointKit/Classes/Controller/PQStuckPointMaterialController.swift
  17. 2 1
      BFStuckPointKit/Classes/Controller/PQStuckPointMusicContentController.swift
  18. 4 2
      BFStuckPointKit/Classes/Controller/PQStuckPointMusicController.swift
  19. 1 0
      BFStuckPointKit/Classes/Controller/PQStuckPointMusicSearchController.swift
  20. 6 5
      BFStuckPointKit/Classes/Controller/PQStuckPointPublicController.swift
  21. 16 0
      BFStuckPointKit/Classes/Custom/BFCustomHeader.h
  22. 1 0
      BFStuckPointKit/Classes/EventTrack/Model/PQVideoMakeEventTrackModel.swift
  23. 1 0
      BFStuckPointKit/Classes/EventTrack/ViewModel/PQEventTrackViewModel.swift
  24. 49 0
      BFStuckPointKit/Classes/Model/BFCoreDataModel.swift
  25. 36 0
      BFStuckPointKit/Classes/Model/BFDataPersistentManager.swift
  26. 182 0
      BFStuckPointKit/Classes/Model/PQLoginUserInfo.swift
  27. 176 0
      BFStuckPointKit/Classes/Model/PQUserInfoModel.swift
  28. 491 0
      BFStuckPointKit/Classes/Model/PQVideoListModel.swift
  29. 1 0
      BFStuckPointKit/Classes/SelectImage/PQImageCropVC.swift
  30. 1 1
      BFStuckPointKit/Classes/SelectImage/PQSelecteVideoItemCell.swift
  31. 2 1
      BFStuckPointKit/Classes/View/PQAssetCategoryCell.swift
  32. 12 10
      BFStuckPointKit/Classes/View/PQBaseVideoInfoView.swift
  33. 3 3
      BFStuckPointKit/Classes/View/PQCustomSpeedSettingView.swift
  34. 1 1
      BFStuckPointKit/Classes/View/PQCuttingPointView.swift
  35. 2 2
      BFStuckPointKit/Classes/View/PQEditPublicCoverImageView.swift
  36. 2 1
      BFStuckPointKit/Classes/View/PQEditPublicTitleView.swift
  37. 6 5
      BFStuckPointKit/Classes/View/PQSelecteMusicView.swift
  38. 1 0
      BFStuckPointKit/Classes/View/PQSelectedMaterialListView.swift
  39. 2 0
      BFStuckPointKit/Classes/View/PQSpeedSettingView.swift
  40. 1 1
      BFStuckPointKit/Classes/View/PQStuckPointLoadingView.swift
  41. 1 1
      BFStuckPointKit/Classes/View/PQStuckPointMaterialHeadView.swift
  42. 8 7
      BFStuckPointKit/Classes/View/PQStuckPointMusicContentCell.swift
  43. 1 0
      BFStuckPointKit/Classes/View/PQStuckPointMusicTagsCell.swift
  44. 1 0
      BFStuckPointKit/Classes/View/PQStuckPointMusicTagsContentCell.swift
  45. 2 2
      BFStuckPointKit/Classes/View/PQVideoCutingOprateView.swift
  46. 1 0
      BFStuckPointKit/Classes/ViewModel/PQBaseViewModel.swift
  47. 1 0
      BFStuckPointKit/Classes/ViewModel/PQDownloadFileManager.swift
  48. 1 0
      BFStuckPointKit/Classes/ViewModel/PQDownloadManager.swift
  49. 3 2
      BFStuckPointKit/Classes/ViewModel/PQGPUImagePlayerView.swift
  50. 1 0
      BFStuckPointKit/Classes/ViewModel/PQPlayerViewModel.swift
  51. 1 0
      BFStuckPointKit/Classes/ViewModel/PQSessionManager.swift
  52. 1 0
      BFStuckPointKit/Classes/ViewModel/PQStuckPointViewModel.swift
  53. 4 0
      Example/BFStuckPointKit.xcodeproj/project.pbxproj
  54. 28 3
      Example/Podfile.lock

+ 10 - 0
BFStuckPointKit.podspec

@@ -27,6 +27,9 @@ TODO: Add long description of the pod here.
   s.resource_bundles = {
     'BFStuckPointKit_Resources' => ['BFStuckPointKit/Assets/**/*']
   }
+  s.resource_bundles = {
+    'BFStuckPointKit_Resources' => ['BFStuckPointKit/Assets/**/*.xcassets', 'BFStuckPointKit/Assets/Resources/*']
+  }
   s.source_files = 'BFStuckPointKit/Classes/**/*'
   s.frameworks = 'UIKit', 'MapKit'
   s.dependency 'BFCommonKit'
@@ -34,4 +37,11 @@ TODO: Add long description of the pod here.
   s.dependency 'BFUIKit'
   s.dependency 'BFMaterialKit'
   s.dependency 'BFMediaKit'
+  s.dependency 'AliyunOSSiOS'             ,'2.10.10'  # 阿里云组件
+  s.dependency 'WechatOpenSDK-Swift'      ,'1.8.7.1'  # 微信组件
+  s.dependency 'MJRefresh'                ,'3.7.2'    # 刷新组件
+  s.dependency 'LMJHorizontalScrollText'  ,'2.0.2'
+  s.dependency 'TXLiteAVSDK_Player'       ,'9.3.10765' # 腾讯播放器组件
+  s.dependency 'Bugly'                    ,'2.5.90'   #crash log 收集
+
 end

+ 21 - 0
BFStuckPointKit/Assets/BFStuckPointKit.xcassets/user_avatar_normal.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "user_avatar_normal.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 0 - 0
BFStuckPointKit/Assets/Resources/user_avatar_normal.png → BFStuckPointKit/Assets/BFStuckPointKit.xcassets/user_avatar_normal.imageset/user_avatar_normal.png


BIN
BFStuckPointKit/Assets/Resources/msg_video_tag.png


BIN
BFStuckPointKit/Assets/Resources/watermark.png


+ 13 - 0
BFStuckPointKit/Classes/BFConfig/BFStuckPointKitConfig.swift

@@ -22,3 +22,16 @@ public class BFStuckPointKitConfig: NSObject {
         return self
     }
 }
+
+public func BFStuckPointImage(by name: String) -> UIImage? {
+    
+    guard let url = currentBundle()?.url(forResource: "BFStuckPointKit_Resources", withExtension: "bundle") else {
+        return nil
+    }
+    return UIImage(named: name, in: Bundle(url: url), compatibleWith: nil)
+}
+
+func currentBundle() -> Bundle? {
+    return Bundle(for: BFStuckPointKitConfig.self)
+}
+

+ 539 - 0
BFStuckPointKit/Classes/BFUtils/PQAliOssUtil.swift

@@ -0,0 +1,539 @@
+//
+//  PQAliOssUtil.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/12/9.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import UIKit
+import BFCommonKit
+import BFNetRequestKit
+
+// MARK: - 阿里OSS工具类
+
+/// 阿里OSS工具类
+public class PQAliOssUtil: NSObject {
+    static public let shared = PQAliOssUtil()
+    public var client: OSSClient?
+    // 文件类型:materialType (1:PICTURE, 2:VIDEO, 3:VOICE, 4:FILE, 5:GIF)
+    public  var aliOssHander: ((_ isMatarialUpload: Bool, _ materialType: StickerType, _ fileExtensions: String, _ code: Int, _ objectKey: String?, _ contentMD5: String, _ width: CGFloat, _ height: CGFloat, _ duration: CGFloat, _ frameNumber: Int, _ netResourceUrl: String?, _ fileURL: URL?, _ data: Data?, _ msg: String?) -> Void)?
+    public  var aliOssProgressHander: ((_ bytesSent: Int64, _ totalBytesSent: Int64, _ totalBytesExpectedToSend: Int64, _ resttime: Int64, _ uploadSpeed: String?) -> Void)?
+    public  var lastInterfaceBytes: Int64 = 0 // 上次网速
+    public var oldTime: Int = 0 // 上次进度时间S
+    public var oloaded: Int64 = 0
+    public var dics: [String: Any] = [:]
+
+    public  var allTasks: [String: OSSMultipartUploadRequest] = [:] // add by ak 保存当前所有任务 用于取消某个任务使用
+    public  func startClient(accessKeyId: String, secretKeyId: String, securityToken: String, endpoint: String) -> PQAliOssUtil {
+        if endpoint.count == 0 {
+            debugPrint("endpoint is nil xxxxxx \(endpoint)")
+        }
+
+        let credential = OSSStsTokenCredentialProvider(accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken)
+        let conf = OSSClientConfiguration()
+        conf.timeoutIntervalForRequest = 60 // 连接超时,默认15秒
+        conf.maxRetryCount = 3
+        conf.maxConcurrentRequestCount = 5 // 最大并发请求书,默认5个
+        client = OSSClient(endpoint: endpoint, credentialProvider: credential, clientConfiguration: conf)
+        return .shared
+    }
+
+    /// 图片上传
+    /// - Parameters:
+    ///   - bucketName: <#bucketName description#>
+    ///   - objectKey: <#objectKey description#>
+    ///   - data: <#data description#>
+    /// - Returns: <#description#>
+    public  func uploadObjectAsync(bucketName: String, objectKey: String, data: Data, materialType: StickerType = .IMAGE, fileExtensions: String, isMatarialUpload: Bool = false, contentMD5: String = "", width: CGFloat = 0, height: CGFloat = 0, imageUploadBlock: @escaping (_ osstask: OSSTask<AnyObject>?, _ code: Int, _ objectKey: String, _ fileExtensions: String) -> Void) -> PQAliOssUtil {
+        let putRequest: OSSPutObjectRequest = OSSPutObjectRequest()
+        putRequest.bucketName = bucketName
+        putRequest.objectKey = objectKey
+        putRequest.uploadingData = data
+        putRequest.uploadProgress = { [weak self] _, totalByteSent, totalBytesExpectedToSend in
+            debugPrint("文件上传进度:totalByteSent = \(totalByteSent),totalBytesExpectedToSend = \(totalBytesExpectedToSend)")
+            if totalBytesExpectedToSend > 0, totalByteSent == totalBytesExpectedToSend {
+                if isMatarialUpload, self?.aliOssHander != nil {
+                    self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, 1, objectKey, contentMD5, width, height, 0, 0, nil, nil, data, "上传成功")
+                }
+            }
+        }
+        putRequest.contentType = "application/octet-stream"
+        //        putRequest.contentMd5 = contentMD5
+        putRequest.contentEncoding = ""
+        putRequest.contentDisposition = ""
+        let putTask: OSSTask = (client?.putObject(putRequest))!
+        putTask.continue(successBlock: { [weak self] (osstask) -> Any? in
+            if osstask.error == nil {
+                let task = self?.client?.presignPublicURL(withBucketName: putRequest.bucketName, withObjectKey: putRequest.objectKey)
+                debugPrint("图片原方法上传完成=\(objectKey)")
+                debugPrint("url == \(task?.result ?? "" as AnyObject)")
+                imageUploadBlock(osstask, 1, objectKey, fileExtensions)
+            } else {
+                if self?.aliOssHander != nil {
+                    self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, 0, objectKey, contentMD5, width, height, 0, 0, nil, nil, data, "上传失败")
+                }
+                debugPrint("图片原方法上传失败=\(objectKey),osstask.error = \(osstask.error ?? PQError(msg: "失败"))")
+                imageUploadBlock(osstask, 0, objectKey, fileExtensions)
+            }
+
+            return nil
+
+        }).waitUntilFinished()
+
+        return .shared
+    }
+
+    /// 快速上传视频
+    /// - Parameters:
+    ///   - localPath: <#localPath description#>
+    ///   - response: <#response description#>
+    /// - Returns: <#description#>
+    class public  func multipartUpload(localPath: String, response: [String: Any]?,videoSource:String? = nil) {
+        let accessKeyId: String = "\(response?["AccessKeyId"] ?? "")"
+        let secretKeyId: String = "\(response?["AccessKeySecret"] ?? "")"
+        let securityToken: String = "\(response?["SecurityToken"] ?? "")"
+        let endpoint: String = "\(response?["Host"] ?? "")"
+        let bucketName: String = "\(response?["Bucket"] ?? "")"
+        let FileName: String = "\(response?["FileName"] ?? "")"
+        let uploadID: String = "\(response?["Upload"] ?? "")"
+        var endpoints: [String] = Array<String>.init()
+        if response?.keys.contains("Hosts") ?? false {
+            endpoints = response?["Hosts"] as! [String]
+            endpoints.append(endpoint)
+        }
+        PQAliOssUtil.shared.PQOSSMultipartUpload(accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken, bucketName: bucketName, endpoints: endpoints, FileName: FileName, fileURL: URL(fileURLWithPath: localPath.replacingOccurrences(of: "file:///", with: "")), ossUploadID: uploadID, videoSource: videoSource)
+    }
+
+    /// 重新上传
+    /// - Parameters:
+    ///   - response: <#response description#>
+    ///   - localPath: <#localPath description#>
+    /// - Returns: <#description#>
+    public  func reloadTask(response: [String: Any]?, localPath: String?,videoSource:String? = nil) {
+        if response == nil {
+            return
+        }
+        let accessKeyId: String = "\(response?["AccessKeyId"] ?? "")"
+        let secretKeyId: String = "\(response?["AccessKeySecret"] ?? "")"
+        let securityToken: String = "\(response?["SecurityToken"] ?? "")"
+        let endpoint: String = "\(response?["Host"] ?? "")"
+        let bucketName: String = "\(response?["Bucket"] ?? "")"
+        let FileName: String = "\(response?["FileName"] ?? "")"
+        let uploadID: String = "\(response?["Upload"] ?? "")"
+        var endpoints: [String] = Array<String>.init()
+        if response?.keys.contains("Hosts") ?? false {
+            endpoints = response?["Hosts"] as! [String]
+            endpoints.append(endpoint)
+        }
+        debugPrint("取我方服务器STS 返回数据 \(String(describing: response))")
+        PQAliOssUtil.shared.PQOSSMultipartUpload(accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken, bucketName: bucketName, endpoints: endpoints, FileName: FileName, fileURL: URL(fileURLWithPath: (localPath ?? "").replacingOccurrences(of: "file:///", with: "")), ossUploadID: uploadID,videoSource: videoSource)
+    }
+
+    /// 分片上传
+    /// - Parameters:
+    ///   - accessKeyId: <#accessKeyId description#>
+    ///   - secretKeyId: <#secretKeyId description#>
+    ///   - securityToken: <#securityToken description#>
+    ///   - bucketName: <#bucketName description#>
+    ///   - endpoints: oss host域名
+    ///   - FileName: oss 资源地址
+    ///   - fileURL: 文件本地地址
+    ///   - ossUploadID: oss 上传ID
+    ///   - contentType: 文件类型
+    ///   - materialType: 素材类型
+    ///   - isMatarialUpload: 是否是素材上传
+    ///   - contentMD5: 素材内容MD5值 isMatarialUpload = true时传
+    ///   - width: 视频宽 isMatarialUpload = true时传
+    ///   - height: 视频高 isMatarialUpload = true时传
+    /// - Returns: <#description#>
+    public  func PQOSSMultipartUpload(accessKeyId: String, secretKeyId: String, securityToken: String, bucketName: String, endpoints: [String], FileName: String, fileURL: URL, ossUploadID: String, materialType: StickerType = .VIDEO, isMatarialUpload: Bool = false, contentMD5: String = "", width: CGFloat = 0, height: CGFloat = 0,videoSource:String? = nil) -> PQAliOssUtil {
+        debugPrint("上传数据 参数\n accessKeyId:\(accessKeyId)\n secretKeyId:\(secretKeyId)\n  securityToken:\(securityToken) \n bucketName:\(bucketName)\n endpoint:\(endpoints)\n FileName:\(FileName)")
+        if endpoints.count <= 0 || (endpoints.first?.count ?? 0) <= 0 {
+            debugPrint("endpoints 为空")
+            return .shared
+        }
+        #if DEBUG
+            // 打开 oss 日志
+            OSSLog.enable()
+        #endif
+
+        // 1,设置鉴权
+        let url = PQENVUtil.shared.longvideoapi + (isMatarialUpload ? materialUploadStsTokenUrl : getStsTokenUrl)
+        
+        let authServerUrl = url + "?appType=\(commonParams()["appType"] as? String ?? "")" + "&machineCode=" + getMachineCode()
+            + "&token=" + (BFConfig.shared.token ?? "") + "&loginUid" + (BFConfig.shared.uid ?? "") + "&fileType=2" + "&uploadId=\(ossUploadID)"
+        debugPrint("authServerUrl is: \(authServerUrl)")
+        debugPrint("当前上传authServerUrl线程:\(Thread.isMainThread) ")
+
+        let provider = OSSAuthCredentialProvider(authServerUrl: authServerUrl) { (data) -> Data? in
+            debugPrint("当前上传provider线程:\(Thread.isMainThread) ")
+            // 在 OSSModel 代码中解析有自己的格式 所以接收到我方服务器后的数据要进行二次处理 AccessKeyId 等信息
+            let str = String(data: data, encoding: .utf8)
+            let jsonDic = jsonStringToDictionary(str!)
+            var respDic: [String: Any] = [:]
+            if jsonDic?.keys.contains("code") ?? false, "\(jsonDic?["code"] ?? "")" == "0" {
+                respDic = [
+                    "StatusCode": "200",
+                    "AccessKeyId": (jsonDic?["data"] as? [String: Any])?["AccessKeyId"] ?? accessKeyId,
+                    "AccessKeySecret": (jsonDic?["data"] as? [String: Any])?["AccessKeySecret"] ?? secretKeyId,
+                    "SecurityToken": (jsonDic?["data"] as? [String: Any])?["SecurityToken"] ?? securityToken,
+                    "Expiration": (jsonDic?["data"] as? [String: Any])?["Expiration"] ?? securityToken,
+                ]
+            }
+            let proStr = dictionaryToJsonString(respDic as [String: Any])
+            debugPrint("处理后准备给 OSS SDK数据 \(String(describing: proStr))")
+            let decodedData = proStr?.data(using: .utf8)
+            if decodedData != nil {
+                return decodedData
+            }
+            return data
+        }
+
+        // 2 拼装 request
+        let request = OSSMultipartUploadRequest()
+        // add by ak  set  contect type . doc https://help.aliyun.com/knowledge_detail/39522.html
+        request.contentType = fileURL.absoluteString.mimeType() // 媒体类型
+        request.uploadingFileURL = fileURL
+        request.bucketName = bucketName
+        request.objectKey = FileName
+        // 根据文件大小 设置 part size
+        var fileSize: UInt64 = 0
+        do {
+            let attr = try FileManager.default.attributesOfItem(atPath: fileURL.relativePath)
+            fileSize = attr[FileAttributeKey.size] as! UInt64
+
+        } catch {
+            debugPrint("取文件大小 Error: \(error)")
+            if aliOssHander != nil {
+                DispatchQueue.main.async { [weak self] in
+                    self?.aliOssHander!(isMatarialUpload, materialType, fileURL.absoluteString.pathExtension, 260, FileName, contentMD5, width, height, 0, 0, nil, fileURL, nil, "文件已丢失")
+                }
+            }
+        }
+        debugPrint("文件已经存在  \(fileURL) 文件大小 \(fileSize)")
+        var partSize: Int = 0
+        // sdk中规定kClientMaximumOfChunks = 5000;
+        let defaultChunkSize: UInt64 = 1024 * 1024
+        if ceil(Float(fileSize / defaultChunkSize)) <= 1 {
+            partSize = 500 * 1024
+        } else if ceil(Float(fileSize / defaultChunkSize)) < 5000 {
+            partSize = Int(fileSize / UInt64(ceil(Float(fileSize / defaultChunkSize))))
+        } else {
+            partSize = Int(ceil(Float(fileSize / 5000)))
+        }
+        debugPrint("partSize \(partSize)")
+        // 除最后一片外 不能小于 102400(100kb)
+        request.partSize = UInt(partSize)
+        request.uploadId = ossUploadID
+        request.uploadProgress = { [weak self] (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
+            debugPrint("当前上传uploadProgress线程:\(Thread.isMainThread) ")
+            self?.paraseSpeedAndRestTime(bytesSent: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend) { uploadSpeed, resttime in
+                if self?.aliOssProgressHander != nil {
+                    DispatchQueue.main.async { [weak self] in
+                        self?.aliOssProgressHander!(bytesSent, totalBytesSent, totalBytesExpectedToSend, resttime, uploadSpeed)
+                    }
+                }
+            }
+            // 多个界面都要处理上传进度 所以使用通知 ? 通知有回到主线?
+            // postNotification(name: cOSSUploadFileProgress,userInfo: ["bytesSent":bytesSent,"totalBytesSent":totalBytesSent,"totalBytesExpectedToSend":totalBytesExpectedToSend,"resttime":resttime])
+        }
+
+        // 3,设置 config
+        let conf = OSSClientConfiguration()
+        conf.timeoutIntervalForRequest = 30 // 连接超时,默认15秒
+        conf.maxConcurrentRequestCount = 5 // 最大并发请求书,默认5个
+        conf.maxRetryCount = 3 // 失败后最大重试次数,默认2次
+        // 打开后台上传
+        conf.enableBackgroundTransmitService = true
+        conf.backgroundSesseionIdentifier = getUniqueId(desc: "\(ossUploadID)\(fileURL.absoluteString)\(FileName)")
+        // 4,设置 clinet
+        let client = OSSClient(endpoint: endpoints[0], credentialProvider: provider, clientConfiguration: conf)
+        if !isMatarialUpload {
+            allTasks[FileName] = request
+        }
+        // 5,发起任务
+        let task = client.multipartUpload(request)
+
+        task.continue ({ [weak self] (osstask) -> Any? in
+            debugPrint("当前上传线程:\(Thread.isMainThread) ")
+            if osstask.error == nil {
+                debugPrint("上传成功")
+                // 文件URL的格式为:BucketName.Endpoint/ObjectName。
+                debugPrint(" 上传成功注意使用时拼接域名 url == \(FileName)")
+                request.callbackParam = ["code": 1, "objectKey": FileName, "msg": "上传成功"]
+                DispatchQueue.main.async { [weak self] in
+                    if self?.aliOssHander != nil {
+                        self?.aliOssHander!(isMatarialUpload, materialType, fileURL.absoluteString.pathExtension, 1, FileName, contentMD5, width, height, 0, 0, nil, fileURL, nil, "上传成功")
+                    }
+                }
+                // 如果在后台发送上传成功的本地通知
+                if !isMatarialUpload {
+                    DispatchQueue.main.async {
+                        if UIApplication.shared.applicationState == .background {
+                            sendUploadNotification(isSuccess: true)
+                        }
+                    }
+                    postNotification(name: cUploadSuccessKey, userInfo: ["code": 1, "objectKey": FileName, "msg": "上传成功"])
+                }
+                // 上传完成
+                var extParams:Dictionary<String,Any>?
+                if videoSource != nil && (videoSource?.count ?? 0) > 0 {
+                    extParams = ["source":videoSource ?? ""]
+                }
+                PQEventTrackViewModel.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_success, pageSource: nil,extParams: extParams, remindmsg: "上传相关")
+            } else {
+                debugPrint("上传失败 \(osstask.error!)")
+
+                request.callbackParam = ["code": (osstask.error! as NSError).code, "objectKey": FileName, "msg": osstask.error?.localizedDescription ?? ""]
+                if self?.aliOssHander != nil {
+                    DispatchQueue.main.async { [weak self] in
+                        self?.aliOssHander!(isMatarialUpload, materialType, fileURL.absoluteString.pathExtension, (osstask.error! as NSError).code, FileName, contentMD5, width, height, 0, 0, nil, fileURL, nil, osstask.error?.localizedDescription)
+                    }
+                }
+                // 如果在后台发送上传失败的本地通知
+                if !isMatarialUpload {
+                    DispatchQueue.main.async {
+                        if UIApplication.shared.applicationState == .background {
+                            sendUploadNotification(isSuccess: false)
+                        }
+                    }
+                }
+                // 上传失败
+                var extParams:Dictionary<String,Any>?
+                if videoSource != nil && (videoSource?.count ?? 0) > 0 {
+                    extParams = ["source":videoSource ?? ""]
+                }
+                PQEventTrackViewModel.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_fail, pageSource: nil,extParams: extParams, remindmsg: "上传相关")
+            }
+            return nil
+        })
+        // 开始上传
+        var extParams:Dictionary<String,Any>?
+        if videoSource != nil && (videoSource?.count ?? 0) > 0 {
+            extParams = ["source":videoSource ?? ""]
+        }
+        PQEventTrackViewModel.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_start, pageSource: .sp_upload_coverSelect,extParams: extParams, remindmsg: "上传相关")
+        return .shared
+    }
+
+    /// add by ak  简单上传方式 本方法支持后台运行 设置支持后台时会把 DATA
+    /// - Parameters:
+    ///   - accessKeyId: <#accessKeyId description#>
+    ///   - secretKeyId: <#secretKeyId description#>
+    ///   - securityToken: <#securityToken description#>
+    ///   - bucketName: <#bucketName description#>
+    ///   - endpoint: oss host域名
+    ///   - objectKey: oss 资源地址
+    ///   - fileURL: 文件本地地址
+    ///   - data: 文件数据
+    ///   - fileExtensions: 文件后缀名 mp4/mp3
+    ///   - enableBackground: 是否支持后台下载
+    ///   - materialType: 素材类型
+    ///   - isMatarialUpload: 是否是素材上传
+    ///   - contentMD5: 素材内容MD5值 isMatarialUpload = true时传
+    ///   - width: 视频宽 isMatarialUpload = true时传
+    ///   - height: 视频高 isMatarialUpload = true时传
+    ///   - duration: 素材时长 isMatarialUpload = true时传
+    ///   - frameNumber: gif素材帧数 isMatarialUpload = true时传
+    ///   - netResourceUrl: 网络素材地址 isMatarialUpload = true时传
+    /// - Returns: <#description#>
+    public  func putObjectAsync(accessKeyId: String, secretKeyId: String, securityToken: String, bucketName: String, endpoint: [String], objectKey: String, fileURL: URL?, data: Data?, fileExtensions: String, enableBackground: Bool, materialType: StickerType = .VIDEO, isMatarialUpload: Bool = false, contentMD5: String = "", width: CGFloat = 0, height: CGFloat = 0, duration: CGFloat, frameNumber: Int, netResourceUrl: String? = nil,videoSource:String? = nil) -> PQAliOssUtil {
+        #if DEBUG
+            OSSLog.enable()
+        #endif
+        debugPrint("普通上传数据 参数 accessKeyId:\(accessKeyId) secretKeyId:\(secretKeyId) securityToken:\(securityToken) bucketName:\(bucketName) endpoint:\(endpoint) objectKey:\(objectKey) enableBackground:\(enableBackground) fileURL : \(fileURL!)")
+        if endpoint.count <= 0 || (endpoint.first?.count ?? 0) <= 0 {
+            debugPrint("endpoints 为空")
+            return .shared
+        }
+        let provider = OSSStsTokenCredentialProvider(accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken)
+//            let configuration = OSSClientConfiguration()
+//            if enableBackground {
+//                configuration.enableBackgroundTransmitService = true
+//                configuration.backgroundSesseionIdentifier = objectKey
+//            }
+//            configuration.timeoutIntervalForRequest = 60 // 连接超时,默认15秒
+//            configuration.maxConcurrentRequestCount = 20 // 最大并发请求书,默认5个
+//            configuration.maxRetryCount = 3 // 失败后最大重试次数,默认2次
+        startClient(accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken, endpoint: endpoint.first ?? "")
+        let request = OSSPutObjectRequest()
+        if fileURL != nil {
+            request.uploadingFileURL = fileURL!
+            request.contentType = fileURL!.absoluteString.mimeType() // 媒体类型
+        } else if data != nil {
+            request.uploadingData = data!
+            request.contentType = materialType.mimeType() // 媒体类型
+        }
+        request.bucketName = bucketName
+        request.objectKey = objectKey
+        // request.contentMd5 = contentMD5
+        request.uploadProgress = { (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
+            var resttime: Int64 = 0
+            if self.oldTime == 0 {
+                self.oldTime = Int(Date().timeIntervalSince1970)
+            }
+            let nowTime = Int(Date().timeIntervalSince1970)
+            let pertime = Int(nowTime - self.oldTime)
+            if pertime != 0 {
+                var speed = totalBytesSent / Int64(pertime) // 单位b/s
+                if speed != 0 {
+                    resttime = ((totalBytesExpectedToSend - totalBytesSent) / speed)
+
+                    var units = "b/s" // 单位名称
+                    if speed / 1024 > 1 {
+                        speed = speed / 1024
+                        units = "k/s"
+                    }
+                    if speed / 1024 > 1 {
+                        speed = speed / 1024
+                        units = "M/s"
+                    }
+                    debugPrint("上传速度: \(speed)\(units)   还剩时间 \(resttime)")
+                }
+            }
+            // 多个界面都要处理上传进度 所以使用通知 ? 通知有回到主线?
+            postNotification(name: cOSSUploadFileProgress, userInfo: ["bytesSent": bytesSent, "totalBytesSent": totalBytesSent, "totalBytesExpectedToSend": totalBytesExpectedToSend, "resttime": resttime])
+        }
+        let task = client?.putObject(request)
+        if fileURL?.absoluteString.contains("_noise_") ?? false {
+            task?.continue ({ [weak self] (osstask) -> Any? in
+                debugPrint("当前上传线程:\(Thread.isMainThread) ")
+                if osstask.error == nil {
+                    debugPrint("上传成功")
+                    // 文件URL的格式为:BucketName.Endpoint/ObjectName。
+                    debugPrint(" 上传成功注意使用时拼接域名 url == \(objectKey)")
+                    request.callbackParam = ["code": 1, "objectKey": objectKey, "msg": "上传成功"]
+                    DispatchQueue.main.async { [weak self] in
+                        if self?.aliOssHander != nil {
+                            self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, 1, objectKey, contentMD5, width, height, duration, frameNumber, netResourceUrl, fileURL, data, "上传成功")
+                        }
+                    }
+                    // 如果在后台发送上传成功的本地通知
+                    if !isMatarialUpload {
+                        DispatchQueue.main.async {
+                            if UIApplication.shared.applicationState == .background {
+                                sendUploadNotification(isSuccess: true)
+                            }
+                        }
+                        postNotification(name: cUploadSuccessKey, userInfo: ["code": 1, "objectKey": objectKey, "msg": "上传成功"])
+                    }
+                    // 上传完成
+                    var extParams:Dictionary<String,Any>?
+                    if videoSource != nil && (videoSource?.count ?? 0) > 0 {
+                        extParams = ["source":videoSource ?? ""]
+                    }
+                    // 上传完成
+                    PQEventTrackViewModel.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_success, pageSource: nil,extParams: extParams, remindmsg: "上传相关")
+                } else {
+                    debugPrint("上传失败 \(osstask.error!)")
+                    request.callbackParam = ["code": (osstask.error! as NSError).code, "objectKey": objectKey, "msg": osstask.error?.localizedDescription ?? ""]
+                    if self?.aliOssHander != nil {
+                        DispatchQueue.main.async { [weak self] in
+                            self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, (osstask.error! as NSError).code, objectKey, contentMD5, width, height, duration, frameNumber, netResourceUrl, fileURL, data, osstask.error?.localizedDescription)
+                        }
+                    }
+                    // 如果在后台发送上传失败的本地通知
+                    if !isMatarialUpload {
+                        DispatchQueue.main.async {
+                            if UIApplication.shared.applicationState == .background {
+                                sendUploadNotification(isSuccess: false)
+                            }
+                        }
+                    }
+                    // 上传失败
+                    PQEventTrackViewModel.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_fail, pageSource: nil, remindmsg: "上传相关")
+                }
+                return nil
+            }).waitUntilFinished()
+        } else {
+            task?.continue ({ [weak self] (osstask) -> Any? in
+                debugPrint("当前上传线程:\(Thread.isMainThread) ")
+                if osstask.error == nil {
+                    debugPrint("上传成功")
+                    // 文件URL的格式为:BucketName.Endpoint/ObjectName。
+                    debugPrint(" 上传成功注意使用时拼接域名 url == \(objectKey)")
+                    request.callbackParam = ["code": 1, "objectKey": objectKey, "msg": "上传成功"]
+                    DispatchQueue.main.async { [weak self] in
+                        if self?.aliOssHander != nil {
+                            self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, 1, objectKey, contentMD5, width, height, duration, frameNumber, netResourceUrl, fileURL, data, "上传成功")
+                        }
+                    }
+                    // 如果在后台发送上传成功的本地通知
+                    if !isMatarialUpload {
+                        DispatchQueue.main.async {
+                            if UIApplication.shared.applicationState == .background {
+                                sendUploadNotification(isSuccess: true)
+                            }
+                        }
+                        postNotification(name: cUploadSuccessKey, userInfo: ["code": 1, "objectKey": objectKey, "msg": "上传成功"])
+                    }
+                    // 上传完成
+                    PQEventTrackViewModel.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_success, pageSource: nil, remindmsg: "上传相关")
+                } else {
+                    debugPrint("上传失败 \(osstask.error!)")
+                    request.callbackParam = ["code": (osstask.error! as NSError).code, "objectKey": objectKey, "msg": osstask.error?.localizedDescription ?? ""]
+                    if self?.aliOssHander != nil {
+                        DispatchQueue.main.async { [weak self] in
+                            self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, (osstask.error! as NSError).code, objectKey, contentMD5, width, height, duration, frameNumber, netResourceUrl, fileURL, data, osstask.error?.localizedDescription)
+                        }
+                    }
+                    // 如果在后台发送上传失败的本地通知
+                    if !isMatarialUpload {
+                        DispatchQueue.main.async {
+                            if UIApplication.shared.applicationState == .background {
+                                sendUploadNotification(isSuccess: false)
+                            }
+                        }
+                    }
+                    // 上传失败
+                    PQEventTrackViewModel.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_fail, pageSource: nil, remindmsg: "上传相关")
+                }
+                return nil
+            })
+        }
+        return .shared
+    }
+
+    /// 取消某个任务
+    /// - Parameter objectKey: 任务唯一标识
+    public  func putObjectCancel(objectKey: String) {
+        for key in allTasks.keys {
+            if key == objectKey {
+                allTasks[key]!.cancel()
+                allTasks.removeValue(forKey: key)
+            }
+        }
+    }
+
+    /// 解析上传速度及剩余时间
+    /// - Parameters:
+    ///   - bytesSent: <#bytesSent description#>
+    ///   - totalBytesSent: <#totalBytesSent description#>
+    ///   - totalBytesExpectedToSend: <#totalBytesExpectedToSend description#>
+    ///   - complateHandle: <#complateHandle description#>
+    /// - Returns: <#description#>
+    public  func paraseSpeedAndRestTime(bytesSent _: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64, complateHandle: (_ uploadSpeed: String, _ resttime: Int64) -> Void) {
+        let newInterfaceBytes = PQBridgeObject.getInterfaceBytes()
+        var interfaceBytes = abs(newInterfaceBytes - lastInterfaceBytes)
+        if interfaceBytes <= 0 {
+            interfaceBytes = 1
+        }
+        lastInterfaceBytes = newInterfaceBytes
+        let resttime = (totalBytesExpectedToSend - totalBytesSent) / interfaceBytes
+        debugPrint("interfaceBytes = \(interfaceBytes),totalBytesExpectedToSend = \(totalBytesExpectedToSend),totalBytesSent = \(totalBytesSent),")
+        complateHandle("\(PQBridgeObject.formatNetWork(interfaceBytes))", resttime)
+    }
+
+    override private init() {
+        super.init()
+    }
+
+    public override func copy() -> Any {
+        return self
+    }
+
+    public override func mutableCopy() -> Any {
+        return self
+    }
+}

+ 144 - 0
BFStuckPointKit/Classes/BFUtils/PQSingletoMemoryUtil.swift

@@ -0,0 +1,144 @@
+//
+//  PQSingletoMemoryUtil.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/6/9.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import UIKit
+import Photos
+import BFCommonKit
+
+// MARK: - 内存缓存数据
+
+/// 内存缓存数据r
+public class PQSingletoMemoryUtil: NSObject {
+    public static let shared = PQSingletoMemoryUtil()
+    public var isShowAttendPoint: Bool = false // 是否有关注更新
+    public var attendIdosUpdateCount: Int = 0 // 关注更新条数
+    public var isLeftSlipRemind: Bool = false
+    public var needLogin: Bool = false // 是否需要显示手机登录
+    public var isFinishedCoging: Bool = false // 是否已完成配置请求
+    public var isFinishedAlias: Bool = false // 是否完成绑定别名
+    public var selectedTabIndex: String?
+    public var commandVideoItem: BFVideoItemProtocol? // 口令内容
+    public var commandLaunchParams: String = "" // 口令启动时的值
+    public var commandReportParams: [String: Any]? // 口令启动时解析的其他值
+    public var h5MsgVid: String? // 剪切板id
+    public var isShowNoWiFiRemind: Bool = false // 是否提示过非WiFi提示
+    public var isNeedRefreshAttend: Bool = false // 是否需要刷新关注
+    public var isNeedRefreshJoinTopic: Bool = false // 是否需要刷新加入的话题
+    
+    // 剪切板content是否为空,如果为空走承接逻辑,冷启动不再加载缓存
+    public var isEmptyPasteContent: Bool = false {
+        didSet {
+            BFLog(message: "isEmptyPasteContent = \(isEmptyPasteContent)")
+        }
+    }
+
+    // 展示无服务提示视图(剪切板 data.reportData.downloadButtonType为 weapp_share_noServiceDownloadApp/weapp_categoryTab_noServiceDownloadApp
+    public var isShowPasteNoServeView: Bool = false {
+        didSet {
+            BFLog(message: "isShowPasteNoServeView = \(isShowPasteNoServeView)")
+        }
+    }
+    /// 是否正在展示广告view
+    public var isLoadingSplashAdView: Bool = false {
+        didSet {
+            BFLog(message: "isLoadingSplashAdView = \(isLoadingSplashAdView)")
+        }
+    }
+    public var cutBoardInfo: String? // 剪切板信息
+    public var cutBoard: String? // 剪切板来源cutBoardInfo
+    public var isPushLoading: Bool = false // 是否正在加载推送数据
+
+    public var coldLaunchType: coldLaunchType?
+    public var isColdLaunch: Bool = false // 冷启动
+    public var coldLaunchStatus: Int = 0 // 1-请求中 2-请求成功 3-请求失败
+    public var deviceToken: String = "" // 推送deviceToken
+    public var activityData: Dictionary<String,Any>? // 活动数据信息
+    public var isShowTodaySuccess: Bool = false // 是否已经展示过今日已完成
+    public var sessionId: String = getUniqueId(desc: "sessionId")
+    public var subSessionid: String?
+    public var isDefaultAtten: Bool = false // 是否默认进入关注
+    public var isDefaultMineSingleVideoDetail: Bool = false // 是否默认进入我的单个视频详情
+    public var playCount: Int = 0 // 播放次数
+    public var uploadDatas: [BFVideoItemProtocol] = Array<BFVideoItemProtocol>.init() // 正在上传的视频集合
+    public var isShowUpSlideGuide: Bool = false // 是否展示了滑动向上的提示
+    public var showUpSlideData: PQVideoListModel? // 是否展示了滑动向上的提示
+    public var makeVideoProjectId: String? // 制作视频项目Id
+    public var draftboxId: String? // 草稿箱ID
+    // add by ak 结构化数据 saveDraft api 要 用到的参数
+    public var sdata: String?
+    // add by ak 结构化数据 saveDraft api 要 用到的参数
+    public var title: String?
+    // add by ak 结构化数据 saveDraft api 要 用到的参数
+    public var coverUrl: String?
+
+    // 未读数字
+    public var unReadInfo: Dictionary<String,Any>?
+    public var abInfoData: [String: Any] = Dictionary<String, Any>.init() // 实验数据
+    public var allExportSession: [PHAsset:AVAssetExportSession] = [PHAsset:AVAssetExportSession].init()
+    public func updateTabPoint() {
+//        let tabBar =
+//            rootViewController()?.tabBar
+//        if isShowAttendPoint {
+//            tabBar?.showPoint(index: 1)
+//        } else {
+////          tabBar.removePoint(index: 1)
+//        }
+    }
+
+    /// 创建sessionId
+    /// - Returns: <#description#>
+   public func createSesstionId() {
+        sessionId = getUniqueId(desc: "sessionId")
+        subSessionid = sessionId
+        BFLog(message: "生成的sessionId = \(sessionId)")
+    }
+
+    /// 创建subSessionid
+    /// - Returns: <#description#>
+    public func createSubSesstionId() {
+        subSessionid = getUniqueId(desc: "subSessionid")
+        BFLog(message: "生成的subSessionid = \(String(describing: subSessionid))")
+    }
+
+    /// 制作视频项目Id
+    /// - Returns: <#description#>
+    public func createMakeVideoProjectId() {
+        makeVideoProjectId = cProjectIdPrefix + getUniqueId(desc: "makeVideoProjectId")
+        BFLog(message: "生成的projectId = \(String(describing: makeVideoProjectId))")
+    }
+
+    /// 解析abInfoData
+    /// - Parameter abInfo: <#abInfo description#>
+    /// - Returns: description
+    public func parasABinfoData(abInfo: String?) {
+        if abInfo != nil, (abInfo?.count ?? 0) > 0, abInfo != "{}" {
+            guard let infoDic = jsonStringToDictionary(abInfo!) else { return }
+            abInfoData.merge(infoDic, uniquingKeysWith: { (key, _) -> Any in
+                key
+            })
+            BFLog(message: "=====abInfoData = \(abInfoData)")
+        }
+    }
+
+    override private init() {
+        super.init()
+        selectedTabIndex = getUserDefaults(key: cSelectedTabIndex) as? String
+        if selectedTabIndex == nil {
+            saveUserDefaults(key: cSelectedTabIndex, value: "categoryTab")
+            selectedTabIndex = "categoryTab"
+        }
+    }
+
+    public override func copy() -> Any {
+        return self
+    }
+
+    public override func mutableCopy() -> Any {
+        return self
+    }
+}

+ 483 - 0
BFStuckPointKit/Classes/BFUtils/PQSingletoRealmUtil.swift

@@ -0,0 +1,483 @@
+//
+//  PQSingletoRealmUtil.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/7/7.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import RealmSwift
+import UIKit
+import BFCommonKit
+import BFMediaKit
+
+public class PQSingletoRealmUtil: NSObject {
+    public  var schemaVersion : UInt64 = 40
+    public var realmEntry: Realm? {
+        let config = Realm.Configuration(
+            schemaVersion: schemaVersion,
+            migrationBlock: { _, oldSchemaVersion in
+                if oldSchemaVersion < 1 {}
+            }
+        )
+        Realm.Configuration.defaultConfiguration = config
+        do {
+            let realmEntry = try Realm(configuration: config, queue: DispatchQueue.main)
+            return realmEntry
+        } catch let error as NSError {
+            // handle error
+            BFLog(message: "Realm-realm创建失败:\(error)")
+        }
+        return nil
+    }
+
+    public static let shared = PQSingletoRealmUtil()
+
+    // 根据不同的用户ID 初始化不同的数据库 th1
+    public func getDraftDB(uid: String) -> Realm {
+        let docPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] as String
+        let dbPath = docPath.appending("/\(uid)Draft.realm")
+        BFLog(message: "生成登录人数据库地址\(dbPath)")
+
+        var config = Realm.Configuration(
+            schemaVersion: schemaVersion,
+            migrationBlock: { _, oldSchemaVersion in
+                if oldSchemaVersion < 1 {}
+            }
+        )
+        config.fileURL = URL(string: dbPath)!
+        let defaultRealm = try! Realm(configuration: config)
+        return defaultRealm
+    }
+
+    /// 写入单个数据
+    /// - Parameter objects: <#objects description#>
+    /// - Returns: <#description#>
+    public func saveObject(object: Object) {
+        do {
+            try realmEntry?.write {
+                realmEntry?.add(object, update: .modified)
+                BFLog(message: "Realm-添加单个数据成功:\(object)")
+            }
+        } catch let error as NSError {
+            // handle error
+            BFLog(message: "Realm-添加单个数据失败:\(error)")
+        }
+    }
+
+    /// 写入多个数据
+    /// - Parameter objects: <#objects description#>
+    /// - Returns: <#description#>
+    public func saveObjects(objects: [Object]) {
+        do {
+            try realmEntry?.write {
+                realmEntry?.add(objects, update: .modified)
+                BFLog(message: "Realm-添加多个数据成功:\(objects)")
+            }
+        } catch let error as NSError {
+            // handle error
+            BFLog(message: "Realm-添加多个数据失败:\(error)")
+        }
+    }
+
+    /// 写入本地存储数据
+    /// - Parameter objects: <#objects description#>
+    /// - Returns: <#description#>
+    public func saveLocalSaveObject(uniqueId: String, isSelected: Bool) {
+        let currentObject = queryLocalStoreObjects(uniqueId: uniqueId)
+        if (currentObject?.count ?? 0) > 0 {
+            do {
+                try realmEntry?.write {
+                    currentObject?.first?.isSelected = isSelected
+                    (currentObject?.first as? PQLocalStoreModel)?.currentDate = systemCurrentDate()
+                    BFLog(message: "Realm-本地已存储数据更新成功:\(currentObject)")
+                }
+            } catch let error as NSError {
+                // handle error
+                BFLog(message: "Realm-本地已存储数据更新失败:\(error)")
+            }
+        } else {
+            let storeObject = PQLocalStoreModel()
+            storeObject.uniqueId = uniqueId
+            storeObject.isSelected = isSelected
+            // 未存在则保存
+            BFLog(message: "Realm-本地数据未存储,开始存储")
+            saveObject(object: storeObject)
+        }
+    }
+
+    /// 查询本地缓存数据
+    /// - Returns: <#description#>
+    public func queryLocalStoreObjects(uniqueId: String) -> Results<BFBaseModel>? {
+        return queryObjects(PQLocalStoreModel.self, filter: "uniqueId == '\(uniqueId)'")
+    }
+
+    /// 查询所有未过期视频缓存数据
+    /// - Returns: description
+    public func queryVideoObjects() -> Results<BFBaseModel>? {
+        // "isSelected == false AND date >= \(Int(Date.init().timeIntervalSince1970) - 24 * 60)"
+        return queryObjects(PQVideoListModel.self, filter: "isSelected == false")
+    }
+
+    /// 删除已缓存视频数据
+    /// - Returns: <#description#>
+    public func deleteAllCacheObject() {
+        guard let deleteObjecs = realmEntry?.objects(PQVideoListModel.self) else {
+            BFLog(message: "Realm-删除已缓存为空")
+            return
+        }
+        do {
+            try realmEntry?.write {
+                realmEntry?.delete(deleteObjecs)
+                BFLog(message: "Realm-删除已缓存视频成功")
+            }
+        } catch let error as NSError {
+            // handle error
+            BFLog(message: "Realm-删除已缓存视频失败:\(error)")
+        }
+    }
+
+    /// 根据条件查询数据
+    /// - Parameters:
+    ///   - type: 类型
+    ///   - filter: <#filter description#>
+    /// - Returns: <#description#>
+    public func queryObjects(_ type: BFBaseModel.Type, filter: String) -> Results<BFBaseModel>? {
+        let puppies = realmEntry?.objects(type).filter(filter)
+        BFLog(message: "查询已缓存数据:type = \(type) \(puppies ?? nil)")
+        return puppies
+    }
+
+    public func deleteObject() {
+        guard let deleteObjecs = realmEntry?.objects(PQVideoListModel.self).filter("isSelected == true") else { return }
+        do {
+            try realmEntry?.write {
+                realmEntry?.delete(deleteObjecs)
+                BFLog(message: "Realm-删除已缓存视频成功")
+            }
+        } catch let error as NSError {
+            // handle error
+            BFLog(message: "Realm-删除已缓存视频失败:\(error)")
+        }
+    }
+
+    /// 删除
+    /// - Returns: <#description#>
+    public func deleteObject(object: PQVideoListModel) {
+        let videoId: Int = object.videoId
+        guard let deleteObjecs = realmEntry?.objects(PQVideoListModel.self).filter("videoId == \(videoId)") else { return }
+        do {
+            try realmEntry?.write {
+                realmEntry?.delete(deleteObjecs)
+                BFLog(message: "Realm-删除已缓存视频成功")
+            }
+        } catch let error as NSError {
+            // handle error
+            BFLog(message: "Realm-删除已缓存视频失败:\(error)")
+        }
+    }
+
+    public func deleteObject(_ type: BFBaseModel.Type, filter: String) {
+        guard let deleteObjecs = queryObjects(type, filter: filter) else { return }
+        do {
+            try realmEntry?.write {
+                realmEntry?.delete(deleteObjecs)
+                BFLog(message: "Realm-删除已缓存视频成功")
+            }
+        } catch let error as NSError {
+            // handle error
+            BFLog(message: "Realm-删除已缓存视频失败:\(error)")
+        }
+    }
+
+    /// 更新缓存视频是否读取过
+    /// - Parameter object: <#object description#>
+    /// - Returns: <#description#>
+    public func updateObject(object: PQVideoListModel) {
+        guard let newObject = realmEntry?.objects(PQVideoListModel.self).filter("videoId == \(object.videoId)") else { BFLog(message: "更新 newObject 为空")
+            return
+        }
+        BFLog(message: "更新 newObject = \(newObject)")
+        do {
+            try realmEntry?.write {
+                newObject.first?.isSelected = true
+                BFLog(message: "Realm-更新缓存视频已播放成功:\(newObject)")
+            }
+        } catch let error as NSError {
+            // handle error
+            BFLog(message: "Realm-更新缓存视频已播放失败:\(error)")
+        }
+    }
+
+    override private init() {
+        super.init()
+    }
+
+    override public func copy() -> Any {
+        return self
+    }
+
+    override public func mutableCopy() -> Any {
+        return self
+    }
+
+    // MARK: - add by ak 数据库操作 V2 方法
+
+    // MARK: add
+
+    /// 添加一个
+    ///
+    /// - Parameter object: 添加元素
+    public func realmAdd(realm: Realm, object: Object) {
+        try! realm.write {
+            realm.add(object, update: .modified)
+        }
+    }
+
+    /// 添加多个
+    ///
+    /// - Parameter objects: 添加数组
+    public func realmAdds(realm: Realm, objects: [Object]) {
+        try! realm.write {
+            realm.add(objects, update: .modified)
+        }
+    }
+
+    // MARK: delete
+
+    /// 删除一个
+    ///
+    /// - Parameter object: 删除元素
+    public func realmDelete(realm: Realm, object: Object) {
+        try! realm.write {
+            realm.delete(object)
+        }
+    }
+
+    /// 删除多个
+    ///
+    /// - Parameter objects: 元素数组
+    public func realmDeletes(realm: Realm, objects: [Object]) {
+        try! realm.write {
+            realm.delete(objects)
+        }
+    }
+
+    /// 条件删除
+    ///
+    /// - Parameters:
+    ///   - object: 元素类型
+    ///   - predicate: 条件
+    public func realmDeletesWithPredicate(realm: Realm, object: Object.Type, predicate: NSPredicate) {
+        let results: [Object] = realmQueryWithParameters(realm: realm, object: object, predicate: predicate)
+        if results.count > 0 {
+            try! realm.write {
+                realm.delete(results)
+            }
+        }
+    }
+
+    /// 删除该类型所有
+    ///
+    /// - Parameter object: 元素类型
+    public func realmDeleteTypeList(realm: Realm, object: Object.Type) {
+        let objListResults = realmQueryWithType(realm: realm, object: object)
+        if objListResults.count > 0 {
+            try! realm.write {
+                realm.delete(objListResults)
+            }
+        }
+    }
+
+    /// 删除当前数据库所有
+    public func realmDeleteAll(realm: Realm) {
+        try! realm.write {
+            realm.deleteAll()
+        }
+    }
+
+    // MARK: update
+
+    /// 更新元素(元素必须有主键)
+    ///
+    /// - Parameter object: 要更新的元素
+    public func realmUpdte(realm: Realm, object _: Object) {
+        try! realm.write {
+//             realm.up(object, update: true)
+        }
+    }
+
+    /// 更新元素集合(元素必须有主键)
+    ///
+    /// - Parameter objects: 元素集合(集合内元素所有属性都要有值)
+    public func realmUpdtes(realm: Realm, objects _: [Object]) {
+        try! realm.write {
+//             realm.add(objects, update: true)
+        }
+    }
+
+    /// 更新操作 -> 对于realm搜索结果集当中的元素,在action当中直接负值即可修改
+    ///
+    /// - Parameter action:操作
+    public func realmUpdateWithTranstion(realm: Realm, action: (Bool) -> Void) {
+        try! realm.write {
+            action(true)
+        }
+    }
+
+    // MARK: query
+
+    /// 查询元素
+    /// - Parameter type: 元素类型
+    /// - Parameter filter: 查询条件
+    public func reamlQueryObjects(realm: Realm, _ type: Object.Type, filter: String) -> Results<Object>? {
+        let puppies = realm.objects(type).filter(filter)
+        BFLog(message: "查询已缓存数据:type = \(type) \(puppies)")
+        return puppies
+    }
+
+    public func reamlQueryObjectsV2(realm: Realm, _ type: Object.Type, filter: String) -> String {
+        var json: String?
+        try! realm.write {
+            let puppies: Results<Object> = realm.objects(type).filter(filter)
+            BFLog(message: "查询已缓存数据:type = \(type) \(puppies)")
+            if puppies.count > 0 {
+                json = (puppies.first as? PQEditProjectModel)!.toJSONString(prettyPrint: false)
+            }
+        }
+
+        return json ?? ""
+    }
+
+    /// 查询元素
+    ///
+    /// - Parameters:
+    ///   - object: 元素类型
+    /// - Returns: 查询结果
+    public func realmQueryWith(realm: Realm, object: Object.Type) -> [Object] {
+        let results = realmQueryWithType(realm: realm, object: object)
+        var resultsArray = [Object]()
+        if results.count > 0 {
+            for i in 0...results.count - 1 {
+                resultsArray.append(results[i])
+            }
+        }
+        return resultsArray
+    }
+
+    /// 查询元素
+    ///
+    /// - Parameters:
+    ///   - object: 元素类型
+    ///   - predicate: 查询条件
+    /// - Returns: 查询结果
+    public func realmQueryWithParameters(realm: Realm, object: Object.Type, predicate: NSPredicate) -> [Object] {
+        let results = realmQueryWith(realm: realm, object: object, predicate: predicate)
+        var resultsArray = [Object]()
+        if results.count > 0 {
+            for i in 0...results.count - 1 {
+                resultsArray.append(results[i])
+            }
+        }
+        return resultsArray
+    }
+
+    /// 分页查询
+    ///
+    /// - Parameters:
+    ///   - object: 查询类型
+    ///   - fromIndex: 起始页
+    ///   - pageSize: 每页多少个
+    /// - Returns: 查询结果
+    public func realmQueryWithParametersPage(realm: Realm, object: Object.Type, fromIndex: Int, pageSize: Int) -> [Object] {
+        let results = realmQueryWithType(realm: realm, object: object)
+        var resultsArray = [Object]()
+        if results.count <= pageSize * (fromIndex - 1) || fromIndex <= 0 {
+            return resultsArray
+        }
+        if results.count > 0 {
+            for i in pageSize * (fromIndex - 1)...fromIndex * pageSize - 1 {
+                resultsArray.append(results[i])
+            }
+        }
+        return resultsArray
+    }
+
+    /// 条件排序查询
+    ///
+    /// - Parameters:
+    ///   - object: 查询类型
+    ///   - predicate: 查询条件
+    ///   - sortedKey: 排序key
+    ///   - isAssending: 是否升序
+    /// - Returns: 查询结果
+    public func realmQueryWithParametersAndSorted(realm: Realm, object: Object.Type, predicate: NSPredicate, sortedKey: String, isAssending: Bool) -> [Object] {
+        let results = realmQueryWithSorted(realm: realm, object: object, predicate: predicate, sortedKey: sortedKey, isAssending: isAssending)
+        var resultsArray = [Object]()
+        if results.count > 0 {
+            for i in 0...results.count - 1 {
+                resultsArray.append(results[i])
+            }
+        }
+        return resultsArray
+    }
+
+    ///  分页条件排序查询
+    ///
+    /// - Parameters:
+    ///   - object: 查询类型
+    ///   - predicate: 查询条件
+    ///   - sortedKey: 排序key
+    ///   - isAssending: 是否升序
+    ///   - fromIndex: 起始页
+    ///   - pageSize: 每页多少个
+    /// - Returns: 查询结果
+    public func realmQueryWithParametersAndSortedAndPaged(realm: Realm, object: Object.Type, predicate: NSPredicate, sortedKey: String, isAssending: Bool, fromIndex: Int, pageSize: Int) -> [Object] {
+        let results = realmQueryWithSorted(realm: realm, object: object, predicate: predicate, sortedKey: sortedKey, isAssending: isAssending)
+        var resultsArray = [Object]()
+
+        if results.count <= pageSize * (fromIndex - 1) || fromIndex <= 0 {
+            return resultsArray
+        }
+
+        if results.count > 0 {
+            for i in pageSize * (fromIndex - 1)...fromIndex * pageSize - 1 {
+                resultsArray.append(results[i])
+            }
+        }
+        return resultsArray
+    }
+
+    // MARK: private method
+
+    /// 按类型查询
+    ///
+    /// - Parameter object: 查询元素类型
+    /// - Returns: 查询结果
+    public func realmQueryWithType(realm: Realm, object: Object.Type) -> Results<Object> {
+        return realm.objects(object)
+    }
+
+    /// 条件查询
+    ///
+    /// - Parameters:
+    ///   - object: 查询元素类型
+    ///   - predicate: 查询条件
+    /// - Returns: 查询结果
+    public func realmQueryWith(realm: Realm, object: Object.Type, predicate: NSPredicate) -> Results<Object> {
+        return realm.objects(object).filter(predicate)
+    }
+
+    /// 条件排序查询
+    ///
+    /// - Parameters:
+    ///   - object: 查询类型
+    ///   - predicate: 查询条件
+    ///   - sortedKey: 排序key
+    ///   - isAssending: 是否升序
+    /// - Returns: 查询结果
+    public func realmQueryWithSorted(realm: Realm, object: Object.Type, predicate: NSPredicate, sortedKey: String, isAssending: Bool) -> Results<Object> {
+        return realm.objects(object).filter(predicate)
+            .sorted(byKeyPath: sortedKey, ascending: isAssending)
+    }
+}

+ 75 - 0
BFStuckPointKit/Classes/BFUtils/PQSingletoSourcesFileUtil.swift

@@ -0,0 +1,75 @@
+
+//
+//  PQSourcesFileUtil.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/7/23.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import UIKit
+import BFCommonKit
+
+public class PQSingletoSourcesFileUtil: NSObject {
+    static public let shared = PQSingletoSourcesFileUtil()
+
+   public let cPlistPath = NSString(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!).appendingPathComponent("sourcesFile.plist")
+
+    public func createFile() -> Bool {
+        let fm = FileManager.default
+        if fm.fileExists(atPath: cPlistPath) {
+            return true
+        }
+        return fm.createFile(atPath: cPlistPath, contents: nil, attributes: nil)
+    }
+
+    public func writeFile(isString: Bool, key: String, sourceNmae: String) -> Bool {
+        // 获取加密文件路径
+        var object: String?
+        if isString {
+            object = DES3Util.encryptUseDES(sourceNmae, key: key)
+        } else {
+            guard let imDataPath = Bundle.main.path(forResource: sourceNmae, ofType: "png") else { return false }
+            let imageData = NSData(contentsOfFile: imDataPath)
+            object = DES3Util.encryptUseDES(imageData?.base64EncodedString(options: .lineLength64Characters), key: key)
+        }
+        if object != nil, (object?.count ?? 0) > 0 {
+            let oldData: NSDictionary = NSMutableDictionary(contentsOfFile: cPlistPath) ?? NSMutableDictionary()
+            oldData.setValue(object, forKey: key.md5)
+            let success: Bool = oldData.write(toFile: cPlistPath, atomically: true)
+            if success {
+                BFLog(message: "写入成功:\(oldData)")
+            } else {
+                BFLog(message: "写入失败:\(oldData)")
+            }
+            return success
+        }
+        return false
+    }
+
+    public func readFile(isString: Bool, key: String) -> Any {
+        let oldData = NSDictionary(contentsOfFile: Bundle.main.path(forResource: "sourcesFile.plist", ofType: nil) ?? "")
+        BFLog(message: "oldData = \(oldData),key = \(key.md5)")
+        let enStr = oldData?.object(forKey: key.md5)
+        let desStr = DES3Util.decryptUseDES(enStr as? String, key: key)
+        BFLog(message: "读取成功:\(desStr)")
+        if isString {
+            return desStr ?? ""
+        } else {
+            let data = NSData(base64Encoded: desStr ?? "", options: .ignoreUnknownCharacters)
+            return UIImage(data: data! as Data) ?? ""
+        }
+    }
+
+    override public init() {
+        super.init()
+    }
+
+    public override func copy() -> Any {
+        return self
+    }
+
+    public override func mutableCopy() -> Any {
+        return self
+    }
+}

+ 475 - 0
BFStuckPointKit/Classes/BFUtils/PQSingletoVideoPlayer.swift

@@ -0,0 +1,475 @@
+
+//
+//  PQVideoPlayer.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/6/3.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import BFCommonKit
+import UIKit
+
+public class PQSingletoVideoPlayer: NSObject {
+    public static let shared = PQSingletoVideoPlayer()
+    public var isPlayEnd: Bool = false // 是否已播放完成
+    public var isHomePageAllList: Bool = false // 首页加入/关注是否是全部列表
+    public var lastPlaybackTime: Float = 0 // 上次上报播放时长
+    public var isRealPlay: Bool = false // 是否已真实播放
+    public var isSemiRealPlay: Bool = false // 是否已播放到十秒
+    public var isPlayBegin: Bool = false // 是否已缓冲完成开始播放
+    public var isFirstFrame: Bool = false // 是否已显示第一帧加载完成
+    public var isPlayerError: Bool = false // 是否播放失败
+    public var loadingTime: TimeInterval = 0 // 加载时长
+    public var playId: String = getUniqueId(desc: "playId") // 播放ID
+    /// 进度回调
+    public var progressBloc: ((_ loadProgress: Float, _ playProgress: Float, _ duration: Float) -> Void)?
+    /// 播放状态回调
+    public var playStatusBloc: ((_ playStatus: PQVIDEO_PLAY_STATUS) -> Void)?
+    public var playControllerView: UIView?
+    public var playVideoData: BFVideoItemProtocol?
+    public var isPlaying: Bool {
+        return player.isPlaying()
+    }
+
+    public var autoResumePlayWhenEnterForeground: Bool = true
+    var shouldResumePlayWhenEnterForeground: Bool = false
+
+    public lazy var player: TXVodPlayer = {
+        let player = TXVodPlayer()
+        let config = TXVodPlayConfig()
+        config.cacheFolderPath = videoCacheDirectory
+        config.maxCacheItems = 5
+        player.config = config
+        player.vodDelegate = self
+        player.setRenderMode(.RENDER_MODE_FILL_SCREEN)
+        return player
+    }()
+
+    /// 配置播放器
+    /// - Parameters:
+    ///   - videoData: <#videoData description#>
+    ///   - controllerView: <#controllerView description#>
+    public func configPlyer(videoData: BFVideoItemProtocol, controllerView: UIView, renderMode: TX_Enum_Type_RenderMode = .RENDER_MODE_FILL_SCREEN, isAllList: Bool = false) {
+//        if playVideoData?.id == videoData.id {
+//            return
+//        }
+        isHomePageAllList = isAllList
+        isPlayEnd = false
+        isRealPlay = false
+        isSemiRealPlay = false
+        isPlayBegin = false
+        isFirstFrame = false
+        isPlayerError = false
+        loadingTime = Date().timeIntervalSince1970 * 1000
+        player.setRenderMode(renderMode)
+        player.stopPlay()
+        player.removeVideoWidget()
+        player.setupVideoWidget(controllerView, insert: 0)
+        player.enableHWAcceleration = true
+        playControllerView = controllerView
+        playVideoData = videoData
+        if playVideoData!.playProgress >= 0.0 {
+            var progress: CGFloat = CGFloat(playVideoData!.playProgress)
+            if progress > 5.0, progress < 20 {
+                progress = progress - 5.0
+                if progress <= 0 {
+                    progress = 0
+                }
+            }
+            BFLog(message: "xxx.playProgress =\(playVideoData!.playProgress),\(progress)")
+            player.setStartTime(progress)
+        }
+        playId = getUniqueId(desc: "playId")
+        lastPlaybackTime = 0
+        BFLog(message: "\(String(describing: videoData.title)) 开始播放 \(videoData.videoPath ?? "")")
+        if PQSingletoMemoryUtil.shared.playCount < 4 {
+            PQSingletoMemoryUtil.shared.playCount = PQSingletoMemoryUtil.shared.playCount + 1
+        }
+    }
+
+    public func reRenderView(newView: UIView) {
+        player.removeVideoWidget()
+        player.setupVideoWidget(newView, insert: 0)
+    }
+
+    /// 重制播放
+    public func resetPlayer() {
+        if playControllerView != nil {
+            let playDuration = player.currentPlaybackTime() - lastPlaybackTime
+            debugPrint("当前播放时长:\(player.currentPlaybackTime()),lastPlaybackTime:\(lastPlaybackTime),playDuration:\(playDuration)")
+            if playDuration > 0 {
+                var extParams: [String: Any]?
+                if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                    extParams = ["topicId": isHomePageAllList ? "all" : "\(playVideoData?.topicData?["id"] ?? "")"]
+                } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                    extParams = ["followedUid": isHomePageAllList ? "all" : "\(playVideoData?.user?["uid"] ?? "")"]
+                } else if (playVideoData?.pageSource == .sp_cmunit_newTopicDetail || playVideoData?.pageSource == .sp_cmunit_hotTopicDetail) {
+                    extParams = ["topicId": "\(playVideoData?.topicData?["id"] ?? "")"]
+                }
+                PQEventTrackViewModel.baseReportUpload(logType: .st_log_type_pLayaction, businessType: .bt_videoPlayDuration, objectType: nil, pageSource: playVideoData?.pageSource ?? .sp_cmunit_recommend, eventData: ["pageSource": (playVideoData?.pageSource ?? .sp_cmunit_recommend).rawValue, "playDuration": Int64(playDuration * 1000), "playId": playId, "uid": "\(playVideoData?.user?["uid"] ?? "")", "videoId": playVideoData?.id ?? 0], extParams: extParams, remindmsg: "播放时长统计")
+                lastPlaybackTime = player.currentPlaybackTime()
+            }
+            player.removeVideoWidget()
+            player.setupVideoWidget(playControllerView, insert: 0)
+            if playVideoData!.playProgress >= 0.0 {
+                //
+                var progress: CGFloat = CGFloat(playVideoData!.playProgress)
+                if progress > 5.0, progress < 20 {
+                    progress = progress - 5.0
+                    if progress <= 0 {
+                        progress = 0
+                    }
+                }
+                BFLog(message: "xxx.playProgress =\(playVideoData!.playProgress),\(progress)")
+                player.setStartTime(progress)
+            }
+            playId = getUniqueId(desc: "playId")
+            // 开始播放
+            if let vc = bf_getCurrentViewController(), let playview = self.playControllerView{
+                if playview.isDescendant(of: vc.view){
+                    startPlayr()
+                }
+            }
+        }
+    }
+
+    /// 开始播放
+    public func startPlayr() {
+        BFLog(message: "开始播放 \(playVideoData?.videoPath ?? "")")
+        if isValidURL(url: playVideoData?.videoPath) {
+            player.startPlay(playVideoData?.videoPath)
+        }
+    }
+
+    /// 暂停播放
+    public func pausePlayer() {
+        player.pause()
+        let playDuration = player.currentPlaybackTime() - lastPlaybackTime
+        debugPrint("当前播放时长:\(player.currentPlaybackTime()),lastPlaybackTime:\(lastPlaybackTime),playDuration:\(playDuration)")
+        if playDuration > 0 {
+            var extParams: [String: Any]?
+            if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                extParams = ["topicId": isHomePageAllList ? "all" : "\(playVideoData?.topicData?["id"] ?? "")"]
+            } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                extParams = ["followedUid": isHomePageAllList ? "all" : "\(playVideoData?.user?["uid"] ?? "")"]
+            } else if (playVideoData?.pageSource == .sp_cmunit_newTopicDetail || playVideoData?.pageSource == .sp_cmunit_hotTopicDetail) {
+                extParams = ["topicId": "\(playVideoData?.topicData?["id"] ?? "")"]
+            }
+            PQEventTrackViewModel.baseReportUpload(logType: .st_log_type_pLayaction, businessType: .bt_videoPlayDuration, objectType: nil, pageSource: playVideoData?.pageSource ?? .sp_cmunit_recommend, eventData: ["pageSource": (playVideoData?.pageSource ?? .sp_cmunit_recommend).rawValue, "playDuration": Int64(playDuration * 1000), "playId": playId, "uid": "\(playVideoData?.user?["uid"] ?? "")", "videoId": playVideoData?.id ?? 0], extParams: extParams, remindmsg: "播放时长统计")
+            lastPlaybackTime = player.currentPlaybackTime()
+        }
+    }
+
+    /// 继续播放
+    public func resumePlayer(renderMode: TX_Enum_Type_RenderMode? = nil) {
+        player.resume()
+        if renderMode != nil {
+            player.setRenderMode(renderMode!)
+        }
+        let playDuration = player.currentPlaybackTime() - lastPlaybackTime
+        debugPrint("当前播放时长:\(player.currentPlaybackTime()),lastPlaybackTime:\(lastPlaybackTime),playDuration:\(playDuration)")
+        if playDuration > 0 {
+            var extParams: [String: Any]?
+            if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                extParams = ["topicId": isHomePageAllList ? "all" : "\(playVideoData?.topicData?["id"] ?? "")"]
+            } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                extParams = ["followedUid": isHomePageAllList ? "all" : "\(playVideoData?.user?["uid"] ?? "")"]
+            } else if (playVideoData?.pageSource == .sp_cmunit_newTopicDetail || playVideoData?.pageSource == .sp_cmunit_hotTopicDetail) {
+                extParams = ["topicId": "\(playVideoData?.topicData?["id"] ?? "")"]
+            }
+            PQEventTrackViewModel.baseReportUpload(logType: .st_log_type_pLayaction, businessType: .bt_videoPlayDuration, objectType: nil, pageSource: playVideoData?.pageSource ?? .sp_cmunit_recommend, eventData: ["pageSource": (playVideoData?.pageSource ?? .sp_cmunit_recommend).rawValue, "playDuration": Int64(playDuration * 1000), "playId": playId, "uid": "\(playVideoData?.user?["uid"] ?? "")", "videoId": playVideoData?.id ?? 0], extParams: extParams, remindmsg: "播放时长统计")
+            lastPlaybackTime = player.currentPlaybackTime()
+        }
+    }
+
+    /// 停止播放
+    /// - Parameter isRemove: 是否移除载体视图
+    public func stopPlayer(isRemove: Bool = true) {
+        player.stopPlay()
+        if isRemove {
+            player.removeVideoWidget()
+            playControllerView = nil
+            playVideoData = nil
+        }
+        let playDuration = player.currentPlaybackTime() - lastPlaybackTime
+        debugPrint("当前播放时长:\(player.currentPlaybackTime()),lastPlaybackTime:\(lastPlaybackTime),playDuration:\(playDuration)")
+        if playDuration > 0 {
+            var extParams: [String: Any]?
+            if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                extParams = ["topicId": isHomePageAllList ? "all" : "\(playVideoData?.topicData?["id"] ?? "")"]
+            } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                extParams = ["followedUid": isHomePageAllList ? "all" : "\(playVideoData?.user?["uid"] ?? "")"]
+            } else if (playVideoData?.pageSource == .sp_cmunit_newTopicDetail || playVideoData?.pageSource == .sp_cmunit_hotTopicDetail){
+                extParams = ["topicId": "\(playVideoData?.topicData?["id"] ?? "")"]
+            }
+            PQEventTrackViewModel.baseReportUpload(logType: .st_log_type_pLayaction, businessType: .bt_videoPlayDuration, objectType: nil, pageSource: playVideoData?.pageSource ?? .sp_cmunit_recommend, eventData: ["pageSource": (playVideoData?.pageSource ?? .sp_cmunit_recommend).rawValue, "playDuration": Int64(playDuration * 1000), "playId": playId, "uid": "\(playVideoData?.user?["uid"] ?? "")", "videoId": playVideoData?.id ?? 0], extParams: extParams, remindmsg: "播放时长统计")
+            lastPlaybackTime = player.currentPlaybackTime()
+        }
+    }
+
+    /// seek
+    /// - Parameter time: <#time description#>
+    public func seek(time: Float) {
+        player.seek(time)
+    }
+
+    override private init() {
+        super.init()
+
+        NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in
+            if self.isPlaying {
+                self.pausePlayer()
+                self.shouldResumePlayWhenEnterForeground = true
+            }
+        }
+        NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil) { _ in
+
+            if self.autoResumePlayWhenEnterForeground, self.shouldResumePlayWhenEnterForeground {
+                if let vc = bf_getCurrentViewController(), let playview = self.playControllerView{
+                    if playview.isDescendant(of: vc.view){
+                        self.shouldResumePlayWhenEnterForeground = false
+                        self.resumePlayer()
+                    }
+                }
+            }
+        }
+    }
+
+    override public func copy() -> Any {
+        return self
+    }
+
+    override public func mutableCopy() -> Any {
+        return self
+    }
+}
+
+extension PQSingletoVideoPlayer: TXVodPlayListener {
+    /// 播放进度回调
+    /// - Parameters:
+    ///   - player: <#player description#>
+    ///   - EvtID: <#EvtID description#>
+    ///   - param: <#param description#>
+    public func onPlayEvent(_ player: TXVodPlayer!, event: Int32, withParam param: [AnyHashable: Any]!) {
+        if event != PLAY_EVT_PLAY_PROGRESS.rawValue {
+            BFLog(message: "播放器状态回调:event:\(event),status:\(EventID(event)),param:\(param ?? [:])")
+        }
+        switch event {
+        case PLAY_EVT_PLAY_PROGRESS.rawValue: // 播放进度
+            // 加载进度, 单位是秒
+            let loadProgress: Float = param[EVT_PLAYABLE_DURATION] as! Float
+            // 播放进度, 单位是秒
+            let playProgress: Float = param[EVT_PLAY_PROGRESS] as! Float
+            // 总长度, 单位是秒
+            let duration: Float = param[EVT_PLAY_DURATION] as! Float
+            if player.currentPlaybackTime() >= 10.0, player.currentPlaybackTime() <= 10.1 {
+                // 10.0上报
+                if !isSemiRealPlay, playVideoData != nil {
+                    isSemiRealPlay = true
+                    // 播放上报
+                    var extParams: [String: Any]?
+                    if isHomePageAllList {
+                        if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                            extParams = ["topicId": "all"]
+                        } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                            extParams = ["followedUid": "all"]
+                        }
+                    }
+                    PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_Action, videoData: playVideoData, pageSource: nil, businessType: .bt_videoSemiRealPlay, objectType: nil, extParams: extParams, shareId: nil, videoIds: nil, playId: playId)
+                }
+            }
+            if player.currentPlaybackTime() >= 20.0 || ((playProgress / duration) >= 0.3) {
+                // 视频播放到20s或播放到总时长30%,哪个先到为准
+                if !isRealPlay, playVideoData != nil {
+                    isRealPlay = true
+                    // 播放上报
+                    var extParams: [String: Any]?
+                    if isHomePageAllList {
+                        if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                            extParams = ["topicId": "all"]
+                        } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                            extParams = ["followedUid": "all"]
+                        }
+                    }
+                    PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_realPlay, videoData: playVideoData, pageSource: nil, businessType: .bt_videoRealPlay, objectType: nil, extParams: extParams, shareId: nil, videoIds: nil, playId: playId)
+                }
+            }
+            playVideoData?.playProgress = Float64(playProgress)
+            if progressBloc != nil {
+                progressBloc!(loadProgress, playProgress, duration)
+            }
+        case PLAY_EVT_RCV_FIRST_I_FRAME.rawValue:
+            // 首帧完成开始播放
+            if !isPlayBegin, playVideoData != nil {
+                isPlayBegin = true
+                // 播放上报
+                var extParams: [String: Any]?
+                if isHomePageAllList {
+                    if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                        extParams = ["topicId": "all"]
+                    } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                        extParams = ["followedUid": "all"]
+                    }
+                }
+                PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_Action, videoData: playVideoData, pageSource: nil, businessType: .bt_videoPlaySuccess, objectType: nil, extParams: extParams, shareId: nil, videoIds: nil, playId: playId)
+            }
+
+        case PLAY_EVT_PLAY_LOADING.rawValue: // 视频播放loading
+            if playStatusBloc != nil {
+                playStatusBloc!(.PQVIDEO_PLAY_STATUS_LOADING)
+            }
+        case PLAY_EVT_PLAY_BEGIN.rawValue: // 开始播放
+            if playStatusBloc != nil {
+                playStatusBloc!(.PQVIDEO_PLAY_STATUS_BEGIN)
+            }
+            // 首帧加载完成
+            if !isFirstFrame, playVideoData != nil {
+                isFirstFrame = true
+                // 播放上报
+                var extParams: [String: Any]?
+                if isHomePageAllList {
+                    if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                        extParams = ["topicId": "all"]
+                    } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                        extParams = ["followedUid": "all"]
+                    }
+                }
+                PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_play, videoData: playVideoData, pageSource: nil, businessType: nil, objectType: nil, extParams: extParams, shareId: nil, videoIds: nil, playId: playId)
+                let duration = Int((Date().timeIntervalSince1970 * 1000) - loadingTime)
+                BFLog(message: "加载时长:\(duration)")
+                // 加载时间上报
+                var timeExtParams: [String: Any] = ["duration": duration, "proxyEnable": "0", "url": playVideoData?.videoPath ?? "", "videoId": playVideoData?.uniqueId ?? "0"]
+                if extParams != nil {
+                    timeExtParams.merge(extParams!) { keyValue, _ in
+                        keyValue
+                    }
+                }
+                PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_Frontend, videoData: playVideoData, pageSource: nil, businessType: .bt_videoPlaySuccessTime, objectType: nil, extParams: timeExtParams, shareId: nil, videoIds: nil, playId: playId)
+            }
+            BFLog(message: "首帧加载完成")
+        case PLAY_EVT_PLAY_END.rawValue: // 播放结束
+            player.pause()
+            playId = getUniqueId(desc: "playId")
+            if playStatusBloc != nil {
+                playStatusBloc!(.PQVIDEO_PLAY_STATUS_END)
+            }
+            if !isPlayEnd, playVideoData != nil {
+                isPlayEnd = true
+                // 播放结束上报
+                var extParams: [String: Any]?
+                if isHomePageAllList {
+                    if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                        extParams = ["topicId": "all"]
+                    } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                        extParams = ["followedUid": "all"]
+                    }
+                }
+                PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_Action, videoData: playVideoData, pageSource: nil, businessType: .bt_videoPlayEnd, objectType: nil, extParams: extParams, shareId: nil, videoIds: nil, playId: playId)
+            }
+            let playDuration = player.currentPlaybackTime() - lastPlaybackTime
+            debugPrint("当前播放时长:\(player.currentPlaybackTime()),lastPlaybackTime:\(lastPlaybackTime),playDuration:\(playDuration)")
+            if playDuration > 0 {
+                var extParams: [String: Any]?
+                if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                    extParams = ["topicId": isHomePageAllList ? "all" : "\(playVideoData?.topicData?["id"] ?? "")"]
+                } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                    extParams = ["followedUid": isHomePageAllList ? "all" : "\(playVideoData?.user?["uid"] ?? "")"]
+                } else if (playVideoData?.pageSource == .sp_cmunit_newTopicDetail || playVideoData?.pageSource == .sp_cmunit_hotTopicDetail) {
+                    extParams = ["topicId": "\(playVideoData?.topicData?["id"] ?? "")"]
+                }
+                PQEventTrackViewModel.baseReportUpload(logType: .st_log_type_pLayaction, businessType: .bt_videoPlayDuration, objectType: nil, pageSource: playVideoData?.pageSource ?? .sp_cmunit_recommend, eventData: ["pageSource": (playVideoData?.pageSource ?? .sp_cmunit_recommend).rawValue, "playDuration": Int64(playDuration * 1000), "playId": playId, "uid": "\(playVideoData?.user?["uid"] ?? "")", "videoId": playVideoData?.id ?? 0], extParams: extParams, remindmsg: "播放时长统计")
+                lastPlaybackTime = player.currentPlaybackTime()
+            }
+        case PLAY_ERR_NET_DISCONNECT.rawValue, -2301: // 重连失败
+            if playStatusBloc != nil {
+                playStatusBloc!(.PQVIDEO_PLAY_STATUS_DISCONNECT)
+            }
+            if !isPlayerError, playVideoData != nil {
+                isPlayerError = true
+                // 播放失败
+                var extParams: [String: Any] = ["pageSource": playVideoData!.pageSource.rawValue, "networkType": networkStatus(), "extra": "0", "hasRetry": true, "url": playVideoData?.videoPath ?? "", "videoId": playVideoData?.uniqueId ?? "0", "what": event]
+                if isHomePageAllList {
+                    if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                        extParams["topicId"] = "all"
+                    } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                        extParams["followedUid"] = "all"
+                    }
+                }
+                PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_Action, videoData: playVideoData, pageSource: nil, businessType: .bt_videoPlayError, objectType: nil, extParams: extParams, shareId: nil, videoIds: nil, playId: playId)
+            }
+        case PLAY_ERR_FILE_NOT_FOUND.rawValue: // 播放文件不存在
+            if playStatusBloc != nil {
+                playStatusBloc!(.PQVIDEO_PLAY_STATUS_NOT_FOUND)
+            }
+            if !isPlayerError, playVideoData != nil {
+                isPlayerError = true
+                // 播放失败
+                var extParams: [String: Any] = ["pageSource": playVideoData!.pageSource.rawValue, "networkType": networkStatus(), "extra": "0", "hasRetry": false, "url": playVideoData?.videoPath ?? "", "videoId": playVideoData?.uniqueId ?? "0", "what": event]
+                if isHomePageAllList {
+                    if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                        extParams["topicId"] = "all"
+                    } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                        extParams["followedUid"] = "all"
+                    }
+                }
+                PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_Action, videoData: playVideoData, pageSource: nil, businessType: .bt_videoPlayError, objectType: nil, extParams: extParams, shareId: nil, videoIds: nil, playId: playId)
+            }
+        case PLAY_ERR_HEVC_DECODE_FAIL.rawValue, PLAY_ERR_HLS_KEY.rawValue, PLAY_ERR_GET_PLAYINFO_FAIL.rawValue, PLAY_WARNING_VIDEO_DECODE_FAIL.rawValue, PLAY_WARNING_AUDIO_DECODE_FAIL.rawValue: // H265解码失败,HLS解码key获取失败,获取点播文件信息失败,当前视频解码失败,当前音频解码失败
+            if playStatusBloc != nil {
+                playStatusBloc!(PQVIDEO_PLAY_STATUS.PQVIDEO_PLAY_STATUS_FAUILE)
+            }
+            if !isPlayerError, playVideoData != nil {
+                isPlayerError = true
+                // 播放失败
+                var extParams: [String: Any] = ["pageSource": playVideoData!.pageSource.rawValue, "networkType": networkStatus(), "extra": "0", "hasRetry": false, "url": playVideoData?.videoPath ?? "", "videoId": playVideoData?.uniqueId ?? "0", "what": event]
+                if isHomePageAllList {
+                    if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                        extParams["topicId"] = "all"
+                    } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                        extParams["followedUid"] = "all"
+                    }
+                }
+                PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_Action, videoData: playVideoData, pageSource: nil, businessType: .bt_videoPlayError, objectType: nil, extParams: extParams, shareId: nil, videoIds: nil, playId: playId)
+            }
+        case PLAY_WARNING_RECONNECT.rawValue: // 断线重连已启动重新连接
+            if playStatusBloc != nil {
+                playStatusBloc!(.PQVIDEO_PLAY_STATUS_RECONNECT)
+            }
+            var extParams: [String: Any] = ["pageSource": playVideoData!.pageSource.rawValue, "networkType": networkStatus(), "extra": "0", "hasRetry": true, "url": playVideoData?.videoPath ?? "", "videoId": playVideoData?.uniqueId ?? "0", "what": event]
+            if isHomePageAllList {
+                if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                    extParams["topicId"] = "all"
+                } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                    extParams["followedUid"] = "all"
+                }
+            }
+            PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_Action, videoData: playVideoData, pageSource: nil, businessType: .bt_videoPlayException, objectType: nil, extParams: extParams, shareId: nil, videoIds: nil, playId: playId)
+        case PLAY_WARNING_RECV_DATA_LAG.rawValue, PLAY_WARNING_VIDEO_PLAY_LAG.rawValue: // 网络来包不稳:可能是下行带宽不足 | 当前视频播放出现卡顿(用户直观感受)
+            // 播放失败
+            var extParams1: [String: Any] = ["pageSource": playVideoData!.pageSource.rawValue, "networkType": networkStatus(), "url": playVideoData?.videoPath ?? "", "videoId": playVideoData?.uniqueId ?? "0", "what": event, "position": player.currentPlaybackTime()]
+            var extParams2: [String: Any] = ["pageSource": playVideoData!.pageSource.rawValue, "networkType": networkStatus(), "extra": "0", "hasRetry": true, "url": playVideoData?.videoPath ?? "", "videoId": playVideoData?.uniqueId ?? "0", "what": event]
+            if isHomePageAllList {
+                if playVideoData?.pageSource == .sp_cmunit_joinTopic {
+                    extParams1["topicId"] = "all"
+                    extParams2["topicId"] = "all"
+                } else if playVideoData?.pageSource == .sp_cmunit_follow {
+                    extParams1["followedUid"] = "all"
+                    extParams2["topicId"] = "all"
+                }
+            }
+            PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_Frontend, videoData: playVideoData, pageSource: nil, businessType: .bt_videoPlaySlow, objectType: nil, extParams: extParams1, shareId: nil, videoIds: nil, playId: playId)
+            PQEventTrackViewModel.videoRelationReportUpload(reportLogType: .reportLogType_Action, videoData: playVideoData, pageSource: nil, businessType: .bt_videoPlayException, objectType: nil, extParams: extParams2, shareId: nil, videoIds: nil, playId: playId)
+        default:
+            break
+        }
+    }
+
+    /// 网络状态回调
+    /// - Parameters:
+    ///   - player: <#player description#>
+    ///   - param: <#param description#>
+    public func onNetStatus(_: TXVodPlayer!, withParam _: [AnyHashable: Any]!) {
+        // BFLog(message: "onNetStatus:\(param)")
+    }
+}

+ 129 - 0
BFStuckPointKit/Classes/BFUtils/PQSingletonEnvUtil.swift

@@ -0,0 +1,129 @@
+//
+//  PQSingletonEnvUtil.swift
+//  PQSpeed
+//
+//  Created by lieyunye on 2020/5/27.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import Foundation
+import KeychainAccess
+import BFCommonKit
+
+// add by ak 开发和发布版本的不同设置  1 为正式版本发布的苹果  0 是测试 XXXX 上传苹果前要检查
+public let DEVELOPMENT_ENVIRONMENT: Int = 1
+
+public enum ENVMode: String {
+    case ENVModeOnline // 线上环境
+    case ENVModePre // 预发布环境
+    case ENVModeTest // 测试环境
+    case ENVModeBJPre // 北京区预发布环境
+}
+
+public class PQENVUtil {
+    public var envMode: ENVMode {
+        let config = NSDictionary(contentsOfFile: Bundle.main.path(forResource: "PQConfig.plist", ofType: nil) ?? "")
+        BFLog(message: "config  is  = \(String(describing: config))")
+
+        let enStr: String = (config?.object(forKey: "ENVMode") ?? "ENVModeOnline") as! String
+        BFLog(message: "ENVMode is \(enStr)")
+
+        return ENVMode(rawValue: enStr)!
+    }
+
+     static public let shared: PQENVUtil = {
+        let instance = PQENVUtil()
+        // setup code
+
+        return instance
+    }()
+
+    // 票圈视频主域名
+   public var longvideoapi: String {
+        switch envMode {
+        case .ENVModeTest:
+            return testLongvideoapi
+        case .ENVModePre:
+            return preLongvideoapi
+        case .ENVModeBJPre:
+            return preBJLongvideoapi
+        default:
+            return onlineLongvideoapi
+        }
+    }
+
+    // 票圈视频域名(热榜)
+   public var distribution: String {
+        switch envMode {
+        case .ENVModeTest:
+            return testDistributionApi
+        case .ENVModePre:
+            return preDistributionApi
+        case .ENVModeBJPre:
+            return preBJDistributionApi
+        default:
+            return onlineDistributionApi
+        }
+    }
+
+    // 通用域名-eg:数据上报
+    public var commonapi: String {
+        switch envMode {
+        case .ENVModeTest:
+            return testCommonapi
+        case .ENVModePre:
+            return preCommonapi
+        case .ENVModeBJPre:
+            return preBJCommonapi
+        default:
+            return onlineCommonapi
+        }
+    }
+
+    // 视频创作相关域名
+    public var clipapiapi: String {
+        switch envMode {
+        case .ENVModeTest:
+            return testLongvideoapi
+        case .ENVModePre, .ENVModeBJPre:
+            return preLongvideoapi
+        default:
+            return onlineClipapiApi
+        }
+    }
+
+    // 创作工具搜索素材相关域名
+    public var materialSearchApi: String {
+        switch envMode {
+        case .ENVModeTest:
+            return testMaterialSearchApi
+        case .ENVModePre, .ENVModeBJPre:
+            return preMaterialSearchApi
+        default:
+            return onlineMaterialSearchApi
+        }
+    }
+
+    // 消息相关域名
+    public var messageApi: String {
+        switch envMode {
+        case .ENVModeTest:
+            return testMessageApi
+        case .ENVModePre, .ENVModeBJPre:
+            return preMessageApi
+        default:
+            return onlineMessageApi
+        }
+    }
+    // 票圈Api域名
+    public var pqTvApi: String {
+        switch envMode {
+        case .ENVModeTest:
+            return testPQTvApi
+        case .ENVModePre, .ENVModeBJPre:
+            return prePQTvApi
+        default:
+            return onlinePQTvApi
+        }
+    }
+}

+ 327 - 0
BFStuckPointKit/Classes/BFUtils/PQThirdPlatformUtil.swift

@@ -0,0 +1,327 @@
+//
+//  PQThirdPlatformUtil.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/5/30.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import Kingfisher
+import UIKit
+import UserNotifications
+import WechatOpenSDK
+import BFCommonKit
+
+// MARK: - Bugly初始化工具类
+
+public class PQSingletoBuglyUtil: NSObject {
+    public static let shared = PQSingletoBuglyUtil()
+
+    public func registerBugly(appID: String) {
+        let versionName = "\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "1.0.0")"
+
+        // 注册bugly
+        let buglyConfig = BuglyConfig()
+        buglyConfig.reportLogLevel = .warn
+        #if DEBUG
+            buglyConfig.version = versionName + ".6666"
+            buglyConfig.debugMode = true
+        #else
+            if PQENVUtil.shared.envMode == .ENVModeOnline {
+                buglyConfig.version = versionName + "." + versionCode
+            } else if PQENVUtil.shared.envMode == .ENVModePre {
+                buglyConfig.version = versionName + ".8888"
+            } else {
+                buglyConfig.version = versionName + ".9999"
+            }
+        #endif
+        BFLog(message: "Bugly版本号:\(BuglyConfig.version())")
+        buglyConfig.channel = channelID
+        buglyConfig.deviceIdentifier = BFLoginUserInfo.shared.isLogin() ? BFLoginUserInfo.shared.uid : getMachineCode()
+        buglyConfig.unexpectedTerminatingDetectionEnable = true
+        buglyConfig.blockMonitorEnable = true
+        buglyConfig.blockMonitorTimeout = 2
+        Bugly.start(withAppId: appID, config: buglyConfig)
+    }
+}
+
+// MARK: - 微信相关工具类
+
+// 初始化微信要使用的参数 e.g.
+/*
+     let state: String = "com.piaoquan.pqspeed"
+     let appid: String = "wxfc2fc07ab379e4bf"
+     let secret: String = "06f696424accb17b7234dce32e4821b4"
+     let universalLink: String = "https://speed.piaoquantv.com/"
+     let scope = "snsapi_userinfo"
+ */
+public class WXApiInfo: NSObject {
+    public var state: String = ""
+    public var appid: String = ""
+    public var secret: String = ""
+    public var universalLink: String = ""
+    public var scope = ""
+}
+
+public class PQSingletoWXApiUtil: NSObject {
+    public static let shared = PQSingletoWXApiUtil()
+    var openId: String? // openID
+    // 回调
+    public var wxApiUtilHander: ((_ userData: [String: Any]?, _ errorMsg: String?) -> Void)?
+    var mAppInfo: WXApiInfo = WXApiInfo()
+    public func registerApp(appInfo: WXApiInfo?) {
+        if appInfo == nil {
+            return
+        }
+        mAppInfo = appInfo!
+        #if DEBUG
+            WXApi.startLog(by: .detail) { msg in
+                BFLog(message: "微信回调Log--\(msg)")
+            }
+        #endif
+        WXApi.registerApp(mAppInfo.appid, universalLink: mAppInfo.universalLink)
+//        #if DEBUG
+//            WXApi.checkUniversalLinkReady { step, result in
+//                BFLog(message: "微信回调自检--\(step),result = \(result)")
+//            }
+//        #endif
+    }
+
+    public func handleOpen(url: URL) -> Bool {
+        return WXApi.handleOpen(url, delegate: self)
+    }
+
+    public func handleOpenUniversalLink(userActivity: NSUserActivity) -> Bool {
+        return WXApi.handleOpenUniversalLink(userActivity, delegate: self)
+    }
+
+    public func authorize() -> PQSingletoWXApiUtil {
+        if !isInstallWX() {
+            if wxApiUtilHander != nil {
+                wxApiUtilHander!(nil, "您还未安装微信客户端!")
+            }
+            return .shared
+        }
+        let req = SendAuthReq()
+        req.scope = mAppInfo.scope
+        req.state = mAppInfo.state
+        WXApi.send(req) { [weak self] isSuccess in
+            if !isSuccess {
+                self?.wxApiUtilHander!(nil, "您还未安装微信客户端!")
+            }
+        }
+        return .shared
+    }
+
+    /// 分享
+    /// - Parameters:
+    ///   - type: 类型 1- 分享视频 2-分享活动 3-分享卡点视频
+    ///   - scene: 场景
+    ///   - title: 标题
+    ///   - description:描述
+    ///   - imageUrl: 图片url
+    ///   - path: 网页地址
+    ///   - videoId: 视频ID type == 1时不为空
+    ///   - pageSource: 页面pageSource type == 1时不为空
+    /// - Returns: <#description#>
+    public func share(type: Int = 1, scene: Int32, shareWeappRawId: String? = nil, title: String?, description: String?, imageUrl: String?, path: String?, videoId: String, pageSource: PAGESOURCE, shareId: String) -> PQSingletoWXApiUtil {
+        if !isInstallWX() {
+            if wxApiUtilHander != nil {
+                wxApiUtilHander!(nil, "您还未安装微信客户端!")
+            }
+            return .shared
+        }
+        let message = WXMediaMessage()
+        message.title = (title?.count ?? 0) > 0 ? title! : "分享一个视频给你"
+        message.description = description ?? ""
+        BFLog(1, message: "test - \(String(describing: imageUrl))")
+        
+        ImageDownloader.default.downloadImage(with: URL(string: imageUrl ?? "")!, options: nil) { result in
+            switch result {
+            case let .success(imageResult):
+                message.thumbData = zipImage(image: imageResult.image, size: 64)
+                if type == 1 || type == 3, scene == Int32(WXSceneSession.rawValue) {
+                    let wxMiniObject = WXMiniProgramObject()
+                    wxMiniObject.miniProgramType = PQENVUtil.shared.envMode == .ENVModeOnline ? .release : .test
+                    wxMiniObject.userName = shareWeappRawId ?? cShareWeappRawId
+                    var homePagePath: String = ""
+                    var targetPagePath: String = ""
+                    var isRhythmVideo = 0
+                    if type == 3 {
+                        homePagePath = "pages/post/post"
+                        targetPagePath = "package-point/show/show"
+                        isRhythmVideo = 1
+                    } else {
+                        homePagePath = "pages/category"
+                        targetPagePath = "user-videos"
+                        isRhythmVideo = 0
+                    }
+                    var params = (targetPagePath + "?id=\(videoId)&tp=share&rootPageSource=\(pageSource.rawValue)&shareDepth=1&rootLaunchShareId=\(shareId)&parentShareId=\(shareId)&shareId=\(shareId)&shareAppType=\(BFConfig.shared.appType)&mid=\(getMachineCode())&su=\(BFLoginUserInfo.shared.uid)&isRhythmVideo=\(isRhythmVideo)").addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
+                    ["/", "=", "?", "&"].forEach { char in
+                        let custom = CharacterSet(charactersIn: char).inverted
+                        params = params?.addingPercentEncoding(withAllowedCharacters: custom) ?? ""
+                    }
+                    wxMiniObject.path = "\(homePagePath)?jumpPage=" + (params ?? "")
+                    wxMiniObject.webpageUrl = "https://piaoquan.yishihui.com/"
+                    wxMiniObject.hdImageData = message.thumbData
+                    message.mediaObject = wxMiniObject
+
+                } else {
+                    let webpageObject = WXWebpageObject()
+                    webpageObject.webpageUrl = path ?? ""
+                    message.mediaObject = webpageObject
+                }
+
+                let req = SendMessageToWXReq()
+                req.message = message
+                req.scene = scene
+                WXApi.send(req) { [weak self] isSuccess in
+                    if self?.wxApiUtilHander != nil {
+                        self?.wxApiUtilHander!(nil, isSuccess ? "分享成功~" : "分享失败了~")
+                    }
+                }
+            case let .failure(error):
+                BFLog(message: "下载图片失败:\(error.localizedDescription)")
+            }
+        }
+        return .shared
+    }
+
+    /// 处理发起数据
+    /// - Parameter response: <#response description#>
+    /// - Returns: <#description#>
+    public func dealWithPayParams(response: [String: Any]) -> PQSingletoWXApiUtil {
+        var partnerid: String = ""
+        var prepayId: String = ""
+        var nonceStr: String = ""
+        var timeStamp: UInt32 = 0
+        var package: String = "Sign=WXPay"
+        var sign: String = ""
+        if response.keys.contains("partnerid") {
+            partnerid = response["partnerid"] as! String
+        }
+        if response.keys.contains("prepayid") {
+            prepayId = response["prepayid"] as! String
+        }
+        if response.keys.contains("noncestr") {
+            nonceStr = response["noncestr"] as! String
+        }
+        if response.keys.contains("timestamp") {
+            timeStamp = UInt32(Int("\(response["timestamp"] ?? "0")") ?? 0)
+        }
+        if response.keys.contains("package") {
+            package = response["package"] as! String
+        }
+        if response.keys.contains("sign") {
+            sign = response["sign"] as! String
+        }
+        requestPayReq(partnerid: partnerid, prepayId: prepayId, nonceStr: nonceStr, timeStamp: timeStamp, package: package, sign: sign)
+        return .shared
+    }
+
+    /// 发起
+    /// - Parameters:
+    ///   - partnerid: 商家向财付通申请的商家id
+    ///   - prepayId: 预
+    ///   - nonceStr: 随机串,防重发
+    ///   - timeStamp: 时间戳,防重发
+    ///   - package: 商家根据财付通文档填写的数据和签名
+    ///   - sign: 商家根据微信开放平台文档对数据做的签名
+    /// - Returns: <#description#>
+    public func requestPayReq(partnerid: String, prepayId: String, nonceStr: String, timeStamp: UInt32, package: String, sign: String) {
+        // 调起微信
+        let payReq: PayReq = PayReq()
+        payReq.partnerId = partnerid
+        payReq.prepayId = prepayId
+        payReq.nonceStr = nonceStr
+        payReq.timeStamp = timeStamp
+        payReq.package = package
+        payReq.sign = sign
+        WXApi.send(payReq) { _ in
+        }
+    }
+
+    /// 是否安装了微信
+    /// - Returns: <#description#>
+    public func isInstallWX() -> Bool {
+        return UIApplication.shared.canOpenURL(URL(string: "weixin://")!)
+    }
+
+    override private init() {
+        super.init()
+    }
+
+    override public func copy() -> Any {
+        return self
+    }
+
+    override public func mutableCopy() -> Any {
+        return self
+    }
+}
+
+extension PQSingletoWXApiUtil: WXApiDelegate {
+    public func onReq(_ req: BaseReq) {
+        BFLog(message: "微信回调拉起 -- \(req)")
+        if req.isKind(of: LaunchFromWXReq.self) && ((req as! LaunchFromWXReq).message.messageExt?.count ?? 0) > 0 {
+            BFLog(message: "小程序回调数据--\(String(describing: (req as! LaunchFromWXReq).message.messageExt))")
+//            PQBoardCommandUtil.dealWithPasteboardData(messageExt: (req as! LaunchFromWXReq).message.messageExt)
+        }
+    }
+
+    public func onResp(_ resp: BaseResp) {
+        BFLog(message: "微信回调返回 -- \(resp)")
+
+        if resp.isKind(of: SendAuthResp.self) {
+            if (resp as! SendAuthResp).state == mAppInfo.state {
+                accessToken(code: (resp as! SendAuthResp).code!)
+            } else {
+                if wxApiUtilHander != nil {
+                    wxApiUtilHander!(nil, "网络不可用")
+                }
+            }
+        } else if resp.isKind(of: PayResp.self) {
+            if wxApiUtilHander != nil {
+                wxApiUtilHander!(["returnKey": (resp as! PayResp).returnKey, "code": resp.errCode], resp.errStr)
+            }
+        }
+    }
+
+    public func accessToken(code: String) {
+        let url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=\(mAppInfo.appid)&secret=\(mAppInfo.secret)&code=\(code)&grant_type=authorization_code"
+        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
+
+            let zoneStr = try? String(contentsOf: URL(string: url)!, encoding: .utf8)
+            if zoneStr == nil {
+                if self?.wxApiUtilHander != nil {
+                    self?.wxApiUtilHander!(nil, "授权失败了~")
+                }
+                return
+            }
+            let data = zoneStr!.data(using: .utf8)
+            let dict: [String: Any] = try! JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: Any]
+            self?.openId = "\(dict["openid"] ?? "")"
+            BFLoginUserInfo.shared.openId = self?.openId ?? ""
+            let access_token: String = "\(dict["access_token"] ?? "")"
+
+            let userUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=\(access_token)&openid=\(self?.openId ?? "")"
+
+            DispatchQueue.global(qos: .userInitiated).async {
+                let userInfoStr = try? String(contentsOf: URL(string: userUrl)!, encoding: .utf8)
+                let userInfoData = userInfoStr!.data(using: .utf8)
+                let userDict: [String: Any] = try! JSONSerialization.jsonObject(with: userInfoData!, options: .mutableContainers) as! [String: Any]
+                DispatchQueue.main.async { [weak self] in
+                    if userDict.count > 0 {
+                        if self?.wxApiUtilHander != nil {
+                            self?.wxApiUtilHander!(userDict, nil)
+                        }
+                    } else {
+                        if self?.wxApiUtilHander != nil {
+                            self?.wxApiUtilHander!(nil, "授权失败了~")
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 4 - 2
BFStuckPointKit/Classes/Controller/PQEditMusicSearchController.swift

@@ -7,6 +7,8 @@
 
 import Foundation
 import BFUIKit
+import BFMediaKit
+import BFCommonKit
 
 class PQEditMusicSearchController: BFBaseViewController {
     // 当前播放的音乐
@@ -46,7 +48,7 @@ class PQEditMusicSearchController: BFBaseViewController {
     // 输入框清空按钮
     lazy var clearBtn: UIButton = {
         let clearBtn = UIButton(type: .custom)
-        clearBtn.setImage(UIImage.moduleImage(named: "icon_search_delete", moduleName: "BFFramework", isAssets: false), for: .normal)
+        clearBtn.setImage(UIImage.moduleImage(named: "icon_search_delete", moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
         clearBtn.frame = CGRect(x: 0, y: 0, width: 28, height: 32)
         clearBtn.tag = 1
         clearBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
@@ -64,7 +66,7 @@ class PQEditMusicSearchController: BFBaseViewController {
 
         searchTF.leftViewMode = .always
         let leftView = UIView(frame: CGRect(x: 0, y: 0, width: 35, height: 32))
-        let imageView = UIImageView(image: UIImage.moduleImage(named: "icon_search_s", moduleName: "BFFramework", isAssets: false))
+        let imageView = UIImageView(image: UIImage.moduleImage(named: "icon_search_s", moduleName: "BFStuckPointKit", isAssets: false))
         imageView.frame = CGRect(x: 15, y: 8, width: 16, height: 16)
         leftView.addSubview(imageView)
         searchTF.leftView = leftView

+ 12 - 11
BFStuckPointKit/Classes/Controller/PQStuckPointEditerController.swift

@@ -19,6 +19,7 @@ import ObjectMapper
 import Photos
 import RealmSwift
 import UIKit
+import BFMediaKit
 
 class PQStuckPointEditerController: BFBaseViewController {
     // 是否导出视频成功
@@ -309,9 +310,9 @@ class PQStuckPointEditerController: BFBaseViewController {
     lazy var pointEditerBtn: UIButton = {
         let pointEdterBtn = UIButton(type: .custom)
 
-        pointEdterBtn.setImage(UIImage.moduleImage(named: "pointEditerBtn_n", moduleName: "BFFramework", isAssets: false), for: .normal)
+        pointEdterBtn.setImage(UIImage.moduleImage(named: "pointEditerBtn_n", moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
 
-        pointEdterBtn.setImage(UIImage.moduleImage(named: "pointEditerBtn_h", moduleName: "BFFramework", isAssets: false)?.withRenderingMode(.alwaysTemplate), for: .selected)
+        pointEdterBtn.setImage(UIImage.moduleImage(named: "pointEditerBtn_h", moduleName: "BFStuckPointKit", isAssets: false)?.withRenderingMode(.alwaysTemplate), for: .selected)
         pointEdterBtn.tintColor = UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue)
         pointEdterBtn.addTarget(self, action: #selector(pointEditerBtnClick(sender:)), for: .touchUpInside)
         pointEdterBtn.isSelected = true
@@ -322,8 +323,8 @@ class PQStuckPointEditerController: BFBaseViewController {
     // 音乐编辑 btn
     lazy var musicEditerBtn: UIButton = {
         let musicEditerBtn = UIButton(type: .custom)
-        musicEditerBtn.setImage(UIImage.moduleImage(named: "musicEditerBtn_n", moduleName: "BFFramework", isAssets: false), for: .normal)
-        musicEditerBtn.setImage(UIImage.moduleImage(named: "musicEditerBtn_h", moduleName: "BFFramework", isAssets: false)?.withRenderingMode(.alwaysTemplate), for: .selected)
+        musicEditerBtn.setImage(UIImage.moduleImage(named: "musicEditerBtn_n", moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
+        musicEditerBtn.setImage(UIImage.moduleImage(named: "musicEditerBtn_h", moduleName: "BFStuckPointKit", isAssets: false)?.withRenderingMode(.alwaysTemplate), for: .selected)
         musicEditerBtn.tintColor = UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue)
         musicEditerBtn.addTarget(self, action: #selector(musicEditerBtnClick(sender:)), for: .touchUpInside)
         musicEditerBtn.adjustsImageWhenHighlighted = false
@@ -343,8 +344,8 @@ class PQStuckPointEditerController: BFBaseViewController {
         speedStuckBtn.imagePosition(at: .top, space: 8)
         speedStuckBtn.tag = 1
         speedStuckBtn.adjustsImageWhenHighlighted = false
-        speedStuckBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.speedStuckBtnImage_N, moduleName: "BFFramework", isAssets: false), for: .normal)
-        speedStuckBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.speedStuckBtnImage_H, moduleName: "BFFramework", isAssets: false), for: .selected)
+        speedStuckBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.speedStuckBtnImage_N, moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
+        speedStuckBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.speedStuckBtnImage_H, moduleName: "BFStuckPointKit", isAssets: false), for: .selected)
         return speedStuckBtn
     }()
 
@@ -371,8 +372,8 @@ class PQStuckPointEditerController: BFBaseViewController {
         jumpPointBtn.tag = 2
         jumpPointBtn.addTarget(self, action: #selector(editModelClick1(sender:)), for: .touchUpInside)
         jumpPointBtn.adjustsImageWhenHighlighted = false
-        jumpPointBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.jumpPointBtnImage_N, moduleName: "BFFramework", isAssets: false), for: .normal)
-        jumpPointBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.jumpPointBtnImage_H, moduleName: "BFFramework", isAssets: false), for: .selected)
+        jumpPointBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.jumpPointBtnImage_N, moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
+        jumpPointBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.jumpPointBtnImage_H, moduleName: "BFStuckPointKit", isAssets: false), for: .selected)
         return jumpPointBtn
     }()
 
@@ -397,8 +398,8 @@ class PQStuckPointEditerController: BFBaseViewController {
         onlyMusicBtn.tag = 3
         onlyMusicBtn.addTarget(self, action: #selector(editModelClick1(sender:)), for: .touchUpInside)
         onlyMusicBtn.adjustsImageWhenHighlighted = false
-        onlyMusicBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.onlyMusicBtnImage_N, moduleName: "BFFramework", isAssets: false), for: .normal)
-        onlyMusicBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.onlyMusicBtnImage_H, moduleName: "BFFramework", isAssets: false), for: .selected)
+        onlyMusicBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.onlyMusicBtnImage_N, moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
+        onlyMusicBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.onlyMusicBtnImage_H, moduleName: "BFStuckPointKit", isAssets: false), for: .selected)
         return onlyMusicBtn
     }()
 
@@ -497,7 +498,7 @@ class PQStuckPointEditerController: BFBaseViewController {
         musicNameView.addCorner(corner: musicNameView.frame.height / 2)
         let musicImageView = UIImageView()
         musicImageView.tintColor = BFConfig.shared.styleTitleColor
-        musicImageView.image = UIImage.moduleImage(named: "stuckPoint_reCreate_music", moduleName: "BFFramework", isAssets: false)?.withRenderingMode(.alwaysTemplate)
+        musicImageView.image = UIImage.moduleImage(named: "stuckPoint_reCreate_music", moduleName: "BFStuckPointKit", isAssets: false)?.withRenderingMode(.alwaysTemplate)
         musicImageView.frame = CGRect(x: musicNameView.frame.height / 2 - 5, y: (musicNameView.frame.height - 22) / 2, width: 22, height: 22)
         musicNameView.addSubview(musicImageView)
         musicNameLab.frame.origin.x = musicImageView.frame.maxX + 5

+ 5 - 3
BFStuckPointKit/Classes/Controller/PQStuckPointMaterialController.swift

@@ -8,8 +8,10 @@
 
 import UIKit
 import Photos
-import BFUIKit
+@_exported import BFUIKit
 import BFMaterialKit
+import BFMediaKit
+import BFCommonKit
 
 public class PQStuckPointMaterialController: BFBaseViewController {
     public var isToPublicHandle:((_ isReCreate:Bool,_ selectedTotalDuration: Float64,_ selectedDataCount:Int,_ selectedImageDataCount: Int,_ mStickers: [PQEditVisionTrackMaterialsModel]?,_ stuckPointMusicData: PQVoiceModel?,_ editProjectModel: PQEditProjectModel?,_ rhythmMode:createStickersModel,_ speedMin:Float,_ speedMax:Float,_ audioTime:Float,_ clipAudioRange:CMTimeRange,_ playeTimeRange:CMTimeRange) -> Void)?
@@ -36,8 +38,8 @@ public class PQStuckPointMaterialController: BFBaseViewController {
         changeCollecBtn.titleLabel?.lineBreakMode = .byTruncatingTail
         changeCollecBtn.tintColor = BFConfig.shared.styleTitleColor
         changeCollecBtn.setTitle("我的相册", for: .normal)
-        changeCollecBtn.setImage(UIImage.moduleImage(named: "icon_selected_down", moduleName: "BFFramework",isAssets: false)?.withRenderingMode(.alwaysTemplate), for: .normal)
-        changeCollecBtn.setImage(UIImage.moduleImage(named: "icon_selected_up", moduleName: "BFFramework",isAssets: false)?.withRenderingMode(.alwaysTemplate), for: .selected)
+        changeCollecBtn.setImage(UIImage.moduleImage(named: "icon_selected_down", moduleName: "BFStuckPointKit",isAssets: false)?.withRenderingMode(.alwaysTemplate), for: .normal)
+        changeCollecBtn.setImage(UIImage.moduleImage(named: "icon_selected_up", moduleName: "BFStuckPointKit",isAssets: false)?.withRenderingMode(.alwaysTemplate), for: .selected)
         changeCollecBtn.setTitleColor(BFConfig.shared.styleTitleColor, for: .normal)
         changeCollecBtn.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
         changeCollecBtn.tag = 1

+ 2 - 1
BFStuckPointKit/Classes/Controller/PQStuckPointMusicContentController.swift

@@ -7,8 +7,9 @@
 //
 
 import BFCommonKit
-@_exported import BFUIKit
+import BFUIKit
 import UIKit
+import BFMediaKit
 
 class PQStuckPointMusicContentController: BFBaseViewController {
     var itemList: [Any] = Array<Any>.init() // 所有分类数据

+ 4 - 2
BFStuckPointKit/Classes/Controller/PQStuckPointMusicController.swift

@@ -10,6 +10,8 @@ import AVFoundation
 import UIKit
 import Photos
 import BFUIKit
+import BFMediaKit
+import BFCommonKit
 
 class PQStuckPointMusicController: BFBaseViewController {
     // 选中的总时长
@@ -101,7 +103,7 @@ class PQStuckPointMusicController: BFBaseViewController {
     // 输入框清空按钮
     lazy var clearBtn: UIButton = {
         let clearBtn = UIButton(type: .custom)
-        clearBtn.setImage(UIImage.moduleImage(named: "icon_search_delete", moduleName: "BFFramework",isAssets: false), for: .normal)
+        clearBtn.setImage(UIImage.moduleImage(named: "icon_search_delete", moduleName: "BFStuckPointKit",isAssets: false), for: .normal)
         clearBtn.frame = CGRect(x: 0, y: 0, width: 28, height: 32)
         clearBtn.tag = 1
         clearBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
@@ -120,7 +122,7 @@ class PQStuckPointMusicController: BFBaseViewController {
 
         searchTF.leftViewMode = .always
         let leftView = UIView(frame: CGRect(x: 0, y: 0, width: 35, height: 32))
-        let imageView = UIImageView(image:UIImage.moduleImage(named: "icon_search_s", moduleName: "BFFramework",isAssets: false))
+        let imageView = UIImageView(image:UIImage.moduleImage(named: "icon_search_s", moduleName: "BFStuckPointKit",isAssets: false))
         imageView.frame = CGRect(x: 15, y: 8, width: 16, height: 16)
         leftView.addSubview(imageView)
         searchTF.leftView = leftView

+ 1 - 0
BFStuckPointKit/Classes/Controller/PQStuckPointMusicSearchController.swift

@@ -9,6 +9,7 @@
 import UIKit
 import BFCommonKit
 import BFUIKit
+import BFMediaKit
 
 class PQStuckPointMusicSearchController: PQStuckPointMusicContentController {
     // 选中的总时长

+ 6 - 5
BFStuckPointKit/Classes/Controller/PQStuckPointPublicController.swift

@@ -14,6 +14,7 @@ import ObjectMapper
 import Photos
 import UIKit
 import WechatOpenSDK
+import BFMediaKit
 
 // mdf by ak 按 UI图 下方操作区的高度是固定的, 其它区高度和设备自适应
 public let bottomOprationBgViewHeight: CGFloat = 322.0
@@ -194,7 +195,7 @@ class PQStuckPointPublicController: BFBaseViewController {
         playerHeaderCoverImageView.clipsToBounds = true
 
         let playBtn = UIButton(type: .custom)
-        playBtn.setImage(UIImage.moduleImage(named: "icon_video_play", moduleName: "BFFramework", isAssets: false), for: .normal)
+        playBtn.setImage(UIImage.moduleImage(named: "icon_video_play", moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
         playBtn.tag = 4
         playBtn.isUserInteractionEnabled = false
         playerHeaderCoverImageView.addSubview(playBtn)
@@ -242,7 +243,7 @@ class PQStuckPointPublicController: BFBaseViewController {
     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.moduleImage(named: "icon_video_play", moduleName: "BFFramework", isAssets: false), for: .normal)
+        playBtn.setImage(UIImage.moduleImage(named: "icon_video_play", moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
         playBtn.tag = 4
         playBtn.isHidden = true
         playBtn.isUserInteractionEnabled = false
@@ -378,7 +379,7 @@ class PQStuckPointPublicController: BFBaseViewController {
     lazy var shareWechatBtn: UIButton = {
         let shareWechatBtn = UIButton(type: .custom)
         shareWechatBtn.frame = CGRect(x: 0, y: 0, width: 70, height: 70)
-        shareWechatBtn.setImage(UIImage.moduleImage(named: "reCreate_opration_wechat", moduleName: "BFFramework", isAssets: false), for: .normal)
+        shareWechatBtn.setImage(UIImage.moduleImage(named: "reCreate_opration_wechat", moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
         shareWechatBtn.backgroundColor = BFConfig.shared.styleBackGroundColor
         shareWechatBtn.addCorner(corner: 6)
         shareWechatBtn.tag = 2
@@ -390,7 +391,7 @@ class PQStuckPointPublicController: BFBaseViewController {
     lazy var shareFriendBtn: UIButton = {
         let shareFriendBtn = UIButton(type: .custom)
         shareFriendBtn.frame = CGRect(x: 0, y: 0, width: 70, height: 70)
-        shareFriendBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.shareFriendBtnImage, moduleName: "BFFramework", isAssets: false), for: .normal)
+        shareFriendBtn.setImage(UIImage.moduleImage(named: BFConfig.shared.shareFriendBtnImage, moduleName: "BFStuckPointKit", isAssets: false), for: .normal)
         shareFriendBtn.addCorner(corner: 6)
         shareFriendBtn.tag = 1
         shareFriendBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside)
@@ -1831,7 +1832,7 @@ extension PQStuckPointPublicController {
                 image = UIImage.nx_circleImage(imageResult.image)
 
             case let .failure(error):
-                image = UIImage.moduleImage(named: "user_avatar_normal", moduleName: "BFFramework", isAssets: false)
+                image = UIImage.moduleImage(named: "user_avatar_normal", moduleName: "BFStuckPointKit", isAssets: false)
                 BFLog(message: "下载头像图片失败:\(error.localizedDescription)")
             }
             if image == nil {

+ 16 - 0
BFStuckPointKit/Classes/Custom/BFCustomHeader.h

@@ -0,0 +1,16 @@
+//
+//  BFCustomHeader.h
+//  Pods
+//
+//  Created by SanW on 2021/12/9.
+//
+
+#ifndef BFCustomHeader_h
+#define BFCustomHeader_h
+
+#import "AliyunOSSiOS/AliyunOSSiOS.h"
+#import "MJRefresh/MJRefresh.h"
+#import "LMJHorizontalScrollText/LMJHorizontalScrollText.h"
+#import <TXLiteAVSDK_Player/TXLiteAVSDK.h>
+#import <Bugly/Bugly.h>
+#endif /* BFCustomHeader_h */

+ 1 - 0
BFStuckPointKit/Classes/EventTrack/Model/PQVideoMakeEventTrackModel.swift

@@ -8,6 +8,7 @@
 
 import UIKit
 import BFCommonKit
+import BFMediaKit
 
 public class PQVideoMakeEventTrackModel: NSObject {
     // 进入创作工具的入口

+ 1 - 0
BFStuckPointKit/Classes/EventTrack/ViewModel/PQEventTrackViewModel.swift

@@ -9,6 +9,7 @@
 import UIKit
 import BFCommonKit
 import BFNetRequestKit
+import BFMediaKit
 
 // MARK: - 埋点数据上报
 

+ 49 - 0
BFStuckPointKit/Classes/Model/BFCoreDataModel.swift

@@ -0,0 +1,49 @@
+//
+//  BFCoreDataModel.swift
+//  BFFramework
+//
+//  Created by 胡志强 on 2021/11/12.
+//
+
+import Foundation
+import CoreData
+
+let persistentContext = BFDataPersistentManager.shared.persistentContainer.viewContext
+open class BFCoreDataModel: NSObject, NSFetchRequestResult {
+    
+    
+    public class func createModel(modelName:String) -> AnyObject {
+        return NSEntityDescription.insertNewObject(forEntityName: modelName, into: persistentContext)
+    }
+    
+    public func save(){
+        do {
+            try persistentContext.save()
+        } catch {
+            fatalError("不能保存:\(error)")
+        }
+    }
+    
+    public class func getList(fetchRequest:NSFetchRequest<NSFetchRequestResult>) -> [NSFetchRequestResult]?{
+        do {
+            let fetchedObjects = try persistentContext.fetch(fetchRequest)
+            return fetchedObjects as? [NSFetchRequestResult]
+        }
+        catch {
+            fatalError("不能保存:\(error)")
+        }
+    }
+    
+    
+    public func deleteMode(fetchRequest:NSFetchRequest<NSFetchRequestResult>){
+        do{
+            let fetchedObjects = try persistentContext.fetch(fetchRequest)
+            for info in fetchedObjects{
+                persistentContext.delete(info as! NSManagedObject)
+            }
+            try! persistentContext.save()
+        }catch {
+            fatalError("不能保存:\(error)")
+        }
+    }
+}

+ 36 - 0
BFStuckPointKit/Classes/Model/BFDataPersistentManager.swift

@@ -0,0 +1,36 @@
+//
+//  BFDataPersistentManager.swift
+//  BFFramework
+//
+//  Created by 胡志强 on 2021/11/12.
+//
+
+import Foundation
+import CoreData
+
+class BFDataPersistentManager: NSObject {
+    
+    static let shared = BFDataPersistentManager()
+    
+    lazy var persistentContainer: NSPersistentContainer = {
+        let container = NSPersistentContainer(name: "CoreDataDemo")
+        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
+            if let error = error as NSError? {
+                fatalError("Unresolved error \(error), \(error.userInfo)")
+            }
+        })
+        return container
+    }()
+    
+    func saveContext () {
+        let context = persistentContainer.viewContext
+        if context.hasChanges {
+            do {
+                try context.save()
+            } catch {
+                let nserror = error as NSError
+                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
+            }
+        }
+    }
+}

+ 182 - 0
BFStuckPointKit/Classes/Model/PQLoginUserInfo.swift

@@ -0,0 +1,182 @@
+//
+//  BFLoginUserInfo.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/5/27.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import UIKit
+import BFCommonKit
+
+// MARK: - 登录用户信息
+
+/// 登录用户信息s
+public class BFLoginUserInfo: NSObject {
+    static public let shared = BFLoginUserInfo()
+    public var uid: String = "" // 账号
+    public  var userCode: String = "" // 账号
+    public var accessToken: String = "" // token
+    public var avatarUrl: String = "" // 头像
+    public var city: String = "" // 城市
+    public var province: String = "" // 省市
+    public  var country: String = "" // 国家
+    public  var phoneNumber: String = "" // 电话
+    public var openId: String = "" // 微信openId
+    public var nickName: String = "" // 昵称
+    public var gender: String = "" // 性别
+    public var expiredTime: String = "" // 过期时间
+    public var videos: String = "0" // 视频数
+    public  var idols: String = "0" // 关注数
+    public var fans: String = "0" // 粉丝数
+    public var otherSubscribes: String = "0" // 别人订阅我的数量
+    public  var userStatus: String = "1" // 1有效,2 已删除,3 已屏蔽,4 敏感
+    public var isVirtualUser: Bool = false // 是否是虚拟账号
+    public  var mid = getMachineCode() // 设备ID
+
+    @objc  public  func toString() -> String {
+        let json: [String: Any] = [
+            "accessToken": accessToken,
+            "avatarUrl": avatarUrl,
+            "city": city,
+            "country": country,
+            "gender": gender,
+            "nickName": nickName,
+            "openId": openId,
+            "phoneNumber": phoneNumber,
+            "province": province,
+            "uid": uid,
+            "userCode": userCode,
+            "expiredTime": expiredTime,
+            "videos": videos,
+            "idols": idols,
+            "fans": fans,
+            "userStatus": userStatus,
+            "isVirtualUser": isVirtualUser,
+            "mid": mid,
+        ]
+        return dictionaryToJsonString(json) ?? ""
+    }
+
+    override public init() {
+        super.init()
+        resetData(isClear: false)
+        if getUserDefaults(key: cMineVideos) != nil {
+            videos = getUserDefaults(key: cMineVideos) as! String
+        }
+        if getUserDefaults(key: cMineIdols) != nil {
+            idols = getUserDefaults(key: cMineIdols) as! String
+        }
+        if getUserDefaults(key: cMineFans) != nil {
+            fans = getUserDefaults(key: cMineFans) as! String
+        }
+        if getUserDefaults(key: cOtherSubscribes) != nil {
+            otherSubscribes = getUserDefaults(key: cOtherSubscribes) as! String
+        }
+        if getUserDefaults(key: cAvatarUrl) != nil {
+            avatarUrl = getUserDefaults(key: cAvatarUrl) as! String
+        }
+        if getUserDefaults(key: cUpdatePhone) != nil {
+            phoneNumber = getUserDefaults(key: cUpdatePhone) as! String
+        }
+    }
+
+    @objc  public  func resetData(isClear: Bool) {
+        if isClear {
+            UserDefaults.standard.removeObject(forKey: cUserInfoStorageKey)
+            UserDefaults.standard.removeObject(forKey: cMineVideos)
+            UserDefaults.standard.removeObject(forKey: cMineFans)
+            UserDefaults.standard.removeObject(forKey: cOtherSubscribes)
+            UserDefaults.standard.removeObject(forKey: cMineIdols)
+            UserDefaults.standard.removeObject(forKey: cAvatarUrl)
+            UserDefaults.standard.removeObject(forKey: cUpdatePhone)
+            UserDefaults.standard.synchronize()
+
+            accessToken = ""
+            avatarUrl = ""
+            city = ""
+            country = ""
+            gender = ""
+            nickName = ""
+            openId = ""
+            phoneNumber = ""
+            province = ""
+            uid = ""
+            userCode = ""
+            expiredTime = ""
+            videos = "0"
+            idols = "0"
+            fans = "0"
+            userStatus = "1"
+            isVirtualUser = false
+            mid = ""
+            return
+        }
+        let userInfo: [String: Any] = jsonStringToDictionary(UserDefaults.standard.string(forKey: cUserInfoStorageKey) ?? "") ?? [:]
+        updateData(userInfo: userInfo)
+    }
+
+    public  func updateData(userInfo: [String: Any]?) {
+        if userInfo != nil, userInfo?.count ?? 0 > 0 {
+            accessToken = "\(userInfo?["accessToken"] ?? "")"
+            if userInfo?.keys.contains("token") ?? false {
+                accessToken = "\(userInfo?["token"] ?? "")"
+            }
+            avatarUrl = "\(userInfo?["avatarUrl"] ?? "")"
+            city = "\(userInfo?["city"] ?? "")"
+            country = "\(userInfo?["country"] ?? "")"
+            gender = "\(userInfo?["gender"] ?? "")"
+            nickName = "\(userInfo?["nickName"] ?? "")"
+            openId = "\(userInfo?["openId"] ?? "")"
+            if userInfo?.keys.contains("phoneNumber") ?? false, !(userInfo?["phoneNumber"] is NSNull) {
+                phoneNumber = "\(userInfo?["phoneNumber"] ?? "")"
+            }
+            if userInfo?.keys.contains("userStatus") ?? false, !(userInfo?["userStatus"] is NSNull) {
+                userStatus = "\(userInfo?["userStatus"] ?? "")"
+            }
+            if userInfo?.keys.contains("mid") ?? false, !(userInfo?["mid"] is NSNull) {
+                mid = "\(userInfo?["mid"] ?? "")"
+            }
+            if userInfo?.keys.contains("isVirtualUser") ?? false, !(userInfo?["isVirtualUser"] is NSNull) {
+                isVirtualUser = (userInfo?["isVirtualUser"] as? Bool) ?? false
+            }
+            province = "\(userInfo?["province"] ?? "")"
+            uid = "\(userInfo?["uid"] ?? "")"
+            userCode = "\(userInfo?["userCode"] ?? "")"
+            expiredTime = "\(userInfo?["expiredTime"] ?? "")"
+            if getUserDefaults(key: cMineVideos) != nil {
+                videos = getUserDefaults(key: cMineVideos) as! String
+            }
+            if getUserDefaults(key: cAvatarUrl) != nil {
+                avatarUrl = getUserDefaults(key: cAvatarUrl) as! String
+            }
+            if getUserDefaults(key: cUpdatePhone) != nil {
+                phoneNumber = getUserDefaults(key: cUpdatePhone) as! String
+            }
+            if getUserDefaults(key: cMineIdols) != nil {
+                idols = getUserDefaults(key: cMineIdols) as! String
+            }
+            if getUserDefaults(key: cMineFans) != nil {
+                fans = getUserDefaults(key: cMineFans) as! String
+            }
+            if getUserDefaults(key: cOtherSubscribes) != nil {
+                otherSubscribes = getUserDefaults(key: cOtherSubscribes) as! String
+            }
+            BFConfig.shared.token = accessToken
+            BFConfig.shared.loginUid = uid
+            BFConfig.shared.uid = uid
+        }
+    }
+
+    public  func isLogin() -> Bool {
+        return accessToken.count > 0
+    }
+
+    public override func copy() -> Any {
+        return self
+    }
+
+    public override func mutableCopy() -> Any {
+        return self
+    }
+}

+ 176 - 0
BFStuckPointKit/Classes/Model/PQUserInfoModel.swift

@@ -0,0 +1,176 @@
+//
+//  PQUserInfoModel.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/5/27.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import UIKit
+import BFCommonKit
+import BFUIKit
+
+open class PQUserInfoModel: BFBaseModel {
+    public  var avatarUrl: String? // 头像地址
+    public var backgroundImage: String? // 背景图
+
+    public var bothFollow: Bool = false // 是否相互关注
+    public var fans: Int = 0 // 粉丝数
+    public var followed: Int = 0 // 是否关注
+    public var idols: Int = 0 // 关注的人数
+    public var introduction: String?
+    public var nickName: String? // 昵称
+
+    public var otherVideoShowCount: Int = 0
+    public var playCountTotal: Int = 0 // 用户视频总播放数,按人去重 ,
+    public var playCountFormatStr: String? // 用户视频总播放数,格式化后的值,前端直接显示 ,
+    public var positionType: Int = 0
+    public var sensitiveStatus: Int = 0
+    public var subscribeStatus: Int = 0 // 0:未订阅,1:已订阅
+    public var mySubscribes: Int = 0 // 我的订阅数
+    public var otherSubscribes: Int = 0 // 别人订阅我的数量
+    public var uid: Int = 0
+    public var uploadDate: String?
+    public var userType: Int = 0
+    public var videos: Int = 0 // 视频数
+    public var videosDescr: String = "0" // 视频数
+    public var vipStatus: Int = 0 // vip状态,0:不是vip,1:是vip
+    public var vipDesc: String? // vip身份描述
+    public var tagList: [String]? // 标签
+    public var picPathList: [[String: Any]]? // 推荐列表
+    public var isLoginUser: Bool = false // 是否是登录用户
+    public var gmtCreateTimestamp: Int = 0
+    public var intimacy: Int = 0
+    public var isBothFollow: Int = 0 // 是否相互关注 ,
+    public var isFollowed: Bool = false // 是否关注
+    public var lastTimestamp: Int = 0
+    public var latestSendvideoId: Int = 0
+    public var updated: Int = 0
+    public var favoriteCount: Int = 0 // 喜欢的视频数
+    public var shareCount: Int = 0 // 分享的视频数
+    public var isBanned: Bool = false // 是否被拉黑
+    public var tab_pageType: TAB_PAGETYPE = .TAB_PAGETYPE_NORMAL // 0-推荐 1-关注
+
+    required public init() {
+        super.init()
+    }
+
+    public override init(jsonDict: [String: Any]) {
+        super.init()
+        if jsonDict.keys.contains("avatarUrl") {
+            avatarUrl = "\(jsonDict["avatarUrl"] ?? "")"
+        }
+        if jsonDict.keys.contains("bothFollow") {
+            bothFollow = Bool("\(jsonDict["bothFollow"] ?? "")") ?? false
+        }
+        if jsonDict.keys.contains("fans") {
+            fans = Int("\(jsonDict["fans"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("followed") {
+            followed = Int("\(jsonDict["followed"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("idols") {
+            idols = Int("\(jsonDict["idols"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("introduction") {
+            introduction = "\(jsonDict["introduction"] ?? "0")"
+        }
+        if jsonDict.keys.contains("nickName") {
+            nickName = "\(jsonDict["nickName"] ?? "0")"
+        }
+        if jsonDict.keys.contains("otherVideoShowCount") {
+            otherVideoShowCount = Int("\(jsonDict["otherVideoShowCount"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("playCountTotal") {
+            playCountTotal = Int("\(jsonDict["playCountTotal"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("playCountFormatStr") {
+            playCountFormatStr = "\(jsonDict["playCountFormatStr"] ?? "0")"
+        }
+        if jsonDict.keys.contains("positionType") {
+            positionType = Int("\(jsonDict["positionType"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("favoriteCount") {
+            favoriteCount = Int("\(jsonDict["favoriteCount"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("shareCount") {
+            shareCount = Int("\(jsonDict["shareCount"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("sensitiveStatus") {
+            sensitiveStatus = Int("\(jsonDict["sensitiveStatus"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("subscribeStatus") {
+            subscribeStatus = Int("\(jsonDict["subscribeStatus"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("mySubscribes") {
+            mySubscribes = Int("\(jsonDict["mySubscribes"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("otherSubscribes") {
+            otherSubscribes = Int("\(jsonDict["otherSubscribes"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("uid") {
+            uid = Int("\(jsonDict["uid"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("uploadDate") {
+            uploadDate = "\(jsonDict["uploadDate"] ?? "0")"
+        }
+        if jsonDict.keys.contains("userType") {
+            userType = Int("\(jsonDict["userType"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("videos") {
+            videos = Int("\(jsonDict["videos"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("videosDescr") {
+            videosDescr = "\(jsonDict["videosDescr"] ?? "")"
+        }
+        if jsonDict.keys.contains("vipStatus") {
+            vipStatus = Int("\(jsonDict["vipStatus"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("tagList") {
+            tagList = jsonDict["tagList"] as? [String]
+        }
+        if jsonDict.keys.contains("picPathList") {
+            picPathList = jsonDict["picPathList"] as? [[String: Any]]
+        }
+        if jsonDict.keys.contains("vipDesc") {
+            vipDesc = "\(jsonDict["vipDesc"] ?? "")"
+        }
+        if jsonDict.keys.contains("isLoginUser") {
+            isLoginUser = Bool("\(jsonDict["isLoginUser"] ?? "")") ?? false
+        }
+        if jsonDict.keys.contains("gmtCreateTimestamp") {
+            gmtCreateTimestamp = Int("\(jsonDict["gmtCreateTimestamp"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("intimacy") {
+            intimacy = Int("\(jsonDict["intimacy"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("isBothFollow") {
+            isBothFollow = Int("\(jsonDict["isBothFollow"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("isFollowed") {
+            isFollowed = jsonDict["isFollowed"] as! Bool
+        }
+        if jsonDict.keys.contains("lastTimestamp") {
+            lastTimestamp = Int("\(jsonDict["lastTimestamp"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("latestSendvideoId") {
+            latestSendvideoId = Int("\(jsonDict["latestSendvideoId"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("updated") {
+            updated = Int("\(jsonDict["updated"] ?? "0")") ?? 0
+        }
+    }
+
+    /// 创建虚拟用户数据
+    /// - Parameter virtual: <#virtual description#>
+   public init(avatarIcon: String?, userName: String?) {
+        super.init()
+        avatarUrl = avatarIcon
+        nickName = userName
+        fans = Int(arc4random() % 10)
+        followed = Int(arc4random() % 10)
+        idols = Int(arc4random() % 10)
+        mySubscribes = Int(arc4random() % 10)
+        otherSubscribes = Int(arc4random() % 10)
+    }
+}

+ 491 - 0
BFStuckPointKit/Classes/Model/PQVideoListModel.swift

@@ -0,0 +1,491 @@
+//
+//  PQRecommVideoModel.swift
+//  PQSpeed
+//
+//  Created by SanW on 2020/5/26.
+//  Copyright © 2020 BytesFlow. All rights reserved.
+//
+
+import BFCommonKit
+import UIKit
+import BFUIKit
+
+public protocol BFVideoItemProtocol {
+    dynamic var uniqueId: String? { get set } // 唯一ID
+    dynamic var videoId: Int { get set } // 视频ID
+    dynamic var id: UInt64 { get set } // 视频ID
+    dynamic var eventId: String? { get set } // 事件ID
+    dynamic var title: String? { get set } // 标题
+    dynamic var attributedTitle: NSMutableAttributedString? { get set } // 富文本标题
+    dynamic var summary: String? { get set } // 描述
+    dynamic var imageUrl: String { get set } // 图片地址
+    dynamic var selectedImage: String { get set } // 图片地址
+    dynamic var isSelected: Bool { get set }
+    dynamic var recommendLogVO: String? { get set } // 推荐日志对象
+    dynamic var flowPool: String? { get set } // 流量池数据 eg:#1#12#5#169182931029
+    dynamic var abInfoData: String? { get set } // AB
+    dynamic var pageCategoryId: Int { get set } // 页面分类ID
+    dynamic var version: String { get set } // 版本号
+    dynamic var mid: String { get set } // 设备ID
+    dynamic var date: Int { get set } // 当前时间戳  Float64(Date.init().timeIntervalSince1970) * 1000
+    var headVideoId: String? { get set } // 当前的相关推荐视频是属于哪个视频的相关推荐,值为那个头部视频的videoId
+    var auditStatus: Int { get set } // 审核状态 1 审核中,2 不通过 3 待修改,4 自己可见 5 通过 ,
+    var barrageCount: Int { get set } // 弹幕数量
+    var barrageSwitch: Int { get set } // 是否打开弹幕 1打开 -1关闭 ,
+    var auditReason: String? { get set } // 审核不通过或者待修改的原因
+    var barrage: Any? { get set } // 弹幕集合
+    var chargeDetail: [String: Any]? { get set } // 收费的相关信息
+    var commentCount: Int { get set } // 评论数量
+    var coverImg: [String: Any]? { get set } // 封面对象 ,
+    var cutVoStr: String? { get set } // h5剪切板内容 ,
+    var descr: String? { get set } // 视频简介 ,
+    var encryption: Int { get set } // 是否加密视频:0是1不是 ,
+    var favorited: Bool { get set } // 是否收藏 ,
+    var favoriteds: Int { get set } //  收藏数 ,
+    var fileExtensions: String? { get set } // 视频后缀 ,
+    var firstPicture: Bool { get set } // 封面是否是第一帧,false不是true是 ,
+    var gmtCreate: String? { get set } // 创建时间 ,
+    var gmtCreateDescr: String? { get set } // 发视频时间描述 ,
+    var gmtCreateTimestamp: Int { get set } // 创建时间戳 ,
+    var gmtModifie: String? { get set } // 修改时间 ,
+    var gmtModifiedTimestamp: Int { get set } // 修改时间戳 ,
+    var h5ShareImgPath: String? { get set } // h5分享图URL ,
+    var hasShareSpaceData: Bool { get set } // 是否有分享空间数据,
+    var isRecommendShare: Int { get set } // 是否有分发推荐的封面和标题 1 有 0 无 传空或者不传默认为0,
+    var lastTimestamp: Int { get set } // 时间戳 ,
+    //  liteVideoData (LiteVideoDataVO, optional): lite数据,
+    var measure: Int { get set }
+    var measureId: Int { get set }
+    var measureType: Int { get set } // 0 非流量池 1曝光池2普通推荐测试池3待推荐测试池 ,
+    var playBeforeDay: Int { get set } // 播放时间距离今天的天数 ,
+    var playCount: Int { get set } // 播放次数 ,
+    var playCountFormatStr: String? { get set } //  用户视频总播放数,格式化后的值,前端直接显示 ,
+    var playCountTotal: Int { get set } // 总播放次数 ,
+    var playTime: Int { get set } // 播放时间 ,
+    var processShareHeadLab: [String: Any]? { get set } // 视频分享片尾数据 ,
+    var processShareTailLab: [String: Any]? { get set } // 视频分享片尾数据 ,
+    var pwd: String? { get set } // 视频密码 ,
+    var recommendId: String? { get set } // 推荐链路ID ,
+    var recommendSource: Int { get set } // 0 默认 1 第四范式
+    var recommendStatus: Int { get set } // 推荐状态 ,
+    var rotate: Int { get set } // 旋转角度 ,
+    var sampleJobId: String? { get set }
+    var sampleRequestId: String? { get set }
+    var sampleTotalTime: Int { get set }
+    var sampleTranscodeStatus: Int { get set }
+    var sampleTransedVideoPath: String? { get set }
+    var sendBeforeDay: Int { get set } // 发视频距离今天的天数 ,
+    var sensitiveMsg: String? { get set } // 敏感提示信息 ,
+    var sensitiveStatus: Int { get set } //  内容敏感状态(0:未检验,1:不敏感,2:敏感,3:敏感已审) ,
+    var shareCount: Int { get set } // 分享到朋友圈次数 ,
+    var shareId: String? { get set } // 分享的 ID
+    var shareCountFriend: Int { get set } // 分享到微信好友 ,
+    var shareImgPath: String? { get set } // 分享图URL ,
+    var shareLinkType: Int { get set } // 分享到微信好友的图片的链接的类型 ,
+    var sharePageType: Int { get set } // 0 综合模块 1 feed流 ,
+    var shareTitle: String? { get set } // 分享到微信好友的图片的title ,
+    var showHotRecommend: Bool { get set } // 是否需要显示热门推荐 ,
+    var size: Int { get set } //  大小 ,
+    var status: Int { get set } // 数据状态,1 有效,2 已删除,3 已屏蔽,4 关注可见,5 分享可见 6 自己可见 ,
+    var tabShareImgPath: String? { get set } // 转发分享图URL ,
+    var thumbnailImagePath: String? { get set } // 缩略图URL ,
+    var totalTime: Int { get set } // 视频时长 ,
+    var totalTimeParas: String? { get set } // 视频时长十分秒 ,
+    var transcodeStatus: Int { get set } // 转码状态:1-不需转码 2-转码中 3-转码完成 4-转码失败 ,
+    var transcodeVOList: [Any]? { get set } // 多码率数据 ,
+    var uid: Int { get set } // 视频的用户ID ,
+    var user: [String: Any]? { get set } //  用户对象 ,
+    var videoCollectionId: Int { get set } // 视频所在的视频集ID ,
+    var videoCoverSnapshotPath: String? { get set } // 原始封面图片 ,
+    var videoPath: String? { get set } // 视频地址 ,
+    var videoReportMeta: String? { get set } // 视频上报数据,上报时原样返回 ,
+    var videoShareJumpModel: [String: Any]? { get set } // 分享页跳转的信息 ,
+    var playProgress: Float64 { get set } // 已播放时长
+    var duration: Float64 { get set } // 视频总时长
+    var tab_pageType: TAB_PAGETYPE { get set } // 0-推荐 1-关注
+    var pageSource: PAGESOURCE { get set }
+    var isVerticality: Bool { get set }
+    var isShareList: Bool { get set } // 是否是分享列表
+    var uplpadBucketKey: String? { get set } // 上传视频地址
+    var uplpadStatus: Int { get set } // 上传视频状态  1-上传中 2-上传完成 3-上传失败 4-发布中 4-发布完成
+    var uplpadRequest: OSSMultipartUploadRequest? { get set }
+    var stsToken: [String: Any]? { get set } // 上传信息
+    var localPath: String? { get set } // 地址
+    var progress: Float { get set }
+    var projectId: String? { get set } // 项目ID-发布创作的视频时必传,会在进入创作工具页时生成,以app_no_projectdata为前缀
+    var autoType: autoType? { get set } // autoType 自动动作的类型
+    // 发布视频来源类型
+    var videoFromScene: videoFromScene { get set }
+    // extParams: 额外参数-传入发布页 - 主要中秋红包塞活动使用
+    var extParams:String { get set }
+    // 话题信息
+    var topicData: [String: Any]? { get set }
+    var uplpadImage: UIImage? { get set } // 上传的图片封面
+}
+
+open class PQVideoListModel: BFBaseModel, BFVideoItemProtocol {
+    @objc public required init() {
+        super.init()
+    }
+
+    public var id: UInt64 = 0
+    public var headVideoId: String? // 当前的相关推荐视频是属于哪个视频的相关推荐,值为那个头部视频的videoId
+    public var auditStatus: Int = 0 // 审核状态 1 审核中,2 不通过 3 待修改,4 自己可见 5 通过 ,
+    public var barrageCount: Int = 0 // 弹幕数量
+    public var barrageSwitch: Int = 0 // 是否打开弹幕 1打开 -1关闭 ,
+    public var auditReason: String? // 审核不通过或者待修改的原因
+    public var barrage: Any? // 弹幕集合
+    public var chargeDetail: [String: Any]? // 收费的相关信息
+    public var commentCount: Int = 0 // 评论数量
+    public var coverImg: [String: Any]? // 封面对象 ,
+    public var cutVoStr: String? // h5剪切板内容 ,
+    public var descr: String? // 视频简介 ,
+    public var encryption: Int = 0 // 是否加密视频:0是1不是 ,
+    public var favorited: Bool = false // 是否收藏 ,
+    public var favoriteds: Int = 0 //  收藏数 ,
+    public var fileExtensions: String? // 视频后缀 ,
+    public var firstPicture: Bool = false // 封面是否是第一帧,false不是true是 ,
+    public var gmtCreate: String? // 创建时间 ,
+    public var gmtCreateDescr: String? // 发视频时间描述 ,
+    public var gmtCreateTimestamp: Int = 0 // 创建时间戳 ,
+    public var gmtModifie: String? // 修改时间 ,
+    public var gmtModifiedTimestamp: Int = 0 // 修改时间戳 ,
+    public var h5ShareImgPath: String? // h5分享图URL ,
+    public var hasShareSpaceData: Bool = false // 是否有分享空间数据,
+    public var height: CGFloat = 0 // 视频高 ,
+    public var isRecommendShare: Int = 0 // 是否有分发推荐的封面和标题 1 有 0 无 传空或者不传默认为0,
+    public var lastTimestamp: Int = 0 // 时间戳 ,
+    //  liteVideoData (LiteVideoDataVO, optional): lite数据,
+    public var measure: Int = 0
+    public var measureId: Int = 0
+    public var measureType: Int = 0 // 0 非流量池 1曝光池2普通推荐测试池3待推荐测试池 ,
+    public var playBeforeDay: Int = 0 // 播放时间距离今天的天数 ,
+    public var playCount: Int = 0 // 播放次数 ,
+    public var playCountFormatStr: String? //  用户视频总播放数,格式化后的值,前端直接显示 ,
+    public var playCountTotal: Int = 0 // 总播放次数 ,
+    public var playTime: Int = 0 // 播放时间 ,
+    public var processShareHeadLab: [String: Any]? // 视频分享片尾数据 ,
+    public var processShareTailLab: [String: Any]? // 视频分享片尾数据 ,
+    public var pwd: String? // 视频密码 ,
+    public var recommendId: String? // 推荐链路ID ,
+    public var recommendSource: Int = 0 // 0 默认 1 第四范式
+    public var flowPool: String?
+    public var recommendStatus: Int = 0 // 推荐状态 ,
+    public var rotate: Int = 0 // 旋转角度 ,
+    public var sampleJobId: String?
+    public var sampleRequestId: String?
+    public var sampleTotalTime: Int = 0
+    public var sampleTranscodeStatus: Int = 0
+    public var sampleTransedVideoPath: String?
+    public var sendBeforeDay: Int = 0 // 发视频距离今天的天数 ,
+    public var sensitiveMsg: String? // 敏感提示信息 ,
+    public var sensitiveStatus: Int = 0 //  内容敏感状态(0:未检验,1:不敏感,2:敏感,3:敏感已审) ,
+    public var shareCount: Int = 0 // 分享到朋友圈次数 ,
+    // add by ak 个人中心里我分享的视频列表返回的参数 e.g. 78C10B44-6892-42A8-AE69-F1B35F9E676F-534530
+    public var shareId: String? // 分享的 ID
+    public var shareCountFriend: Int = 0 // 分享到微信好友 ,
+    public var shareImgPath: String? // 分享图URL ,
+    public var shareLinkType: Int = 0 // 分享到微信好友的图片的链接的类型 ,
+    public var sharePageType: Int = 0 // 0 综合模块 1 feed流 ,
+    public var shareTitle: String? // 分享到微信好友的图片的title ,
+    public var showHotRecommend: Bool = false // 是否需要显示热门推荐 ,
+    public var size: Int = 0 //  大小 ,
+    public var status: Int = 0 // 数据状态,1 有效,2 已删除,3 已屏蔽,4 关注可见,5 分享可见 6 自己可见 ,
+    public var tabShareImgPath: String? // 转发分享图URL ,
+    public var thumbnailImagePath: String? // 缩略图URL ,
+    public var totalTime: Int = 0 // 视频时长 ,
+    public var totalTimeParas: String? // 视频时长十分秒 ,
+    public var transcodeStatus: Int = 0 // 转码状态:1-不需转码 2-转码中 3-转码完成 4-转码失败 ,
+    public var transcodeVOList: [Any]? // 多码率数据 ,
+    public var uid: Int = 0 // 视频的用户ID ,
+    public var user: [String: Any]? //  用户对象 ,
+    public var userInfo: PQUserInfoModel? //  用户对象 ,
+    public var videoCollectionId: Int = 0 // 视频所在的视频集ID ,
+    public var videoCoverSnapshotPath: String? // 原始封面图片 ,
+    public var videoPath: String? // 视频地址 ,
+    public var videoURL: String? // 视频地址 ,
+    public var videoReportMeta: String? // 视频上报数据,上报时原样返回 ,
+    public var videoShareJumpModel: [String: Any]? // 分享页跳转的信息 ,
+    public var width: CGFloat = 0 // 视频宽
+    public var itemHeight: CGFloat = 0 // 个人中心cell高
+    public var originImageH: CGFloat = 0 // 原始图片的宽
+    public var originImageW: CGFloat = 0 // 原始图片的高
+    public var imageH: CGFloat = 0 // 图片的高
+    public var imageW: CGFloat = 0 // 图片的宽
+    public var titleH: CGFloat = 0 // 标题的高
+    public var descH: CGFloat = 0 // 描述的高
+    public var titleFontSize: CGFloat = 0 // add by ak 标题字号
+    public var usnameW: CGFloat = 0 // add by ak 用户名宽度
+    public var rotationH: CGFloat = 0 // add by ak 三个推荐视频高度包括标题
+    public var watchInfoH: CGFloat = 0 // add by ak watch info 高度
+    public var watchInfoY: CGFloat = 0 // add by ak watch info Y 值
+    public var relationData: [PQVideoListModel]?
+    public var playProgress: Float64 = 0 // 已播放时长
+    public var duration: Float64 = 0 // 视频总时长
+    public var tab_pageType: TAB_PAGETYPE = .TAB_PAGETYPE_NORMAL // 0-推荐 1-关注
+    public var pageSource: PAGESOURCE = .sp_category
+    public var isVerticality: Bool = false
+    public var isShareList: Bool = false // 是否是分享列表
+
+    public var funcH: CGFloat = cDefaultMargin * 33
+    public var funcW: CGFloat = cDefaultMargin * 5
+    public var uplpadImage: UIImage? // 上传的图片封面
+    public var uplpadBucketKey: String? // 上传视频地址
+    public var uplpadStatus: Int = 0 // 上传视频状态  1-上传中 2-上传完成 3-上传失败 4-发布中 4-发布完成
+    public var uplpadRequest: OSSMultipartUploadRequest?
+    public var stsToken: [String: Any]? // 上传信息
+    public var localPath: String? // 地址
+    public var progress: Float = 0
+    public var projectId: String? // 项目ID-发布创作的视频时必传,会在进入创作工具页时生成,以app_no_projectdata为前缀
+    public var reCreateVideoData: PQReCreateModel? // 再创作数据
+    // 视频创作埋点数据
+    public var eventTrackData: PQVideoMakeEventTrackModel?
+    public var autoType: autoType? // autoType 自动动作的类型
+    // add by ak 发布视频来源类型
+    public var videoFromScene: videoFromScene = .UploadNormal
+    // extParams: 额外参数-传入发布页 - 主要中秋红包塞活动使用
+    public var extParams:String = ""
+    // 视频分类
+    public var categoryName: String = ""
+    // 视频分类图
+    public var categoryImage: String?
+    // 话题信息
+    public var topicData: [String: Any]?
+    // 垂直方向是否自动播放下一个
+    public var isVerticalAutoPlayNext: Bool = false
+    // 是否上报过
+    public var isViewExposureReport: Bool = false
+    override public init(jsonDict: [String: Any]) {
+        super.init(jsonDict: jsonDict)
+        if jsonDict.keys.contains("id") {
+            id = UInt64("\(jsonDict["id"] ?? "")") ?? 0
+        }
+        if jsonDict.keys.contains("videoPath") {
+            videoPath = "\(jsonDict["videoPath"] ?? "")"
+            videoURL = videoPath
+            if videoPath?.contains(".m3u8") ?? false {
+                videoURL = videoPath?.components(separatedBy: "?").first
+            }
+        }
+        if jsonDict.keys.contains("videoCoverSnapshotPath") {
+            videoCoverSnapshotPath = "\(jsonDict["videoCoverSnapshotPath"] ?? "")"
+            if (videoCoverSnapshotPath?.count ?? 0) > 0, !(videoCoverSnapshotPath?.contains("http") ?? false) {
+                videoCoverSnapshotPath = "https://rescdn.yishihui.com/" + videoCoverSnapshotPath!
+            }
+        }
+        if jsonDict.keys.contains("thumbnailImagePath") {
+            thumbnailImagePath = "\(jsonDict["thumbnailImagePath"] ?? "")"
+        }
+        if jsonDict.keys.contains("rotate") {
+            rotate = Int("\(jsonDict["rotate"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("descr") {
+            descr = "\(jsonDict["descr"] ?? "")"
+            if descr != nil, (descr?.count ?? 0) > 2, descr?.hasSuffix("\n") ?? false {
+                descr = String(descr?.prefix((descr?.count ?? 2) - 2) ?? "")
+            }
+        }
+        if jsonDict.keys.contains("favorited") {
+            favorited = jsonDict["favorited"] as! Bool
+        }
+        if jsonDict.keys.contains("firstPicture") {
+            firstPicture = jsonDict["firstPicture"] as! Bool
+        }
+        if jsonDict.keys.contains("shareImgPath") {
+            shareImgPath = "\(jsonDict["shareImgPath"] ?? "")"
+        }
+        if jsonDict.keys.contains("coverImg") {
+            coverImg = jsonDict["coverImg"] as? [String: Any]
+        }
+        if jsonDict.keys.contains("user") {
+            user = jsonDict["user"] as? [String: Any]
+            userInfo = PQUserInfoModel(jsonDict: user!)
+        }
+        if jsonDict.keys.contains("topicData") {
+            topicData = jsonDict["topicData"] as? [String: Any]
+        }
+        if jsonDict.keys.contains("topicInfo") {
+            topicData = jsonDict["topicInfo"] as? [String: Any]
+        }
+        if jsonDict.keys.contains("shareCountFriend") {
+            shareCountFriend = Int("\(jsonDict["shareCountFriend"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("shareId") {
+            shareId = "\(jsonDict["shareId"] ?? "")"
+        }
+        if jsonDict.keys.contains("shareCount") {
+            shareCount = Int("\(jsonDict["shareCount"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("favoriteds") {
+            favoriteds = Int("\(jsonDict["favoriteds"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("uid") {
+            uid = Int("\(jsonDict["uid"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("lastTimestamp") {
+            lastTimestamp = Int("\(jsonDict["lastTimestamp"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("auditStatus") {
+            auditStatus = Int("\(jsonDict["auditStatus"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("status") {
+            status = Int("\(jsonDict["status"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("auditReason") {
+            auditReason = "\(jsonDict["auditReason"] ?? "")"
+        }
+        if jsonDict.keys.contains("transcodeStatus") {
+            transcodeStatus = Int("\(jsonDict["transcodeStatus"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("playCountTotal") {
+            playCountTotal = Int("\(jsonDict["playCountTotal"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("measure") {
+            measure = Int("\(jsonDict["measure"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("measureId") {
+            measureId = Int("\(jsonDict["measureId"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("measureType") {
+            measureType = Int("\(jsonDict["measureType"] ?? "0")") ?? 0
+        }
+        if jsonDict.keys.contains("playCountFormatStr") {
+            playCountFormatStr = "\(jsonDict["playCountFormatStr"] ?? "")"
+        }
+        if jsonDict.keys.contains("recommendId") {
+            recommendId = "\(jsonDict["recommendId"] ?? "")"
+        }
+        if jsonDict.keys.contains("abInfoData") {
+            abInfoData = "\(jsonDict["abInfoData"] ?? "")"
+        }
+        if jsonDict.keys.contains("flowPool") {
+            flowPool = "\(jsonDict["flowPool"] ?? "")"
+        }
+        if jsonDict.keys.contains("totalTime") {
+            totalTime = Int("\(jsonDict["totalTime"] ?? "0")") ?? 0
+            totalTimeParas = Float64(totalTime).formatDurationToHMS()
+        }
+        if jsonDict.keys.contains("width") {
+            width = CGFloat(Float("\(jsonDict["width"] ?? 0)") ?? 0)
+            if rotate > 0 {
+                originImageH = width
+            } else {
+                originImageW = width
+            }
+        }
+        if jsonDict.keys.contains("height") {
+            height = CGFloat(Float("\(jsonDict["height"] ?? 0)") ?? 0)
+            if rotate > 0 {
+                originImageW = height
+            } else {
+                originImageH = height
+            }
+        }
+        if jsonDict.keys.contains("produceProjectDataV2"), ((jsonDict["produceProjectDataV2"] as? [String: Any])?.keys.count ?? 0) > 0 {
+            reCreateVideoData = PQReCreateModel(jsonDict: jsonDict["produceProjectDataV2"] as! [String: Any])
+            if reCreateVideoData?.canReproduce == 1 {
+                funcH = funcH + cDefaultMargin * 7
+            }
+            reCreateVideoData?.videoId = uniqueId
+        }
+        if coverImg != nil, coverImg?.count ?? 0 > 0 {
+            imageW = cScreenWidth
+            imageH = imageW * originImageH / originImageW
+            isVerticality = originImageH > originImageW
+            if imageH > cScreenHeigth {
+                imageH = cScreenHeigth
+                imageW = imageH * originImageW / originImageH
+            }
+        }
+        // 计算个人中心高度
+        var tempTitleH: CGFloat = -cDefaultMargin
+        if title != nil, (title?.count ?? 0) > 0 {
+            tempTitleH = sizeWithText(text: title ?? "", font: UIFont.systemFont(ofSize: 16), size: CGSize(width: (cScreenWidth - cDefaultMargin * 3) / 2, height: cDefaultMargin * 4)).height
+        }
+        itemHeight = (cScreenWidth - cDefaultMargin * 3) / 2 * originImageH / originImageW + tempTitleH + cDefaultMargin * 4.5
+
+        if title != nil, (title?.count ?? 0) > 0 {
+            titleH = sizeWithText(text: title ?? "", font: UIFont.systemFont(ofSize: 26, weight: .medium), size: CGSize(width: cScreenWidth - cDefaultMargin * 2, height: CGFloat.greatestFiniteMagnitude)).height + cDefaultMargin
+        }
+        if titleH > 70 {
+            titleH = 70
+            titleFontSize = cScreenWidth <= 320 ? 19 : 21
+        } else {
+            titleFontSize = 25
+        }
+
+        BFLog(message: "title \(String(describing: title))   titleH :\(titleH)  titleFontSize :\(titleFontSize)")
+
+        var isFollowed: Bool = false
+
+        if tab_pageType == .TAB_PAGETYPE_RECOMM {
+            isFollowed = userInfo?.followed != 1
+        } else {
+            isFollowed = true
+        }
+
+        let attM: CGFloat = cDefaultMargin * 4.5
+        usnameW = sizeWithText(text: userInfo?.nickName ?? "", font: UIFont.systemFont(ofSize: 16, weight: .medium), size: CGSize(width: !isFollowed ? (cScreenWidth - cDefaultMargin * 8) : (cScreenWidth - cDefaultMargin * 9 - attM), height: cDefaultMargin * 2)).width + cDefaultMargin * 2
+
+        BFLog(message: "nickname is \(userInfo?.nickName ?? "") '    'usnameW is \(usnameW) isFollowed is \(isFollowed)")
+
+        updateReommendAgent()
+
+        watchInfoY = -cDefaultMargin
+
+        BFLog(message: "watchInfoY11111 is \(watchInfoY)")
+    }
+
+    public func updateReommendAgent() {
+        // 计算相关推荐高度
+        var itemH: CGFloat = 0
+        var verticality: Bool = false
+        var haveTitle: Bool = false
+        if (relationData?.count ?? 0) > 0 {
+            for item in (relationData)! {
+                if item.imageH > item.imageW {
+                    verticality = true
+                    break
+                }
+                BFLog(message: "item.title  \(String(describing: item.title))")
+                if item.title != nil, (item.title?.count ?? 0) > 0 {
+                    haveTitle = true
+                    break
+                }
+            }
+        }
+        itemH = verticality ? 130 : (haveTitle ? 90 : 55)
+        itemH = (relationData?.count ?? 0) > 0 ? itemH : 0
+
+        BFLog(message: "itemH is: \(itemH) sss \(String(describing: relationData?.count))")
+
+        rotationH = itemH
+        // 描述部分
+        if (relationData?.count ?? 0) > 0, tab_pageType != .TAB_PAGETYPE_ATTEN {
+            watchInfoY = (isVerticality ? -cDefaultMargin : -(cDefaultMargin * 2 + rotationH))
+        } else {
+            watchInfoY = -cDefaultMargin
+        }
+
+        BFLog(message: "watchInfoY2222 is \(watchInfoY)")
+
+        let nomalH: CGFloat = cDefaultMargin * 1.5
+//        let likeH: CGFloat = favoriteds <= 0 ? 0 : nomalH
+        let likeH: CGFloat = 0
+        watchInfoH = cDefaultMargin * 2 + nomalH + likeH + cDefaultMargin
+        if reCreateVideoData != nil, reCreateVideoData?.canReproduce == 1 {
+            watchInfoH = watchInfoH + ((reCreateVideoData?.rhythmMusicFlag != 1 && (reCreateVideoData?.parentVideoId != nil)) ? cDefaultMargin * 8 : 43)
+        }
+        if descr != nil, !(descr?.isEmpty ?? true) {
+            if Thread.isMainThread {
+                descH = sizeTextFits(attributedText: NSMutableAttributedString(string: descr ?? ""), text: nil, numberOfLines: 5, font: UIFont.systemFont(ofSize: 14), maxSize: CGSize(width: cScreenWidth - funcW - cDefaultMargin * 3, height: CGFloat.greatestFiniteMagnitude)).height
+                watchInfoH = watchInfoH + descH
+            } else {
+                DispatchQueue.main.async { [weak self] in
+                    self?.descH = sizeTextFits(attributedText: NSMutableAttributedString(string: (self?.descr)!), text: nil, numberOfLines: 5, font: UIFont.systemFont(ofSize: 14), maxSize: CGSize(width: cScreenWidth - (self?.funcW ?? 0) - cDefaultMargin * 3, height: CGFloat.greatestFiniteMagnitude)).height
+                    self?.watchInfoH = (self?.watchInfoH ?? 0) + (self?.descH ?? 0)
+                }
+            }
+        }
+    }
+}

+ 1 - 0
BFStuckPointKit/Classes/SelectImage/PQImageCropVC.swift

@@ -12,6 +12,7 @@
 
 import BFUIKit
 import UIKit
+import BFCommonKit
 
 class PQImageCropVC: BFBaseViewController, UIScrollViewDelegate {
     var uploadData: PQUploadModel?

+ 1 - 1
BFStuckPointKit/Classes/SelectImage/PQSelecteVideoItemCell.swift

@@ -36,7 +36,7 @@ class PQSelecteVideoItemCell: UICollectionViewCell {
     }()
  
     lazy var seleImage: UIImageView = {
-        let seleImage = UIImageView(image:UIImage.moduleImage(named: "icon_upload_do", moduleName: "BFFramework",isAssets: false))
+        let seleImage = UIImageView(image:UIImage.moduleImage(named: "icon_upload_do", moduleName: "BFStuckPointKit",isAssets: false))
         return seleImage
     }()
 

+ 2 - 1
BFStuckPointKit/Classes/View/PQAssetCategoryCell.swift

@@ -8,6 +8,7 @@
 
 import UIKit
 import BFCommonKit
+import BFUIKit
 
 public class PQAssetCategoryCell: UICollectionViewCell {
     public var representedAssetIdentifier: String!
@@ -35,7 +36,7 @@ public class PQAssetCategoryCell: UICollectionViewCell {
     lazy public var seleImage: UIImageView = {
         let seleImage = UIImageView()
         seleImage.tintColor = BFConfig.shared.styleTitleColor
-        seleImage.image = bfFramworkImage(by: "icon_uploadVideo_do")?.withRenderingMode(.alwaysTemplate)//UIImage.moduleImage(named: , moduleName: "BFFramework",isAssets: false)?.withRenderingMode(.alwaysTemplate)
+        seleImage.image = BFStuckPointImage(by: "icon_uploadVideo_do")?.withRenderingMode(.alwaysTemplate)//UIImage.moduleImage(named: , moduleName: "BFStuckPointKit",isAssets: false)?.withRenderingMode(.alwaysTemplate)
         seleImage.isHidden = true
         return seleImage
     }()

+ 12 - 10
BFStuckPointKit/Classes/View/PQBaseVideoInfoView.swift

@@ -6,23 +6,25 @@
 //  Copyright © 2020 BytesFlow. All rights reserved.
 //
 
-import UIKit
 import BFCommonKit
+import BFMediaKit
+import BFUIKit
+import UIKit
 
 open class PQBaseVideoInfoView: UIView {
-    lazy public var imageView: UIImageView = {
-        let imageView = UIImageView(image:UIImage.moduleImage(named: "msg_default", moduleName: "BFFramework",isAssets: false))
+    public lazy var imageView: UIImageView = {
+        let imageView = UIImageView(image: UIImage.moduleImage(named: "msg_default", moduleName: "BFStuckPointKit", isAssets: false))
         imageView.addCorner(corner: 4)
         imageView.contentMode = .scaleAspectFill
         return imageView
     }()
 
-    lazy public var videoTagView: UIImageView = {
-        let videoTagView = UIImageView(image:UIImage.moduleImage(named: "msg_video_tag", moduleName: "BFFramework",isAssets: false))
+    public lazy var videoTagView: UIImageView = {
+        let videoTagView = UIImageView(image: UIImage.moduleImage(named: "msg_video_tag", moduleName: "BFStuckPointKit", isAssets: false))
         return videoTagView
     }()
 
-    lazy public var titleLab: UILabel = {
+    public lazy var titleLab: UILabel = {
         let titleLab = UILabel()
         titleLab.textColor = UIColor.hexColor(hexadecimal: "#CCCCCC")
         titleLab.numberOfLines = 3
@@ -38,21 +40,21 @@ open class PQBaseVideoInfoView: UIView {
         backgroundColor = UIColor.hexColor(hexadecimal: "#171718")
     }
 
-    required public init?(coder _: NSCoder) {
+    public required init?(coder _: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }
 
-   open var videoData: PQVideoListModel? {
+    open var videoData: PQVideoListModel? {
         didSet {
             addData()
             addLayout()
         }
     }
 
-   open func addData() {
+    open func addData() {
         // 这里会crash
         let coverImg = (videoData?.videoCoverSnapshotPath != nil && (videoData?.videoCoverSnapshotPath?.count ?? 0) > 0) ? videoData?.videoCoverSnapshotPath ?? "" : (videoData?.coverImg?["coverImgPath"] as? String ?? "")
-    imageView.setNetImage(url: coverImg, placeholder: UIImage.moduleImage(named: "msg_default", moduleName: "BFFramework",isAssets: false)!)
+        imageView.setNetImage(url: coverImg, placeholder: UIImage.moduleImage(named: "msg_default", moduleName: "BFStuckPointKit", isAssets: false)!)
         titleLab.text = videoData?.title
     }
 

+ 3 - 3
BFStuckPointKit/Classes/View/PQCustomSpeedSettingView.swift

@@ -66,7 +66,7 @@ class PQCustomSpeedSettingView: UIView {
 
     lazy var fastSlider: BFUISlider = {
         let fastSlider = BFUISlider()
-        let thbImage = UIImage.moduleImage(named: BFConfig.shared.silderPinUsedImageName, moduleName: "BFFramework", isAssets: false)
+        let thbImage = UIImage.moduleImage(named: BFConfig.shared.silderPinUsedImageName, moduleName: "BFStuckPointKit", isAssets: false)
         fastSlider.setMinimumTrackImage(thbImage, for: .normal)
         fastSlider.setMaximumTrackImage(thbImage, for: .normal)
         fastSlider.setThumbImage(thbImage, for: .highlighted)
@@ -85,7 +85,7 @@ class PQCustomSpeedSettingView: UIView {
 
     lazy var slowSlider: BFUISlider = {
         let slowSlider = BFUISlider()
-        let thbImage =  UIImage.moduleImage(named: BFConfig.shared.silderPinUsedImageName, moduleName: "BFFramework", isAssets: false)
+        let thbImage =  UIImage.moduleImage(named: BFConfig.shared.silderPinUsedImageName, moduleName: "BFStuckPointKit", isAssets: false)
         slowSlider.setMinimumTrackImage(thbImage, for: .normal)
         slowSlider.setMaximumTrackImage(thbImage, for: .normal)
         slowSlider.setThumbImage(thbImage, for: .highlighted)
@@ -104,7 +104,7 @@ class PQCustomSpeedSettingView: UIView {
 
     lazy var jumpSpeedSlider: BFUISlider = {
         let jumpSpeedSlider = BFUISlider()
-        let thbImage =  UIImage.moduleImage(named: BFConfig.shared.silderPinUsedImageName, moduleName: "BFFramework", isAssets: false)
+        let thbImage =  UIImage.moduleImage(named: BFConfig.shared.silderPinUsedImageName, moduleName: "BFStuckPointKit", isAssets: false)
         jumpSpeedSlider.setMinimumTrackImage(thbImage, for: .normal)
         jumpSpeedSlider.setMaximumTrackImage(thbImage, for: .normal)
         jumpSpeedSlider.setThumbImage(thbImage, for: .highlighted)

+ 1 - 1
BFStuckPointKit/Classes/View/PQCuttingPointView.swift

@@ -18,7 +18,7 @@ class PQCuttingPointView: UIView {
     }()
 
     lazy var dragingImageView: UIImageView = {
-        let dragingImageView = UIImageView(image:UIImage.moduleImage(named: "stuckPoint_dragingImage", moduleName: "BFFramework",isAssets: false)?.withRenderingMode(.alwaysTemplate))
+        let dragingImageView = UIImageView(image:UIImage.moduleImage(named: "stuckPoint_dragingImage", moduleName: "BFStuckPointKit",isAssets: false)?.withRenderingMode(.alwaysTemplate))
         dragingImageView.tintColor = UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue)
         return dragingImageView
     }()

+ 2 - 2
BFStuckPointKit/Classes/View/PQEditPublicCoverImageView.swift

@@ -51,7 +51,7 @@ class PQEditPublicCoverImageView: UIView {
     lazy var selectPhotoBtn: UIButton = {
         let selectPhotoBtn = UIButton(type: .custom)
        
-        selectPhotoBtn.setImage(UIImage.moduleImage(named:  BFConfig.shared.editCoverimageSelectImage, moduleName: "BFFramework",isAssets: false), for: .normal)
+        selectPhotoBtn.setImage(UIImage.moduleImage(named:  BFConfig.shared.editCoverimageSelectImage, moduleName: "BFStuckPointKit",isAssets: false), for: .normal)
         selectPhotoBtn.adjustsImageWhenHighlighted = false
         selectPhotoBtn.addCorner(corner: 11)
         selectPhotoBtn.tag = 1
@@ -91,7 +91,7 @@ class PQEditPublicCoverImageView: UIView {
             coverImageBtn.addTarget(self, action: #selector(coverImageBtnClick(sender:)), for: .touchUpInside)
             //选中后的角标
             let iconView = UIImageView.init(frame: CGRect(x: frame.size.width  - 22.0 - 6, y: 6, width: 22, height: 22))
-            iconView.image = UIImage.moduleImage(named:  BFConfig.shared.editCoverimageSelectedImage, moduleName: "BFFramework",isAssets: false)
+            iconView.image = UIImage.moduleImage(named:  BFConfig.shared.editCoverimageSelectedImage, moduleName: "BFStuckPointKit",isAssets: false)
            
           
             iconView.tag = 1000

+ 2 - 1
BFStuckPointKit/Classes/View/PQEditPublicTitleView.swift

@@ -7,6 +7,7 @@
 
 import Foundation
 import BFUIKit
+import BFCommonKit
 
 class PQEditPublicTitleView: UIView {
     
@@ -241,7 +242,7 @@ class PQEditPublicTitleViewContentCell: UICollectionViewCell {
     lazy var iconView: UIImageView = {
         let iconView = UIImageView()
         iconView.backgroundColor = .clear
-        iconView.image = UIImage.moduleImage(named: "editTitleTips", moduleName: "BFFramework",isAssets: false)
+        iconView.image = UIImage.moduleImage(named: "editTitleTips", moduleName: "BFStuckPointKit",isAssets: false)
         return iconView
     }()
 

+ 6 - 5
BFStuckPointKit/Classes/View/PQSelecteMusicView.swift

@@ -8,6 +8,7 @@
 import Foundation
 import BFUIKit
 import BFCommonKit
+import BFMediaKit
 
 class PQSelecteMusicView: UIView {
     // 所有分类数据
@@ -535,7 +536,7 @@ class PQSelectMusicCell: UICollectionViewCell {
     var contentType: stuckPointMusicContentType = .catagery
 
     lazy var audioImageView: UIImageView = {
-        let audioImageView = UIImageView(image: bfFramworkImage(by: "videomk_music_default"))
+        let audioImageView = UIImageView(image: BFStuckPointImage(by: "videomk_music_default"))
         audioImageView.addCorner(corner: 4)
         audioImageView.contentMode = .scaleAspectFill
         return audioImageView
@@ -550,7 +551,7 @@ class PQSelectMusicCell: UICollectionViewCell {
 
     lazy var playImageView: UIImageView = {
         let playImageView = UIImageView()
-        playImageView.image = UIImage.moduleImage(named: "stuckPoint_music_pause", moduleName: "BFFramework",isAssets: false)
+        playImageView.image = UIImage.moduleImage(named: "stuckPoint_music_pause", moduleName: "BFStuckPointKit",isAssets: false)
         playImageView.contentMode = .scaleAspectFit
         return playImageView
     }()
@@ -622,7 +623,7 @@ class PQSelectMusicCell: UICollectionViewCell {
     }
 
     func addData() {
-        audioImageView.setNetImage(url: "\(bgmData?.avatarUrl ?? "")", placeholder: bfFramworkImage(by: "videomk_music_default")!)
+        audioImageView.setNetImage(url: "\(bgmData?.avatarUrl ?? "")", placeholder: BFStuckPointImage(by: "videomk_music_default")!)
         
         if((bgmData?.musicName ?? "").count <= 4){
             musicNameLab.text = (bgmData?.musicName ?? "").appending("\n ")
@@ -645,7 +646,7 @@ class PQSelectMusicCell: UICollectionViewCell {
         } else if  bgmData?.voiceStatue == .isPause{
             playImageView.isHidden = false
             imageMaskView.isHidden = false
-            playImageView.image = UIImage.moduleImage(named: "stuckPoint_music_pause", moduleName: "BFFramework",isAssets: false)
+            playImageView.image = UIImage.moduleImage(named: "stuckPoint_music_pause", moduleName: "BFStuckPointKit",isAssets: false)
       
         }else {
             playImageView.isHidden = true
@@ -662,7 +663,7 @@ class PQSelectMusicCell: UICollectionViewCell {
 //                playImageView.kf.setImage(with: URL(fileURLWithPath: currentBundle()!.path(forResource: "stuckPoint_music_playing", ofType: ".gif")!))
 //
 //            } else {
-//                playImageView.image = UIImage.moduleImage(named: "stuckPoint_music_pause", moduleName: "BFFramework",isAssets: false)
+//                playImageView.image = UIImage.moduleImage(named: "stuckPoint_music_pause", moduleName: "BFStuckPointKit",isAssets: false)
 //            }
 //
 //        } else {

+ 1 - 0
BFStuckPointKit/Classes/View/PQSelectedMaterialListView.swift

@@ -8,6 +8,7 @@
 
 import UIKit
 import BFMaterialKit
+import BFMediaKit
 
 class PQSelectedMaterialListView: UIView {
     var photoData: [PQEditVisionTrackMaterialsModel] = Array<PQEditVisionTrackMaterialsModel>.init() // 相册数据

+ 2 - 0
BFStuckPointKit/Classes/View/PQSpeedSettingView.swift

@@ -6,6 +6,8 @@
 //  功能:设置快慢速 跳越卡点 的倍速 VIEW
 
 import Foundation
+import BFCommonKit
+
 class PQSpeedSettingView: UIView {
     // 速度列表
     lazy var titleCollectionView: UICollectionView = {

+ 1 - 1
BFStuckPointKit/Classes/View/PQStuckPointLoadingView.swift

@@ -17,7 +17,7 @@ class PQStuckPointLoadingView: UIView {
     /// 同步进度显示
     lazy var loadingView: AnimatedImageView = {
         let videoLoadingView = AnimatedImageView()
-        videoLoadingView.kf.setImage(with: URL(fileURLWithPath: (Bundle.current(moduleName: "BFFramework", isAssets: false)?.path(forResource: "stuckPoint_edit_loading", ofType: ".gif"))!))
+        videoLoadingView.kf.setImage(with: URL(fileURLWithPath: (Bundle.current(moduleName: "BFStuckPointKit", isAssets: false)?.path(forResource: "stuckPoint_edit_loading", ofType: ".gif"))!))
         videoLoadingView.stopAnimating()
         return videoLoadingView
     }()

+ 1 - 1
BFStuckPointKit/Classes/View/PQStuckPointMaterialHeadView.swift

@@ -12,7 +12,7 @@ import BFCommonKit
 
 class PQStuckPointMaterialHeadView: UIView {
     lazy var iconImageView: UIImageView = {
-        let iconImageView = UIImageView(image:UIImage.moduleImage(named: "videomk_netMaterial_selected", moduleName: "BFFramework",isAssets: false))
+        let iconImageView = UIImageView(image:UIImage.moduleImage(named: "videomk_netMaterial_selected", moduleName: "BFStuckPointKit",isAssets: false))
         return iconImageView
     }()
 

+ 8 - 7
BFStuckPointKit/Classes/View/PQStuckPointMusicContentCell.swift

@@ -8,6 +8,7 @@
 
 import UIKit
 import BFCommonKit
+import BFMediaKit
 
 class PQStuckPointMusicContentCell: UICollectionViewCell {
     // 按钮点击的回调
@@ -15,7 +16,7 @@ class PQStuckPointMusicContentCell: UICollectionViewCell {
     var contentType: stuckPointMusicContentType = .catagery
 
     lazy var audioImageView: UIImageView = {
-         let audioImageView = UIImageView(image:bfFramworkImage(by: "videomk_music_default")!)
+         let audioImageView = UIImageView(image:BFStuckPointImage(by: "videomk_music_default")!)
         audioImageView.addCorner(corner: 4)
         audioImageView.contentMode = .scaleAspectFill
         return audioImageView
@@ -30,7 +31,7 @@ class PQStuckPointMusicContentCell: UICollectionViewCell {
 
     lazy var playImageView: UIImageView = {
         let playImageView = UIImageView()
-        playImageView.image = UIImage.moduleImage(named: "stuckPoint_music_pause", moduleName: "BFFramework",isAssets: false)
+        playImageView.image = UIImage.moduleImage(named: "stuckPoint_music_pause", moduleName: "BFStuckPointKit",isAssets: false)
         return playImageView
     }()
 
@@ -123,7 +124,7 @@ class PQStuckPointMusicContentCell: UICollectionViewCell {
         musicNameLab.isHidden = !(bgmData is PQVoiceModel)
         titleLab.isHidden = (bgmData is PQVoiceModel)
         if bgmData is PQVoiceModel {
-            audioImageView.setNetImage(url: "\((bgmData as? PQVoiceModel)?.avatarUrl ?? "")", placeholder: bfFramworkImage(by: "videomk_music_default")!)
+            audioImageView.setNetImage(url: "\((bgmData as? PQVoiceModel)?.avatarUrl ?? "")", placeholder: BFStuckPointImage(by: "videomk_music_default")!)
             confirmContentView.isHidden = !((bgmData as? PQVoiceModel)?.isSelected ?? false)
             if (bgmData as? PQVoiceModel)?.isSelected ?? false {
                 imageMaskView.isHidden = false
@@ -131,7 +132,7 @@ class PQStuckPointMusicContentCell: UICollectionViewCell {
                 if (bgmData as? PQVoiceModel)?.isPlaying ?? false {
 
                     if playImageView.image == nil {
-                        playImageView.image = UIImage.moduleImage(named: "loading", moduleName: "BFFramework", isAssets: false)
+                        playImageView.image = UIImage.moduleImage(named: "loading", moduleName: "BFStuckPointKit", isAssets: false)
                         startLoadingAnimation()
                     }else {
                         playImageView.kf.setImage(with: URL(fileURLWithPath: (currentBundlePath()!.path(forResource: "stuckPoint_music_playing", ofType: ".gif")!)))
@@ -139,7 +140,7 @@ class PQStuckPointMusicContentCell: UICollectionViewCell {
                     }
                     musicNameLab.move()
                 } else {
-                    playImageView.image = UIImage.moduleImage(named: "stuckPoint_music_pause", moduleName: "BFFramework",isAssets: false)
+                    playImageView.image = UIImage.moduleImage(named: "stuckPoint_music_pause", moduleName: "BFStuckPointKit",isAssets: false)
                     musicNameLab.stop()
                 }
             } else {
@@ -149,9 +150,9 @@ class PQStuckPointMusicContentCell: UICollectionViewCell {
             }
         } else {
             if (bgmData as? PQStuckPointMusicTagsModel)?.tagEmoji != nil {
-                audioImageView.setNetImage(url: "\((bgmData as? PQStuckPointMusicTagsModel)?.tagEmoji ?? "")", placeholder:bfFramworkImage(by: "videomk_music_default")!)
+                audioImageView.setNetImage(url: "\((bgmData as? PQStuckPointMusicTagsModel)?.tagEmoji ?? "")", placeholder:BFStuckPointImage(by: "videomk_music_default")!)
             } else {
-                audioImageView.image = bfFramworkImage(by: "videomk_music_default")
+                audioImageView.image = BFStuckPointImage(by: "videomk_music_default")
             }
             titleLab.text = " \((bgmData as? PQStuckPointMusicTagsModel)?.tagName ?? "")"
             if (titleLab.text?.count ?? 0) > 8 {

+ 1 - 0
BFStuckPointKit/Classes/View/PQStuckPointMusicTagsCell.swift

@@ -7,6 +7,7 @@
 //
 
 import UIKit
+import BFMediaKit
 
 class PQStuckPointMusicTagsCell: UICollectionViewCell {
     /// 点击标签

+ 1 - 0
BFStuckPointKit/Classes/View/PQStuckPointMusicTagsContentCell.swift

@@ -8,6 +8,7 @@
 
 import UIKit
 import BFCommonKit
+import BFMediaKit
 
 class PQStuckPointMusicTagsContentCell: UICollectionViewCell {
     lazy var titleLab: UILabel = {

+ 2 - 2
BFStuckPointKit/Classes/View/PQVideoCutingOprateView.swift

@@ -68,7 +68,7 @@ class PQVideoCutingOprateView: UIView {
     }()
 
     lazy var leftOprateView: UIImageView = {
-        let leftOprateView = UIImageView(image:UIImage.moduleImage(named: "videomk_crop_left", moduleName: "BFFramework",isAssets: false)?.withRenderingMode(.alwaysTemplate))
+        let leftOprateView = UIImageView(image:UIImage.moduleImage(named: "videomk_crop_left", moduleName: "BFStuckPointKit",isAssets: false)?.withRenderingMode(.alwaysTemplate))
         leftOprateView.tintColor = BFConfig.shared.cutViewTintColor
         leftOprateView.contentMode = .scaleAspectFill
         leftOprateView.isUserInteractionEnabled = true
@@ -81,7 +81,7 @@ class PQVideoCutingOprateView: UIView {
     }()
 
     lazy var rightOprateView: UIImageView = {
-        let rightOprateView = UIImageView(image:UIImage.moduleImage(named: "videomk_crop_right", moduleName: "BFFramework",isAssets: false)?.withRenderingMode(.alwaysTemplate))
+        let rightOprateView = UIImageView(image:UIImage.moduleImage(named: "videomk_crop_right", moduleName: "BFStuckPointKit",isAssets: false)?.withRenderingMode(.alwaysTemplate))
         rightOprateView.tintColor = BFConfig.shared.cutViewTintColor
         rightOprateView.contentMode = .scaleAspectFill
         rightOprateView.isUserInteractionEnabled = true

+ 1 - 0
BFStuckPointKit/Classes/ViewModel/PQBaseViewModel.swift

@@ -12,6 +12,7 @@ import ObjectMapper
 import RealmSwift
 import BFCommonKit
 import BFNetRequestKit
+import BFMediaKit
 
 public class PQBaseViewModel: NSObject {
     

+ 1 - 0
BFStuckPointKit/Classes/ViewModel/PQDownloadFileManager.swift

@@ -8,6 +8,7 @@
 
 import UIKit
 import BFCommonKit
+import BFMediaKit
 
 class PQDownloadFileManager: NSObject {
     /// 创建文件

+ 1 - 0
BFStuckPointKit/Classes/ViewModel/PQDownloadManager.swift

@@ -10,6 +10,7 @@
 import UIKit
 import BFCommonKit
 import BFNetRequestKit
+import BFMediaKit
 
 // MARK: - 下载管理
 

+ 3 - 2
BFStuckPointKit/Classes/ViewModel/PQGPUImagePlayerView.swift

@@ -11,6 +11,7 @@ import AVKit
 import UIKit
 import ObjectMapper
 import BFCommonKit
+import BFMediaKit
 
 // import GPUImage
 struct AVAssetKey {
@@ -171,7 +172,7 @@ public class PQGPUImagePlayerView: UIView {
     lazy var playView: UIImageView = {
         let view = UIImageView(frame: CGRect(x: (self.frame.size.width - self.frame.size.height / 3.6) / 2, y: (self.frame.size.height - self.frame.size.height / 3.6) / 2, width: self.frame.size.height / 3.6, height: self.frame.size.height / 3.6))
 //        view.tintColor = UIColor.white
-        view.image = UIImage.moduleImage(named: "gpuplayBtn", moduleName: "BFFramework",isAssets: false)?.withRenderingMode(.alwaysTemplate)
+        view.image = UIImage.moduleImage(named: "gpuplayBtn", moduleName: "BFStuckPointKit",isAssets: false)?.withRenderingMode(.alwaysTemplate)
         view.tintColor = UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue)
         view.isHidden = true
         return view
@@ -240,7 +241,7 @@ public class PQGPUImagePlayerView: UIView {
         backgroundColor = BFConfig.shared.styleBackGroundColor
         playerEmptyView = UIImageView(frame: bounds)
         playerEmptyView.backgroundColor = .black
-        playerEmptyView.image = UIImage.moduleImage(named: "playEmpty", moduleName: "BFFramework",isAssets: false)
+        playerEmptyView.image = UIImage.moduleImage(named: "playEmpty", moduleName: "BFStuckPointKit",isAssets: false)
         playerEmptyView.contentMode = .center
         addSubview(playerEmptyView)
 

+ 1 - 0
BFStuckPointKit/Classes/ViewModel/PQPlayerViewModel.swift

@@ -10,6 +10,7 @@ import RealmSwift
 import UIKit
 import BFCommonKit
 import BFUIKit
+import BFMediaKit
 
 open class PQPlayerViewModel: NSObject {
     /// 根据贴纸信息转成种 fitler ,编辑 ,总览,导出共用

+ 1 - 0
BFStuckPointKit/Classes/ViewModel/PQSessionManager.swift

@@ -9,6 +9,7 @@
 import UIKit
 import BFCommonKit
 import BFNetRequestKit
+import BFMediaKit
 
 open class PQSessionManager: NSObject {
     public var downloadTaskDatas: [String: PQDownloadModel] = Dictionary<String, PQDownloadModel>.init()

+ 1 - 0
BFStuckPointKit/Classes/ViewModel/PQStuckPointViewModel.swift

@@ -9,6 +9,7 @@
 import UIKit
 import BFCommonKit
 import BFNetRequestKit
+import BFMediaKit
 
 public class PQStuckPointViewModel: NSObject {
     /// 获取卡点音乐分类列表

+ 4 - 0
Example/BFStuckPointKit.xcodeproj/project.pbxproj

@@ -274,6 +274,7 @@
 			inputPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-BFStuckPointKit_Example/Pods-BFStuckPointKit_Example-frameworks.sh",
 				"${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework",
+				"${BUILT_PRODUCTS_DIR}/AliyunOSSiOS/AliyunOSSiOS.framework",
 				"${BUILT_PRODUCTS_DIR}/BFCommonKit/BFCommonKit.framework",
 				"${BUILT_PRODUCTS_DIR}/BFNetRequestKit/BFNetRequestKit.framework",
 				"${BUILT_PRODUCTS_DIR}/BFUIKit/BFUIKit.framework",
@@ -281,6 +282,7 @@
 				"${BUILT_PRODUCTS_DIR}/KeychainAccess/KeychainAccess.framework",
 				"${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework",
 				"${BUILT_PRODUCTS_DIR}/KingfisherWebP/KingfisherWebP.framework",
+				"${BUILT_PRODUCTS_DIR}/LMJHorizontalScrollText/LMJHorizontalScrollText.framework",
 				"${BUILT_PRODUCTS_DIR}/MGSwipeTableCell/MGSwipeTableCell.framework",
 				"${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework",
 				"${BUILT_PRODUCTS_DIR}/ObjectMapper/ObjectMapper.framework",
@@ -294,6 +296,7 @@
 			name = "[CP] Embed Pods Frameworks";
 			outputPaths = (
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AliyunOSSiOS.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BFCommonKit.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BFNetRequestKit.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BFUIKit.framework",
@@ -301,6 +304,7 @@
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainAccess.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KingfisherWebP.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LMJHorizontalScrollText.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MGSwipeTableCell.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ObjectMapper.framework",

+ 28 - 3
Example/Podfile.lock

@@ -1,10 +1,12 @@
 PODS:
   - Alamofire (5.4.4)
+  - AliyunOSSiOS (2.10.10)
   - BFCommonKit (1.5.2):
     - BFCommonKit/BFCategorys (= 1.5.2)
     - BFCommonKit/BFConfig (= 1.5.2)
     - BFCommonKit/BFEnums (= 1.5.2)
     - BFCommonKit/BFUtility (= 1.5.2)
+    - BFCommonKit/BFVendors (= 1.5.2)
   - BFCommonKit/BFCategorys (1.5.2):
     - KingfisherWebP (= 1.3.0)
   - BFCommonKit/BFConfig (1.5.2):
@@ -19,18 +21,27 @@ PODS:
     - Kingfisher (= 6.3.1)
     - KingfisherWebP (= 1.3.0)
     - Toast-Swift (= 5.0.1)
+  - BFCommonKit/BFVendors (1.5.2)
   - BFMaterialKit (0.2.0):
     - BFUIKit
   - BFMediaKit (0.1.0):
+    - BFCommonKit
     - ObjectMapper (= 4.2.0)
+    - RealmSwift (= 10.7.6)
   - BFNetRequestKit (1.0.1):
     - Alamofire (= 5.4.4)
   - BFStuckPointKit (0.1.0):
+    - AliyunOSSiOS (= 2.10.10)
     - BFCommonKit
     - BFMaterialKit
     - BFMediaKit
     - BFNetRequestKit
     - BFUIKit
+    - Bugly (= 2.5.90)
+    - LMJHorizontalScrollText (= 2.0.2)
+    - MJRefresh (= 3.7.2)
+    - TXLiteAVSDK_Player (= 9.3.10765)
+    - WechatOpenSDK-Swift (= 1.8.7.1)
   - BFUIKit (0.1.2):
     - BFCommonKit
     - BFUIKit/Comm (= 0.1.2)
@@ -70,6 +81,7 @@ PODS:
     - RealmSwift (= 10.7.6)
     - SnapKit (~> 5.0)
     - SVProgressHUD (~> 2.0)
+  - Bugly (2.5.90)
   - FDFullscreenPopGesture (1.1)
   - KeychainAccess (4.2.2)
   - Kingfisher (6.3.1)
@@ -85,6 +97,7 @@ PODS:
   - libwebp/mux (1.2.1):
     - libwebp/demux
   - libwebp/webp (1.2.1)
+  - LMJHorizontalScrollText (2.0.2)
   - MGSwipeTableCell (1.6.11)
   - MJRefresh (3.7.2)
   - ObjectMapper (4.2.0)
@@ -96,6 +109,8 @@ PODS:
   - SnapKit (5.0.1)
   - SVProgressHUD (2.2.5)
   - Toast-Swift (5.0.1)
+  - TXLiteAVSDK_Player (9.3.10765)
+  - WechatOpenSDK-Swift (1.8.7.1)
 
 DEPENDENCIES:
   - BFCommonKit (from `../../../BFCommonKit/Trunk`)
@@ -107,7 +122,12 @@ DEPENDENCIES:
 
 SPEC REPOS:
   https://github.com/CocoaPods/Specs.git:
+    - AliyunOSSiOS
+    - Bugly
+    - LMJHorizontalScrollText
     - ObjectMapper
+    - TXLiteAVSDK_Player
+    - WechatOpenSDK-Swift
   trunk:
     - Alamofire
     - FDFullscreenPopGesture
@@ -139,17 +159,20 @@ EXTERNAL SOURCES:
 
 SPEC CHECKSUMS:
   Alamofire: f3b09a368f1582ab751b3fff5460276e0d2cf5c9
-  BFCommonKit: a730c6fb330ac0521a691c7da6d7a4fe764e2767
+  AliyunOSSiOS: b8f1dfc229cd9abf68c8ee0cb245c2d66e00dd96
+  BFCommonKit: e5b316ffd438574754886fbd93c904318bc7df7a
   BFMaterialKit: 0a15786e2a55587f1b2b4b74c0bff5321ebf3630
-  BFMediaKit: a65e482064bf459a08bf8290d79a48a40a6bc84d
+  BFMediaKit: 84a6f93a937e09a1b5b8ef56759a24d7c293b09e
   BFNetRequestKit: 1d074023eafe7c272fab4ed3a608e685902235d0
-  BFStuckPointKit: bc8a8dba6ddab6aeccd375d0e697aae27d224d20
+  BFStuckPointKit: 404c005065083cb6bd7aa962ffe362ad97ccc0d9
   BFUIKit: f209190fb92c8f9050554ac5950a2e4852e8a481
+  Bugly: 88bc32c0acc6fef7b74d610f0319ee7560d6b9fe
   FDFullscreenPopGesture: a8a620179e3d9c40e8e00256dcee1c1a27c6d0f0
   KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
   Kingfisher: 016c8b653a35add51dd34a3aba36b580041acc74
   KingfisherWebP: dec17a5eb1af2658791bde1f93ae9a853678f826
   libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
+  LMJHorizontalScrollText: ebc9b908db297f603c5b98c9b4e5f4582f5a14b8
   MGSwipeTableCell: b804e4e450dee439c42250be90bd50458bf67fce
   MJRefresh: 30997d30b347c8e9508a4db11e3a690da0c9b85a
   ObjectMapper: 1eb41f610210777375fa806bf161dc39fb832b81
@@ -158,6 +181,8 @@ SPEC CHECKSUMS:
   SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
   SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
   Toast-Swift: 9b6a70f28b3bf0b96c40d46c0c4b9d6639846711
+  TXLiteAVSDK_Player: 2b60edf893a8e82165a5e4b961a6cb347b10be4a
+  WechatOpenSDK-Swift: 18a8f7b12e745c30acc013f72a9f8a25aad6e216
 
 PODFILE CHECKSUM: 39940a1badb68d5479ef7469431e0a2326a62470