/*
* Copyright (c) 2024-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include <objc/NSObjCRuntime.h>
#include <sys/_types/_int32_t.h>
#include "StageViewController.h"
#include <vector>
#include "base/log/log.h"
#import "AcePlatformView.h"
#import "AceTextureHolder.h"
#import <AVFoundation/AVFoundation.h>
#import "render/MetalTextureRenderer.h"
#import "StageApplication.h"
#import "WindowView.h"
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
#import <CoreImage/CoreImage.h>
#define PLATFORMVIEW_FLAG @"platformview@"
#define PARAM_AND @"#HWJS-&-#"
#define PARAM_EQUALS @"#HWJS-=-#"
#define PARAM_BEGIN @"#HWJS-?-#"
#define PARAM_SURFACETYPE @"surface"
#define METHOD @"method"
#define EVENT @"event"
#define SUCCESS @"success"
#define FAIL @"fail"
#define KEY_TEXTUREID @"textureId"
#define PLATFORMVIEW_WIDTH @"platformViewWidth"
#define PLATFORMVIEW_HEIGHT @"platformViewHeight"
#define PLATFORMVIEW_TOP @"platformViewTop"
#define PLATFORMVIEW_LEFT @"platformViewLeft"
#define SCROLL_EDGE_EFFECT_CLASS @"ScrollEdgeEffect"
#define SCROLL_EDGE_EFFECT_BACKDROP @"Backdrop"
#define SCROLL_EDGE_EFFECT_LUMINANCE @"Luminance"
#define SCROLL_EDGE_EFFECT_POCKETMASK @"PocketMask"
const static CGFloat PLATFORMVIEW_Z_POSITION = -1000.0f;
const static CGFloat PLATFORMVIEW_DEFAULT_CAMERA_DISTANCE_PX = 576.0f;
const static CGFloat PLATFORMVIEW_INCH = 72.0f;
const static size_t QueueSize = 3;
const static NSInteger PLATFORMVIEW_MIN_ACTIVE_FRAME_RATE = 60;
const static NSInteger PLATFORMVIEW_FRAME_RATE_DIVISOR = 2;
@interface AcePlatformView()
@property (nonatomic, assign) int32_t instanceId;
@property (nonatomic, strong) NSObject<AcePlatformViewDelegate>* delegate;
@property (nonatomic, strong) NSMutableDictionary<NSString *, IAceOnCallSyncResourceMethod> *callSyncMethodMap;
@property (nonatomic, strong) NSObject<IPlatformView>* curPlatformView;
@property (nonatomic, strong) CADisplayLink *displayLink;
@property(nonatomic, assign) int64_t id;
@property(nonatomic, assign) int64_t textureResourceId;
@property(nonatomic, copy) IAceOnResourceEvent onEvent;
@property (nonatomic, strong) AceTexture *renderTexture;
@property(nonatomic, assign) CGFloat frameWidth;
@property(nonatomic, assign) CGFloat frameHeight;
@property(nonatomic, assign) CGFloat frameTop;
@property(nonatomic, assign) CGFloat frameLeft;
@property(nonatomic, assign) CGFloat screenScale;
@property(nonatomic, strong) NSDictionary *pendingScaleParams;
@property(nonatomic, strong) NSDictionary *pendingTransformMatrixParams;
@property(nonatomic, strong) NSDictionary *pendingTranslateParams;
@property(nonatomic, strong) NSDictionary *pendingRotateParams;
@property(nonatomic, assign) CATransform3D originalTransform;
@property(nonatomic, strong) MetalTextureRenderer *metalTextureRenderer;
@end
@implementation AcePlatformView
{
AVPlayer *_player;
AVPlayerLayer *_playerLayer;
bool _initView;
bool _isVideo;
bool _isRenderFinish;
UIView *_scrollEdgeEffectView;
}
namespace {
inline CGFloat NormalizeCenterValue(UIView *targetView, NSString *key, CGFloat numberValue, CGFloat defaultValue)
{
if ([key isEqualToString:@"centerX"]) {
CGFloat width = targetView.frame.size.width;
return width > 0 ? numberValue / width : defaultValue;
}
if ([key isEqualToString:@"centerY"]) {
CGFloat height = targetView.frame.size.height;
return height > 0 ? numberValue / height : defaultValue;
}
return numberValue;
}
inline NSString *TrimmedNumberString(NSString *value, NSString *suffix)
{
static NSCharacterSet *whitespaceSet = nil;
if (whitespaceSet == nullptr) {
whitespaceSet = [NSCharacterSet whitespaceCharacterSet];
}
return [[value stringByReplacingOccurrencesOfString:suffix withString:@""]
stringByTrimmingCharactersInSet:whitespaceSet];
}
inline CGFloat ParsePercent(UIView *targetView, NSString *key, NSString *value)
{
CGFloat percentValue = [TrimmedNumberString(value, @"%") doubleValue] / 100.0f;
if ([key isEqualToString:@"X"]) {
return percentValue * targetView.frame.size.width;
}
if ([key isEqualToString:@"Y"]) {
return percentValue * targetView.frame.size.height;
}
if ([key isEqualToString:@"Z"]) {
return 0;
}
return percentValue;
}
inline NSString *RunOnMainSync(NSString* (^block)(void))
{
if ([NSThread isMainThread]) {
return block();
}
__block NSString *result = FAIL;
dispatch_sync(dispatch_get_main_queue(), ^{
result = block();
});
return result;
}
} // namespace
- (instancetype)initWithEvents:(IAceOnResourceEvent)callback
id:(int64_t)id abilityInstanceId:(int32_t)abilityInstanceId
viewdelegate:(NSObject<AcePlatformViewDelegate>*)viewdelegate
{
if ((self = [super init]) != nullptr) {
self.instanceId = abilityInstanceId;
self.onEvent = callback;
self.id = id;
self.delegate = viewdelegate;
self.displayLink = nil;
self.renderTexture = nil;
self.screenScale = [UIScreen mainScreen].scale;
self.frameWidth = 0.00f;
self.frameHeight = 0.00f;
self.frameTop = 0.00f;
self.frameLeft = 0.00f;
self.pendingScaleParams = nil;
self.pendingTransformMatrixParams = nil;
self.pendingTranslateParams = nil;
self.pendingRotateParams = nil;
self.originalTransform = CATransform3DIdentity;
_initView = false;
_isRenderFinish = false;
_isVideo = false;
self.callSyncMethodMap = [[NSMutableDictionary alloc] init];
[self initEventCallback];
}
return self;
}
- (void)initEventCallback
{
[self registerPlatformViewCallback];
[self registerUpdateLayoutCallback];
[self registerExchangeBindCallback];
[self registerPlatformViewTypeCallback];
[self registerSetRotationCallback];
[self registerSetScaleCallback];
[self registerSetTranslateCallback];
[self registerSetTransformMatrixCallback];
}
- (void)registerPlatformViewCallback
{
__weak __typeof(self)weakSelf = self;
IAceOnCallSyncResourceMethod register_callback = ^NSString *(NSDictionary * param){
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf != nullptr) {
return RunOnMainSync(^NSString *{
return [strongSelf registerPlatformView:param];
});
} else {
LOGE("AcePlatformView: registerPlatformView fail");
return FAIL;
}
};
NSString *register__method_hash = [self method_hashFormat:@"registerPlatformView"];
[self.callSyncMethodMap setObject:[register_callback copy] forKey:register__method_hash];
}
- (void)registerUpdateLayoutCallback
{
__weak __typeof(self)weakSelf = self;
NSString *updatelayout_method_hash = [self method_hashFormat:@"updateLayout"];
IAceOnCallSyncResourceMethod updatelayout_callback = ^NSString *(NSDictionary * param){
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf != nullptr) {
return RunOnMainSync(^NSString *{
return [strongSelf updatelayout:param];
});
} else {
LOGE("AcePlatformView: updatelayout fail");
return FAIL;
}
};
[self.callSyncMethodMap setObject:[updatelayout_callback copy] forKey:updatelayout_method_hash];
}
- (void)registerExchangeBindCallback
{
__weak __typeof(self)weakSelf = self;
NSString *exchange_bind_method_hash = [self method_hashFormat:@"exchangeBind"];
IAceOnCallSyncResourceMethod exchange_bind_callback = ^NSString *(NSDictionary * param){
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf != nullptr) {
return RunOnMainSync(^NSString *{
return [strongSelf exchangeBind:param];
});
} else {
LOGE("AcePlatformView: exchangeBind fail");
return FAIL;
}
};
[self.callSyncMethodMap setObject:[exchange_bind_callback copy] forKey:exchange_bind_method_hash];
}
- (void)registerPlatformViewTypeCallback
{
__weak __typeof(self)weakSelf = self;
NSString *platform_view_type_hash = [self method_hashFormat:@"platformViewType"];
IAceOnCallSyncResourceMethod platform_view_type_callback = ^NSString *(NSDictionary * param){
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf != nullptr) {
return RunOnMainSync(^NSString *{
return [strongSelf platformViewType:param];
});
} else {
LOGE("AcePlatformView: platformViewType fail");
return FAIL;
}
};
[self.callSyncMethodMap setObject:[platform_view_type_callback copy] forKey:platform_view_type_hash];
}
- (void)registerSetRotationCallback
{
__weak __typeof(self)weakSelf = self;
NSString *setRotation_method_hash = [self method_hashFormat:@"setRotation"];
IAceOnCallSyncResourceMethod setRotation_callback = ^NSString *(NSDictionary * param){
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf != nullptr) {
return RunOnMainSync(^NSString *{
return [strongSelf setRotation:param];
});
} else {
LOGE("AcePlatformView: SetRotation fail");
return FAIL;
}
};
[self.callSyncMethodMap setObject:[setRotation_callback copy] forKey:setRotation_method_hash];
}
- (void)registerSetScaleCallback
{
__weak __typeof(self)weakSelf = self;
NSString *setScale_method_hash = [self method_hashFormat:@"setScale"];
IAceOnCallSyncResourceMethod setScale_callback = ^NSString *(NSDictionary * param) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf != nullptr) {
return RunOnMainSync(^NSString *{
return [strongSelf setScale:param];
});
} else {
return FAIL;
}
};
[self.callSyncMethodMap setObject:[setScale_callback copy] forKey:setScale_method_hash];
}
- (void)registerSetTranslateCallback
{
__weak __typeof(self)weakSelf = self;
NSString *setTranslate_method_hash = [self method_hashFormat:@"setTranslate"];
IAceOnCallSyncResourceMethod setTranslate_callback = ^NSString *(NSDictionary * param) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf != nullptr) {
return RunOnMainSync(^NSString *{
return [strongSelf setTranslate:param];
});
} else {
return FAIL;
}
};
[self.callSyncMethodMap setObject:[setTranslate_callback copy] forKey:setTranslate_method_hash];
}
- (void)registerSetTransformMatrixCallback
{
__weak __typeof(self)weakSelf = self;
NSString *setTransformMatrix_method_hash = [self method_hashFormat:@"setTransformMatrix"];
IAceOnCallSyncResourceMethod setTransformMatrix_callback = ^NSString *(NSDictionary * param) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf != nullptr) {
return RunOnMainSync(^NSString *{
return [strongSelf setTransformMatrix:param];
});
} else {
return FAIL;
}
};
[self.callSyncMethodMap setObject:[setTransformMatrix_callback copy] forKey:setTransformMatrix_method_hash];
}
- (CGFloat)getDataFromParams:(UIView *)targetView params:(NSDictionary *)params
key:(NSString *)key defaultValue:(CGFloat)defaultValue
{
id value = params[key];
if (value == nullptr || value == [NSNull null] || targetView == nullptr) {
return defaultValue;
}
if (![value isKindOfClass:[NSString class]] || [(NSString *)value length] == 0) {
return defaultValue;
}
NSString *stringValue = (NSString *)value;
if ([stringValue containsString:@"%"]) {
return ParsePercent(targetView, key, stringValue);
}
if ([stringValue containsString:@"vp"]) {
return [TrimmedNumberString(stringValue, @"vp") doubleValue];
}
if ([stringValue containsString:@"px"]) {
return [TrimmedNumberString(stringValue, @"px") doubleValue] / self.screenScale;
}
if ([stringValue containsString:@"deg"]) {
return [TrimmedNumberString(stringValue, @"deg") doubleValue];
}
return NormalizeCenterValue(targetView, key, [stringValue doubleValue], defaultValue);
}
- (void)applyScaleToTargetView:(UIView *)targetView
{
if (targetView == nullptr || self.pendingScaleParams == nullptr) {
return;
}
CGFloat scaleX = [self getDataFromParams:targetView params:self.pendingScaleParams key:@"X" defaultValue:1.0f];
CGFloat scaleY = [self getDataFromParams:targetView params:self.pendingScaleParams key:@"Y" defaultValue:1.0f];
CGFloat scaleZ = [self getDataFromParams:targetView params:self.pendingScaleParams key:@"Z" defaultValue:1.0f];
CGFloat centerX = [self getDataFromParams:targetView params:self.pendingScaleParams key:@"centerX" defaultValue:0];
CGFloat centerY = [self getDataFromParams:targetView params:self.pendingScaleParams key:@"centerY" defaultValue:0];
targetView.layer.anchorPoint = CGPointMake(centerX, centerY);
CATransform3D transform3D = CATransform3DMakeScale(scaleX, scaleY, scaleZ);
targetView.layer.zPosition = PLATFORMVIEW_Z_POSITION;
targetView.layer.transform = CATransform3DConcat(targetView.layer.transform, transform3D);
}
- (void)applyTransformMatrixToTargetView:(UIView *)targetView
{
if (targetView == nullptr || self.pendingTransformMatrixParams == nullptr) {
return;
}
for (int i = 0; i < 16; i++) {
NSString *key = [NSString stringWithFormat:@"m%d", i];
if (self.pendingTransformMatrixParams[key] == nullptr) {
LOGE("AcePlatformView: Transform matrix parameter %{public}d missing", i);
return;
}
}
CATransform3D transform3D = CATransform3DIdentity;
transform3D.m11 = (CGFloat)[self.pendingTransformMatrixParams[@"m0"] doubleValue];
transform3D.m12 = (CGFloat)[self.pendingTransformMatrixParams[@"m1"] doubleValue];
transform3D.m13 = (CGFloat)[self.pendingTransformMatrixParams[@"m2"] doubleValue];
transform3D.m14 = (CGFloat)[self.pendingTransformMatrixParams[@"m3"] doubleValue];
transform3D.m21 = (CGFloat)[self.pendingTransformMatrixParams[@"m4"] doubleValue];
transform3D.m22 = (CGFloat)[self.pendingTransformMatrixParams[@"m5"] doubleValue];
transform3D.m23 = (CGFloat)[self.pendingTransformMatrixParams[@"m6"] doubleValue];
transform3D.m24 = (CGFloat)[self.pendingTransformMatrixParams[@"m7"] doubleValue];
transform3D.m31 = (CGFloat)[self.pendingTransformMatrixParams[@"m8"] doubleValue];
transform3D.m32 = (CGFloat)[self.pendingTransformMatrixParams[@"m9"] doubleValue];
transform3D.m33 = (CGFloat)[self.pendingTransformMatrixParams[@"m10"] doubleValue];
transform3D.m34 = (CGFloat)[self.pendingTransformMatrixParams[@"m11"] doubleValue];
transform3D.m41 = (CGFloat)[self.pendingTransformMatrixParams[@"m12"] doubleValue] / self.screenScale;
transform3D.m42 = (CGFloat)[self.pendingTransformMatrixParams[@"m13"] doubleValue] / self.screenScale;
transform3D.m43 = (CGFloat)[self.pendingTransformMatrixParams[@"m14"] doubleValue] / self.screenScale;
transform3D.m44 = (CGFloat)[self.pendingTransformMatrixParams[@"m15"] doubleValue];
targetView.layer.zPosition = PLATFORMVIEW_Z_POSITION;
targetView.layer.transform = CATransform3DConcat(targetView.layer.transform, transform3D);
}
- (void)applyPendingTransformations:(UIView *)targetView
{
if (targetView == nullptr) {
return;
}
[self applyScaleToTargetView:targetView];
[self applyTranslateToTargetView:targetView];
[self applyRotateToTargetView:targetView];
[self applyTransformMatrixToTargetView:targetView];
}
- (void)applyTranslateToTargetView:(UIView *)targetView
{
if (targetView == nullptr || self.pendingTranslateParams == nullptr) {
return;
}
CGFloat translateX = [self getDataFromParams:targetView params:self.pendingTranslateParams key:@"X" defaultValue:0.0f];
CGFloat translateY = [self getDataFromParams:targetView params:self.pendingTranslateParams key:@"Y" defaultValue:0.0f];
CGFloat translateZ = [self getDataFromParams:targetView params:self.pendingTranslateParams key:@"Z" defaultValue:0.0f];
CATransform3D transform3D = CATransform3DIdentity;
transform3D.m34 = -1.0f / [self getDefaultCameraDistanceForView:targetView];
transform3D = CATransform3DTranslate(transform3D, 0.0f, 0.0f, -translateZ);
targetView.layer.zPosition = PLATFORMVIEW_Z_POSITION;
targetView.layer.transform = CATransform3DConcat(targetView.layer.transform, transform3D);
CGPoint basePosition = targetView.layer.position;
targetView.layer.position = CGPointMake(basePosition.x + translateX, basePosition.y + translateY);
}
- (void)applyRotateToTargetView:(UIView *)targetView
{
if (targetView == nullptr || self.pendingRotateParams == nullptr) {
return;
}
CGFloat rotationX = [self getDataFromParams:targetView params:self.pendingRotateParams key:@"X" defaultValue:0.0f];
CGFloat rotationY = [self getDataFromParams:targetView params:self.pendingRotateParams key:@"Y" defaultValue:0.0f];
CGFloat rotationZ = [self getDataFromParams:targetView params:self.pendingRotateParams key:@"Z" defaultValue:1.0f];
CGFloat rotationAngle = [self getDataFromParams:targetView params:self.pendingRotateParams key:@"angle" defaultValue:0.0f];
CGFloat centerX = [self getDataFromParams:targetView params:self.pendingRotateParams key:@"centerX" defaultValue:0.5f];
CGFloat centerY = [self getDataFromParams:targetView params:self.pendingRotateParams key:@"centerY" defaultValue:0.5f];
CGFloat centerZ = [self getDataFromParams:targetView params:self.pendingRotateParams key:@"centerZ" defaultValue:0.0f];
CGFloat perspective = [self getDataFromParams:targetView params:self.pendingRotateParams key:@"perspective" defaultValue:0.0f];
targetView.layer.anchorPoint = CGPointMake(centerX, centerY);
targetView.layer.anchorPointZ = centerZ / self.screenScale;
CATransform3D transform = CATransform3DIdentity;
if (fabs(perspective) > FLT_EPSILON) {
transform.m34 = 1.0f / (perspective / self.screenScale * PLATFORMVIEW_INCH);
} else {
transform.m34 = -1.0f / [self getDefaultCameraDistanceForView:targetView];
}
CGFloat norm = sqrt(rotationX * rotationX + rotationY * rotationY + rotationZ * rotationZ);
if (norm > FLT_EPSILON) {
CGFloat angleX = rotationAngle * rotationX / norm;
CGFloat angleY = rotationAngle * rotationY / norm;
CGFloat angleZ = rotationAngle * rotationZ / norm;
transform = CATransform3DRotate(transform, angleX * M_PI / 180.0, 1.0f, 0.0f, 0.0f);
transform = CATransform3DRotate(transform, angleY * M_PI / 180.0, 0.0f, 1.0f, 0.0f);
transform = CATransform3DRotate(transform, angleZ * M_PI / 180.0, 0.0f, 0.0f, 1.0f);
targetView.layer.zPosition = PLATFORMVIEW_Z_POSITION;
targetView.layer.transform = CATransform3DConcat(targetView.layer.transform, transform);
}
}
- (NSString *)setRotation:(NSDictionary *)params
{
if (params == nullptr) {
return FAIL;
}
self.pendingRotateParams = params;
return SUCCESS;
}
- (NSString *)setScale:(NSDictionary *)params
{
if (params == nullptr) {
return FAIL;
}
self.pendingScaleParams = params;
return SUCCESS;
}
- (NSString *)setTranslate:(NSDictionary *)params
{
if (params == nullptr) {
return FAIL;
}
self.pendingTranslateParams = params;
return SUCCESS;
}
- (NSString *)setTransformMatrix:(NSDictionary *)params
{
UIView* targetView = [self getPlatformView];
if (targetView == nullptr) {
return FAIL;
}
self.pendingTransformMatrixParams = params;
return SUCCESS;
}
- (void)setPlatformView:(NSObject<IPlatformView>*)platformView
{
self.curPlatformView = platformView;
UIView *view = [platformView view];
if (view != nullptr && view.superview != nullptr) {
self.originalTransform = view.layer.transform;
}
NSObject<IPlatformView>* embeddedView = self.curPlatformView;
if (embeddedView == nullptr) {
LOGE("AcePlatformView: setPlatformView failed: platformView is null");
return ;
}
UIView* pv = [embeddedView view];
[pv.layer.sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[AVPlayerLayer class]]) {
AVPlayerLayer *pLayer = ((AVPlayerLayer *)obj);
_isVideo = true;
_playerLayer = pLayer;
_player = pLayer.player;
} else {
*stop = YES;
}
}];
}
- (NSDictionary<NSString *, IAceOnCallSyncResourceMethod> *)getSyncCallMethod
{
return self.callSyncMethodMap;
}
- (NSString *)exchangeBind:(NSDictionary *)params
{
return SUCCESS;
}
- (NSString *)platformViewType:(NSDictionary *)params
{
NSDictionary * param = @{@"type": [NSString stringWithFormat:@"%d", _isVideo]};
return [self convertMapToString:param];
}
- (NSString *)convertMapToString:(NSDictionary *)data
{
NSArray *pairs = [data.allKeys sortedArrayUsingSelector:@selector(compare:)];
NSMutableString *string = [[NSMutableString alloc] init];
for (NSString *key in pairs) {
id value = data[key];
[string appendFormat:@"%@=%@;", key, value];
}
[string deleteCharactersInRange:NSMakeRange(string.length - 1, 1)];
return string;
}
- (NSString *)registerPlatformView:(NSDictionary *)params
{
if (params == nullptr) {
LOGE("AcePlatformView: registerPlatformView failed: params is null");
return FAIL;
}
UIView* platformView = [self getPlatformView];
if (params[KEY_TEXTUREID] == nil) {
[self initWithSurfaceView:platformView];
return SUCCESS;
}
self.textureResourceId = [params[KEY_TEXTUREID] longLongValue];
if (self.renderTexture == nullptr) {
AceTexture *newTexture = (AceTexture*)[AceTextureHolder getTextureWithId:self.textureResourceId
inceId:self.instanceId];
self.renderTexture = newTexture;
}
_metalTextureRenderer = [[MetalTextureRenderer alloc] initWithFrame:CGRectZero];
[self initPlatformView];
if (_isVideo && _player != nullptr) {
[_player.currentItem addOutput:self.renderTexture.videoOutput];
[self initWithEmbeddedView:platformView];
[self initRenderTexture];
} else {
[self.delegate registerBufferWithInstanceId:self.instanceId
textureId:self.textureResourceId
texturePixelBuffer:(__bridge void*)_metalTextureRenderer];
[self initWithEmbeddedView:platformView];
[self platformViewReady];
_initView = true;
}
return SUCCESS;
}
- (void)platformViewReady
{
if (self.onEvent != nullptr) {
NSString *prepared_method_hash = [NSString stringWithFormat:@"%@%lld%@%@%@%@",
PLATFORMVIEW_FLAG, self.id, EVENT, PARAM_EQUALS, @"platformViewReady", PARAM_BEGIN];
LOGI("[PlatformView] platformViewReady");
self.onEvent(prepared_method_hash, @"");
}
}
- (NSString *)updatelayout:(NSDictionary *)params
{
if (params == nullptr) {
LOGE("AcePlatformView: setSurface failed: params is null");
return FAIL;
}
@try {
UIView* platformView = [self getPlatformView];
platformView.layer.transform = self.originalTransform;
self.frameWidth = [params[PLATFORMVIEW_WIDTH] floatValue];
self.frameHeight = [params[PLATFORMVIEW_HEIGHT] floatValue];
self.frameTop = [params[PLATFORMVIEW_TOP] floatValue];
self.frameLeft = [params[PLATFORMVIEW_LEFT] floatValue];
[self updateNativeFrame];
[self applyPendingTransformations:platformView];
} @catch (NSException *exception) {
LOGE("AcePlatformView: IOException, updatelayout failed");
return FAIL;
}
return SUCCESS;
}
- (BOOL)initPlatformView
{
if (self.displayLink != nullptr) {
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
return YES;
}
return NO;
}
- (CADisplayLink *)displayLink
{
if (_displayLink == nullptr) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkDidrefresh)];
[self updateDisplayLinkFrameRate];
}
return _displayLink;
}
- (void)updateDisplayLinkFrameRate
{
if (_displayLink == nullptr) {
return;
}
NSInteger mainMaxFrameRate = [UIScreen mainScreen].maximumFramesPerSecond;
NSInteger activeFrameRate = MAX(mainMaxFrameRate, PLATFORMVIEW_MIN_ACTIVE_FRAME_RATE);
if (@available(iOS 15.0, *)) {
double targetMaxFrameRate = activeFrameRate;
double targetMinFrameRate = fmin(activeFrameRate / PLATFORMVIEW_FRAME_RATE_DIVISOR, targetMaxFrameRate);
_displayLink.preferredFrameRateRange =
CAFrameRateRangeMake(targetMinFrameRate, targetMaxFrameRate, targetMaxFrameRate);
} else {
_displayLink.preferredFramesPerSecond = activeFrameRate;
}
}
- (void)displayLinkDidrefresh
{
if (_isVideo) {
if (self.displayLink != nullptr) {
[self refreshPixelBuffer];
}
return;
}
NSObject<IPlatformView>* embeddedView = self.curPlatformView;
if (embeddedView == nullptr) {
LOGE("AcePlatformView: register failed: platformView is null");
return;
}
UIView* platformView = [embeddedView view];
if (@available(iOS 26.0, *) && [platformView isKindOfClass:[WKWebView class]]) {
[self hideScrollEdgeEffectSubviews:platformView];
}
if (_initView) {
_isRenderFinish = [_metalTextureRenderer startRender:platformView];
}
if (self.displayLink != nullptr && _isRenderFinish) {
[self refreshPixelBuffer];
}
}
/*
* For iOS 26 and above, the subview CARenderer of WKWebView named "ScrollEdgeEffect" causes unexpected rendering
* effects during off-screen rendering.
*/
- (void)hideScrollEdgeEffectSubviews:(UIView*)view
{
if (_scrollEdgeEffectView != nullptr) {
return;
}
NSString* className = NSStringFromClass([view class]);
if ([className containsString:SCROLL_EDGE_EFFECT_CLASS]) {
view.alpha = 0;
_scrollEdgeEffectView = view;
for (UIView* effectSubview in view.subviews) {
NSString* effectClassName = NSStringFromClass([effectSubview class]);
if ([effectClassName containsString:SCROLL_EDGE_EFFECT_BACKDROP] ||
[effectClassName containsString:SCROLL_EDGE_EFFECT_LUMINANCE] ||
[effectClassName containsString:SCROLL_EDGE_EFFECT_POCKETMASK]) {
effectSubview.alpha = 0;
}
}
return;
}
for (UIView* subview in view.subviews) {
[self hideScrollEdgeEffectSubviews:subview];
}
}
- (void)initRenderTexture
{
if (self.renderTexture != nullptr) {
[self.renderTexture refreshPixelBuffer];
}
}
- (void)refreshPixelBuffer
{
if (self.renderTexture != nullptr) {
[self.renderTexture refreshPixelBuffer];
}
}
- (NSString *)method_hashFormat:(NSString *)method
{
return [NSString stringWithFormat:@"%@%lld%@%@%@%@", PLATFORMVIEW_FLAG, self.id, METHOD, PARAM_EQUALS, method, PARAM_BEGIN];
}
- (CGFloat)getDefaultCameraDistanceForView:(UIView *)view
{
CGFloat widthPx = view ? (CGRectGetWidth(view.bounds) * self.screenScale) : 0.0f;
CGFloat heightPx = view ? (CGRectGetHeight(view.bounds) * self.screenScale) : 0.0f;
CGFloat zOffsetPx = sqrt(widthPx * widthPx + heightPx * heightPx) / 2.0f;
CGFloat distancePx = PLATFORMVIEW_DEFAULT_CAMERA_DISTANCE_PX + zOffsetPx;
CGFloat distancePt = distancePx / self.screenScale;
return distancePt > FLT_EPSILON ? distancePt : 1.0f;
}
- (CGRect)getTargetPlatformViewFrame
{
CGFloat scaledLeft = self.frameLeft / self.screenScale;
CGFloat scaledTop = self.frameTop / self.screenScale;
StageViewController* controller = [StageApplication getApplicationTopViewController];
if (!controller.navigationController.navigationBarHidden) {
scaledTop += [self getSafeAreaHeight];
}
CGFloat scaledWidth = self.frameWidth / self.screenScale;
CGFloat scaledHeight = self.frameHeight / self.screenScale;
return CGRectMake(scaledLeft, scaledTop, scaledWidth, scaledHeight);
}
- (void)updateNativeFrame
{
UIView* platformView = [self getPlatformView];
if (platformView == nullptr) {
return;
}
if (_metalTextureRenderer != nullptr) {
StageViewController* controller = [StageApplication getApplicationTopViewController];
if (controller.navigationController.navigationBarHidden) {
_metalTextureRenderer.frame =
CGRectMake(0, 0, controller.view.bounds.size.width, controller.view.bounds.size.height);
} else {
_metalTextureRenderer.frame = CGRectMake(0, -[self getSafeAreaHeight], controller.view.bounds.size.width,
controller.view.bounds.size.height + [self getSafeAreaHeight]);
}
}
CGRect targetFrame = [self getTargetPlatformViewFrame];
if (!CGRectEqualToRect(platformView.frame, targetFrame)) {
platformView.frame = targetFrame;
}
if (_isVideo && _playerLayer != nullptr) {
_playerLayer.frame = platformView.bounds;
}
}
-(UIView*)getPlatformView {
NSObject<IPlatformView>* embeddedView = self.curPlatformView;
if (embeddedView == nullptr) {
return nil;
}
UIView* platformView = [embeddedView view];
return platformView;
}
- (void)releaseDisplayLinkAndCallbacks
{
if (self.displayLink != nullptr) {
[self.displayLink invalidate];
self.displayLink = nil;
}
self.onEvent = nil;
if (self.callSyncMethodMap != nullptr) {
for (id key in self.callSyncMethodMap) {
IAceOnCallSyncResourceMethod block = [self.callSyncMethodMap objectForKey:key];
block = nil;
}
[self.callSyncMethodMap removeAllObjects];
self.callSyncMethodMap = nil;
}
}
- (void)unregisterBufferIfNeeded:(NSObject<AcePlatformViewDelegate>*)viewDelegate
textureResourceId:(int64_t)releaseTextureResourceId
isVideo:(bool)isVideo
{
if (!isVideo && releaseTextureResourceId > 0) {
[viewDelegate unregisterBufferWithInstanceId:self.instanceId textureId:releaseTextureResourceId];
}
}
- (void)disposePlatformView:(UIView*)platformView
embeddedView:(NSObject<IPlatformView>*)embeddedView
renderer:(MetalTextureRenderer*)renderer
{
if (renderer != nullptr) {
[renderer destroy];
}
if (platformView == nullptr) {
if (embeddedView != nullptr) {
[embeddedView onDispose];
}
return;
}
platformView.userInteractionEnabled = NO;
platformView.hidden = YES;
[platformView removeFromSuperview];
StageViewController* controller = [StageApplication getApplicationTopViewController];
UIWindow* window = controller ? controller.view.window : nil;
if (window != nullptr) {
[window addSubview:platformView];
}
if (embeddedView != nullptr) {
[embeddedView onDispose];
}
[platformView removeFromSuperview];
}
- (void)clearReleaseState
{
if (self.renderTexture != nullptr) {
self.renderTexture = nil;
}
if (_player != nil) {
_player = nil;
}
if (_playerLayer != nil) {
_playerLayer = nil;
}
self.pendingScaleParams = nil;
self.pendingTransformMatrixParams = nil;
self.pendingTranslateParams = nil;
self.pendingRotateParams = nil;
self.delegate = nil;
_scrollEdgeEffectView = nil;
}
- (void)releaseObject
{
[self releaseDisplayLinkAndCallbacks];
NSObject<IPlatformView>* embeddedView = self.curPlatformView;
UIView* platformView = embeddedView ? [embeddedView view] : nil;
MetalTextureRenderer* renderer = _metalTextureRenderer;
NSObject<AcePlatformViewDelegate>* viewDelegate = self.delegate;
int32_t releaseInstanceId = self.instanceId;
int64_t releaseTextureResourceId = self.textureResourceId;
bool isVideo = _isVideo;
self.curPlatformView = nil;
_metalTextureRenderer = nil;
void (^releaseBlock)(void) = ^{
[self unregisterBufferIfNeeded:viewDelegate textureResourceId:releaseTextureResourceId isVideo:isVideo];
[self disposePlatformView:platformView embeddedView:embeddedView renderer:renderer];
};
if ([NSThread isMainThread]) {
releaseBlock();
} else {
dispatch_async(dispatch_get_main_queue(), releaseBlock);
}
[self clearReleaseState];
}
- (void)onActivityResume
{
if (self.displayLink != nullptr) {
[self updateDisplayLinkFrameRate];
self.displayLink.paused = NO;
LOGI("AcePlatformView displayLink resume.");
}
}
- (void)onActivityPause
{
if (self.displayLink != nullptr) {
self.displayLink.paused = YES;
LOGI("AcePlatformView displayLink paused.");
}
}
- (UIView *)findWindowViewInView:(UIView *)view {
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:[WindowView class]]) {
return subview;
}
}
return nil;
}
- (void)initWithSurfaceView:(UIView*)surfaceView {
StageViewController* controller = [StageApplication getApplicationTopViewController];
surfaceView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
UIView *windowView = [controller getWindowView];
windowView.backgroundColor = [UIColor clearColor];
[windowView.superview insertSubview:surfaceView belowSubview:windowView];
}
- (void)initWithEmbeddedView:(UIView*)embeddedView {
if (_isVideo) {
StageViewController* controller = [StageApplication getApplicationTopViewController];
embeddedView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
if (![controller isKindOfClass:[StageViewController class]]) {
return;
}
UIView *windowView = [controller getWindowView];
[windowView.superview insertSubview:embeddedView belowSubview:windowView];
return;
}
if (_metalTextureRenderer != nullptr) {
[_metalTextureRenderer ensureMetalSetup:embeddedView];
[_metalTextureRenderer addSubview:embeddedView];
StageViewController* controller = [StageApplication getApplicationTopViewController];
embeddedView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
UIView *windowView = [controller getWindowView];
[windowView.superview insertSubview:_metalTextureRenderer belowSubview:windowView];
}
}
- (CGFloat)getSafeAreaHeight
{
CGFloat statusBarHeight = 0;
if (@available(iOS 13.0, *)) {
UIWindow* window = [UIApplication sharedApplication].windows.firstObject;
statusBarHeight = window.safeAreaInsets.top;
} else {
statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
}
CGFloat navigationBarHeight = 0;
StageViewController* controller = [StageApplication getApplicationTopViewController];
if (controller.navigationController != nullptr) {
navigationBarHeight = controller.navigationController.navigationBar.frame.size.height;
}
return statusBarHeight + navigationBarHeight;
}
@end