| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- import UIKit
- import CoreVideo
- @objcMembers
- public final class LNVAPPlayerView: UIView {
- private let playbackView = UIView()
- private let delegateBridge = LNPlayerDelegateBridge()
- private var didStart = false
- public weak var delegate: LNVAPPlaybackDelegate?
- public var enterBackgroundOperation: LNEnterBackgroundOperation = .stop {
- didSet { LNPlaybackRuntime.set(playbackView, key: "hwd_enterBackgroundOP", value: enterBackgroundOperation.legacyRawValue) }
- }
- public var renderByOpenGL: Bool {
- get { LNPlaybackRuntime.boolValue(playbackView, key: "hwd_renderByOpenGL") }
- set { LNPlaybackRuntime.set(playbackView, key: "hwd_renderByOpenGL", value: newValue) }
- }
- public var fps: Int {
- get { LNPlaybackRuntime.intValue(playbackView, key: "hwd_fps") }
- set { LNPlaybackRuntime.set(playbackView, key: "hwd_fps", value: newValue) }
- }
- public override init(frame: CGRect) {
- super.init(frame: frame)
- delegateBridge.owner = self
- setupPlaybackView()
- }
- public required init?(coder: NSCoder) {
- super.init(coder: coder)
- delegateBridge.owner = self
- setupPlaybackView()
- }
- public override func layoutSubviews() {
- super.layoutSubviews()
- playbackView.frame = bounds
- }
- @objc(lnPlayWithFilePath:repeatCount:)
- public func lnPlay(filePath: String, repeatCount: Int) {
- didStart = false
- let played = LNPlaybackRuntime.playHWDMP4(playbackView, filePath: filePath, repeatCount: repeatCount, delegate: delegateBridge)
- if !played {
- let error = NSError(domain: "LNVAPPlayerView", code: -1, userInfo: [NSLocalizedDescriptionKey: "Missing runtime selector playHWDMP4:repeatCount:delegate:"])
- delegate?.lnPlayerDidFail?(self, error: error)
- }
- }
- @objc(lnPlayWithFilePath:)
- public func lnPlay(filePath: String) {
- lnPlay(filePath: filePath, repeatCount: 0)
- }
- @objc(lnStop)
- public func lnStop() {
- LNPlaybackRuntime.callNoArg(playbackView, selectorName: "stopHWDMP4")
- notifyStopIfNeeded()
- }
- @objc(lnPause)
- public func lnPause() {
- LNPlaybackRuntime.callNoArg(playbackView, selectorName: "pauseHWDMP4")
- }
- @objc(lnResume)
- public func lnResume() {
- LNPlaybackRuntime.callNoArg(playbackView, selectorName: "resumeHWDMP4")
- }
- @objc(lnSetMute:)
- public func lnSetMute(_ mute: Bool) {
- LNPlaybackRuntime.callBool(playbackView, selectorName: "setMute:", value: mute)
- }
- @objc(lnEnableOldVersion:)
- public func lnEnableOldVersion(_ enable: Bool) {
- LNPlaybackRuntime.callBool(playbackView, selectorName: "enableOldVersion:", value: enable)
- }
- @objc(lnAddTapGestureWithTarget:action:)
- public func lnAddTapGesture(target: Any, action: Selector) {
- let tap = UITapGestureRecognizer(target: target, action: action)
- playbackView.addGestureRecognizer(tap)
- playbackView.isUserInteractionEnabled = true
- }
- @objc(lnAddVapTapGesture:)
- public func lnAddVapTapGesture(_ handler: @escaping LNVAPGestureEventBlock) {
- let added = LNPlaybackRuntime.addVapTapGesture(playbackView) { gesture, insideSource, source in
- handler(gesture, insideSource, source)
- }
- if !added {
- let tap = UITapGestureRecognizer()
- tap.ln_addActionBlock { sender in
- guard let gesture = sender as? UIGestureRecognizer else { return }
- handler(gesture, false, nil)
- }
- playbackView.addGestureRecognizer(tap)
- playbackView.isUserInteractionEnabled = true
- }
- }
- @objc(lnAddVapGesture:callback:)
- public func lnAddVapGesture(_ gestureRecognizer: UIGestureRecognizer, callback: @escaping LNVAPGestureEventBlock) {
- let added = LNPlaybackRuntime.addVapGesture(playbackView, gestureRecognizer: gestureRecognizer) { gesture, insideSource, source in
- callback(gesture, insideSource, source)
- }
- if !added {
- gestureRecognizer.ln_addActionBlock { sender in
- guard let gesture = sender as? UIGestureRecognizer else { return }
- callback(gesture, false, nil)
- }
- playbackView.addGestureRecognizer(gestureRecognizer)
- playbackView.isUserInteractionEnabled = true
- }
- }
- fileprivate func notifyShouldStart(_ config: LNVAPConfigModel) -> Bool {
- delegate?.lnPlayerShouldStart?(self, config: config) ?? true
- }
- fileprivate func notifyStart() {
- didStart = true
- delegate?.lnPlayerDidStart?(self)
- }
- fileprivate func notifyPlay(_ frame: LNMP4AnimatedImageFrame) {
- delegate?.lnPlayerDidPlay?(self, frame: frame)
- }
- fileprivate func notifyFinish(_ totalFrameCount: Int) {
- delegate?.lnPlayerDidFinish?(self, totalFrameCount: totalFrameCount)
- }
- fileprivate func notifyFail(_ error: NSError) {
- delegate?.lnPlayerDidFail?(self, error: error)
- }
- fileprivate func contentForTag(_ tag: String, resource: LNVAPSourceInfo) -> String? {
- delegate?.lnPlayerContent?(forTag: tag, resource: resource)
- }
- fileprivate func loadImage(withURL url: String, context: NSDictionary, completion: @escaping LNVAPImageCompletion) {
- if let handler = delegate?.lnPlayerLoadImage {
- handler(url, context, completion)
- return
- }
- completion(nil, nil, url)
- }
- fileprivate func notifyStopIfNeeded() {
- if didStart {
- delegate?.lnPlayerDidStop?(self)
- }
- didStart = false
- }
- private func setupPlaybackView() {
- playbackView.frame = bounds
- playbackView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
- addSubview(playbackView)
- LNPlaybackRuntime.set(playbackView, key: "hwd_enterBackgroundOP", value: enterBackgroundOperation.legacyRawValue)
- }
- }
- private final class LNPlayerDelegateBridge: NSObject {
- weak var owner: LNVAPPlayerView?
- @objc(shouldStartPlayMP4:config:)
- func shouldStartPlayMP4(_ container: UIView, config: NSObject) -> Bool {
- guard let owner else { return true }
- return owner.notifyShouldStart(LNPlaybackLegacyMapper.toConfig(config))
- }
- @objc(viewDidStartPlayMP4:)
- func viewDidStartPlayMP4(_ container: UIView) {
- owner?.notifyStart()
- }
- @objc(viewDidPlayMP4AtFrame:view:)
- func viewDidPlayMP4AtFrame(_ frame: NSObject, view container: UIView) {
- owner?.notifyPlay(LNPlaybackLegacyMapper.toFrame(frame))
- }
- @objc(viewDidStopPlayMP4:view:)
- func viewDidStopPlayMP4(_ lastFrameIndex: Int, view container: UIView) {
- owner?.notifyStopIfNeeded()
- }
- @objc(viewDidFinishPlayMP4:view:)
- func viewDidFinishPlayMP4(_ totalFrameCount: Int, view container: UIView) {
- owner?.notifyFinish(totalFrameCount)
- }
- @objc(viewDidFailPlayMP4:)
- func viewDidFailPlayMP4(_ error: NSError) {
- owner?.notifyFail(error)
- }
- @objc(contentForVapTag:resource:)
- func contentForVapTag(_ tag: String, resource: NSObject) -> String? {
- guard let owner else { return nil }
- return owner.contentForTag(tag, resource: LNPlaybackLegacyMapper.toSource(resource))
- }
- @objc(loadVapImageWithURL:context:completion:)
- func loadVapImageWithURL(_ url: String, context: NSDictionary, completion: @escaping (UIImage?, NSError?, String) -> Void) {
- owner?.loadImage(withURL: url, context: context, completion: completion)
- }
- }
- private enum LNPlaybackLegacyMapper {
- static func toConfig(_ object: NSObject) -> LNVAPConfigModel {
- if let config = object as? LNVAPConfigModel {
- return config
- }
- let config = LNVAPConfigModel()
- if let infoObj = object.value(forKey: "info") as? NSObject {
- config.info = toCommonInfo(infoObj)
- }
- if let resources = object.value(forKey: "resources") as? [NSObject] {
- config.resources = resources.map { toSource($0) }
- }
- return config
- }
- static func toCommonInfo(_ object: NSObject) -> LNVAPCommonInfo {
- let info = LNVAPCommonInfo()
- info.version = intValue(object, key: "version")
- info.framesCount = intValue(object, key: "framesCount")
- info.size = cgSizeValue(object, key: "size")
- info.videoSize = cgSizeValue(object, key: "videoSize")
- info.targetOrientaion = LNVAPOrientation(rawValue: intValue(object, key: "targetOrientaion")) ?? .none
- info.fps = intValue(object, key: "fps")
- info.isMerged = boolValue(object, key: "isMerged")
- info.alphaAreaRect = cgRectValue(object, key: "alphaAreaRect")
- info.rgbAreaRect = cgRectValue(object, key: "rgbAreaRect")
- return info
- }
- static func toSource(_ object: NSObject) -> LNVAPSourceInfo {
- if let source = object as? LNVAPSourceInfo {
- return source
- }
- let source = LNVAPSourceInfo()
- source.type = stringValue(object, key: "type")
- source.loadType = stringValue(object, key: "loadType")
- source.contentTag = stringValue(object, key: "contentTag")
- source.contentTagValue = stringValue(object, key: "contentTagValue")
- source.color = object.value(forKey: "color") as? UIColor
- source.style = stringValue(object, key: "style")
- source.size = cgSizeValue(object, key: "size")
- source.fitType = stringValue(object, key: "fitType")
- source.sourceImage = object.value(forKey: "sourceImage") as? UIImage
- return source
- }
- static func toFrame(_ object: NSObject) -> LNMP4AnimatedImageFrame {
- if let frame = object as? LNMP4AnimatedImageFrame {
- return frame
- }
- let frame = LNMP4AnimatedImageFrame()
- frame.frameIndex = intValue(object, key: "frameIndex")
- frame.duration = timeIntervalValue(object, key: "duration")
- frame.pts = uint64Value(object, key: "pts")
- frame.defaultFps = Int32(intValue(object, key: "defaultFps"))
- return frame
- }
- static func toSourceDisplayItem(_ object: AnyObject?) -> LNVAPSourceDisplayItem? {
- guard let object else { return nil }
- if let item = object as? LNVAPSourceDisplayItem {
- return item
- }
- guard let sourceObj = object as? NSObject else { return nil }
- let item = LNVAPSourceDisplayItem()
- item.frame = cgRectValue(sourceObj, key: "frame")
- if let sourceInfoObj = sourceObj.value(forKey: "sourceInfo") as? NSObject {
- item.sourceInfo = toSource(sourceInfoObj)
- }
- return item
- }
- private static func intValue(_ object: NSObject, key: String) -> Int {
- (object.value(forKey: key) as? NSNumber)?.intValue ?? 0
- }
- private static func uint64Value(_ object: NSObject, key: String) -> UInt64 {
- (object.value(forKey: key) as? NSNumber)?.uint64Value ?? 0
- }
- private static func timeIntervalValue(_ object: NSObject, key: String) -> TimeInterval {
- (object.value(forKey: key) as? NSNumber)?.doubleValue ?? 0
- }
- private static func boolValue(_ object: NSObject, key: String) -> Bool {
- (object.value(forKey: key) as? NSNumber)?.boolValue ?? false
- }
- private static func stringValue(_ object: NSObject, key: String) -> String? {
- object.value(forKey: key) as? String
- }
- private static func cgSizeValue(_ object: NSObject, key: String) -> CGSize {
- (object.value(forKey: key) as? NSValue)?.cgSizeValue ?? .zero
- }
- private static func cgRectValue(_ object: NSObject, key: String) -> CGRect {
- (object.value(forKey: key) as? NSValue)?.cgRectValue ?? .zero
- }
- }
- private enum LNPlaybackRuntime {
- private typealias LNObjCGestureBlock = @convention(block) (UIGestureRecognizer, Bool, AnyObject?) -> Void
- static func callNoArg(_ target: NSObject, selectorName: String) {
- let selector = NSSelectorFromString(selectorName)
- guard target.responds(to: selector) else { return }
- typealias Function = @convention(c) (AnyObject, Selector) -> Void
- let imp = target.method(for: selector)
- unsafeBitCast(imp, to: Function.self)(target, selector)
- }
- static func callBool(_ target: NSObject, selectorName: String, value: Bool) {
- let selector = NSSelectorFromString(selectorName)
- guard target.responds(to: selector) else { return }
- typealias Function = @convention(c) (AnyObject, Selector, Bool) -> Void
- let imp = target.method(for: selector)
- unsafeBitCast(imp, to: Function.self)(target, selector, value)
- }
- static func playHWDMP4(_ target: NSObject, filePath: String, repeatCount: Int, delegate: AnyObject?) -> Bool {
- let selector = NSSelectorFromString("playHWDMP4:repeatCount:delegate:")
- guard target.responds(to: selector) else { return false }
- typealias Function = @convention(c) (AnyObject, Selector, NSString, Int, AnyObject?) -> Void
- let imp = target.method(for: selector)
- unsafeBitCast(imp, to: Function.self)(target, selector, filePath as NSString, repeatCount, delegate)
- return true
- }
- static func addVapTapGesture(_ target: NSObject, callback: @escaping LNVAPGestureEventBlock) -> Bool {
- let selector = NSSelectorFromString("addVapTapGesture:")
- guard target.responds(to: selector) else { return false }
- let block: LNObjCGestureBlock = { gesture, insideSource, sourceObj in
- callback(gesture, insideSource, LNPlaybackLegacyMapper.toSourceDisplayItem(sourceObj))
- }
- typealias Function = @convention(c) (AnyObject, Selector, AnyObject) -> Void
- let imp = target.method(for: selector)
- unsafeBitCast(imp, to: Function.self)(target, selector, unsafeBitCast(block, to: AnyObject.self))
- return true
- }
- static func addVapGesture(_ target: NSObject, gestureRecognizer: UIGestureRecognizer, callback: @escaping LNVAPGestureEventBlock) -> Bool {
- let selector = NSSelectorFromString("addVapGesture:callback:")
- guard target.responds(to: selector) else { return false }
- let block: LNObjCGestureBlock = { gesture, insideSource, sourceObj in
- callback(gesture, insideSource, LNPlaybackLegacyMapper.toSourceDisplayItem(sourceObj))
- }
- typealias Function = @convention(c) (AnyObject, Selector, UIGestureRecognizer, AnyObject) -> Void
- let imp = target.method(for: selector)
- unsafeBitCast(imp, to: Function.self)(target, selector, gestureRecognizer, unsafeBitCast(block, to: AnyObject.self))
- return true
- }
- static func set(_ target: NSObject, key: String, value: Any) {
- let setter = setterSelectorName(for: key)
- let setterSelector = NSSelectorFromString(setter)
- guard target.responds(to: setterSelector) else { return }
- target.setValue(value, forKey: key)
- }
- static func intValue(_ target: NSObject, key: String) -> Int {
- let selector = NSSelectorFromString(key)
- guard target.responds(to: selector) else { return 0 }
- if let value = target.value(forKey: key) as? NSNumber { return value.intValue }
- return 0
- }
- static func boolValue(_ target: NSObject, key: String) -> Bool {
- let selector = NSSelectorFromString(key)
- guard target.responds(to: selector) else { return false }
- if let value = target.value(forKey: key) as? NSNumber { return value.boolValue }
- return false
- }
- private static func setterSelectorName(for key: String) -> String {
- guard let first = key.first else { return "" }
- return "set\(String(first).uppercased())\(key.dropFirst()):"
- }
- }
|