Files
SellyCloudSDK_demo/Example/SellyCloudSDK/Live/SCLiveStatsView.m
2026-03-01 15:59:27 +08:00

324 lines
14 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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