| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907 |
- import Foundation
- import UIKit
- import AVFoundation
- import VideoToolbox
- import CoreMedia
- import CoreVideo
- @objcMembers
- public final class LNAnimatedImageDecodeConfig: NSObject {
- public var threadCount: Int = 1
- public var bufferCount: Int = 5
- @objc(defaultConfig)
- public static func defaultConfig() -> LNAnimatedImageDecodeConfig {
- let config = LNAnimatedImageDecodeConfig()
- config.threadCount = 1
- config.bufferCount = 5
- return config
- }
- }
- @objcMembers
- public final class LNAnimatedImageDecodeThread: Thread {
- public var occupied: Bool = false
- public var sequenceDec: String {
- #if DEBUG
- return description
- #else
- return description
- #endif
- }
- }
- @objcMembers
- public final class LNAnimatedImageDecodeThreadPool: NSObject {
- public static let shared = LNAnimatedImageDecodeThreadPool()
- private let lock = NSLock()
- private var threads: [LNAnimatedImageDecodeThread] = []
- @objc(sharedPool)
- public static func sharedPool() -> LNAnimatedImageDecodeThreadPool {
- shared
- }
- @objc(getDecodeThread)
- public func getDecodeThread() -> LNAnimatedImageDecodeThread {
- lock.lock()
- defer { lock.unlock() }
- if let freeThread = threads.first(where: { !$0.occupied }) {
- return freeThread
- }
- let thread = LNAnimatedImageDecodeThread(target: self, selector: #selector(run), object: nil)
- thread.start()
- threads.append(thread)
- return thread
- }
- @objc private func run() {
- autoreleasepool {
- let runLoop = RunLoop.current
- runLoop.add(Port(), forMode: .default)
- while !Thread.current.isCancelled {
- runLoop.run(mode: .default, before: .distantFuture)
- }
- }
- }
- }
- @objcMembers
- public final class LNAnimatedImageBufferManager: NSObject {
- public var buffers: NSMutableArray
- private let config: LNAnimatedImageDecodeConfig
- public init(config: LNAnimatedImageDecodeConfig) {
- self.config = config
- self.buffers = NSMutableArray(capacity: max(config.bufferCount, 0))
- super.init()
- }
- @objc(getBufferedFrame:)
- public func getBufferedFrame(_ frameIndex: Int) -> LNBaseAnimatedImageFrame? {
- if buffers.count == 0 { return nil }
- let bufferIndex = frameIndex % buffers.count
- if bufferIndex > buffers.count - 1 { return nil }
- guard let frame = buffers.object(at: bufferIndex) as? LNBaseAnimatedImageFrame else { return nil }
- guard frame.frameIndex == frameIndex else { return nil }
- return frame
- }
- @objc(popVideoFrame)
- public func popVideoFrame() -> LNBaseAnimatedImageFrame? {
- guard buffers.count > 0 else { return nil }
- guard let frame = buffers.firstObject as? LNBaseAnimatedImageFrame else { return nil }
- buffers.removeObject(at: 0)
- return frame
- }
- @objc(isBufferFull)
- public func isBufferFull() -> Bool {
- if buffers.count < config.bufferCount { return false }
- for case let obj in buffers {
- if !(obj is LNBaseAnimatedImageFrame) {
- return false
- }
- }
- return true
- }
- }
- @objc public protocol LNAnimatedImageDecoderDelegate: AnyObject {
- @objc(decoderClassForManager:)
- func decoderClass(for manager: LNAnimatedImageDecodeManager) -> AnyClass
- @objc optional func shouldSetupAudioPlayer() -> Bool
- @objc optional func decoderDidFinishDecode(_ decoder: LNBaseDecoder)
- @objc optional func decoderDidFailDecode(_ decoder: LNBaseDecoder?, error: NSError)
- }
- public let kLNVAPDecoderSeekStart = "kLNVAPDecoderSeekStart"
- public let kLNVAPDecoderSeekFinish = "kLNVAPDecoderSeekFinish"
- public let kQGVAPDecoderSeekStart = kLNVAPDecoderSeekStart
- public let kQGVAPDecoderSeekFinish = kLNVAPDecoderSeekFinish
- @objcMembers
- open class LNBaseDecoder: NSObject {
- public dynamic var currentDecodeFrame: Int = -1
- public private(set) var fileInfo: LNBaseDFileInfo
- public var initializationError: NSError?
- public required init(fileInfo: LNBaseDFileInfo) {
- self.fileInfo = fileInfo
- super.init()
- self.fileInfo.occupiedCount += 1
- }
- @objc(initWith:error:)
- public convenience init(fileInfo: LNBaseDFileInfo, error: NSErrorPointer) {
- self.init(fileInfo: fileInfo)
- if let err = initializationError {
- error?.pointee = err
- }
- }
- open func decodeFrame(_ frameIndex: Int, buffers: NSMutableArray) {}
- open func shouldStopDecode(_ nextFrameIndex: Int) -> Bool { false }
- open func isFrameIndexBeyondEnd(_ frameIndex: Int) -> Bool { false }
- }
- @objc public enum LNMP4HWDErrorCode: Int {
- case fileNotExist = 10000
- case invalidMP4File = 10001
- case canNotGetStreamInfo = 10002
- case canNotGetStream = 10003
- case errorCreateVTBDesc = 10004
- case errorCreateVTBSession = 10005
- }
- @objcMembers
- public final class LNMP4FrameHWDecoder: LNBaseDecoder {
- public static let errorDomain = "LNMP4HWDErrorDomain"
- private var buffers: NSMutableArray?
- private var parser: LNMP4ParserProxy?
- private var decodeQueue = DispatchQueue(label: "com.ln.vap.decode")
- private var decodeSession: VTDecompressionSession?
- private var formatDescription: CMFormatDescription?
- private var isFinish = false
- private var finishFrameIndex: Int = -1
- private var invalidRetryCount = 0
- private var spsData: Data?
- private var ppsData: Data?
- private var vpsData: Data?
- private var lastDecodeFrame: Int = -1
- @objc(errorDescriptionForCode:)
- public static func errorDescription(for code: LNMP4HWDErrorCode) -> String {
- switch code {
- case .fileNotExist: return "文件不存在"
- case .invalidMP4File: return "非法文件格式"
- case .canNotGetStreamInfo: return "无法获取视频流信息"
- case .canNotGetStream: return "无法获取视频流"
- case .errorCreateVTBDesc: return "VTB创建desc失败"
- case .errorCreateVTBSession: return "VTB创建session失败"
- }
- }
- public required init(fileInfo: LNBaseDFileInfo) {
- super.init(fileInfo: fileInfo)
- parser = (fileInfo as? LNMP4HWDFileInfo)?.mp4Parser
- if !onInputStart() {
- isFinish = true
- }
- }
- public override func decodeFrame(_ frameIndex: Int, buffers: NSMutableArray) {
- if frameIndex == currentDecodeFrame { return }
- currentDecodeFrame = frameIndex
- self.buffers = buffers
- decodeQueue.async { [weak self] in
- guard let self else { return }
- if frameIndex != self.lastDecodeFrame + 1 { return }
- self.decodeFrameInner(frameIndex, drop: false)
- }
- }
- public override func shouldStopDecode(_ nextFrameIndex: Int) -> Bool {
- isFinish
- }
- public override func isFrameIndexBeyondEnd(_ frameIndex: Int) -> Bool {
- if finishFrameIndex > 0 {
- return frameIndex >= finishFrameIndex
- }
- return false
- }
- deinit {
- onInputEnd()
- fileInfo.occupiedCount -= 1
- }
- private func onInputStart() -> Bool {
- guard !fileInfo.filePath.isEmpty else {
- initializationError = NSError(domain: Self.errorDomain, code: LNMP4HWDErrorCode.fileNotExist.rawValue, userInfo: ["location": fileInfo.filePath])
- return false
- }
- guard FileManager.default.fileExists(atPath: fileInfo.filePath) else {
- initializationError = NSError(domain: Self.errorDomain, code: LNMP4HWDErrorCode.fileNotExist.rawValue, userInfo: ["location": fileInfo.filePath])
- return false
- }
- isFinish = false
- spsData = nil
- ppsData = nil
- vpsData = nil
- return initPPSAndSPS()
- }
- private func initPPSAndSPS() -> Bool {
- guard let parser else { return false }
- spsData = parser.spsData
- ppsData = parser.ppsData
- vpsData = parser.vpsData
- guard let spsData, let ppsData else {
- initializationError = NSError(domain: Self.errorDomain, code: LNMP4HWDErrorCode.canNotGetStreamInfo.rawValue, userInfo: ["location": fileInfo.filePath])
- return false
- }
- if parser.videoCodecID == .h264 {
- guard spsData.count > 0, ppsData.count > 0 else { return false }
- let spsPtr = spsData.withUnsafeBytes { $0.baseAddress!.assumingMemoryBound(to: UInt8.self) }
- let ppsPtr = ppsData.withUnsafeBytes { $0.baseAddress!.assumingMemoryBound(to: UInt8.self) }
- var parameterSetPointers: [UnsafePointer<UInt8>] = [spsPtr, ppsPtr]
- var parameterSetSizes: [Int] = [spsData.count, ppsData.count]
- let status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
- allocator: kCFAllocatorDefault,
- parameterSetCount: 2,
- parameterSetPointers: ¶meterSetPointers,
- parameterSetSizes: ¶meterSetSizes,
- nalUnitHeaderLength: 4,
- formatDescriptionOut: &formatDescription
- )
- if status != noErr {
- initializationError = NSError(domain: Self.errorDomain, code: LNMP4HWDErrorCode.errorCreateVTBDesc.rawValue, userInfo: ["location": fileInfo.filePath])
- return false
- }
- } else if parser.videoCodecID == .h265 {
- guard #available(iOS 11.0, *), let vpsData else {
- initializationError = NSError(domain: Self.errorDomain, code: LNMP4HWDErrorCode.canNotGetStreamInfo.rawValue, userInfo: ["location": fileInfo.filePath])
- return false
- }
- guard vpsData.count > 0, spsData.count > 0, ppsData.count > 0 else { return false }
- let vpsPtr = vpsData.withUnsafeBytes { $0.baseAddress!.assumingMemoryBound(to: UInt8.self) }
- let spsPtr = spsData.withUnsafeBytes { $0.baseAddress!.assumingMemoryBound(to: UInt8.self) }
- let ppsPtr = ppsData.withUnsafeBytes { $0.baseAddress!.assumingMemoryBound(to: UInt8.self) }
- var parameterSetPointers: [UnsafePointer<UInt8>] = [vpsPtr, spsPtr, ppsPtr]
- var parameterSetSizes: [Int] = [vpsData.count, spsData.count, ppsData.count]
- let status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(
- allocator: kCFAllocatorDefault,
- parameterSetCount: 3,
- parameterSetPointers: ¶meterSetPointers,
- parameterSetSizes: ¶meterSetSizes,
- nalUnitHeaderLength: 4,
- extensions: nil,
- formatDescriptionOut: &formatDescription
- )
- if status != noErr {
- initializationError = NSError(domain: Self.errorDomain, code: LNMP4HWDErrorCode.errorCreateVTBDesc.rawValue, userInfo: ["location": fileInfo.filePath])
- return false
- }
- }
- return createDecompressionSession()
- }
- private func createDecompressionSession() -> Bool {
- guard let formatDescription else { return false }
- let pixelFormat: UInt32 = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
- let attrs = [
- kCVPixelBufferPixelFormatTypeKey as String: NSNumber(value: pixelFormat)
- ] as NSDictionary
- let status = VTDecompressionSessionCreate(
- allocator: kCFAllocatorDefault,
- formatDescription: formatDescription,
- decoderSpecification: nil,
- imageBufferAttributes: attrs,
- outputCallback: nil,
- decompressionSessionOut: &decodeSession
- )
- if status != noErr {
- initializationError = NSError(domain: Self.errorDomain, code: LNMP4HWDErrorCode.errorCreateVTBSession.rawValue, userInfo: ["location": fileInfo.filePath])
- return false
- }
- return true
- }
- private func decodeFrameInner(_ frameIndex: Int, drop: Bool) {
- guard !isFinish else { return }
- guard let buffers else { return }
- guard let parser else { return }
- guard let _ = spsData, let _ = ppsData else { return }
- guard let packetData = parser.readPacket(ofSample: frameIndex), !packetData.isEmpty else {
- finishFrameIndex = frameIndex
- onInputEnd()
- return
- }
- let currentPts: UInt64 = {
- guard frameIndex >= 0, frameIndex < parser.videoSamples.count else { return 0 }
- return parser.videoSamples[frameIndex].pts
- }()
- let startDate = Date()
- var blockBuffer: CMBlockBuffer?
- var status = CMBlockBufferCreateWithMemoryBlock(
- allocator: kCFAllocatorDefault,
- memoryBlock: nil,
- blockLength: packetData.count,
- blockAllocator: kCFAllocatorDefault,
- customBlockSource: nil,
- offsetToData: 0,
- dataLength: packetData.count,
- flags: 0,
- blockBufferOut: &blockBuffer
- )
- guard status == kCMBlockBufferNoErr, let blockBuffer else { return }
- packetData.withUnsafeBytes { rawPtr in
- if let base = rawPtr.baseAddress {
- CMBlockBufferReplaceDataBytes(with: base, blockBuffer: blockBuffer, offsetIntoDestination: 0, dataLength: packetData.count)
- }
- }
- var sampleBuffer: CMSampleBuffer?
- var sampleSizeArray = [packetData.count]
- status = CMSampleBufferCreateReady(
- allocator: kCFAllocatorDefault,
- dataBuffer: blockBuffer,
- formatDescription: formatDescription,
- sampleCount: 1,
- sampleTimingEntryCount: 0,
- sampleTimingArray: nil,
- sampleSizeEntryCount: 1,
- sampleSizeArray: &sampleSizeArray,
- sampleBufferOut: &sampleBuffer
- )
- guard status == noErr, let sampleBuffer, let decodeSession else { return }
- var flagOut: VTDecodeInfoFlags = []
- let decodeStatus = VTDecompressionSessionDecodeFrame(
- decodeSession,
- sampleBuffer: sampleBuffer,
- flags: [],
- infoFlagsOut: &flagOut
- ) { [weak self] status, _, imageBuffer, _, _ in
- guard let self else { return }
- self.handleDecodePixelBuffer(
- imageBuffer,
- frameIndex: frameIndex,
- currentPts: currentPts,
- startDate: startDate,
- status: status,
- needDrop: drop,
- buffers: buffers,
- fps: parser.fps
- )
- }
- if decodeStatus == kVTInvalidSessionErr {
- invalidRetryCount += 1
- if invalidRetryCount >= 3 { return }
- resetDecoder()
- findKeyFrameAndDecodeToCurrent(frameIndex)
- } else {
- invalidRetryCount = 0
- }
- }
- private func handleDecodePixelBuffer(
- _ pixelBuffer: CVImageBuffer?,
- frameIndex: Int,
- currentPts: UInt64,
- startDate: Date,
- status: OSStatus,
- needDrop: Bool,
- buffers: NSMutableArray,
- fps: Int
- ) {
- lastDecodeFrame = frameIndex
- if status != noErr { return }
- if needDrop { return }
- guard let pixelBuffer = pixelBuffer else { return }
- let newFrame = LNMP4AnimatedImageFrame()
- newFrame.pixelBuffer = pixelBuffer
- newFrame.frameIndex = frameIndex
- newFrame.decodeTime = Date().timeIntervalSince(startDate) * 1000.0
- newFrame.defaultFps = Int32(fps)
- newFrame.pts = currentPts
- buffers.add(newFrame)
- let sorted = (buffers as? [LNMP4AnimatedImageFrame] ?? []).sorted { $0.pts < $1.pts }
- buffers.removeAllObjects()
- sorted.forEach { buffers.add($0) }
- }
- private func resetDecoder() {
- if let decodeSession {
- VTDecompressionSessionWaitForAsynchronousFrames(decodeSession)
- VTDecompressionSessionInvalidate(decodeSession)
- self.decodeSession = nil
- }
- _ = createDecompressionSession()
- }
- private func findKeyFrameAndDecodeToCurrent(_ frameIndex: Int) {
- NotificationCenter.default.post(name: NSNotification.Name(kLNVAPDecoderSeekStart), object: self)
- guard let parser else { return }
- let keyframeIndexes = parser.videoSyncSampleIndexes
- var index = keyframeIndexes.first?.intValue ?? 0
- for num in keyframeIndexes {
- if num.intValue < frameIndex {
- index = num.intValue
- continue
- }
- break
- }
- while index < frameIndex {
- decodeFrameInner(index, drop: true)
- index += 1
- }
- decodeFrameInner(frameIndex, drop: false)
- NotificationCenter.default.post(name: NSNotification.Name(kLNVAPDecoderSeekFinish), object: self)
- }
- private func onInputEnd() {
- if isFinish { return }
- isFinish = true
- if let decodeSession {
- VTDecompressionSessionWaitForAsynchronousFrames(decodeSession)
- VTDecompressionSessionInvalidate(decodeSession)
- self.decodeSession = nil
- }
- formatDescription = nil
- spsData = nil
- ppsData = nil
- vpsData = nil
- }
- }
- @objcMembers
- public final class LNAnimatedImageDecodeManager: NSObject {
- public weak var decoderDelegate: LNAnimatedImageDecoderDelegate?
- private let config: LNAnimatedImageDecodeConfig
- private let fileInfo: LNBaseDFileInfo
- private var decoders: [LNBaseDecoder] = []
- private var bufferManager: LNAnimatedImageBufferManager
- private var audioPlayer: AVAudioPlayer?
- public init(fileInfo: LNBaseDFileInfo, config: LNAnimatedImageDecodeConfig, delegate: LNAnimatedImageDecoderDelegate?) {
- self.fileInfo = fileInfo
- self.config = config
- self.decoderDelegate = delegate
- self.bufferManager = LNAnimatedImageBufferManager(config: config)
- super.init()
- createDecoders(by: config)
- initializeBuffers(fromIndex: 0)
- setupAudioPlayerIfNeed()
- }
- @objc(initWith:config:delegate:)
- public convenience init(_ fileInfo: LNBaseDFileInfo, config: LNAnimatedImageDecodeConfig, delegate: LNAnimatedImageDecoderDelegate?) {
- self.init(fileInfo: fileInfo, config: config, delegate: delegate)
- }
- @objc(consumeDecodedFrame:)
- public func consumeDecodedFrame(_ frameIndex: Int) -> LNBaseAnimatedImageFrame? {
- objc_sync_enter(self)
- defer { objc_sync_exit(self) }
- if frameIndex == 0 && bufferManager.buffers.count < config.bufferCount {
- return nil
- }
- let decodeFinished = checkIfDecodeFinish(frameIndex)
- let frame = bufferManager.popVideoFrame()
- if let frame {
- frame.frameIndex = frameIndex
- decodeFrame(frameIndex + config.bufferCount)
- return frame
- }
- if !decodeFinished {
- guard !decoders.isEmpty else { return nil }
- let decoderIndex = decoders.count == 1 ? 0 : frameIndex % decoders.count
- let decoder = decoders[decoderIndex]
- if decoder.shouldStopDecode(frameIndex) {
- decoderDelegate?.decoderDidFinishDecode?(decoder)
- return nil
- }
- initializeBuffers(fromIndex: frameIndex)
- }
- return nil
- }
- @objc(tryToStartAudioPlay)
- public func tryToStartAudioPlay() {
- audioPlayer?.play()
- }
- @objc(tryToStopAudioPlay)
- public func tryToStopAudioPlay() {
- audioPlayer?.stop()
- }
- @objc(tryToPauseAudioPlay)
- public func tryToPauseAudioPlay() {
- audioPlayer?.pause()
- }
- @objc(tryToResumeAudioPlay)
- public func tryToResumeAudioPlay() {
- audioPlayer?.play()
- }
- @objc(containsThisDeocder:)
- public func containsThisDeocder(_ decoder: Any) -> Bool {
- decoders.contains { $0 === (decoder as AnyObject) }
- }
- private func checkIfDecodeFinish(_ frameIndex: Int) -> Bool {
- guard !decoders.isEmpty else { return true }
- let decoderIndex = decoders.count == 1 ? 0 : frameIndex % decoders.count
- let decoder = decoders[decoderIndex]
- if decoder.isFrameIndexBeyondEnd(frameIndex) {
- decoderDelegate?.decoderDidFinishDecode?(decoder)
- return true
- }
- return false
- }
- private func decodeFrame(_ frameIndex: Int) {
- guard !decoders.isEmpty else { return }
- let decoderIndex = decoders.count == 1 ? 0 : frameIndex % decoders.count
- let decoder = decoders[decoderIndex]
- if decoder.shouldStopDecode(frameIndex) { return }
- decoder.decodeFrame(frameIndex, buffers: bufferManager.buffers)
- }
- private func createDecoders(by config: LNAnimatedImageDecodeConfig) {
- guard let decoderDelegate else { return }
- for _ in 0..<max(config.threadCount, 1) {
- guard let cls = decoderDelegate.decoderClass(for: self) as? LNBaseDecoder.Type else { continue }
- let decoder = cls.init(fileInfo: fileInfo)
- if let error = decoder.initializationError {
- decoderDelegate.decoderDidFailDecode?(nil, error: error)
- break
- }
- decoders.append(decoder)
- }
- }
- private func initializeBuffers(fromIndex start: Int) {
- for i in 0..<max(config.bufferCount, 0) {
- decodeFrame(start + i)
- }
- }
- private func setupAudioPlayerIfNeed() {
- if decoderDelegate?.shouldSetupAudioPlayer?() == false {
- return
- }
- guard let fileInfo = fileInfo as? LNMP4HWDFileInfo,
- let mp4Parser = fileInfo.mp4Parser,
- mp4Parser.audioTrackBox != nil else {
- audioPlayer = nil
- return
- }
- let url = URL(fileURLWithPath: self.fileInfo.filePath)
- audioPlayer = try? AVAudioPlayer(contentsOf: url)
- }
- }
- @objc public protocol LNVAPConfigDelegate: AnyObject {
- @objc(onVAPConfigResourcesLoaded:error:)
- func onVAPConfigResourcesLoaded(_ config: LNVAPConfigModel, error: NSError?)
- @objc optional func vap_contentForTag(_ tag: String, resource: LNVAPSourceInfo) -> String?
- @objc optional func vap_loadImageWithURL(_ urlStr: String, context: [AnyHashable: Any], completion: @escaping (UIImage?, NSError?, String) -> Void)
- }
- @objcMembers
- public final class LNVAPConfigManager: NSObject {
- public weak var delegate: LNVAPConfigDelegate?
- public var hasValidConfig: Bool = false
- public var model: LNVAPConfigModel = .init()
- private let fileInfo: LNMP4HWDFileInfo
- public init(fileInfo: LNMP4HWDFileInfo) {
- self.fileInfo = fileInfo
- super.init()
- setupConfig()
- }
- @objc(initWith:)
- public convenience init(_ fileInfo: LNMP4HWDFileInfo) {
- self.init(fileInfo: fileInfo)
- }
- @objc(loadConfigResources)
- public func loadConfigResources() {
- if model.resources.isEmpty {
- delegate?.onVAPConfigResourcesLoaded(model, error: nil)
- return
- }
- if let contentProvider = delegate?.vap_contentForTag {
- model.resources.forEach { resource in
- resource.contentTagValue = contentProvider(resource.contentTag ?? "", resource)
- }
- }
- guard let loadImage = delegate?.vap_loadImageWithURL else {
- // Keep parity with OC: if no image loader delegate, return directly.
- return
- }
- let group = DispatchGroup()
- var loadError: NSError?
- for resource in model.resources {
- guard resource.type == LNVAPAttachmentConstants.sourceTypeImg,
- resource.loadType == LNVAPAttachmentConstants.sourceLoadTypeNet,
- let url = resource.contentTagValue else {
- if resource.type == LNVAPAttachmentConstants.sourceTypeText,
- resource.loadType == LNVAPAttachmentConstants.sourceLoadTypeLocal {
- resource.sourceImage = LNVAPTextureLoader.drawingImage(
- forText: resource.contentTagValue,
- color: resource.color,
- size: resource.size,
- bold: resource.style == LNVAPAttachmentConstants.sourceStyleBoldText
- )
- }
- continue
- }
- group.enter()
- loadImage(url, ["resource": resource]) { image, error, imageURL in
- if image == nil || error != nil {
- loadError = loadError ?? error ?? NSError(domain: "loadImageError:\(imageURL)", code: -1)
- }
- resource.sourceImage = image
- group.leave()
- }
- }
- group.notify(queue: .main) { [weak self] in
- guard let self else { return }
- self.delegate?.onVAPConfigResourcesLoaded(self.model, error: loadError)
- }
- }
- @objc(loadMTLTextures:)
- public func loadMTLTextures(_ device: MTLDevice) {
- model.resources.forEach { resource in
- resource.texture = LNVAPTextureLoader.loadTexture(with: resource.sourceImage, device: device)
- resource.sourceImage = nil
- }
- }
- @objc(loadMTLBuffers:)
- public func loadMTLBuffers(_ device: MTLDevice) {
- model.resources.forEach { resource in
- resource.colorParamsBuffer = LNVAPTextureLoader.loadVapColorFillBuffer(with: resource.color, device: device)
- }
- }
- private func setupConfig() {
- guard let vapc = fileInfo.mp4Parser?.rootBox?.subBox(ofType: .vapc) else {
- hasValidConfig = false
- LNVAPLogger.log(level: 3, file: #file, line: #line, func: #function, module: "LNVAPConfigManager", message: "config can not find vapc box")
- return
- }
- hasValidConfig = true
- guard let vapcData = fileInfo.mp4Parser?.readData(of: vapc, length: Int(vapc.length - 8), offset: 8),
- let dict = (try? JSONSerialization.jsonObject(with: vapcData)) as? [String: Any] else {
- LNVAPLogger.log(level: 3, file: #file, line: #line, func: #function, module: "LNVAPConfigManager", message: "fail to parse config as dictionary")
- return
- }
- parseConfigDictionary(dict)
- }
- private func parseConfigDictionary(_ configDic: [String: Any]) {
- guard let commonInfoDic = lnDictionary(configDic["info"]) else {
- LNVAPLogger.log(level: 3, file: #file, line: #line, func: #function, module: "LNVAPConfigManager", message: "has no commonInfoDic")
- return
- }
- let sourcesArr = lnArray(configDic["src"])
- let framesArr = lnArray(configDic["frame"])
- let configModel = LNVAPConfigModel()
- self.model = configModel
- let commonInfo = LNVAPCommonInfo()
- commonInfo.version = lnInt(commonInfoDic["v"])
- commonInfo.framesCount = lnInt(commonInfoDic["f"])
- commonInfo.size = CGSize(width: lnFloat(commonInfoDic["w"]), height: lnFloat(commonInfoDic["h"]))
- commonInfo.videoSize = CGSize(width: lnFloat(commonInfoDic["videoW"]), height: lnFloat(commonInfoDic["videoH"]))
- commonInfo.targetOrientaion = LNVAPOrientation(rawValue: lnInt(commonInfoDic["orien"])) ?? .none
- commonInfo.fps = lnInt(commonInfoDic["fps"])
- commonInfo.isMerged = lnInt(commonInfoDic["isVapx"]) == 1
- commonInfo.alphaAreaRect = lnRect(commonInfoDic["aFrame"])
- commonInfo.rgbAreaRect = lnRect(commonInfoDic["rgbFrame"])
- configModel.info = commonInfo
- fileInfo.mp4Parser?.fps = commonInfo.fps
- var sources: [String: LNVAPSourceInfo] = [:]
- for sourceObj in sourcesArr {
- guard let sourceDic = lnDictionary(sourceObj) else {
- LNVAPLogger.log(level: 3, file: #file, line: #line, func: #function, module: "LNVAPConfigManager", message: "sourceDic is not dic")
- continue
- }
- let sourceID = lnString(sourceDic["srcId"])
- guard !sourceID.isEmpty else {
- LNVAPLogger.log(level: 3, file: #file, line: #line, func: #function, module: "LNVAPConfigManager", message: "has no sourceID")
- continue
- }
- let sourceInfo = LNVAPSourceInfo()
- sourceInfo.type = lnString(sourceDic["srcType"])
- sourceInfo.loadType = lnString(sourceDic["loadType"])
- sourceInfo.contentTag = lnString(sourceDic["srcTag"])
- sourceInfo.style = lnString(sourceDic["style"])
- sourceInfo.fitType = lnString(sourceDic["fitType"])
- sourceInfo.size = CGSize(width: lnFloat(sourceDic["w"]), height: lnFloat(sourceDic["h"]))
- let colorHex = lnString(sourceDic["color"])
- if !colorHex.isEmpty {
- sourceInfo.color = UIColor.lnColor(hex: colorHex)
- }
- sources[sourceID] = sourceInfo
- }
- configModel.resources = Array(sources.values)
- var mergedConfig: [NSNumber: [LNVAPMergedInfo]] = [:]
- for frameObj in framesArr {
- guard let frameMergedDic = lnDictionary(frameObj) else {
- LNVAPLogger.log(level: 3, file: #file, line: #line, func: #function, module: "LNVAPConfigManager", message: "frameMergedDic is not dic")
- continue
- }
- let frameIndex = lnInt(frameMergedDic["i"])
- let mergedObjs = lnArray(frameMergedDic["obj"])
- var mergedInfos: [LNVAPMergedInfo] = []
- for mergeObj in mergedObjs {
- guard let mergeInfoDic = lnDictionary(mergeObj) else {
- LNVAPLogger.log(level: 3, file: #file, line: #line, func: #function, module: "LNVAPConfigManager", message: "mergeInfoDic is not dic")
- continue
- }
- let sourceID = lnString(mergeInfoDic["srcId"])
- guard let sourceInfo = sources[sourceID] else {
- LNVAPLogger.log(level: 3, file: #file, line: #line, func: #function, module: "LNVAPConfigManager", message: "sourceInfo is nil")
- continue
- }
- let mergeInfo = LNVAPMergedInfo()
- mergeInfo.source = sourceInfo
- mergeInfo.renderIndex = lnInt(mergeInfoDic["z"])
- mergeInfo.needMask = mergeInfoDic["mFrame"] != nil
- mergeInfo.renderRect = lnRect(mergeInfoDic["frame"])
- mergeInfo.maskRect = lnRect(mergeInfoDic["mFrame"])
- mergeInfo.maskRotation = lnInt(mergeInfoDic["mt"])
- mergedInfos.append(mergeInfo)
- }
- mergedConfig[NSNumber(value: frameIndex)] = mergedInfos.sorted(by: { $0.renderIndex < $1.renderIndex })
- }
- configModel.mergedConfig = mergedConfig
- }
- }
- private extension LNVAPConfigManager {
- func lnInt(_ value: Any?) -> Int {
- if let n = value as? NSNumber { return n.intValue }
- if let s = value as? NSString { return s.integerValue }
- if let s = value as? String { return Int(s) ?? 0 }
- return 0
- }
- func lnFloat(_ value: Any?) -> CGFloat {
- if let n = value as? NSNumber { return CGFloat(n.doubleValue) }
- if let s = value as? NSString { return CGFloat(s.doubleValue) }
- if let s = value as? String { return CGFloat(Double(s) ?? 0) }
- return 0
- }
- func lnString(_ value: Any?) -> String {
- if let s = value as? String { return s }
- if let s = value as? NSString { return s as String }
- if let n = value as? NSNumber { return n.stringValue }
- return ""
- }
- func lnArray(_ value: Any?) -> [Any] {
- if let arr = value as? [Any] { return arr }
- if let arr = value as? NSArray { return arr.compactMap { $0 } }
- return []
- }
- func lnDictionary(_ value: Any?) -> [String: Any]? {
- if let dict = value as? [String: Any] { return dict }
- if let dict = value as? NSDictionary {
- var out: [String: Any] = [:]
- for (k, v) in dict {
- if let key = k as? String {
- out[key] = v
- }
- }
- return out
- }
- return nil
- }
- func lnRect(_ value: Any?) -> CGRect {
- if let arr = value as? NSArray {
- return arr.ln_rectValue()
- }
- if let arr = value as? [Any] {
- return (arr as NSArray).ln_rectValue()
- }
- return .zero
- }
- }
- private extension UIColor {
- static func lnColor(hex: String) -> UIColor? {
- let cleaned = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
- var int: UInt64 = 0
- guard Scanner(string: cleaned).scanHexInt64(&int) else { return nil }
- let a, r, g, b: UInt64
- switch cleaned.count {
- case 3:
- (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
- case 6:
- (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
- case 8:
- (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
- default:
- return nil
- }
- return UIColor(
- red: CGFloat(r) / 255,
- green: CGFloat(g) / 255,
- blue: CGFloat(b) / 255,
- alpha: CGFloat(a) / 255
- )
- }
- }
|