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,384 @@
//
// SCPlayerConfigView.m
// SellyCloudSDK_Example
//
// Created by Caleb on 16/12/25.
// Copyright © 2025 Caleb. All rights reserved.
//
#import "SCPlayerConfigView.h"
#import <Masonry/Masonry.h>
// key
static NSString * const kSCPlayerConfigProtocol = @"SCPlayerConfigProtocol";
static NSString * const kSCPlayerConfigStreamId = @"SCPlayerConfigStreamId";
@implementation SCPlayerConfig
- (void)saveToUserDefaults {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:self.protocol forKey:kSCPlayerConfigProtocol];
if (self.streamId) {
[defaults setObject:self.streamId forKey:kSCPlayerConfigStreamId];
}
[defaults synchronize];
}
+ (instancetype)loadFromUserDefaults {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
SCPlayerConfig *config = [[SCPlayerConfig alloc] init];
// RTMP
if ([defaults objectForKey:kSCPlayerConfigProtocol]) {
config.protocol = [defaults integerForKey:kSCPlayerConfigProtocol];
} else {
config.protocol = SellyLiveMode_RTMP;
}
// streamId
NSString *streamId = [defaults objectForKey:kSCPlayerConfigStreamId];
config.streamId = streamId ? streamId : @"test";
return config;
}
@end
@interface SCPlayerConfigView () <UITextFieldDelegate>
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UIVisualEffectView *backgroundView;
@property (nonatomic, strong) UISegmentedControl *protocolSegment;
@property (nonatomic, strong) UITextField *streamIdField;
@property (nonatomic, strong) UIButton *playButton;
@property (nonatomic, copy) void(^callback)(SCPlayerConfig *config);
@property (nonatomic, strong) MASConstraint *backgroundViewCenterYConstraint; //
@end
@implementation SCPlayerConfigView
- (instancetype)init {
self = [super init];
if (self) {
[self setupView];
[self loadSavedConfig];
[self registerKeyboardNotifications];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setupView {
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
// -
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(backgroundTapped:)];
tapGesture.cancelsTouchesInView = NO; //
[self addGestureRecognizer:tapGesture];
//
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemMaterial];
_backgroundView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
_backgroundView.layer.cornerRadius = 16;
_backgroundView.layer.masksToBounds = YES;
_backgroundView.userInteractionEnabled = YES; //
[self addSubview:_backgroundView];
[_backgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
//
make.centerX.equalTo(self);
// centerY 便
self.backgroundViewCenterYConstraint = make.centerY.equalTo(self).offset(30); // 30pt
make.left.offset(40);
make.right.offset(-40);
}];
//
_contentView = [[UIView alloc] init];
[_backgroundView.contentView addSubview:_contentView];
[_contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(_backgroundView.contentView).insets(UIEdgeInsetsMake(24, 24, 24, 24));
}];
//
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = @"播放配置";
titleLabel.font = [UIFont systemFontOfSize:22 weight:UIFontWeightBold];
titleLabel.textAlignment = NSTextAlignmentCenter;
[_contentView addSubview:titleLabel];
[titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_contentView);
make.left.right.equalTo(_contentView);
make.height.offset(30);
}];
//
UILabel *protocolLabel = [[UILabel alloc] init];
protocolLabel.text = @"播放协议";
protocolLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
[_contentView addSubview:protocolLabel];
[protocolLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(titleLabel.mas_bottom).offset(24);
make.left.equalTo(_contentView);
make.height.offset(22);
}];
//
_protocolSegment = [[UISegmentedControl alloc] initWithItems:@[@"RTMP", @"RTC"]];
_protocolSegment.selectedSegmentIndex = 0;
[_contentView addSubview:_protocolSegment];
[_protocolSegment mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(protocolLabel.mas_bottom).offset(8);
make.left.right.equalTo(_contentView);
make.height.offset(36);
}];
// Stream ID
UILabel *streamIdLabel = [[UILabel alloc] init];
streamIdLabel.text = @"Stream ID / URL。请输入 Stream ID 或完整 URL";
streamIdLabel.font = [UIFont systemFontOfSize:14];
streamIdLabel.numberOfLines = 0;
[_contentView addSubview:streamIdLabel];
[streamIdLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_protocolSegment.mas_bottom).offset(24);
make.left.equalTo(_contentView);
make.right.equalTo(_contentView);
make.height.offset(34);
}];
// Stream ID
_streamIdField = [[UITextField alloc] init];
_streamIdField.placeholder = @"请输入 Stream ID 或完整 URL";
_streamIdField.borderStyle = UITextBorderStyleRoundedRect;
_streamIdField.font = [UIFont systemFontOfSize:15];
_streamIdField.clearButtonMode = UITextFieldViewModeWhileEditing;
_streamIdField.returnKeyType = UIReturnKeyDone;
_streamIdField.delegate = self;
//
_streamIdField.autocapitalizationType = UITextAutocapitalizationTypeNone;
//
_streamIdField.autocorrectionType = UITextAutocorrectionTypeNo;
// URL URL
_streamIdField.keyboardType = UIKeyboardTypeURL;
[_contentView addSubview:_streamIdField];
[_streamIdField mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(streamIdLabel.mas_bottom).offset(8);
make.left.right.equalTo(_contentView);
make.height.offset(44);
}];
//
_playButton = [UIButton buttonWithType:UIButtonTypeSystem];
[_playButton setTitle:@"开始播放" forState:UIControlStateNormal];
_playButton.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightBold];
_playButton.backgroundColor = [UIColor systemBlueColor];
[_playButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_playButton.layer.cornerRadius = 12;
_playButton.layer.masksToBounds = YES;
[_playButton addTarget:self action:@selector(playButtonTapped) forControlEvents:UIControlEventTouchUpInside];
[_contentView addSubview:_playButton];
[_playButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_streamIdField.mas_bottom).offset(32);
make.left.right.equalTo(_contentView);
make.height.offset(50);
make.bottom.equalTo(_contentView);
}];
//
}
- (void)playButtonTapped {
NSString *streamId = [_streamIdField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (streamId.length == 0) {
//
[self showAlertWithMessage:@"请输入 Stream ID 或 URL"];
return;
}
SCPlayerConfig *config = [[SCPlayerConfig alloc] init];
config.protocol = _protocolSegment.selectedSegmentIndex == 0 ? SellyLiveMode_RTMP : SellyLiveMode_RTC;
config.streamId = streamId;
//
[config saveToUserDefaults];
if (self.callback) {
self.callback(config);
}
[self dismiss];
}
- (void)showAlertWithMessage:(NSString *)message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示"
message:message
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
// viewController
UIViewController *topVC = [self topViewController];
if (topVC) {
[topVC presentViewController:alert animated:YES completion:nil];
}
}
- (UIViewController *)topViewController {
UIViewController *topVC = nil;
UIWindow *keyWindow = nil;
// iOS 13+ keyWindow
if (@available(iOS 13.0, *)) {
NSSet<UIScene *> *scenes = [UIApplication sharedApplication].connectedScenes;
for (UIScene *scene in scenes) {
if ([scene isKindOfClass:[UIWindowScene class]]) {
UIWindowScene *windowScene = (UIWindowScene *)scene;
for (UIWindow *window in windowScene.windows) {
if (window.isKeyWindow) {
keyWindow = window;
break;
}
}
}
if (keyWindow) break;
}
} else {
keyWindow = [UIApplication sharedApplication].keyWindow;
}
topVC = keyWindow.rootViewController;
while (topVC.presentedViewController) {
topVC = topVC.presentedViewController;
}
return topVC;
}
- (void)showInViewController:(UIViewController *)viewController callback:(void (^)(SCPlayerConfig * _Nonnull))callback {
self.callback = callback;
// viewController view window
self.frame = viewController.view.bounds;
[viewController.view addSubview:self];
//
self.alpha = 0;
_backgroundView.transform = CGAffineTransformMakeScale(0.8, 0.8);
[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.8 initialSpringVelocity:0.5 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.alpha = 1;
self.backgroundView.transform = CGAffineTransformIdentity;
} completion:nil];
}
- (void)dismiss {
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 0;
self.backgroundView.transform = CGAffineTransformMakeScale(0.8, 0.8);
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
//
- (void)backgroundTapped:(UITapGestureRecognizer *)gesture {
CGPoint location = [gesture locationInView:self];
//
if (!CGRectContainsPoint(_backgroundView.frame, location)) {
//
NSLog(@"📱 点击背景关闭配置弹窗");
[self dismiss];
}
}
- (void)loadSavedConfig {
SCPlayerConfig *savedConfig = [SCPlayerConfig loadFromUserDefaults];
//
_protocolSegment.selectedSegmentIndex = (savedConfig.protocol == SellyLiveMode_RTMP) ? 0 : 1;
// streamId
if (savedConfig.streamId && savedConfig.streamId.length > 0) {
_streamIdField.text = savedConfig.streamId;
}
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
#pragma mark - Keyboard Notifications
- (void)registerKeyboardNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
//
CGRect keyboardFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardHeight = keyboardFrame.size.height;
// 线
NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
//
CGRect textFieldFrame = [_streamIdField convertRect:_streamIdField.bounds toView:self];
CGFloat textFieldBottom = CGRectGetMaxY(textFieldFrame);
//
CGFloat visibleHeight = self.bounds.size.height - keyboardHeight;
// 20pt
CGFloat desiredSpace = 20;
CGFloat offset = 0;
if (textFieldBottom + desiredSpace > visibleHeight) {
//
offset = -(textFieldBottom + desiredSpace - visibleHeight);
}
//
[self.backgroundViewCenterYConstraint setOffset:offset];
//
[UIView animateWithDuration:duration delay:0 options:(curve << 16) animations:^{
[self layoutIfNeeded];
} completion:nil];
}
- (void)keyboardWillHide:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
// 线
NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
//
[self.backgroundViewCenterYConstraint setOffset:30];
//
[UIView animateWithDuration:duration delay:0 options:(curve << 16) animations:^{
[self layoutIfNeeded];
} completion:nil];
}
@end