// UIView+VAP.m // Tencent is pleased to support the open source community by making vap available. // // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. // // Licensed under the MIT License (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed under the License is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, // either express or implied. See the License for the specific language governing permissions and // limitations under the License. #import #import #import "UIView+VAP.h" #import "QGAnimatedImageDecodeManager.h" #import "QGMP4HWDFileInfo.h" #import "QGMP4FrameHWDecoder.h" #import "QGBaseAnimatedImageFrame+Displaying.h" #import "QGHWDMP4OpenGLView.h" #import "QGVAPWeakProxy.h" #import "NSNotificationCenter+VAPThreadSafe.h" #import "QGHWDMP4OpenGLView.h" #import "QGMP4FrameHWDecoder.h" #import "QGMP4AnimatedImageFrame.h" #import "QGMP4FrameHWDecoder.h" #import "QGHWDMetalView.h" #import "QGVAPMetalView.h" #import "QGBaseAnimatedImageFrame+Displaying.h" #import "QGVAPConfigManager.h" #import "QGHWDMetalRenderer.h" #import "UIGestureRecognizer+VAPUtil.h" NSInteger const kQGHWDMP4DefaultFPS = 20; NSInteger const kQGHWDMP4MinFPS = 1; NSInteger const QGHWDMP4MaxFPS = 60; NSInteger const VapMaxCompatibleVersion = 2; @interface UIView () @property (nonatomic, assign) QGHWDTextureBlendMode hwd_blendMode; //alpha通道混合模式 @property (nonatomic, strong) QGMP4AnimatedImageFrame *hwd_currentFrameInstance; //store the frame value @property (nonatomic, strong) QGMP4HWDFileInfo *hwd_fileInfo; //MP4文件信息 @property (nonatomic, strong) QGAnimatedImageDecodeManager *hwd_decodeManager; //解码逻辑 @property (nonatomic, strong) QGAnimatedImageDecodeConfig *hwd_decodeConfig; //线程数与buffer数 @property (nonatomic, strong) NSOperationQueue *hwd_callbackQueue; //回调执行队列 @property (nonatomic, assign) BOOL hwd_onPause; //标记是否暂停中 @property (nonatomic, strong) QGHWDMP4OpenGLView *hwd_openGLView; //opengl绘制图层 @property (nonatomic, strong) QGHWDMetalView *hwd_metalView; //metal绘制图层 @property (nonatomic, strong) QGVAPMetalView *vap_metalView; //vap格式mp4渲染图层 @property (nonatomic, assign) BOOL hwd_isFinish; //标记是否结束 @property (nonatomic, assign) NSInteger hwd_repeatCount; //播放次数;-1 表示无限循环 @property (nonatomic, strong) QGVAPConfigManager *hwd_configManager; //额外的配置信息 @property (nonatomic, strong) dispatch_queue_t vap_renderQueue; //播放队列 @end @implementation UIView (VAP) #pragma mark - private methods - (void)hwd_registerNotification { [[NSNotificationCenter defaultCenter] hwd_addSafeObserver:self selector:@selector(hwd_didReceiveEnterBackgroundNotification:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] hwd_addSafeObserver:self selector:@selector(hwd_didReceiveEnterBackgroundNotification:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] hwd_addSafeObserver:self selector:@selector(hwd_didReceiveWillEnterForegroundNotification:) name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] hwd_addSafeObserver:self selector:@selector(hwd_didReceiveWillEnterForegroundNotification:) name:UIApplicationDidBecomeActiveNotification object:nil]; } - (void)hwd_didReceiveEnterBackgroundNotification:(NSNotification *)notification { [self hwd_stopHWDMP4]; } //结束播放 - (void)hwd_stopHWDMP4 { VAP_Info(kQGVAPModuleCommon, @"hwd stop playing"); self.hwd_repeatCount = 0; if (self.hwd_isFinish) { VAP_Info(kQGVAPModuleCommon, @"isFinish already set"); return ; } self.hwd_isFinish = YES; self.hwd_onPause = YES; if (self.hwd_openGLView) { self.hwd_openGLView.pause = YES; if ([EAGLContext currentContext] != self.hwd_openGLView.glContext) { [EAGLContext setCurrentContext:self.hwd_openGLView.glContext]; } [self.hwd_openGLView dispose]; glFinish(); } if (self.hwd_metalView) { [self.hwd_metalView dispose]; } if (self.vap_metalView) { [self.vap_metalView dispose]; } [self.hwd_callbackQueue addOperationWithBlock:^{ //此处必须延迟释放,避免野指针 if ([self.hwd_Delegate respondsToSelector:@selector(viewDidStopPlayMP4:view:)]) { [self.hwd_Delegate viewDidStopPlayMP4:self.hwd_currentFrame.frameIndex view:self]; } }]; self.hwd_decodeManager = nil; self.hwd_decodeConfig = nil; self.hwd_currentFrameInstance = nil; self.hwd_fileInfo = nil; [EAGLContext setCurrentContext:nil]; } //播放完成 - (void)hwd_didFinishDisplay { VAP_Info(kQGVAPModuleCommon, @"hwd didFinishDisplay"); [self.hwd_callbackQueue addOperationWithBlock:^{ //此处必须延迟释放,避免野指针 if ([self.hwd_Delegate respondsToSelector:@selector(viewDidFinishPlayMP4:view:)]) { [self.hwd_Delegate viewDidFinishPlayMP4:self.hwd_currentFrame.frameIndex+1 view:self]; } }]; NSInteger currentCount = self.hwd_repeatCount; if (currentCount == -1 || currentCount-- > 0) { //continuing VAP_Info(kQGVAPModuleCommon, @"continue to display. currentCount:%@", @(currentCount)); [self p_playHWDMP4:self.hwd_fileInfo.filePath fps:self.hwd_fps blendMode:self.hwd_blendMode repeatCount:currentCount delegate:self.hwd_Delegate]; return ; } [self hwd_stopHWDMP4]; } - (void)hwd_didReceiveWillEnterForegroundNotification:(NSNotification *)notification { [self resumeHWDMP4]; } - (void)hwd_loadMetalViewIfNeed:(QGHWDTextureBlendMode)mode { if (self.hwd_renderByOpenGL) { return ; } //use vap metal if (self.useVapMetalView) { if (self.vap_metalView) { self.vap_metalView.commonInfo = self.hwd_configManager.model.info; return ; } QGVAPMetalView *vapMetalView = [[QGVAPMetalView alloc] initWithFrame:self.bounds]; vapMetalView.commonInfo = self.hwd_configManager.model.info; vapMetalView.maskInfo = self.vap_maskInfo; vapMetalView.delegate = self; [self addSubview:vapMetalView]; vapMetalView.translatesAutoresizingMaskIntoConstraints = false; NSDictionary *views = @{@"vapMetalView": vapMetalView}; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[vapMetalView]|" options:0 metrics:nil views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[vapMetalView]|" options:0 metrics:nil views:views]]; self.vap_metalView = vapMetalView; [self hwd_registerNotification]; return ; } //use hwd metal if (self.hwd_metalView) { self.hwd_metalView.blendMode = mode; return ; } QGHWDMetalView *metalView = [[QGHWDMetalView alloc] initWithFrame:self.bounds blendMode:mode]; if (!metalView) { VAP_Event(kQGVAPModuleCommon, @"metal view is nil!"); return ; } metalView.blendMode = mode; metalView.delegate = self; [self addSubview:metalView]; metalView.translatesAutoresizingMaskIntoConstraints = false; NSDictionary *views = @{@"metalView": metalView}; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[metalView]|" options:0 metrics:nil views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[metalView]|" options:0 metrics:nil views:views]]; self.hwd_metalView = metalView; [self hwd_registerNotification]; } - (void)hwd_loadMetalDataIfNeed { [self.hwd_configManager loadMTLTextures:kQGHWDMetalRendererDevice];//加载所需的纹理数据 [self.hwd_configManager loadMTLBuffers:kQGHWDMetalRendererDevice];//加载所需的buffer } - (void)hwd_loadOpenglViewIfNeed:(QGHWDTextureBlendMode)mode { if (!self.hwd_renderByOpenGL) { return ; } if (self.hwd_openGLView) { self.hwd_openGLView.blendMode = mode; self.hwd_openGLView.pause = NO; VAP_Info(kQGVAPModuleCommon, @"quit loading openglView for already loaded."); return ; } QGHWDMP4OpenGLView *openGLView = [[QGHWDMP4OpenGLView alloc] initWithFrame:self.bounds]; openGLView.displayDelegate = self; openGLView.blendMode = mode; [self addSubview:openGLView]; openGLView.userInteractionEnabled = NO; [openGLView setupGL]; self.hwd_openGLView = openGLView; NSDictionary *views = @{@"openGLView": openGLView}; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[openGLView]|" options:0 metrics:nil views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[openGLView]|" options:0 metrics:nil views:views]]; [self hwd_registerNotification]; } //fps策略:优先使用调用者指定的fps;若不合法则使用mp4中的数据;若还是不合法则使用默认18 - (NSTimeInterval)hwd_appropriateDurationForFrame:(QGMP4AnimatedImageFrame *)frame { NSInteger fps = self.hwd_fps; if (fps < kQGHWDMP4MinFPS || fps > QGHWDMP4MaxFPS) { if (frame.defaultFps >= kQGHWDMP4MinFPS && frame.defaultFps <= QGHWDMP4MaxFPS) { fps = frame.defaultFps; }else { fps = kQGHWDMP4DefaultFPS; } } return 1000/(double)fps; } #pragma mark - main /** 见playHWDMP4:blendMode:repeatCount:delegate: 播放一遍,alpha数据在左边,不需要回调 */ - (void)playHWDMp4:(NSString *)filePath { [self playHWDMP4:filePath delegate:nil]; } /** 见playHWDMP4:blendMode:repeatCount:delegate: 播放一遍,alpha数据在左边,设置回调 */ - (void)playHWDMP4:(NSString *)filePath delegate:(id)delegate { [self p_playHWDMP4:filePath fps:0 blendMode:QGHWDTextureBlendMode_AlphaLeft repeatCount:0 delegate:delegate]; } /** 见playHWDMP4:blendMode:repeatCount:delegate: alpha数据在左边 */ - (void)playHWDMP4:(NSString *)filePath repeatCount:(NSInteger)repeatCount delegate:(id)delegate { [self p_playHWDMP4:filePath fps:0 blendMode:QGHWDTextureBlendMode_AlphaLeft repeatCount:repeatCount delegate:delegate]; } - (void)p_playHWDMP4:(NSString *)filePath fps:(NSInteger)fps blendMode:(QGHWDTextureBlendMode)mode repeatCount:(NSInteger)repeatCount delegate:(id)delegate { VAP_Info(kQGVAPModuleCommon, @"try to display mp4:%@ blendMode:%@ fps:%@ repeatCount:%@", filePath, @(mode), @(fps), @(repeatCount)); NSAssert([NSThread isMainThread], @"HWDMP4 needs to be accessed on the main thread."); //filePath check if (!filePath || filePath.length == 0) { VAP_Error(kQGVAPModuleCommon, @"playHWDMP4 error! has no filePath!"); return ; } NSFileManager *fileMgr = [NSFileManager defaultManager]; if (![fileMgr fileExistsAtPath:filePath]) { VAP_Error(kQGVAPModuleCommon, @"playHWDMP4 error! fileNotExistsAtPath filePath:%#", filePath); return ; } self.hwd_isFinish = NO; self.hwd_blendMode = mode; self.hwd_fps = fps; self.hwd_repeatCount = repeatCount; self.hwd_Delegate = delegate; if (self.hwd_Delegate && !self.hwd_callbackQueue) { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.maxConcurrentOperationCount = 1; self.hwd_callbackQueue = queue; } //mp4 info QGMP4HWDFileInfo *fileInfo = [[QGMP4HWDFileInfo alloc] init]; fileInfo.filePath = filePath; fileInfo.mp4Parser = [[QGMP4ParserProxy alloc] initWithFilePath:fileInfo.filePath]; [fileInfo.mp4Parser parse]; self.hwd_fileInfo = fileInfo; //config manager QGVAPConfigManager *configManager = [[QGVAPConfigManager alloc] initWith:fileInfo]; configManager.delegate = self; self.hwd_configManager = configManager; if (configManager.model.info.version > VapMaxCompatibleVersion) { VAP_Error(kQGVAPModuleCommon, @"playHWDMP4 error! not compatible vap version:%@!", @(configManager.model.info.version)); [self stopHWDMP4]; return ; } //reset self.hwd_currentFrameInstance = nil; self.hwd_decodeManager = nil; self.hwd_onPause = NO; if (!self.hwd_decodeConfig) { self.hwd_decodeConfig = [QGAnimatedImageDecodeConfig defaultConfig]; } //OpenGLView [self hwd_loadOpenglViewIfNeed:mode]; //metalView [self hwd_loadMetalViewIfNeed:mode]; if ([[UIDevice currentDevice] hwd_isSimulator]) { VAP_Error(kQGVAPModuleCommon, @"playHWDMP4 error! not allowed in Simulator!"); [self stopHWDMP4]; return ; } if (!self.vap_renderQueue) { self.vap_renderQueue = dispatch_queue_create("com.qgame.vap.render", DISPATCH_QUEUE_SERIAL); } self.hwd_decodeManager = [[QGAnimatedImageDecodeManager alloc] initWith:self.hwd_fileInfo config:self.hwd_decodeConfig delegate:self]; [self.hwd_configManager loadConfigResources]; //必须按先加载必要资源才能播放 - onVAPConfigResourcesLoaded } #pragma mark - play run - (void)hwd_renderVideoRun { static NSTimeInterval durationForWaitingFrame = 16/1000.0; static NSTimeInterval minimumDurationForLoop = 1/1000.0; __block NSTimeInterval lastRenderingInterval = 0; __block NSTimeInterval lastRenderingDuration = 0; dispatch_async(self.vap_renderQueue, ^{ if (self.hwd_onPause || self.hwd_isFinish) { return ; } //不能将self.hwd_onPause判断加到while语句中!会导致releasepool不断上涨 while (YES) { @autoreleasepool { if (self.hwd_onPause || self.hwd_isFinish) { break ; } __block QGMP4AnimatedImageFrame *nextFrame = nil; dispatch_sync(dispatch_get_main_queue(), ^{ nextFrame = [self hwd_displayNext]; }); NSTimeInterval duration = nextFrame.duration/1000.0; if (duration == 0) { duration = durationForWaitingFrame; } NSTimeInterval currentTimeInterval = NSDate.timeIntervalSinceReferenceDate; if (nextFrame && nextFrame.frameIndex != 0) { duration -= ((currentTimeInterval-lastRenderingInterval) - lastRenderingDuration); //追回时间 } duration = MAX(minimumDurationForLoop, duration); lastRenderingInterval = currentTimeInterval; lastRenderingDuration = duration; [NSThread sleepForTimeInterval:duration]; } } }); } - (QGMP4AnimatedImageFrame *)hwd_displayNext { if (self.hwd_onPause || self.hwd_isFinish) { return nil; } NSInteger nextIndex = self.hwd_currentFrame.frameIndex + 1; if (!self.hwd_currentFrame) { nextIndex = 0; } QGMP4AnimatedImageFrame *nextFrame = (QGMP4AnimatedImageFrame *)[self.hwd_decodeManager consumeDecodedFrame:nextIndex]; //没取到预期的帧 if (!nextFrame || nextFrame.frameIndex != nextIndex || ![nextFrame isKindOfClass:[QGMP4AnimatedImageFrame class]]) { return nil; } //音频播放 if (nextIndex == 0) { [self.hwd_decodeManager tryToStartAudioPlay]; } nextFrame.duration = [self hwd_appropriateDurationForFrame:nextFrame]; //VAP_Debug(kQGVAPModuleCommon, @"display frame:%@, has frameBuffer:%@",@(nextIndex),@(nextFrame.pixelBuffer != nil)); if (self.hwd_renderByOpenGL) { [self.hwd_openGLView displayPixelBuffer:nextFrame.pixelBuffer]; } else if (self.useVapMetalView) { NSArray *mergeInfos = self.hwd_configManager.model.mergedConfig[@(nextFrame.frameIndex)]; [self.vap_metalView display:nextFrame.pixelBuffer mergeInfos:mergeInfos]; } else { [self.hwd_metalView display:nextFrame.pixelBuffer]; } self.hwd_currentFrameInstance = nextFrame; [self.hwd_callbackQueue addOperationWithBlock:^{ if (nextIndex == 0 && [self.hwd_Delegate respondsToSelector:@selector(viewDidStartPlayMP4:)]) { [self.hwd_Delegate viewDidStartPlayMP4:self]; } //此处必须延迟释放,避免野指针 if ([self.hwd_Delegate respondsToSelector:@selector(viewDidPlayMP4AtFrame:view:)]) { [self.hwd_Delegate viewDidPlayMP4AtFrame:self.hwd_currentFrame view:self]; } }]; return nextFrame; } //结束播放 - (void)stopHWDMP4 { [self hwd_stopHWDMP4]; } - (void)pauseHWDMP4 { VAP_Info(kQGVAPModuleCommon, @"pauseHWDMP4"); self.hwd_onPause = YES; [self.hwd_callbackQueue addOperationWithBlock:^{ //此处必须延迟释放,避免野指针 if ([self.hwd_Delegate respondsToSelector:@selector(viewDidStopPlayMP4:view:)]) { [self.hwd_Delegate viewDidStopPlayMP4:self.hwd_currentFrame.frameIndex view:self]; } }]; } - (void)resumeHWDMP4 { VAP_Info(kQGVAPModuleCommon, @"resumeHWDMP4"); self.hwd_onPause = NO; self.hwd_openGLView.pause = NO; } + (void)registerHWDLog:(QGVAPLoggerFunc)logger { [QGVAPLogger registerExternalLog:logger]; } #pragma mark - delegate #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" //decoder - (Class)decoderClassForManager:(QGAnimatedImageDecodeManager *)manager { return [QGMP4FrameHWDecoder class]; } - (void)decoderDidFinishDecode:(QGBaseDecoder *)decoder { VAP_Info(kQGVAPModuleCommon, @"decoderDidFinishDecode."); [self hwd_didFinishDisplay]; } - (void)decoderDidFailDecode:(QGBaseDecoder *)decoder error:(NSError *)error{ VAP_Error(kQGVAPModuleCommon, @"decoderDidFailDecode:%@", error); [self hwd_stopHWDMP4]; [self.hwd_callbackQueue addOperationWithBlock:^{ //此处必须延迟释放,避免野指针 if ([self.hwd_Delegate respondsToSelector:@selector(viewDidFailPlayMP4:)]) { [self.hwd_Delegate viewDidFailPlayMP4:error]; } }]; } //opengl - (void)onViewUnavailableStatus { VAP_Error(kQGVAPModuleCommon, @"onViewUnavailableStatus"); [self hwd_stopHWDMP4]; } //metal - (void)onMetalViewUnavailable { VAP_Error(kQGVAPModuleCommon, @"onMetalViewUnavailable"); [self stopHWDMP4]; } //config resources loaded - (void)onVAPConfigResourcesLoaded:(QGVAPConfigModel *)config error:(NSError *)error { [self hwd_loadMetalDataIfNeed]; if ([self.hwd_Delegate respondsToSelector:@selector(shouldStartPlayMP4:config:)]) { BOOL shouldStart = [self.hwd_Delegate shouldStartPlayMP4:self config:self.hwd_configManager.model]; if (!shouldStart) { VAP_Event(kQGVAPModuleCommon, @"shouldStartPlayMP4 return no!"); [self hwd_stopHWDMP4]; return ; } } [self hwd_renderVideoRun]; } - (NSString *)vap_contentForTag:(NSString *)tag resource:(QGVAPSourceInfo *)info { if ([self.hwd_Delegate respondsToSelector:@selector(contentForVapTag:resource:)]) { return [self.hwd_Delegate contentForVapTag:tag resource:info]; } return nil; } - (void)vap_loadImageWithURL:(NSString *)urlStr context:(NSDictionary *)context completion:(VAPImageCompletionBlock)completionBlock { if ([self.hwd_Delegate respondsToSelector:@selector(loadVapImageWithURL:context:completion:)]) { [self.hwd_Delegate loadVapImageWithURL:urlStr context:context completion:completionBlock]; } else if (completionBlock) { NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:-1 userInfo:@{@"msg" : @"hwd_Delegate doesn't responds to selector loadVapImageWithURL:context:completion:"}]; completionBlock(nil, error, nil); } } #pragma clang diagnostic pop #pragma mark - setters&getters - (BOOL)useVapMetalView { return self.hwd_configManager.hasValidConfig; } - (QGMP4AnimatedImageFrame *)hwd_currentFrame { return self.hwd_currentFrameInstance; } - (id)hwd_Delegate { return objc_getAssociatedObject(self, @"MP4PlayDelegate"); } - (void)setHwd_Delegate:(id)MP4PlayDelegate { //解决循环播放问题,本身已经是一个weakproxy对象,就不再处理 id weakDelegate = MP4PlayDelegate; if (![MP4PlayDelegate isKindOfClass:[QGVAPWeakProxy class]]) { weakDelegate = [QGVAPWeakProxy proxyWithTarget:MP4PlayDelegate]; } return objc_setAssociatedObject(self, @"MP4PlayDelegate", weakDelegate, OBJC_ASSOCIATION_RETAIN); } //category methods HWDSYNTH_DYNAMIC_PROPERTY_CTYPE(hwd_onPause, setHwd_onPause, BOOL) HWDSYNTH_DYNAMIC_PROPERTY_CTYPE(hwd_renderByOpenGL, setHwd_renderByOpenGL, BOOL) HWDSYNTH_DYNAMIC_PROPERTY_CTYPE(hwd_isFinish, setHwd_isFinish, BOOL) HWDSYNTH_DYNAMIC_PROPERTY_CTYPE(hwd_fps, setHwd_fps, NSInteger) HWDSYNTH_DYNAMIC_PROPERTY_CTYPE(hwd_blendMode, setHwd_blendMode, NSInteger) HWDSYNTH_DYNAMIC_PROPERTY_CTYPE(hwd_repeatCount, setHwd_repeatCount, NSInteger) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(hwd_currentFrameInstance, setHwd_currentFrameInstance, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(hwd_MP4FilePath, setHwd_MP4FilePath, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(hwd_decodeManager, setHwd_decodeManager, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(hwd_fileInfo, setHwd_fileInfo, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(hwd_decodeConfig, setHwd_decodeConfig, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(hwd_callbackQueue, setHwd_callbackQueue, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(hwd_openGLView, setHwd_openGLView, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(hwd_metalView, setHwd_metalView, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(vap_metalView, setVap_metalView, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(hwd_attachmentsModel, setHwd_attachmentsModel, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(hwd_configManager, setHwd_configManager, OBJC_ASSOCIATION_RETAIN) HWDSYNTH_DYNAMIC_PROPERTY_OBJECT(vap_renderQueue, setVap_renderQueue, OBJC_ASSOCIATION_RETAIN) @end /// vap 增加手势识别的能力 @implementation UIView (VAPGesture) /// 手势识别通用接口 /// @param gestureRecognizer 需要的手势识别器 /// @param handler 手势识别事件回调,按照gestureRecognizer回调时机回调 /// @note 例:[mp4View addVapGesture:[UILongPressGestureRecognizer new] callback:^(UIGestureRecognizer *gestureRecognizer, BOOL insideSource,QGVAPSourceDisplayItem *source) { NSLog(@"long press"); }]; - (void)addVapGesture:(UIGestureRecognizer *)gestureRecognizer callback:(VAPGestureEventBlock)handler { if (!gestureRecognizer) { VAP_Event(kQGVAPModuleCommon, @"addVapTapGesture with empty gestureRecognizer!"); return ; } if (!handler) { VAP_Event(kQGVAPModuleCommon, @"addVapTapGesture with empty handler!"); return ; } __weak __typeof(self) weakSelf = self; [gestureRecognizer addVapActionBlock:^(UITapGestureRecognizer *sender) { QGVAPSourceDisplayItem *diplaySource = [weakSelf displayingSourceAt:[sender locationInView:weakSelf]]; if (diplaySource) { handler(sender, YES, diplaySource); } else { handler(sender, NO, nil); } }]; [self addGestureRecognizer:gestureRecognizer]; } /// 增加点击的手势识别 /// @param handler 点击事件回调 - (void)addVapTapGesture:(VAPGestureEventBlock)handler { UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] init]; [self addVapGesture:tapGesture callback:handler]; } /// 获取当前视图中point位置最近的一个source,没有的话返回nil /// @param point 当前view坐标系下的某一个位置 - (QGVAPSourceDisplayItem *)displayingSourceAt:(CGPoint)point { NSArray *mergeInfos = self.hwd_configManager.model.mergedConfig[@(self.hwd_currentFrame.frameIndex)]; mergeInfos = [mergeInfos sortedArrayUsingComparator:^NSComparisonResult(QGVAPMergedInfo *obj1, QGVAPMergedInfo *obj2) { return [@(obj2.renderIndex) compare:@(obj1.renderIndex)]; }]; CGSize renderingPixelSize = self.hwd_configManager.model.info.size; if (renderingPixelSize.width <= 0 || renderingPixelSize.height <= 0) { return nil; } __block QGVAPMergedInfo *targetMergeInfo = nil; __block CGRect targetSourceFrame = CGRectZero; CGSize viewSize = self.frame.size; CGFloat xRatio = viewSize.width / renderingPixelSize.width; CGFloat yRatio = viewSize.height / renderingPixelSize.height; [mergeInfos enumerateObjectsUsingBlock:^(QGVAPMergedInfo * mergeInfo, NSUInteger idx, BOOL * _Nonnull stop) { CGRect sourceRenderingRect = mergeInfo.renderRect; CGRect sourceRenderingFrame = CGRectMake(CGRectGetMinX(sourceRenderingRect) * xRatio, CGRectGetMinY(sourceRenderingRect) * yRatio, CGRectGetWidth(sourceRenderingRect) * xRatio, CGRectGetHeight(sourceRenderingRect) * yRatio); BOOL inside = CGRectContainsPoint(sourceRenderingFrame, point); if (inside) { targetMergeInfo = mergeInfo; targetSourceFrame = sourceRenderingFrame; *stop = YES; } }]; if (!targetMergeInfo) { return nil; } QGVAPSourceDisplayItem *diplayItem = [QGVAPSourceDisplayItem new]; diplayItem.sourceInfo = targetMergeInfo.source; diplayItem.frame = targetSourceFrame; return diplayItem; } @end @implementation UIView (VAPMask) - (void)setVap_maskInfo:(QGVAPMaskInfo *)vap_maskInfo { objc_setAssociatedObject(self, @"VAPMaskInfo", vap_maskInfo, OBJC_ASSOCIATION_RETAIN); [self.vap_metalView setMaskInfo:vap_maskInfo]; } - (QGVAPMaskInfo *)vap_maskInfo { return objc_getAssociatedObject(self, @"VAPMaskInfo"); } @end @implementation UIView (MP4HWDDeprecated) /** 见playHWDMP4:blendMode:repeatCount:delegate: @param fps 每一帧播放时优先使用这个值确定帧展示时长;若不合法则使用mp4中的数据;若还是不合法则使用默认18 播放一遍,alpha数据在左边,设置回调 */ - (void)playHWDMP4:(NSString *)filePath fps:(NSInteger)fps delegate:(id)delegate { [self p_playHWDMP4:filePath fps:fps blendMode:QGHWDTextureBlendMode_AlphaLeft repeatCount:0 delegate:delegate]; } /** 见playHWDMP4:blendMode:repeatCount:delegate: @param fps 每一帧播放时优先使用这个值确定帧展示时长;若不合法则使用mp4中的数据;若还是不合法则使用默认18 alpha数据在左边 */ - (void)playHWDMP4:(NSString *)filePath fps:(NSInteger)fps repeatCount:(NSInteger)repeatCount delegate:(id)delegate { [self p_playHWDMP4:filePath fps:fps blendMode:QGHWDTextureBlendMode_AlphaLeft repeatCount:repeatCount delegate:delegate]; } /** 见playHWDMP4:blendMode:repeatCount:delegate: 播放一遍 */ - (void)playHWDMP4:(NSString *)filePath blendMode:(QGHWDTextureBlendMode)mode delegate:(id)delegate { [self p_playHWDMP4:filePath fps:0 blendMode:mode repeatCount:0 delegate:delegate]; } /** 利用GPU解码并播放mp4-h.264素材,在模拟器中会直接失败无法播放。 @param filePath mp4s素材本地地址 @param mode 确定素材中alpha通道数据位置,默认QGHWDTextureBlendMode_AlphaLeft @param repeatCount 重复播放次数,若repeatCount==n, 则播放n+1次;若repeatCount==-1,则循环播放. @param delegate 播放回调,⚠️注意:不在主线程回调 @note 素材文件需要按照规范生成 */ - (void)playHWDMP4:(NSString *)filePath blendMode:(QGHWDTextureBlendMode)mode repeatCount:(NSInteger)repeatCount delegate:(id)delegate { [self p_playHWDMP4:filePath fps:0 blendMode:mode repeatCount:repeatCount delegate:delegate]; } /** 见playHWDMP4:blendMode:repeatCount:delegate: @param fps 每一帧播放时优先使用这个值确定帧展示时长;若不合法则使用mp4中的数据;若还是不合法则使用默认18 播放一遍 */ - (void)playHWDMP4:(NSString *)filePath fps:(NSInteger)fps blendMode:(QGHWDTextureBlendMode)mode delegate:(id)delegate { [self p_playHWDMP4:filePath fps:fps blendMode:mode repeatCount:0 delegate:delegate]; } /** 见playHWDMP4:blendMode:repeatCount:delegate: @param fps 每一帧播放时优先使用这个值确定帧展示时长;若不合法则使用mp4中的数据;若还是不合法则使用默认18 */ - (void)playHWDMP4:(NSString *)filePath fps:(NSInteger)fps blendMode:(QGHWDTextureBlendMode)mode repeatCount:(NSInteger)repeatCount delegate:(id)delegate { [self p_playHWDMP4:filePath fps:fps blendMode:mode repeatCount:repeatCount delegate:delegate]; } @end