initial commit

This commit is contained in:
Caleb
2026-03-01 15:59:27 +08:00
commit a9e97d56cb
1426 changed files with 172367 additions and 0 deletions

View 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