initial commit
This commit is contained in:
323
Example/SellyCloudSDK/Live/SCLiveStatsView.m
Normal file
323
Example/SellyCloudSDK/Live/SCLiveStatsView.m
Normal file
@@ -0,0 +1,323 @@
|
||||
//
|
||||
// SCLiveStatsView.m
|
||||
// SellyCloudSDK_Example
|
||||
//
|
||||
// Created by Caleb on 21/10/25.
|
||||
// Copyright © 2025 Caleb. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SCLiveStatsView.h"
|
||||
|
||||
@interface SCLiveStatsView ()
|
||||
@property (nonatomic, strong) UIVisualEffectView *blurView;
|
||||
@property (nonatomic, strong) UIView *headerView;
|
||||
@property (nonatomic, strong) UIView *contentView;
|
||||
@property (nonatomic, strong) UIStackView *statsStackView;
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
|
||||
// Stats items
|
||||
@property (nonatomic, strong) UIView *protocolItem;
|
||||
@property (nonatomic, strong) UIView *appCpuItem;
|
||||
@property (nonatomic, strong) UIView *sysCpuItem;
|
||||
@property (nonatomic, strong) UIView *bitrateItem;
|
||||
@property (nonatomic, strong) UIView *fpsItem;
|
||||
@property (nonatomic, strong) UIView *rttItem;
|
||||
@property (nonatomic, strong) UIView *packetLossItem; // 新增:丢包率
|
||||
|
||||
@property (nonatomic, strong) UILabel *protocolLabel;
|
||||
@property (nonatomic, strong) UILabel *appCpuLabel;
|
||||
@property (nonatomic, strong) UILabel *sysCpuLabel;
|
||||
@property (nonatomic, strong) UILabel *bitrateLabel;
|
||||
@property (nonatomic, strong) UILabel *fpsLabel;
|
||||
@property (nonatomic, strong) UILabel *rttLabel;
|
||||
@property (nonatomic, strong) UILabel *packetLossLabel; // 新增:丢包率标签
|
||||
|
||||
@property (nonatomic, strong) UIButton *toggleButton;
|
||||
@property (nonatomic, assign) BOOL isExpanded;
|
||||
|
||||
@property (nonatomic, strong) MASConstraint *contentViewHeightConstraint;
|
||||
@end
|
||||
|
||||
@implementation SCLiveStatsView
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_isExpanded = YES; // 默认展开
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
// 毛玻璃背景
|
||||
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemUltraThinMaterialDark];
|
||||
_blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
|
||||
_blurView.layer.cornerRadius = 12;
|
||||
_blurView.layer.masksToBounds = YES;
|
||||
[self addSubview:_blurView];
|
||||
[_blurView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
// 边框
|
||||
self.layer.cornerRadius = 12;
|
||||
self.layer.borderWidth = 0.5;
|
||||
self.layer.borderColor = [[UIColor whiteColor] colorWithAlphaComponent:0.3].CGColor;
|
||||
|
||||
// 标题栏(可点击)
|
||||
_headerView = [[UIView alloc] init];
|
||||
[_blurView.contentView addSubview:_headerView];
|
||||
[_headerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.left.right.equalTo(_blurView.contentView);
|
||||
make.height.offset(44);
|
||||
}];
|
||||
|
||||
// 添加点击手势
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleButtonTapped)];
|
||||
[_headerView addGestureRecognizer:tap];
|
||||
|
||||
// 标题
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.text = @"📊 直播数据";
|
||||
_titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightSemibold];
|
||||
_titleLabel.textColor = [UIColor whiteColor];
|
||||
[_headerView addSubview:_titleLabel];
|
||||
[_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(_headerView);
|
||||
make.left.offset(12);
|
||||
}];
|
||||
|
||||
// 展开/收起按钮
|
||||
_toggleButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
// 默认展开状态,显示向上箭头(表示可以收起)
|
||||
[_toggleButton setImage:[UIImage systemImageNamed:@"chevron.up.circle.fill"] forState:UIControlStateNormal];
|
||||
_toggleButton.tintColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8];
|
||||
[_toggleButton addTarget:self action:@selector(toggleButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_headerView addSubview:_toggleButton];
|
||||
[_toggleButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(_headerView);
|
||||
make.right.offset(-12);
|
||||
make.width.height.offset(24);
|
||||
}];
|
||||
|
||||
// 分割线
|
||||
UIView *separatorLine = [[UIView alloc] init];
|
||||
separatorLine.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.2];
|
||||
[_blurView.contentView addSubview:separatorLine];
|
||||
[separatorLine mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(_headerView.mas_bottom);
|
||||
make.left.offset(12);
|
||||
make.right.offset(-12);
|
||||
make.height.offset(0.5);
|
||||
}];
|
||||
|
||||
// 内容容器
|
||||
_contentView = [[UIView alloc] init];
|
||||
_contentView.clipsToBounds = YES;
|
||||
[_blurView.contentView addSubview:_contentView];
|
||||
[_contentView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(separatorLine.mas_bottom);
|
||||
make.left.right.bottom.equalTo(_blurView.contentView);
|
||||
// 保存高度约束的引用,用于动态调整
|
||||
self.contentViewHeightConstraint = make.height.offset(0);
|
||||
}];
|
||||
|
||||
// StackView 用于统一管理数据项
|
||||
_statsStackView = [[UIStackView alloc] init];
|
||||
_statsStackView.axis = UILayoutConstraintAxisVertical;
|
||||
_statsStackView.spacing = 6;
|
||||
_statsStackView.distribution = UIStackViewDistributionFill; // 改为 Fill,因为我们会手动设置每个 item 的高度
|
||||
_statsStackView.alignment = UIStackViewAlignmentFill;
|
||||
[_contentView addSubview:_statsStackView];
|
||||
[_statsStackView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.offset(12);
|
||||
make.left.offset(12);
|
||||
make.right.offset(-12);
|
||||
make.bottom.offset(-12);
|
||||
}];
|
||||
|
||||
// 创建数据项
|
||||
_protocolItem = [self createStatsItemWithIcon:@"network" label:_protocolLabel = [self createValueLabel]];
|
||||
_appCpuItem = [self createStatsItemWithIcon:@"cpu" label:_appCpuLabel = [self createValueLabel]];
|
||||
_sysCpuItem = [self createStatsItemWithIcon:@"cpu.fill" label:_sysCpuLabel = [self createValueLabel]];
|
||||
_bitrateItem = [self createStatsItemWithIcon:@"speedometer" label:_bitrateLabel = [self createValueLabel]];
|
||||
_fpsItem = [self createStatsItemWithIcon:@"film" label:_fpsLabel = [self createValueLabel]];
|
||||
_rttItem = [self createStatsItemWithIcon:@"timer" label:_rttLabel = [self createValueLabel]];
|
||||
_packetLossItem = [self createStatsItemWithIcon:@"exclamationmark.triangle" label:_packetLossLabel = [self createValueLabel]]; // 新增丢包率项
|
||||
|
||||
[_statsStackView addArrangedSubview:_protocolItem];
|
||||
[_statsStackView addArrangedSubview:_appCpuItem];
|
||||
[_statsStackView addArrangedSubview:_sysCpuItem];
|
||||
[_statsStackView addArrangedSubview:_bitrateItem];
|
||||
[_statsStackView addArrangedSubview:_fpsItem];
|
||||
[_statsStackView addArrangedSubview:_rttItem];
|
||||
[_statsStackView addArrangedSubview:_packetLossItem]; // 添加到 StackView
|
||||
|
||||
// 设置每个 item 的高度
|
||||
for (UIView *item in _statsStackView.arrangedSubviews) {
|
||||
[item mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.offset(20);
|
||||
}];
|
||||
}
|
||||
|
||||
// 计算展开时的内容高度:item高度 * 数量 + 间距 * (数量-1) + 上下padding
|
||||
CGFloat expandedHeight = 20 * 7 + 6 * 6 + 24; // 20*7 + 6*6 + 12*2 = 200
|
||||
[self.contentViewHeightConstraint setOffset:expandedHeight];
|
||||
}
|
||||
|
||||
- (UIView *)createStatsItemWithIcon:(NSString *)iconName label:(UILabel *)valueLabel {
|
||||
UIView *container = [[UIView alloc] init];
|
||||
|
||||
// 图标
|
||||
UIImageView *iconView = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:iconName]];
|
||||
iconView.tintColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8];
|
||||
iconView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[container addSubview:iconView];
|
||||
[iconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.offset(0);
|
||||
make.centerY.equalTo(container);
|
||||
make.width.height.offset(14);
|
||||
}];
|
||||
|
||||
// 值标签
|
||||
[container addSubview:valueLabel];
|
||||
[valueLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(iconView.mas_right).offset(8);
|
||||
make.centerY.equalTo(container);
|
||||
make.right.offset(0);
|
||||
}];
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
- (UILabel *)createValueLabel {
|
||||
UILabel *label = [[UILabel alloc] init];
|
||||
label.font = [UIFont monospacedSystemFontOfSize:11 weight:UIFontWeightMedium];
|
||||
label.textColor = [UIColor whiteColor];
|
||||
label.adjustsFontSizeToFitWidth = YES;
|
||||
label.minimumScaleFactor = 0.7;
|
||||
label.numberOfLines = 1;
|
||||
return label;
|
||||
}
|
||||
|
||||
- (void)toggleButtonTapped {
|
||||
self.isExpanded = !self.isExpanded;
|
||||
|
||||
// 计算高度
|
||||
CGFloat expandedHeight = 20 * 7 + 6 * 6 + 24; // item高度 * 数量 + 间距 * (数量-1) + 上下padding
|
||||
CGFloat collapsedHeight = 0;
|
||||
|
||||
[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.8 initialSpringVelocity:0.5 options:UIViewAnimationOptionCurveEaseInOut animations:^{
|
||||
if (self.isExpanded) {
|
||||
// 展开状态:显示向上箭头(表示可以收起)
|
||||
[self.toggleButton setImage:[UIImage systemImageNamed:@"chevron.up.circle.fill"] forState:UIControlStateNormal];
|
||||
[self.contentViewHeightConstraint setOffset:expandedHeight];
|
||||
} else {
|
||||
// 收起状态:显示向下箭头(表示可以展开)
|
||||
[self.toggleButton setImage:[UIImage systemImageNamed:@"chevron.down.circle.fill"] forState:UIControlStateNormal];
|
||||
[self.contentViewHeightConstraint setOffset:collapsedHeight];
|
||||
}
|
||||
|
||||
// 显示/隐藏内容(用 alpha 做淡入淡出效果)
|
||||
self.contentView.alpha = self.isExpanded ? 1.0 : 0.0;
|
||||
|
||||
// 更新约束触发布局更新
|
||||
[self.superview layoutIfNeeded];
|
||||
} completion:nil];
|
||||
|
||||
// 触觉反馈
|
||||
UIImpactFeedbackGenerator *feedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
|
||||
[feedback impactOccurred];
|
||||
}
|
||||
|
||||
- (void)setStats:(SellyLivePusherStats *)stats {
|
||||
_stats = stats;
|
||||
|
||||
// 协议 + streamId
|
||||
if (self.streamId.length > 0) {
|
||||
self.protocolLabel.text = [NSString stringWithFormat:@"%@,streamId:%@", stats.protocol, self.streamId];
|
||||
} else {
|
||||
self.protocolLabel.text = stats.protocol;
|
||||
}
|
||||
|
||||
// App CPU(独立显示)
|
||||
UIColor *appCpuColor = [self colorForCPU:stats.appCpu];
|
||||
NSMutableAttributedString *appCpuText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"App CPU: %ld%%", stats.appCpu]];
|
||||
[appCpuText addAttribute:NSForegroundColorAttributeName value:appCpuColor range:NSMakeRange(0, appCpuText.length)];
|
||||
self.appCpuLabel.attributedText = appCpuText;
|
||||
|
||||
// System CPU(独立显示)
|
||||
UIColor *sysCpuColor = [self colorForCPU:stats.systemCpu];
|
||||
NSMutableAttributedString *sysCpuText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"Sys CPU: %ld%%", stats.systemCpu]];
|
||||
[sysCpuText addAttribute:NSForegroundColorAttributeName value:sysCpuColor range:NSMakeRange(0, sysCpuText.length)];
|
||||
self.sysCpuLabel.attributedText = sysCpuText;
|
||||
|
||||
// 码率(音频 + 视频)
|
||||
NSInteger totalBitrate = stats.audioBitrate + stats.videoBitrate;
|
||||
self.bitrateLabel.text = [NSString stringWithFormat:@"%ld kbps (A:%ld V:%ld)",
|
||||
totalBitrate, stats.audioBitrate, stats.videoBitrate];
|
||||
|
||||
// FPS(根据帧率设置颜色)
|
||||
UIColor *fpsColor = [self colorForFPS:stats.fps];
|
||||
NSMutableAttributedString *fpsText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"FPS: %ld", stats.fps]];
|
||||
[fpsText addAttribute:NSForegroundColorAttributeName value:fpsColor range:NSMakeRange(0, fpsText.length)];
|
||||
self.fpsLabel.attributedText = fpsText;
|
||||
|
||||
// RTT(根据延迟设置颜色)
|
||||
UIColor *rttColor = [self colorForRTT:stats.rtt];
|
||||
NSMutableAttributedString *rttText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"RTT: %ld ms", stats.rtt]];
|
||||
[rttText addAttribute:NSForegroundColorAttributeName value:rttColor range:NSMakeRange(0, rttText.length)];
|
||||
self.rttLabel.attributedText = rttText;
|
||||
|
||||
// 丢包率(根据丢包率设置颜色)
|
||||
UIColor *packetLossColor = [self colorForPacketLoss:stats.packetLossRate];
|
||||
NSMutableAttributedString *packetLossText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"丢包: %.2f%%", stats.packetLossRate]];
|
||||
[packetLossText addAttribute:NSForegroundColorAttributeName value:packetLossColor range:NSMakeRange(0, packetLossText.length)];
|
||||
self.packetLossLabel.attributedText = packetLossText;
|
||||
}
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (UIColor *)colorForCPU:(NSInteger)cpu {
|
||||
if (cpu < 40) {
|
||||
return [UIColor colorWithRed:0.3 green:0.85 blue:0.39 alpha:1.0]; // 绿色
|
||||
} else if (cpu < 70) {
|
||||
return [UIColor colorWithRed:1.0 green:0.8 blue:0.0 alpha:1.0]; // 黄色
|
||||
} else {
|
||||
return [UIColor colorWithRed:1.0 green:0.23 blue:0.19 alpha:1.0]; // 红色
|
||||
}
|
||||
}
|
||||
|
||||
- (UIColor *)colorForFPS:(NSInteger)fps {
|
||||
if (fps >= 25) {
|
||||
return [UIColor colorWithRed:0.3 green:0.85 blue:0.39 alpha:1.0]; // 绿色
|
||||
} else if (fps >= 15) {
|
||||
return [UIColor colorWithRed:1.0 green:0.8 blue:0.0 alpha:1.0]; // 黄色
|
||||
} else {
|
||||
return [UIColor colorWithRed:1.0 green:0.23 blue:0.19 alpha:1.0]; // 红色
|
||||
}
|
||||
}
|
||||
|
||||
- (UIColor *)colorForRTT:(NSInteger)rtt {
|
||||
if (rtt < 100) {
|
||||
return [UIColor colorWithRed:0.3 green:0.85 blue:0.39 alpha:1.0]; // 绿色
|
||||
} else if (rtt < 300) {
|
||||
return [UIColor colorWithRed:1.0 green:0.8 blue:0.0 alpha:1.0]; // 黄色
|
||||
} else {
|
||||
return [UIColor colorWithRed:1.0 green:0.23 blue:0.19 alpha:1.0]; // 红色
|
||||
}
|
||||
}
|
||||
|
||||
- (UIColor *)colorForPacketLoss:(CGFloat)packetLoss {
|
||||
if (packetLoss < 2.0) {
|
||||
return [UIColor colorWithRed:0.3 green:0.85 blue:0.39 alpha:1.0]; // 绿色:优秀
|
||||
} else if (packetLoss < 5.0) {
|
||||
return [UIColor colorWithRed:1.0 green:0.8 blue:0.0 alpha:1.0]; // 黄色:一般
|
||||
} else {
|
||||
return [UIColor colorWithRed:1.0 green:0.23 blue:0.19 alpha:1.0]; // 红色:差
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user