下载地址:
开发工具:Xcode8 真机上运行,模拟器会报错
开发语言:Objective-C
SDK:RTMPCHybridEngine-IOS
实现:模仿映客,包含推流、拉流、连麦、弹幕、美颜、发礼物等。
效果图:
热门.png 附近.jpg 个人中心.png 搜索.jpg 游客端(直播).jpg 送礼物.jpg 弹框.jpg 主播端(直播).jpgGif效果图:
主界面.gif 直播间.gif 送礼物.gif 连麦.gif比较
RTMPCHybridEngine是为移动端应用量身打造的基于RTMP和RTC混合引擎的连麦互动流媒体直播系统。通过集成本SDK,只需几个简单API调用,便可实现一套完整的连线麦互动直播流媒体应用。包含了流媒体应用中:『采集->编码->传输->解码->播放->连麦视频互动』的所有步骤。
AnyRTC云通讯创新性实现了RTMP+RTC的技术融合,使用RTC技术实现基于标准的RTMP协议的互动连线视频直播,在视频处理,音频降噪,码率控制,硬件加速,美颜滤镜,实时通讯,移动端性能等方面性能优越。
优势:
-
超低延时
-
超低内存
-
无缝连接(原有方案不变的情况,直接嵌入SDK)
-
文字互动、弹幕消息
-
人员上下线
-
多达4人同时在线连麦视频互动
注意事项:
1、直播间页面为香港卫视rtmp数据(当有两台设备可实现适时直播);
2、演示推流、拉流、连麦至少需要两部iPhone手机;
3、一部手机进入直播页面点击开启直播,开启显示成功后,另一部手机在热门列表下拉刷新,点击列表进入直播间页面。
4、成功后游客端点击左上方连麦按钮,同意后进行连麦。
项目结构:
结构.png常见问题:
1、无法推流、拉流、连麦->AppDelegate配置SDK失效->官方文档
2、SDK接入需要添加网络权限、访问相机、麦克风权限;
3、可能遇到的问题:
ld: library not found for -XXX
clang: error: linker command failed with exit code 1 (use -v to see invocation)
解决:Build Settings->Other Linker Flags->删除XXX
4、 ios Undefined symbols for architecture arm64
解决
开发过程:
准备工作:项目中图片资源通过PP助手获取映客ipa包,接口数据通过抓包工具获得。
SDK集成:项目主要依赖RTMPC SDK 具体请参考官方SDK文档
Code:
++++++++++++游客端拉流、连麦的实现++++++++++++
- (void)repareStartPlay{
//初始化
self.guestKit = [[RTMPCGuestKit alloc] initWithDelegate:self withCaptureDevicePosition:RTMPC_SCRN_Portrait withLivingAudioOnly:NO];
//只支持rtmp流
if (!self.livingItem) {
//假数据 rtmp://live.hkstv.hk.lxdns.com/live/hks
[self.guestKit StartRtmpPlay:@"rtmp://live.hkstv.hk.lxdns.com/live/hks" andRender:self.showView];
} else{
//如果有两部手机运行,且创建项目,可实现实时推流、拉流
[self.guestKit StartRtmpPlay:self.livingItem.rtmp_url andRender:self.showView];
}
// [self.guestKit StartRtmpPlay:self.adressStr andRender:self.showView];
// [self.guestKit setVideoContentMode:VideoShowModeScaleAspectFill];
}
//游客端RTC连麦
- (void)prepareRtc{
self.guestKit.rtc_delegate = self;
NSString *strUrl = [NSString stringWithFormat:@"http://img2.inke.cn/MTQ4MTI4NTI4NTMxMyMzNjYjanBn.jpg"];
NSDictionary *customDict = [NSDictionary dictionaryWithObjectsAndKeys:@"AnyRTC",@"nickName",strUrl,@"headUrl" ,nil];
NSString *customStr = [self JSONTOString:customDict];
//当andyrtcId为空,假数据,RTC连麦失败
[self.guestKit JoinRTCLine:self.livingItem.andyrtcId andCustomID:@"游客A" andUserData:customStr];
}
++++++++++++主播端推流、连麦的实现++++++++++++
#pragma 推流
- (void)prepareStream{
//初始化主播端 推流代理
self.hosterKit = [[RTMPCHosterKit alloc]initWithDelegate:self withCaptureDevicePosition:RTMPC_SCRN_Portrait withLivingAudioOnly:NO];
//推流模式
[self.hosterKit SetNetAdjustMode:RTMP_NA_Fast];
[self.hosterKit SetVideoCapturer:self.view andUseFront:YES];
[self.hosterKit SetBeautyEnable:YES];
//推流质量
[self.hosterKit SetVideoMode:RTMPC_Video_SD];
}
//rtc连麦
- (void)prepareRtc{
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"hostID",@"hosterId",self.rtmpUrl,@"rtmp_url",self.hlsUrl,@"hls_url",self.livingName?self.livingName:[self getTopName],@"topic",self.randomStr,@"anyrtcId",[NSNumber numberWithBool:_isAudioLiving],@"isAudioOnly", nil];
NSString *jsonString = [self JSONTOString:dict];
//rtc的代理
self.hosterKit.rtc_delegate = self;
//打开RTC连麦功能
[self.hosterKit OpenRTCLine:self.randomStr andCustomID:@"主播端" andUserData:jsonString andRtcArea:@"CN"];
}
NearbyController 使用SDWebImage内存暴增
static BOOL SDImageCacheOldShouldDecompressImages = YES;
static BOOL SDImagedownloderOldShouldDecompressImages = YES;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
index = 0;
[self.view addSubview:self.nearCollectView];
[self.locationManager startUpdatingLocation];
self.view.backgroundColor = [UIColor whiteColor];
SDImageCache *canche = [SDImageCache sharedImageCache];
SDImageCacheOldShouldDecompressImages = canche.shouldDecompressImages;
canche.shouldDecompressImages = NO;
//保存原设置,禁用解压缩
SDWebImageDownloader *downloder = [SDWebImageDownloader sharedDownloader];
SDImagedownloderOldShouldDecompressImages = downloder.shouldDecompressImages;
downloder.shouldDecompressImages = NO;
}
- (void)dealloc{
self.nearCollectView.dataSource = nil;
self.nearCollectView.delegate = nil;
self.locationManager.delegate = nil;
//恢复原设置
SDImageCache *canche = [SDImageCache sharedImageCache];
canche.shouldDecompressImages = SDImageCacheOldShouldDecompressImages;
SDWebImageDownloader *downloder = [SDWebImageDownloader sharedDownloader];
downloder.shouldDecompressImages = SDImagedownloderOldShouldDecompressImages;
}
搜索页长按图像动画的实现:
//长按
- (void)longPressClick:(UILongPressGestureRecognizer *)sender{
NSInteger tag = [sender view].tag;
if (sender.state == UIGestureRecognizerStateBegan) {
[UIView animateWithDuration:0.5 animations:^{
switch (tag) {
case 50:
self.imageViewF.transform = CGAffineTransformMakeScale(0.8, 0.8);
break;
case 51:
self.imageViewS.transform = CGAffineTransformMakeScale(0.8, 0.8);
break;
case 52:
self.imageViewT.transform = CGAffineTransformMakeScale(0.8, 0.8);
break;
default:
break;
}
}];
} else if (sender.state == UIGestureRecognizerStateEnded) {
[UIView animateWithDuration:0.5 animations:^{
self.imageViewF.transform = CGAffineTransformMakeScale(1.0, 1.0);
self.imageViewS.transform = CGAffineTransformMakeScale(1.0, 1.0);
self.imageViewT.transform = CGAffineTransformMakeScale(1.0, 1.0);
}];
}
}
+++++++++++++++部分动画的实现+++++++++++++++
弹框实现 CameraView
//关键帧 damp阻尼
[UIView animateWithDuration:0.8 delay:0.1 usingSpringWithDamping:0.6 initialSpringVelocity:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
_liveButton.frame = CGRectMake(0, LiveGetY, LiveWidth, LiveWidth);
} completion:^(BOOL finished) {
}];
附近动画NearbyController
- (void)showAnimation{
self.iconImageView.layer.transform = CATransform3DMakeScale(0.1, 0.1, 1);
[UIView animateWithDuration:0.25 animations:^{
self.iconImageView.layer.transform = CATransform3DMakeScale(1, 1, 1);
}];
}
进入主播页动画CameraViewController:
- (void)viewWillAppear:(BOOL)animated{
//由小变大的圆形动画
CGFloat radius = [UIScreen mainScreen].bounds.size.height;
UIBezierPath *startMask = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.view.centerX, self.view.centerY, 0, 0)];
UIBezierPath *endMask = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(CGRectMake(self.view.centerX, self.view.centerY, 0, 0), -radius, -radius)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = endMask.CGPath;
maskLayer.backgroundColor = (__bridge CGColorRef)([UIColor whiteColor]);
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
maskLayerAnimation.fromValue = (__bridge id)(startMask.CGPath);
maskLayerAnimation.toValue = (__bridge id)((endMask.CGPath));
maskLayerAnimation.duration = 0.8f;
[maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
self.view.layer.mask = maskLayer;
}
声明
本项目仅供学习,侵权立删!
总结
第一次仿写直播项目,希望能帮到有需要的朋友,之前也没接触过直播类的项目,写的不好的地方见谅!项目中发现什么问题@我或者私信我,大家一起交流学习,有时间会修复项目中的BUG,有时间更新。
网友评论