// // SLSVideoGridView.m // SellyCloudSDK_Example // // Created by Caleb on 13/11/25. // Copyright © 2025 Caleb. All rights reserved. // #import "SLSVideoGridView.h" @interface SLSVideoGridView () @property (nonatomic, strong) NSMutableDictionary *participants; @property (nonatomic, strong) NSMutableDictionary *tiles; @end @implementation SLSVideoGridView // 代码创建时走这里 - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self commonInit]; } return self; } // xib / storyboard 反序列化时走这里 - (instancetype)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { [self commonInit]; } return self; } // 公共初始化逻辑 - (void)commonInit { _participants = [NSMutableDictionary dictionary]; _tiles = [NSMutableDictionary dictionary]; _spacing = 8.f; _padding = UIEdgeInsetsMake(8, 8, 8, 8); _keepAspect169 = YES; self.backgroundColor = [UIColor blackColor]; } #pragma mark - Public - (SLSVideoTileView *)ensureRenderContainerForUID:(NSString *)uid displayName:(nullable NSString *)name { if (!uid) return nil; SLSParticipant *p = self.participants[uid]; if (!p) { p = [SLSParticipant new]; p.uid = uid; p.displayName = name ?: uid; p.lastActiveAt = [NSDate date]; self.participants[uid] = p; } SLSVideoTileView *tile = self.tiles[uid]; if (!tile) { tile = [[SLSVideoTileView alloc] initWithFrame:CGRectZero]; self.tiles[uid] = tile; [self addSubview:tile]; } [tile updateWithParticipant:p]; [self _layout]; return tile; } - (void)detachUID:(NSString *)uid { if (!uid) return; SLSVideoTileView *tile = self.tiles[uid]; if (tile) { [tile removeFromSuperview]; [self.tiles removeObjectForKey:uid]; } [self.participants removeObjectForKey:uid]; [self _layout]; } - (void)setLevel:(CGFloat)level forUID:(NSString *)uid { SLSParticipant *p = self.participants[uid]; if (!p) return; p.level = MAX(0.f, MIN(level, 1.f)); if (p.level > 0.1) { p.lastActiveAt = [NSDate date]; } SLSVideoTileView *tile = self.tiles[uid]; [tile updateWithParticipant:p]; } - (void)setAudioMuted:(BOOL)muted forUID:(NSString *)uid { SLSParticipant *p = self.participants[uid]; if (!p) return; p.audioMuted = muted; SLSVideoTileView *tile = self.tiles[uid]; [tile updateWithParticipant:p]; } - (void)setVideoMuted:(BOOL)muted forUID:(NSString *)uid { SLSParticipant *p = self.participants[uid]; if (!p) return; p.videoMuted = muted; SLSVideoTileView *tile = self.tiles[uid]; [tile updateWithParticipant:p]; } #pragma mark - Layout - (void)_layout { NSArray *uids = self.participants.allKeys; NSInteger n = uids.count; if (n == 0) return; UIEdgeInsets p = self.padding; CGFloat W = self.bounds.size.width - p.left - p.right; CGFloat H = self.bounds.size.height - p.top - p.bottom; CGFloat s = self.spacing; // ===== 按人数选择固定的行列数 ===== NSInteger cols = 1; NSInteger rows = 1; if (n <= 4) { cols = 2; rows = 2; } else if (n <= 9) { cols = 3; rows = 3; } else if (n <= 16) { cols = 4; rows = 4; } else { // >16 的情况先简单处理:依旧按 4x4 布局,多出来的不显示 cols = 4; rows = 4; } // 每个 cell 的宽 / 高(都按“可用区域的 1/2、1/3、1/4”来,扣掉间距) CGFloat cellW = 0; CGFloat cellH = 0; if (cols > 0) { cellW = floor((W - s * (cols - 1)) / cols); } if (rows > 0) { cellH = floor((H - s * (rows - 1)) / rows); } NSInteger maxCells = cols * rows; NSInteger countToLayout = MIN(n, maxCells); [uids enumerateObjectsUsingBlock:^(NSString *uid, NSUInteger idx, BOOL *stop) { if (idx >= countToLayout) { *stop = YES; return; } NSInteger r = idx / cols; // 第几行 NSInteger c = idx % cols; // 第几列 CGFloat x = p.left + c * (cellW + s); CGFloat y = p.top + r * (cellH + s); SLSVideoTileView *tile = self.tiles[uid]; tile.frame = CGRectMake(x, y, cellW, cellH); SLSParticipant *part = self.participants[uid]; [tile updateWithParticipant:part]; }]; } - (void)layoutSubviews { [super layoutSubviews]; [self _layout]; } @end