From fc0c518a88fdfb7e0e8b12ba3d9d335443b52f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=96=84=E6=A0=8B?= Date: Fri, 10 Dec 2021 17:20:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=832.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modules/Room/Service/NERoomService.swift | 3 +- .../Modules/Seat/Service/NESeatService.swift | 4 +- .../NLiteAVDemo.xcodeproj/project.pbxproj | 68 +- OnlinePK-iOS/NLiteAVDemo/AppDelegate.m | 79 +- OnlinePK-iOS/NLiteAVDemo/AppKey.h | 10 +- .../Contents.json | 21 + .../blockAnchorVoice_blocked@2x.png | Bin 0 -> 9962 bytes .../Contents.json | 21 + .../blockAnchorVoice_normal@2x.png | Bin 0 -> 7523 bytes OnlinePK-iOS/NLiteAVDemo/Info.plist | 2 +- .../anchorMainVc/NELiveBaseViewController.h | 5 +- .../anchorMainVc/NELiveBaseViewController.m | 75 +- .../anchorMainVc/NEPkLiveViewController.m | 322 +++- .../NLiteAVDemo/Live/Anchor/vc/NETSAnchorVC.m | 1625 +++++++++++++++++ .../More/NETSMoreSettingActionSheet.m | 6 +- .../view/BottomPanel/NETSAnchorBottomPanel.h | 3 - .../view/BottomPanel/NETSAnchorBottomPanel.m | 21 +- .../NETSAudienceChatRoomCell.m | 24 +- .../Audience/view/mask/NETSAudienceMask.h | 5 +- .../Audience/view/mask/NETSAudienceMask.m | 40 +- .../Live/Chatroom/NETSLiveChatView.h | 2 + .../Live/Chatroom/NETSLiveChatView.m | 7 + .../NLiteAVDemo/Live/Config/NETSLiveConfig.m | 5 +- .../Live/LivePK/NETSConnectMicService.m | 189 ++ .../NLiteAVDemo/Live/LivePK/NETSPkEnum.h | 9 + .../NLiteAVDemo/Live/LivePK/NETSPkService.m | 638 +++++++ .../Live/LivePK/NETSPushStreamService.h | 9 +- .../Live/LivePK/NETSPushStreamService.m | 55 +- .../CommonService/NEPkRoomService.m | 31 +- .../CommonService/NEPkService.m | 4 +- .../NEPkLiveAttachment.h | 2 +- .../PKResponseModel/NEPKInviteConfigModel.h | 20 + .../PKResponseModel/NEPKInviteConfigModel.m | 19 + .../PKModel/PKResponseModel/NEPkConfigModel.h | 20 + .../PKModel/PKResponseModel/NEPkConfigModel.m | 13 + .../NEPassthroughPkInviteModel.h | 8 +- .../NEPassthroughPkInviteModel.m | 2 + .../{ => Menu}/Service/NEService.h | 0 .../{ => Menu}/Service/NEService.m | 0 .../{ => Menu}/Service/NEServiceTask.h | 0 .../{ => Menu}/Service/NETSRtcConfig.h | 0 .../{ => Menu}/Service/NETSRtcConfig.m | 0 .../NLiteAVDemo/{ => Menu}/Service/NETask.h | 0 .../NLiteAVDemo/{ => Menu}/Service/NETask.m | 0 .../Menu/View/NEMenuViewController.m | 10 +- .../Person/UI/ViewController/NEPersonInfoVC.m | 30 +- .../Person/UI/ViewController/NEPersonVC.m | 5 +- .../NLiteAVDemo/Utils/Account/NEAccount.h | 4 + .../NLiteAVDemo/Utils/Account/NEAccount.m | 37 + .../Utils/Category/UIControl+repeatclick.h | 18 + .../Utils/Category/UIControl+repeatclick.m | 106 ++ .../NLiteAVDemo/Utils/Macro/NSMacro.h | 3 + .../NEGCDTimerManager/NEGCDTimerManager.h | 53 + .../NEGCDTimerManager/NEGCDTimerManager.m | 150 ++ .../Utils/NETSRequest/NETSRequest.m | 5 + OnlinePK-iOS/NLiteAVDemo/beauty/authpack.h | 4 +- .../NLiteAVDemo/en.lproj/Localizable.strings | 3 + OnlinePK-iOS/Podfile | 14 +- OnlinePK-iOS/Podfile.lock | 46 +- 59 files changed, 3630 insertions(+), 225 deletions(-) create mode 100644 OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_blocked.imageset/Contents.json create mode 100644 OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_blocked.imageset/blockAnchorVoice_blocked@2x.png create mode 100644 OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_normal.imageset/Contents.json create mode 100644 OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_normal.imageset/blockAnchorVoice_normal@2x.png create mode 100644 OnlinePK-iOS/NLiteAVDemo/Live/Anchor/vc/NETSAnchorVC.m create mode 100644 OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSConnectMicService.m create mode 100644 OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPkService.m create mode 100644 OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPKInviteConfigModel.h create mode 100644 OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPKInviteConfigModel.m create mode 100644 OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPkConfigModel.h create mode 100644 OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPkConfigModel.m rename OnlinePK-iOS/NLiteAVDemo/{ => Menu}/Service/NEService.h (100%) rename OnlinePK-iOS/NLiteAVDemo/{ => Menu}/Service/NEService.m (100%) rename OnlinePK-iOS/NLiteAVDemo/{ => Menu}/Service/NEServiceTask.h (100%) rename OnlinePK-iOS/NLiteAVDemo/{ => Menu}/Service/NETSRtcConfig.h (100%) rename OnlinePK-iOS/NLiteAVDemo/{ => Menu}/Service/NETSRtcConfig.m (100%) rename OnlinePK-iOS/NLiteAVDemo/{ => Menu}/Service/NETask.h (100%) rename OnlinePK-iOS/NLiteAVDemo/{ => Menu}/Service/NETask.m (100%) create mode 100755 OnlinePK-iOS/NLiteAVDemo/Utils/Category/UIControl+repeatclick.h create mode 100755 OnlinePK-iOS/NLiteAVDemo/Utils/Category/UIControl+repeatclick.m create mode 100644 OnlinePK-iOS/NLiteAVDemo/Utils/NEGCDTimerManager/NEGCDTimerManager.h create mode 100644 OnlinePK-iOS/NLiteAVDemo/Utils/NEGCDTimerManager/NEGCDTimerManager.m diff --git a/OnlinePK-iOS/NELiveRoom/NELiveRoom/Modules/Room/Service/NERoomService.swift b/OnlinePK-iOS/NELiveRoom/NELiveRoom/Modules/Room/Service/NERoomService.swift index 07e00de..e17907e 100644 --- a/OnlinePK-iOS/NELiveRoom/NELiveRoom/Modules/Room/Service/NERoomService.swift +++ b/OnlinePK-iOS/NELiveRoom/NELiveRoom/Modules/Room/Service/NERoomService.swift @@ -163,10 +163,11 @@ class NERoomService: NERoomAPIService,NERoomServiceProtocol,NIMChatManagerDelega completion?(nil, nil, NSError(domain: NELiveRoomErrorDomain, code: NELiveRoomErrorInvalidParams, userInfo: [NSLocalizedDescriptionKey: "uid不合法!!"])) return } - let ret = NERtcEngine.shared().joinChannel(withToken: self.currentUser?.checksum ?? "", channelName: cname, myUid: myUid) { (error, cid, elapesd) in + let ret = NERtcEngine.shared().joinChannel(withToken: self.currentUser?.checksum ?? "", channelName: cname, myUid: myUid) { (error, cid, elapesd,uid) in dispatchGroup.leave() rtcError = error as NSError? } + if ret == 0 { dispatchGroup.enter() } else { diff --git a/OnlinePK-iOS/NELiveRoom/NELiveRoom/Modules/Seat/Service/NESeatService.swift b/OnlinePK-iOS/NELiveRoom/NELiveRoom/Modules/Seat/Service/NESeatService.swift index 911e2bc..78f018e 100644 --- a/OnlinePK-iOS/NELiveRoom/NELiveRoom/Modules/Seat/Service/NESeatService.swift +++ b/OnlinePK-iOS/NELiveRoom/NELiveRoom/Modules/Seat/Service/NESeatService.swift @@ -71,7 +71,7 @@ class NESeatService: NESeatAPIService,NESeatServiceProtocol,NIMPassThroughManage completion?(NSError(domain: NELiveRoomErrorDomain, code: NELiveRoomErrorInvalidResponse, userInfo: [NSLocalizedDescriptionKey: "uid不能为空!"])) return } - NERtcEngine.shared().joinChannel(withToken: NELiveRoom.shared.roomService.currentUser?.checksum ?? "", channelName: cname, myUid: uid ) { (error, cid, elapsed) in + NERtcEngine.shared().joinChannel(withToken: NELiveRoom.shared.roomService.currentUser?.checksum ?? "", channelName: cname, myUid: uid ) { (error, cid, elapsed,uid) in completion?(error) } } @@ -282,7 +282,7 @@ class NESeatService: NESeatAPIService,NESeatServiceProtocol,NIMPassThroughManage let cname = NELiveRoom.shared.roomService.currentRoom?.cname ?? "" let myUid = NELiveRoom.shared.roomService.currentUser?.uid ?? 0 let checksum = NELiveRoom.shared.roomService.currentUser?.checksum ?? "" - NERtcEngine.shared().joinChannel(withToken: checksum, channelName: cname, myUid: myUid) { (error, cid, elapsed) in + NERtcEngine.shared().joinChannel(withToken: checksum, channelName: cname, myUid: myUid) { (error, cid, elapsed,uid) in completion?(error) } } else { diff --git a/OnlinePK-iOS/NLiteAVDemo.xcodeproj/project.pbxproj b/OnlinePK-iOS/NLiteAVDemo.xcodeproj/project.pbxproj index 59e0faf..fd36ab1 100644 --- a/OnlinePK-iOS/NLiteAVDemo.xcodeproj/project.pbxproj +++ b/OnlinePK-iOS/NLiteAVDemo.xcodeproj/project.pbxproj @@ -3,10 +3,11 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ + 1CB4421E46857BC7298FF8EC /* Pods_NEPkDemo_NLiteAVDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 932CEC9E9F60006D616F81BD /* Pods_NEPkDemo_NLiteAVDemo.framework */; }; 390350FF2660D1E300B87E6B /* NETSRtcConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 390350FE2660D1E300B87E6B /* NETSRtcConfig.m */; }; 39179A33262D846C007F74A4 /* NETSMutiConnectView.m in Sources */ = {isa = PBXBuildFile; fileRef = 39179A32262D846C007F74A4 /* NETSMutiConnectView.m */; }; 39179A37262D88AA007F74A4 /* NETSMultiConnectCollectionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 39179A36262D88AA007F74A4 /* NETSMultiConnectCollectionCell.m */; }; @@ -88,8 +89,12 @@ 395F2CBE26CA0E00006F5638 /* NEVideoOption.m in Sources */ = {isa = PBXBuildFile; fileRef = 395F2CBD26CA0E00006F5638 /* NEVideoOption.m */; }; 395F2D1526CB98D9006F5638 /* NEPkPassthroughService.m in Sources */ = {isa = PBXBuildFile; fileRef = 395F2D1426CB98D9006F5638 /* NEPkPassthroughService.m */; }; 395F2D1926CB9D39006F5638 /* NEPkChatroomMsgHandle.m in Sources */ = {isa = PBXBuildFile; fileRef = 395F2D1826CB9D39006F5638 /* NEPkChatroomMsgHandle.m */; }; + 3970041C2747A70C006A1800 /* NEPkConfigModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 3970041B2747A70C006A1800 /* NEPkConfigModel.m */; }; + 3970041F2747A814006A1800 /* NEPKInviteConfigModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 3970041E2747A814006A1800 /* NEPKInviteConfigModel.m */; }; 39771BDE26DCB952005B7EE1 /* NESeatInfoFilterModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 39771BDD26DCB952005B7EE1 /* NESeatInfoFilterModel.m */; }; 39796F7E25AD77CD0070301E /* UIViewController+Gesture.m in Sources */ = {isa = PBXBuildFile; fileRef = 39796F7D25AD77CD0070301E /* UIViewController+Gesture.m */; }; + 397F3088274B8FF90033A894 /* NEGCDTimerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 397F3087274B8FF90033A894 /* NEGCDTimerManager.m */; }; + 39862819274F5B95007AF632 /* UIControl+repeatclick.m in Sources */ = {isa = PBXBuildFile; fileRef = 39862818274F5B95007AF632 /* UIControl+repeatclick.m */; }; 3987C22226D4E7220033C6B7 /* NEPkInfoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 3987C22126D4E7220033C6B7 /* NEPkInfoModel.m */; }; 3987C25D26D5DCF80033C6B7 /* NEPkRewardParams.m in Sources */ = {isa = PBXBuildFile; fileRef = 3987C25C26D5DCF80033C6B7 /* NEPkRewardParams.m */; }; 3987C26026D5DE730033C6B7 /* NEPKRewardTopParams.m in Sources */ = {isa = PBXBuildFile; fileRef = 3987C25F26D5DE730033C6B7 /* NEPKRewardTopParams.m */; }; @@ -123,7 +128,6 @@ 39DE2F5726304A1F00EDC2D5 /* NTESActionSheetPresentationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 39DE2F5226304A1F00EDC2D5 /* NTESActionSheetPresentationController.m */; }; 39DE2F5E263120E900EDC2D5 /* NTESCollectStatusCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 39DE2F5A263120E800EDC2D5 /* NTESCollectStatusCell.m */; }; 39DE2F5F263120E900EDC2D5 /* NTESCollectStatusItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 39DE2F5D263120E900EDC2D5 /* NTESCollectStatusItem.m */; }; - 3EE4BE731B2ECB534C2AB261 /* libPods-NEPkDemo-NLiteAVDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D566519CEA16AB0A9405AC3 /* libPods-NEPkDemo-NLiteAVDemo.a */; }; 865FD37125B80D3200B28A8A /* 1.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 865FD36F25B80D3200B28A8A /* 1.mp3 */; }; 865FD37225B80D3200B28A8A /* 2.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 865FD37025B80D3200B28A8A /* 2.mp3 */; }; 865FD39725B9BCFF00B28A8A /* NETSGiftAnimationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 865FD39625B9BCFF00B28A8A /* NETSGiftAnimationView.m */; }; @@ -268,7 +272,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0D566519CEA16AB0A9405AC3 /* libPods-NEPkDemo-NLiteAVDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NEPkDemo-NLiteAVDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 390350FD2660D1E300B87E6B /* NETSRtcConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NETSRtcConfig.h; sourceTree = ""; }; 390350FE2660D1E300B87E6B /* NETSRtcConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NETSRtcConfig.m; sourceTree = ""; }; 39179A31262D846C007F74A4 /* NETSMutiConnectView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NETSMutiConnectView.h; sourceTree = ""; }; @@ -433,11 +436,19 @@ 395F2D1426CB98D9006F5638 /* NEPkPassthroughService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NEPkPassthroughService.m; sourceTree = ""; }; 395F2D1726CB9D39006F5638 /* NEPkChatroomMsgHandle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NEPkChatroomMsgHandle.h; sourceTree = ""; }; 395F2D1826CB9D39006F5638 /* NEPkChatroomMsgHandle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NEPkChatroomMsgHandle.m; sourceTree = ""; }; + 3970041A2747A70C006A1800 /* NEPkConfigModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NEPkConfigModel.h; sourceTree = ""; }; + 3970041B2747A70C006A1800 /* NEPkConfigModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NEPkConfigModel.m; sourceTree = ""; }; + 3970041D2747A814006A1800 /* NEPKInviteConfigModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NEPKInviteConfigModel.h; sourceTree = ""; }; + 3970041E2747A814006A1800 /* NEPKInviteConfigModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NEPKInviteConfigModel.m; sourceTree = ""; }; 39771BDC26DCB952005B7EE1 /* NESeatInfoFilterModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NESeatInfoFilterModel.h; sourceTree = ""; }; 39771BDD26DCB952005B7EE1 /* NESeatInfoFilterModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NESeatInfoFilterModel.m; sourceTree = ""; }; 39796F7925AC25E60070301E /* authpack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = authpack.h; sourceTree = ""; }; 39796F7C25AD77CD0070301E /* UIViewController+Gesture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+Gesture.h"; sourceTree = ""; }; 39796F7D25AD77CD0070301E /* UIViewController+Gesture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+Gesture.m"; sourceTree = ""; }; + 397F3086274B8FF90033A894 /* NEGCDTimerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NEGCDTimerManager.h; sourceTree = ""; }; + 397F3087274B8FF90033A894 /* NEGCDTimerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NEGCDTimerManager.m; sourceTree = ""; }; + 39862817274F5B95007AF632 /* UIControl+repeatclick.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+repeatclick.h"; sourceTree = ""; }; + 39862818274F5B95007AF632 /* UIControl+repeatclick.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+repeatclick.m"; sourceTree = ""; }; 3987C22026D4E7220033C6B7 /* NEPkInfoModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NEPkInfoModel.h; sourceTree = ""; }; 3987C22126D4E7220033C6B7 /* NEPkInfoModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NEPkInfoModel.m; sourceTree = ""; }; 3987C25B26D5DCF80033C6B7 /* NEPkRewardParams.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NEPkRewardParams.h; sourceTree = ""; }; @@ -665,6 +676,7 @@ 86D73E2725AE9F1400417AF1 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; 86D73E2925AE9F1D00417AF1 /* CoreML.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreML.framework; path = System/Library/Frameworks/CoreML.framework; sourceTree = SDKROOT; }; 86D73E2B25AE9F2800417AF1 /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; }; + 932CEC9E9F60006D616F81BD /* Pods_NEPkDemo_NLiteAVDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NEPkDemo_NLiteAVDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9C3E631E57C6DCADE269362E /* Pods-NLiteAVDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NLiteAVDemo.debug.xcconfig"; path = "Target Support Files/Pods-NLiteAVDemo/Pods-NLiteAVDemo.debug.xcconfig"; sourceTree = ""; }; C71DDB0E2566449700E28A27 /* NEModifyNicknameTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NEModifyNicknameTask.h; sourceTree = ""; }; C71DDB0F2566449700E28A27 /* NEModifyNicknameTask.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NEModifyNicknameTask.m; sourceTree = ""; }; @@ -783,7 +795,7 @@ files = ( 3958C43226C27A4500A0BCE8 /* NELiveRoom.framework in Frameworks */, 86D73E2625AE9F0B00417AF1 /* AVFoundation.framework in Frameworks */, - 3EE4BE731B2ECB534C2AB261 /* libPods-NEPkDemo-NLiteAVDemo.a in Frameworks */, + 1CB4421E46857BC7298FF8EC /* Pods_NEPkDemo_NLiteAVDemo.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -814,7 +826,7 @@ 86D73E2725AE9F1400417AF1 /* CoreMedia.framework */, 86D73E2525AE9F0B00417AF1 /* AVFoundation.framework */, 86D73E2325AE9F0100417AF1 /* Accelerate.framework */, - 0D566519CEA16AB0A9405AC3 /* libPods-NEPkDemo-NLiteAVDemo.a */, + 932CEC9E9F60006D616F81BD /* Pods_NEPkDemo_NLiteAVDemo.framework */, ); name = Frameworks; sourceTree = ""; @@ -1090,6 +1102,15 @@ path = PKChatRoomMessageHandle; sourceTree = ""; }; + 397F3085274B8FF90033A894 /* NEGCDTimerManager */ = { + isa = PBXGroup; + children = ( + 397F3086274B8FF90033A894 /* NEGCDTimerManager.h */, + 397F3087274B8FF90033A894 /* NEGCDTimerManager.m */, + ); + path = NEGCDTimerManager; + sourceTree = ""; + }; 399440DB26E74C510084BFAF /* anchorMainVc */ = { isa = PBXGroup; children = ( @@ -1740,16 +1761,16 @@ 86D73DC525ABFD0500417AF1 /* NETSRequest */ = { isa = PBXGroup; children = ( - 86D73DC625ABFD0500417AF1 /* NETSApiModelMapping.m */, - 86D73DC725ABFD0500417AF1 /* NETSApiOptions.h */, - 86D73DC825ABFD0500417AF1 /* SKVObject.h */, 86D73DC925ABFD0500417AF1 /* NETSRequest.h */, + 86D73DCF25ABFD0500417AF1 /* NETSRequest.m */, + 86D73DCE25ABFD0500417AF1 /* NETSService.h */, 86D73DCA25ABFD0500417AF1 /* NETSService.m */, 86D73DCB25ABFD0500417AF1 /* NETSApiModelMapping.h */, - 86D73DCC25ABFD0500417AF1 /* SKVObject.m */, + 86D73DC625ABFD0500417AF1 /* NETSApiModelMapping.m */, + 86D73DC725ABFD0500417AF1 /* NETSApiOptions.h */, 86D73DCD25ABFD0500417AF1 /* NETSApiOptions.m */, - 86D73DCE25ABFD0500417AF1 /* NETSService.h */, - 86D73DCF25ABFD0500417AF1 /* NETSRequest.m */, + 86D73DC825ABFD0500417AF1 /* SKVObject.h */, + 86D73DCC25ABFD0500417AF1 /* SKVObject.m */, ); path = NETSRequest; sourceTree = ""; @@ -1817,7 +1838,6 @@ C75E3E3D256379C1001A4296 /* Person */, C7664E862511AE1D005E26A2 /* Login */, C7664E9A2511AE1D005E26A2 /* Menu */, - C7664EA22511AE1D005E26A2 /* Service */, C7664EA82511AE1D005E26A2 /* Utils */, C7B5068624F6439E00C4D992 /* NLiteAVDemo.entitlements */, C758CCD824EB7F5A003C406A /* AppDelegate.h */, @@ -1967,6 +1987,7 @@ C7664E9A2511AE1D005E26A2 /* Menu */ = { isa = PBXGroup; children = ( + C7664EA22511AE1D005E26A2 /* Service */, C7664E9B2511AE1D005E26A2 /* View */, ); path = Menu; @@ -2004,6 +2025,7 @@ C7664EA82511AE1D005E26A2 /* Utils */ = { isa = PBXGroup; children = ( + 397F3085274B8FF90033A894 /* NEGCDTimerManager */, 39BDF0BC2636996300FFB3ED /* NTESSegmentCtrl */, 39DE2F4E26304A1F00EDC2D5 /* NTESActionSheetTransitioning */, 393A6F4B25B1478F00C9A0A2 /* Libs */, @@ -2033,6 +2055,8 @@ 86D73DBD25ABFCC700417AF1 /* NSString+NTES.m */, 86D73DBA25ABFCA300417AF1 /* UIButton+NTES.h */, 86D73DBB25ABFCA300417AF1 /* UIButton+NTES.m */, + 39862817274F5B95007AF632 /* UIControl+repeatclick.h */, + 39862818274F5B95007AF632 /* UIControl+repeatclick.m */, C7664EAD2511AE1D005E26A2 /* NSDictionary+NTESJson.h */, C7664EB12511AE1D005E26A2 /* NSDictionary+NTESJson.m */, C7664EAA2511AE1D005E26A2 /* UIColor+NTES.h */, @@ -2109,6 +2133,10 @@ 3987C26226D5DFC20033C6B7 /* NERewardTopResponseModel.m */, 39771BDC26DCB952005B7EE1 /* NESeatInfoFilterModel.h */, 39771BDD26DCB952005B7EE1 /* NESeatInfoFilterModel.m */, + 3970041A2747A70C006A1800 /* NEPkConfigModel.h */, + 3970041B2747A70C006A1800 /* NEPkConfigModel.m */, + 3970041D2747A814006A1800 /* NEPKInviteConfigModel.h */, + 3970041E2747A814006A1800 /* NEPKInviteConfigModel.m */, ); path = PKResponseModel; sourceTree = ""; @@ -2230,11 +2258,9 @@ files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-NEPkDemo-NLiteAVDemo/Pods-NEPkDemo-NLiteAVDemo-resources-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-NEPkDemo-NLiteAVDemo/Pods-NEPkDemo-NLiteAVDemo-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -2287,11 +2313,9 @@ files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-NEPkDemo-NLiteAVDemo/Pods-NEPkDemo-NLiteAVDemo-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-NEPkDemo-NLiteAVDemo/Pods-NEPkDemo-NLiteAVDemo-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -2426,8 +2450,10 @@ 86D73DA425ABFB6000417AF1 /* NETSLiveEndView.m in Sources */, 393A6FF925B1479000C9A0A2 /* LOTShapeGradientFill.m in Sources */, 86D73DBF25ABFCC700417AF1 /* NSString+NTES.m in Sources */, + 3970041C2747A70C006A1800 /* NEPkConfigModel.m in Sources */, 86D73DA725ABFB6000417AF1 /* NETSAudienceChatRoomCell.m in Sources */, 393A6FDE25B1479000C9A0A2 /* LOTSizeInterpolator.m in Sources */, + 3970041F2747A814006A1800 /* NEPKInviteConfigModel.m in Sources */, 393A700125B1479000C9A0A2 /* LOTShapeTransform.m in Sources */, 393A700E25B1479000C9A0A2 /* LOTCacheProvider.m in Sources */, 39BDF1002636B30F00FFB3ED /* NETSBaseTabViewCell.m in Sources */, @@ -2446,6 +2472,7 @@ 393A6FF225B1479000C9A0A2 /* CALayer+Compat.m in Sources */, 393A6FEB25B1479000C9A0A2 /* LOTGradientFillRender.m in Sources */, C7664EDA2511AE1E005E26A2 /* UIImage+NTES.m in Sources */, + 397F3088274B8FF90033A894 /* NEGCDTimerManager.m in Sources */, C7664EDD2511AE1E005E26A2 /* NEBaseViewController.m in Sources */, 86D73D9825ABFB6000417AF1 /* NETSCanvasModel.m in Sources */, 86D73D7225ABFB6000417AF1 /* NETSLiveListCell.m in Sources */, @@ -2515,6 +2542,7 @@ 86D73DD825ABFD0500417AF1 /* NETSApiModelMapping.m in Sources */, 39D76A3E26CD31DB005DECE5 /* NEPassthroughPkInviteModel.m in Sources */, 393A701225B1479000C9A0A2 /* LOTKeyframe.m in Sources */, + 39862819274F5B95007AF632 /* UIControl+repeatclick.m in Sources */, 86D73DA825ABFB6000417AF1 /* NETSAudienceMask.m in Sources */, 393A6FFE25B1479000C9A0A2 /* LOTShapePath.m in Sources */, 39BDF0DA26369DF800FFB3ED /* NSObject+additions.m in Sources */, @@ -2726,7 +2754,7 @@ "$(inherited)", "$(PROJECT_DIR)/Sources/NELivePlayerFramework.framework", ); - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.netease.yunxin.app.pklive; PRODUCT_NAME = "网易云信互动直播"; PROVISIONING_PROFILE_SPECIFIER = "Wildcard Dev"; @@ -2745,7 +2773,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = NLiteAVDemo/NLiteAVDemo.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 569GNZ5392; @@ -2766,10 +2794,10 @@ "$(inherited)", "$(PROJECT_DIR)/Sources/NELivePlayerFramework.framework", ); - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.netease.yunxin.app.pklive; PRODUCT_NAME = "网易云信互动直播"; - PROVISIONING_PROFILE_SPECIFIER = "Wildcard Dev"; + PROVISIONING_PROFILE_SPECIFIER = enterpriseForWildCard; SWIFT_OBJC_BRIDGING_HEADER = "NLiteAVDemo/Utils/Macro/NLiteAVDemo-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/OnlinePK-iOS/NLiteAVDemo/AppDelegate.m b/OnlinePK-iOS/NLiteAVDemo/AppDelegate.m index 352d4a6..741786c 100644 --- a/OnlinePK-iOS/NLiteAVDemo/AppDelegate.m +++ b/OnlinePK-iOS/NLiteAVDemo/AppDelegate.m @@ -16,6 +16,8 @@ #import "NETabbarController.h" #import "NTELoginVC.h" #import "AppKey.h" +#import "NEPkLiveAttachment.h" + @interface AppDelegate () @end @@ -23,9 +25,10 @@ @interface AppDelegate () @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self initWindow]; + [self setupLoginSDK]; [self setIQKeyboard]; setupLogger(); - [self autoLogin]; + return YES; } @@ -34,16 +37,9 @@ - (void)initWindow { self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.window.backgroundColor = UIColor.whiteColor; - if (![NSObject isNullOrNilWithObject:[NEAccount shared].accessToken]) { - NETabbarController *tabbarCtrl = [[NETabbarController alloc]init]; - self.window.rootViewController = tabbarCtrl; - [NENavigator shared].navigationController = tabbarCtrl.menuNavController; - }else { - UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:[[NTELoginVC alloc] initWithOptions:nil isShowClose:YES]]; - self.window.rootViewController = nav; - [NENavigator shared].loginNavigationController = nav; - } - + NETabbarController *tabbarCtrl = [[NETabbarController alloc]init]; + self.window.rootViewController = tabbarCtrl; + [NENavigator shared].navigationController = tabbarCtrl.menuNavController; [self.window makeKeyAndVisible]; //设置麦位组件 @@ -53,6 +49,7 @@ - (void)initWindow { } - (void)autoLogin { + if ([[NEAccount shared].accessToken length] > 0) { [NEAccount loginByTokenWithCompletion:^(NSDictionary * _Nullable data, NSError * _Nullable error) { if (error) { @@ -67,4 +64,64 @@ - (void)setIQKeyboard { [IQKeyboardManager sharedManager].enableAutoToolbar = NO; } +- (void)setupSDK { + NIMSDKOption *option = [NIMSDKOption optionWithAppKey:kAppKey]; + [[NIMSDK sharedSDK] registerWithOption:option]; + [NIMCustomObject registerCustomDecoder:[[NEPKLiveAttachmentDecoder alloc] init]]; +} + +- (void)setupLoginSDK { + YXConfig *config = [[YXConfig alloc] init]; + config.appKey = kAppKey; + config.parentScope = [NSNumber numberWithInt:1]; + config.scope = [NSNumber numberWithInt:3]; + #ifdef DEBUG + config.isOnline = NO; + #else + config.isOnline = YES; + #endif + config.type = YXLoginEmail; + + [[AuthorManager shareInstance] initAuthorWithConfig:config]; +// __weak typeof(self) weakSelf = self; + if ([LoginManager canAutologin] == YES) { + [LoginManager autoLoginWithCompletion:^(YXUserInfo * _Nullable userinfo, NSError * _Nullable error) { + if (error == nil) { + + NSLog(@"统一登录sdk登录成功"); + [NEAccount syncLoginData:userinfo]; + //[weakSelf imLogin]; + }else { + [UIApplication.sharedApplication.keyWindow makeToast:error.localizedDescription]; + } + }]; + }else { + NSLog(@"LoginManager startEntrance"); + /* + [LoginManager startLoginWithCompletion:^(YXUserInfo * _Nullable userinfo, NSError * _Nullable error) { + if (error == nil) { + NSLog(@"统一登录sdk登录成功"); + [NEAccount syncLoginData:userinfo]; + //[weakSelf imLogin]; + }else { + [UIApplication.sharedApplication.keyWindow makeToast:error.localizedDescription]; + } + }]; */ + + [LoginManager startEntranceWithCompletion:^(YXUserInfo * _Nullable userinfo, NSError * _Nullable error) { + if (error == nil) { + NSLog(@"统一登录sdk登录成功"); + [NEAccount syncLoginData:userinfo]; + //[weakSelf imLogin]; + }else { + [UIApplication.sharedApplication.keyWindow makeToast:error.localizedDescription]; + } + }]; + } +} + +- (void)imLogin{ + [NEAccount imloginWithYXuser:[LoginManager getUserInfo]]; +} + @end diff --git a/OnlinePK-iOS/NLiteAVDemo/AppKey.h b/OnlinePK-iOS/NLiteAVDemo/AppKey.h index c5ddf01..1886c97 100644 --- a/OnlinePK-iOS/NLiteAVDemo/AppKey.h +++ b/OnlinePK-iOS/NLiteAVDemo/AppKey.h @@ -9,14 +9,14 @@ #ifndef AppKey_h #define AppKey_h -/// 线上 IM key +/// IM key static NSString * const kAppKey = @"<#请输入NIMSDK appkey#>"; -/// 线上 RTC key (同 IM key) +/// RTC key (同 IM key) static NSString * const kNertcAppkey = @"<#请输入NSRtc appkey#>"; -/// 线上服务器 -static NSString * const kApiHost = @"<#请输入线上服务器地址#>"; +/// 服务器地址 +static NSString * const kApiHost = @"http://yiyong-ne-live.netease.im"; -/// 数据收集 static NSString * const kApiDataHost = @"https://statistic.live.126.net"; + #endif /* AppKey_h */ diff --git a/OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_blocked.imageset/Contents.json b/OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_blocked.imageset/Contents.json new file mode 100644 index 0000000..4abb384 --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_blocked.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "blockAnchorVoice_blocked@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_blocked.imageset/blockAnchorVoice_blocked@2x.png b/OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_blocked.imageset/blockAnchorVoice_blocked@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1593c6a43a35e2f33c0b98a2b4a10c49e6aec328 GIT binary patch literal 9962 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 literal 0 HcmV?d00001 diff --git a/OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_normal.imageset/Contents.json b/OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_normal.imageset/Contents.json new file mode 100644 index 0000000..729b850 --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_normal.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "blockAnchorVoice_normal@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_normal.imageset/blockAnchorVoice_normal@2x.png b/OnlinePK-iOS/NLiteAVDemo/Assets.xcassets/Live/LiveMain/blockAnchorVoice_normal.imageset/blockAnchorVoice_normal@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0cd0d3d25b20c426d9718f6052feac0d4086e8a3 GIT binary patch literal 7523 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 kGitHashCode - 10d8ed6 + 7f52703 diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NELiveBaseViewController.h b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NELiveBaseViewController.h index 7d66e35..f02ea87 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NELiveBaseViewController.h +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NELiveBaseViewController.h @@ -43,11 +43,14 @@ NS_ASSUME_NONNULL_BEGIN /// 连麦管理按钮点击事件 - (void)connectMicManagerClick; - /// 建立本地canvas模型 - (NERtcVideoCanvas *)setupLocalCanvas; /// 建立单人直播canvas模型 - (NERtcVideoCanvas *)setupSingleCanvas; + +//远端用户加入音视频房间回调 +- (void)onUserJoinWithUid:(int64_t)userId; + //主播关闭房间 - (void)closeLiveRoom; @end diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NELiveBaseViewController.m b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NELiveBaseViewController.m index 70b5a61..4528729 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NELiveBaseViewController.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NELiveBaseViewController.m @@ -140,31 +140,16 @@ - (void)ne_bindViewModel { - (void)setupRTCEngine { NSAssert(![kAppKey isEqualToString:@"AppKey"], @"请设置AppKey"); NERtcEngine *coreEngine = [NERtcEngine sharedEngine]; - - // 设置直播模式 - [coreEngine setChannelProfile:kNERtcChannelProfileLiveBroadcasting]; - + NERtcEngineContext *context = [[NERtcEngineContext alloc] init]; + context.engineDelegate = self; + context.appKey = kNertcAppkey; // 打开推流,回调摄像头采集数据 NSDictionary *params = @{ kNERtcKeyPublishSelfStreamEnabled: @YES, // 打开推流 kNERtcKeyVideoCaptureObserverEnabled: @YES // 将摄像头采集的数据回调给用户 }; [coreEngine setParameters:params]; - [coreEngine setClientRole:kNERtcClientRoleBroadcaster]; - - // 设置视频发送配置(帧率/分辨率) - NERtcVideoEncodeConfiguration *config = [NETSLiveConfig shared].videoConfig; - [coreEngine setLocalVideoConfig:config]; - - // 设置音频质量 - NSUInteger quality = [NETSLiveConfig shared].audioQuality; - [coreEngine setAudioProfile:kNERtcAudioProfileHighQuality scenario:quality]; - [coreEngine setChannelProfile:kNERtcChannelProfileLiveBroadcasting]; - - NERtcEngineContext *context = [[NERtcEngineContext alloc] init]; - context.engineDelegate = self; - context.appKey = kNertcAppkey; NERtcLogSetting *setting = [[NERtcLogSetting alloc] init]; #if DEBUG setting.logLevel = kNERtcLogLevelInfo; @@ -175,11 +160,8 @@ - (void)setupRTCEngine { int res = [coreEngine setupEngineWithContext:context]; YXAlogInfo(@"初始化设置 NERtcEngine, res: %d", res); - - // 启用本地音/视频 - [coreEngine enableLocalAudio:YES]; - [coreEngine enableLocalVideo:YES]; } + /// 预览布局 - (void)layoutPreview { [self.anchorInfo removeFromSuperview]; @@ -290,11 +272,6 @@ - (void)closeLiveRoom { YXAlogError(@"close liveRoom failed,error = %@",error); [NETSToast showToast:NSLocalizedString(@"关闭房间失败", nil)]; } - //销毁rtc资源 - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - int res = [NERtcEngine destroyEngine]; - YXAlogInfo(@"destroyEngine, res: %d", res); - }); if ([self.presentedViewController isKindOfClass:[UIAlertController class]]) {//防止在销毁房间的时候有其他主播邀请pk [self.navigationController dismissViewControllerAnimated:YES completion:^{ [self.navigationController popViewControllerAnimated:YES]; @@ -342,30 +319,21 @@ - (void)layoutPkLive [self.switchCameraBtn removeFromSuperview]; [self.settingPanel removeFromSuperview]; [self.bottomPanel removeFromSuperview]; -// [self.pkSuccessIco removeFromSuperview]; -// [self.pkFailedIco removeFromSuperview]; - -// [self.view addSubview:self.pkStatusBar]; + [self.view addSubview:self.anchorInfo]; [self.view addSubview:self.audienceInfo]; [self.view addSubview:self.chatView]; -// [self.view addSubview:self.pkBtn]; [self.view addSubview:self.livingInputTool]; -// [self.view addSubview:self.inviteeInfo]; [self.view addSubview:self.toolBar]; self.singleRender.hidden = YES; -// self.pkStatusBar.frame = CGRectMake(0, self.localRender.bottom, kScreenWidth, 58); self.anchorInfo.frame = CGRectMake(8, (kIsFullScreen ? 44 : 20) + 4, 124, 36); self.audienceInfo.frame = CGRectMake(kScreenWidth - 8 - 195, self.anchorInfo.top + (36 - 28) / 2.0, 195, 28); CGFloat chatViewHeight = [self chatViewHeight]; self.chatView.frame = CGRectMake(8, kScreenHeight - (kIsFullScreen ? 34 : 0) - 64 - chatViewHeight, kScreenWidth - 16 - 60 - 20, chatViewHeight); -// self.pkBtn.frame = CGRectMake(kScreenWidth - 60 - 8, kScreenHeight - (kIsFullScreen ? 34 : 0) - 64 - 60, 60, 60); self.livingInputTool.frame = CGRectMake(0, kScreenHeight - (kIsFullScreen ? 34 : 0) - 14 - 36, kScreenWidth, 36); -// self.inviteeInfo.frame = CGRectMake(self.remoteRender.right - 8 - 82, self.remoteRender.top + 8, 82, 24); -// -// [self.pkStatusBar refreshWithLeftRewardCoins:0 leftRewardAvatars:@[] rightRewardCoins:0 rightRewardAvatars:@[]]; + } @@ -408,20 +376,24 @@ - (void)clickStartLiveBtn { [NETSToast showLoading]; [[NEPkRoomService sharedRoomService] createLiveRoomWithTopic:topic coverUrl:cover roomType:self.roomType successBlock:^(NECreateRoomResponseModel * _Nonnull roomModel, NERtcLiveStreamTaskInfo * _Nonnull taskInfo) { - YXAlogInfo(@"create room success,roomId = %@",roomModel.live.roomId); + YXAlogInfo(@"create room success,roomId = %@,roomCid = %@",roomModel.live.roomId,roomModel.live.roomCid); [NETSToast hideLoading]; self.warnToast.hidden = YES; self.createRoomModel = roomModel; [self createRoomRefreshUI]; } failedBlock:^(NSError * _Nonnull error) { [NETSToast hideLoading]; - [NERtcEngine destroyEngine]; NSString *msg = error.userInfo[NSLocalizedDescriptionKey] ?: NSLocalizedString(@"开启直播间失败", nil); [NETSToast showToast:msg]; [[NENavigator shared].navigationController popViewControllerAnimated:YES]; YXAlogError(@"create room failed, error: %@", error); }]; } + +-(void)onUserJoinWithUid:(int64_t)userId { + +} + #pragma mark - 当键盘事件 - (void)keyboardWillShow:(NSNotification *)aNotification { @@ -480,10 +452,9 @@ - (void)clickInputToolBarAction:(NETSInputToolBarAction)action { } break; case NETSInputToolBarMore: { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - NSArray *items = [NETSLiveConfig shared].moreSettings; - [NETSMoreSettingActionSheet showWithTarget:self items:items]; - }); + + NSArray *items = [NETSLiveConfig shared].moreSettings; + [NETSMoreSettingActionSheet showWithTarget:self items:items]; } break; @@ -520,9 +491,6 @@ - (void)clickFilterBtn { [NETSFilterSettingActionSheet showWithMask:NO]; } -- (void)clickSettingBtn { - [NETSLiveSettingActionSheet show]; -} #pragma mark - NERtcEngineDelegateEx G2音视频 @@ -534,6 +502,7 @@ - (void)onNERtcEngineUserDidJoinWithUserID:(uint64_t)userID userName:(NSString * { NERtcVideoCanvas *canvas = [self setupRemoteCanvas]; [NERtcEngine.sharedEngine setupRemoteVideoCanvas:canvas forUserID:userID]; + [self onUserJoinWithUid:userID]; } - (void)onNERtcEngineUserVideoDidStartWithUserID:(uint64_t)userID videoProfile:(NERtcVideoProfileType)profile @@ -560,11 +529,21 @@ - (void)onNERtcEngineDidLeaveChannelWithResult:(NERtcError)result } // 离开channel,重置混音索引 [NETSLiveConfig shared].mixingIdx = -1; + + //销毁rtc资源 + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + int res = [NERtcEngine destroyEngine]; + YXAlogInfo(@"destroyEngine, res: %d", res); + }); } - (void)onNERtcEngineDidDisconnectWithReason:(NERtcError)reason { [NETSToast showToast:@"网络断开"]; - [NERtcEngine destroyEngine]; + //销毁rtc资源 + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + int res = [NERtcEngine destroyEngine]; + YXAlogInfo(@"destroyEngine, res: %d", res); + }); [[NENavigator shared].navigationController popViewControllerAnimated:YES]; } diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NEPkLiveViewController.m b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NEPkLiveViewController.m index 06ce516..dc26981 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NEPkLiveViewController.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/Controller/anchorMainVc/NEPkLiveViewController.m @@ -30,10 +30,22 @@ #import "NEPkLiveAttachment.h" #import "NETSChatroomService.h" #import "Reachability.h" +#import "NEPKInviteConfigModel.h" +#import "NEPkConfigModel.h" +#import "NEGCDTimerManager.h" +#import "NEPkRoomApiService.h" +#import "NEPkInfoModel.h" + +//邀请倒计时 timeName +static NSString *const InviteTimeName = @"NEInviteimeName"; +//开始跨频道转发倒计时 +static NSString *const StartRelayTimeName = @"NEStartRelayTimeName"; @interface NEPkLiveViewController () ///// pk直播服务类 //@property (nonatomic, strong) NETSPkService *pkService; +//断网检测 +@property(nonatomic, assign) BOOL isBrokenNetwork; /// pk邀请状态条 @property (nonatomic, strong) NETSInvitingBar *pkInvitingBar; /// pk状态条 @@ -62,6 +74,8 @@ @interface NEPkLiveViewController () 0) { + weakSelf.localPkState = NELocalPkStateInviting; + [weakSelf startPkInviteCountdown:info.pkConfig.inviteTaskTime]; + } successBlock(room.anchor.nickname); } failedBlock:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { if (error) { @@ -315,7 +487,8 @@ -(void)clickCancelInviting:(NETSInviteBarType)barType { - (void)receivePassThrourhPKInviteData:(NEPassthroughPkInviteModel *)data { YXAlogInfo(@"receive pKInvite passThrourhMessage success!"); - + self.localPkState = NELocalPkStateInviting; + if (self.presentedViewController && [self.presentedViewController isKindOfClass:[NTESActionSheetNavigationController class]]) { [self.navigationController dismissViewControllerAnimated:YES completion:nil]; } @@ -331,13 +504,17 @@ - (void)receivePassThrourhPKInviteData:(NEPassthroughPkInviteModel *)data { } }]; }]; - + __weak __typeof(self)weakSelf = self; UIAlertAction *confirm = [UIAlertAction actionWithTitle:NSLocalizedString(@"接受", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [[NEPkService sharedPkService] acceptPkWithOperation:NEPkOperationAgree targetAccountId:data.senderAccountId successBlock:^(NSDictionary * _Nonnull response) { YXAlogInfo(@"acceptPk success"); [NETSToast showLoading]; - [self startRtcChannelRelayWithChannelName:data.actionAnchor.channelName token:data.targetAnchor.checkSum rooomUid:data.targetAnchor.roomUid.longLongValue]; + if (data.pkConfig.agreeTaskTime > 0) { + weakSelf.localPkState = NELocalPkStateAgree; + [weakSelf startMediaRealyCountdown:data.pkConfig.agreeTaskTime]; + } + [weakSelf startRtcChannelRelayWithChannelName:data.actionAnchor.channelName token:data.targetAnchor.checkSum rooomUid:data.targetAnchor.roomUid.longLongValue]; } failedBlock:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { if (error) { YXAlogError(@"acceptPk failed,error:%@",error); @@ -352,13 +529,25 @@ - (void)receivePassThrourhPKInviteData:(NEPassthroughPkInviteModel *)data { - (void)receivePassThrourhAgreePkData:(NEPassthroughPkInviteModel *)data { YXAlogInfo(@"receive agreePk passThrourhMessage success!"); - + self.localPkState = NELocalPkStateAgree; + //停止前一个倒计时 开启下一个倒计时 + if ([[NEGCDTimerManager sharedInstance] existTimer:InviteTimeName]) { + [[NEGCDTimerManager sharedInstance]cancelTimerWithName:InviteTimeName]; + }; + + if (data.pkConfig.agreeTaskTime > 0) { + [self startMediaRealyCountdown:data.pkConfig.agreeTaskTime]; + } [self startRtcChannelRelayWithChannelName:data.actionAnchor.channelName token:data.targetAnchor.checkSum rooomUid:data.targetAnchor.roomUid.longLongValue];; } - (void)receivePassThrourhRefusePKInviteData:(NEPassthroughPkInviteModel *)data { YXAlogInfo(@"refuse pk invite"); self.pkState = NEPKStatusInit; + self.localPkState = NELocalPkStateInitial; + if ([[NEGCDTimerManager sharedInstance] existTimer:InviteTimeName]) { + [[NEGCDTimerManager sharedInstance]cancelTimerWithName:InviteTimeName]; + }; [NETSToast hideLoading]; if ([self.pkInvitingBar superview]) { [self.pkInvitingBar dismiss]; @@ -376,25 +565,11 @@ - (void)receivePassThrourhCancelPKInviteData:(NEPassthroughPkInviteModel *)data } } - - (void)receivePassThrourhTimeOutData:(NEPassthroughPkInviteModel *)data { YXAlogInfo(@"receive timeOut passThrourhMessage success!"); - self.pkState = NEPKStatusInit; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [NETSToast hideLoading];//延时执行 - }); - - [NETSToast showToast:NSLocalizedString(@"PK连接超时,已自动取消", nil)]; - - if (self.pkAlert) { - [self.pkAlert dismissViewControllerAnimated:YES completion:nil]; - } - if ([self.pkInvitingBar superview]) { - [self.pkInvitingBar dismiss]; - } + [self handleTimeOut]; } - #pragma mark - NEPkChatroomMsgHandleDelegate - (void)receivePkStartAttachment:(NEPkLiveStartAttachment *)liveStartData { @@ -402,6 +577,11 @@ - (void)receivePkStartAttachment:(NEPkLiveStartAttachment *)liveStartData { NERtcVideoCanvas *canvas = [self setupLocalCanvas]; [NERtcEngine.sharedEngine setupLocalVideoCanvas:canvas]; self.pkState = NEPKStatusPking; + self.localPkState = NELocalPkStatePkIng; + + if ([[NEGCDTimerManager sharedInstance] existTimer:StartRelayTimeName]) { + [[NEGCDTimerManager sharedInstance]cancelTimerWithName:StartRelayTimeName]; + }; // pk布局 ntes_main_async_safe(^{ @@ -412,21 +592,26 @@ - (void)receivePkStartAttachment:(NEPkLiveStartAttachment *)liveStartData { }); // 开始pk倒计时 - int32_t start = kPkLiveTotalTime - (int32_t)((liveStartData.sendTime - liveStartData.pkStartTime) / 1000); - [self.pkStatusBar countdownWithSeconds:start prefix:@"PK "]; + [self.pkStatusBar countdownWithSeconds:liveStartData.pkCountDown prefix:@"PK "]; [self.pkStatusBar refreshWithLeftRewardCoins:0 leftRewardAvatars:@[] rightRewardCoins:0 rightRewardAvatars:@[]]; if ([liveStartData.inviter.accountId isEqualToString:[NEAccount shared].userModel.accountId]) {//邀请者是自己 self.pkRole = NETSPkServiceInviter; - [self.inviteeInfo reloadAvatar:liveStartData.inviter.avatar nickname:liveStartData.inviter.nickname]; + self.userId = liveStartData.invitee.roomUid; + [self.inviteeInfo reloadAvatar:liveStartData.invitee.avatar nickname:liveStartData.invitee.nickname]; }else { self.pkRole = NETSPkServiceInvitee; - [self.inviteeInfo reloadAvatar:liveStartData.invitee.avatar nickname:liveStartData.invitee.nickname]; + self.userId = liveStartData.inviter.roomUid; + [self.inviteeInfo reloadAvatar:liveStartData.inviter.avatar nickname:liveStartData.inviter.nickname]; + } + //pk开始后恢复对方主播声音 + [[NERtcEngine sharedEngine] adjustUserPlaybackSignalVolume:100 forUserID:self.userId]; //更新推流任务 NSArray *uids = @[@(liveStartData.inviter.roomUid),@(liveStartData.invitee.roomUid)]; + self.anchorUidsArray = uids; [self _updateLiveStreamTask:uids]; } @@ -496,39 +681,20 @@ -(void)receivePunishStartAttachment:(NEStartPunishAttachment *)punishData { -(void)receivePkEndAttachment:(NEPkEndAttachment *)pkEndData { - YXAlogInfo(@"receive pkEnd imMessage success!"); - - NERtcVideoCanvas *canvas = [self setupSingleCanvas]; - [NERtcEngine.sharedEngine setupLocalVideoCanvas:canvas]; - self.pkState = NEPKStatusPkEnd; - // 停止pk计时 - [self.pkStatusBar stopCountdown]; - - //停止跨频道转发 - [[NERtcEngine sharedEngine] stopChannelMediaRelay]; - - // 布局单人直播 - ntes_main_async_safe(^{ - [NETSToast hideLoading]; - [self layoutSingleLive]; - }); + [self handlePkEnd]; - - if (self.pkAlert) { - [self.pkAlert dismissViewControllerAnimated:YES completion:nil]; + if (pkEndData.countDownEnd) {//自然倒计时结束 不弹toast提示 + return; } - //更新推流任务 - NSArray *uids = @[@(self.createRoomModel.anchor.roomUid)]; - [self _updateLiveStreamTask:uids]; - // 若是当前用户取消,不提示 NSString *nickname = [NEAccount shared].userModel.nickname; if (![pkEndData.nickname isEqualToString:nickname]) { NSString *msg = [NSString stringWithFormat:NSLocalizedString(@"%@结束PK", nil), pkEndData.nickname]; [NETSToast showToast:msg]; } + } @@ -595,6 +761,29 @@ - (void)didChatroomClosedWithRoomId:(NSString *)roomId { } +#pragma mark - NERtcDelegate + +-(void)onUserJoinWithUid:(int64_t)userId { + if (self.pkState != NEPKStatusPking) { + [[NERtcEngine sharedEngine] adjustUserPlaybackSignalVolume:0 forUserID:userId]; + } +} + +-(void)dealloc { + + if ([[NEGCDTimerManager sharedInstance] existTimer:InviteTimeName]) { + [[NEGCDTimerManager sharedInstance]cancelTimerWithName:InviteTimeName]; + }; + + if ([[NEGCDTimerManager sharedInstance] existTimer:StartRelayTimeName]) { + [[NEGCDTimerManager sharedInstance]cancelTimerWithName:StartRelayTimeName]; + }; + [[NIMSDK sharedSDK].passThroughManager removeDelegate:self.pkPassthroughService]; + [[NIMSDK sharedSDK].chatManager removeDelegate:self.pkChatRoomMsgHandle]; + [[NIMSDK sharedSDK].chatroomManager removeDelegate:self.pkChatRoomMsgHandle]; + [[NIMSDK sharedSDK].systemNotificationManager removeDelegate:self.pkChatRoomMsgHandle]; +} + #pragma mark - lazyMethod - (UIButton *)pkBtn { if (!_pkBtn) { @@ -626,6 +815,17 @@ - (NETSPkStatusBar *)pkStatusBar { return _pkStatusBar; } +-(UIButton *)muteAudioBtn { + if (!_muteAudioBtn) { + _muteAudioBtn = [[UIButton alloc]init]; + [_muteAudioBtn addTarget:self action:@selector(muteAudioBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + [_muteAudioBtn setImage:[UIImage imageNamed:@"blockAnchorVoice_normal"] forState:UIControlStateNormal]; + [_muteAudioBtn setImage:[UIImage imageNamed:@"blockAnchorVoice_normal"] forState:UIControlStateHighlighted]; + [_muteAudioBtn setImage:[UIImage imageNamed:@"blockAnchorVoice_blocked"] forState:UIControlStateSelected]; + } + return _muteAudioBtn; +} + - (UIImageView *)pkSuccessIco { if (!_pkSuccessIco) { _pkSuccessIco = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; @@ -663,4 +863,10 @@ -(NEPkChatroomMsgHandle *)pkChatRoomMsgHandle { return _pkChatRoomMsgHandle; } +-(NEPkRoomApiService *)roomApiService { + if (!_roomApiService) { + _roomApiService = [[NEPkRoomApiService alloc]init]; + } + return _roomApiService; +} @end diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/vc/NETSAnchorVC.m b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/vc/NETSAnchorVC.m new file mode 100644 index 0000000..ef66ca6 --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/vc/NETSAnchorVC.m @@ -0,0 +1,1625 @@ +// +// NETSAnchorVC.m +// NLiteAVDemo +// +// Created by Ease on 2020/11/10. +// Copyright © 2020 Netease. All rights reserved. +// + +#import "NETSAnchorVC.h" +#import "NEMenuViewController.h" +#import "NETSRequestManageMainController.h" +#import "NTESActionSheetNavigationController.h" +#import "NETSConnectStatusViewController.h" + + +#import "NETSAnchorBottomPanel.h" +#import "NETSAnchorCoverSetting.h" +#import "UIButton+NTES.h" +#import "NETSWarnToast.h" +#import "NETSBeautySettingActionSheet.h" +#import "NETSFilterSettingActionSheet.h" +#import "NETSLiveSettingActionSheet.h" +#import "NTESKeyboardToolbarView.h" +#import "TopmostView.h" +#import "NETSAnchorTopInfoView.h" +#import "NETSInputToolBar.h" +#import "NETSMoreSettingActionSheet.h" +#import "NETSAudioMixingActionSheet.h" +#import "NETSPkStatusBar.h" +#import "NETSChoosePKSheet.h" +#import "NETSLiveChatView.h" +#import "NETSInvitingBar.h" +#import "NETSToast.h" +#import "NETSInviteeInfoView.h" +#import "NETSMutiConnectView.h" +#import "NETSLiveEndView.h" + +#import "AppKey.h" +#import "NETSLiveConfig.h" +#import "NETSLiveSegmentedSettingModel.h" +#import "NETSFUManger.h" +#import "NETSAudienceNum.h" +#import "NETSLiveChatViewHandle.h" +#import "NETSLiveModel.h" +#import "NETSChatroomService.h" +#import "NETSLiveAttachment.h" +#import "NETSLiveUtils.h" +#import "NENavigator.h" +#import "NETSCanvasModel.h" +#import "NETSPushStreamService.h" +#import "NETSPkService.h" +#import "NETSPkService+Inviter.h" +#import "NETSPkService+Invitee.h" +#import "NETSPkService+im.h" +#import "IQKeyboardManager.h" +#import "NETSGCDTimer.h" +#import +#import "NETSLiveApi.h" +#import "NETSAnchorChatroomMessageHandle.h" +#import "NETSLiveAttachment.h" +#import "Reachability.h" + +@interface NETSAnchorVC () +< + NETSAnchorBottomPanelDelegate, + NERtcEngineDelegateEx, + NETSInputToolBarDelegate, + NETSMoreSettingActionSheetDelegate, + NETSLiveChatViewHandleDelegate, + NETSChoosePKSheetDelegate, + NETSInvitingBarDelegate, + NTESKeyboardToolbarDelegate, + NETSPkServiceDelegate, NETSPkInviterDelegate, NETSPkInviteeDelegate, NETSPassThroughHandleDelegate, + NETSMutiConnectViewDelegate, + NETSAnchorChatroomMessageHandleDelegate +> + +/// 绘制单人直播摄像头采集 +@property (nonatomic, strong) UIView *singleRender; +/// 单人直播canvas模型 +@property (nonatomic, strong) NETSCanvasModel *singleCanvas; +/// 绘制摄像头采集 +@property (nonatomic, strong) UIView *localRender; +/// 本地canvas模型 +@property (nonatomic, strong) NETSCanvasModel *localCanvas; +/// 远端视频面板 +@property (nonatomic, strong) UIView *remoteRender; +/// 远端canvas模型 +@property (nonatomic, strong) NETSCanvasModel *remoteCanvas; +/// 底部面板 +@property (nonatomic, strong) NETSAnchorBottomPanel *bottomPanel; +/// 键盘工具条 +@property (nonatomic, strong) NTESKeyboardToolbarView *toolBar; +/// 封面设置面板 +@property (nonatomic, strong) NETSAnchorCoverSetting *settingPanel; +/// 返回按钮 +@property (nonatomic, strong) UIButton *backBtn; +/// 切换摄像头按钮 +@property (nonatomic, strong) UIButton *switchCameraBtn; +/// 试用提示 +@property (nonatomic, strong) NETSWarnToast *warnToast; +/// 主播信息视图 +@property (nonatomic, strong) NETSAnchorTopInfoView *anchorInfo; +/// 直播中 观众数量视图 +@property (nonatomic, strong) NETSAudienceNum *audienceInfo; +/// 直播中 底部工具条 +@property (nonatomic, strong) NETSInputToolBar *livingInputTool; +/// 邀请别人PK按钮 +@property (nonatomic, strong) UIButton *pkBtn; +/// 聊天视图 +@property (nonatomic, strong) NETSLiveChatView *chatView; +/// 聊天室代理 +@property (nonatomic, strong) NETSLiveChatViewHandle *chatHandle; +//分离后的主播聊天室代理 +@property (nonatomic, strong) NETSAnchorChatroomMessageHandle *anchorMessageHandle; + +/// pk状态条 +@property (nonatomic, strong) NETSPkStatusBar *pkStatusBar; +/// pk邀请状态条 +@property (nonatomic, strong) NETSInvitingBar *pkInvitingBar; +//请求上麦状态条 +@property (nonatomic, strong) NETSInvitingBar *requestConnectMicBar; +/// 被邀请者信息视图 +@property (nonatomic, strong) NETSInviteeInfoView *inviteeInfo; + +/// 己方加入视音频房间信号 +@property (nonatomic, strong) RACSubject *joinedPkChannelSubject; +/// 服务端透传pk开始信号 +@property (nonatomic, strong) RACSubject *serverStartPkSubject; + +/// pk胜利图标 +@property (nonatomic, strong) UIImageView *pkSuccessIco; +/// pk失败图标 +@property (nonatomic, strong) UIImageView *pkFailedIco; + +/// pk直播服务类 +@property (nonatomic, strong) NETSPkService *pkService; +/// 是否接受pk邀请对话框 +@property (nonatomic, strong) UIAlertController *pkAlert; +//多人连麦视图 +@property (nonatomic, strong) NETSMutiConnectView *connectMicView; +//直播间模型 +@property(nonatomic, strong) NETSCreateLiveRoomModel *liveRoomModel; +//已上麦人员 +@property(nonatomic, strong) NSArray *connectMicArray; +//已上麦人员uid +@property(nonatomic, strong) NSArray*connectMicUidArray; + +//断网检测 +@property(nonatomic, assign) BOOL isBrokenNetwork; +//直播间关闭的遮罩 +@property(nonatomic, strong) NETSLiveEndView *liveClosedMask; +/// 网络监测类 +@property(nonatomic, strong) Reachability *reachability; +@end + +@implementation NETSAnchorVC + +- (instancetype)init +{ + self = [super init]; + if (self) { + + _chatHandle = [[NETSLiveChatViewHandle alloc] initWithDelegate:self]; + [[NIMSDK sharedSDK].chatManager addDelegate:_chatHandle]; + [[NIMSDK sharedSDK].chatManager addDelegate:self.anchorMessageHandle]; + [[NIMSDK sharedSDK].chatroomManager addDelegate:_chatHandle]; + [[NIMSDK sharedSDK].systemNotificationManager addDelegate:_chatHandle]; + + _pkService = [[NETSPkService alloc] initWithDelegate:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; + // 监测网络 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil]; + self.reachability = [Reachability reachabilityWithHostName:@"www.baidu.com"]; + [self.reachability startNotifier]; + _joinedPkChannelSubject = [RACSubject subject]; + _serverStartPkSubject = [RACSubject subject]; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. + + self.view.backgroundColor = HEXCOLOR(0x1b1919); + + // 重置更多设置 + [[NETSLiveConfig shared] resetConfig]; + // 重置美颜配置 + [[NETSFUManger shared] resetSkinParams]; + // 重置滤镜配置 + [[NETSFUManger shared] resetFilters]; + + [self layoutPreview]; + [self layoutRenders]; + [self bindAction]; + [self setupRTCEngine]; + + [self _authCameraAndPrevew]; + +} + +- (void)_authCameraAndPrevew +{ + void(^quitBlock)(void) = ^(void) { + [NETSToast showToast:@"直播需要开启相机权限"]; + ntes_main_sync_safe(^{ + [[NENavigator shared].navigationController popViewControllerAnimated:YES]; + }); + }; + + AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + switch (authStatus) { + case AVAuthorizationStatusRestricted: + case AVAuthorizationStatusDenied: + quitBlock(); + break; + case AVAuthorizationStatusNotDetermined: + { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { + if (!granted) { + quitBlock(); + } else { + dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1* NSEC_PER_SEC)), queue, ^{ + ntes_main_async_safe(^{ + [self startPrevew]; + }); + }); + } + }]; + } + break; + case AVAuthorizationStatusAuthorized: + { + ntes_main_async_safe(^{ + [self startPrevew]; + }); + } + break; + default: + break; + } +} + +- (void)dealloc { + // 关闭屏幕常亮 + [[UIApplication sharedApplication] setIdleTimerDisabled:NO]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NIMSDK sharedSDK].chatManager removeDelegate:_chatHandle]; + [[NIMSDK sharedSDK].chatManager removeDelegate:self.anchorMessageHandle]; + [[NIMSDK sharedSDK].chatroomManager removeDelegate:_chatHandle]; + [[NIMSDK sharedSDK].systemNotificationManager removeDelegate:_chatHandle]; + YXAlogInfo(@"dealloc NETSAnchorVC: %p...", self); +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + [self.navigationController setNavigationBarHidden:YES animated:YES]; + [IQKeyboardManager sharedManager].enable = NO; +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + [IQKeyboardManager sharedManager].enable = YES; +} + +- (void)bindAction +{ + @weakify(self); + [RACObserve(self.pkService, liveStatus) subscribeNext:^(id _Nullable x) { + ntes_main_async_safe(^{ + @strongify(self); + NSString *pkBtnIco = (self.pkService.liveStatus != NETSPkServicePkLive) ? @"pk_ico" : @"end_pk_ico"; + [self.pkBtn setImage:[UIImage imageNamed:pkBtnIco] forState:UIControlStateNormal]; + NSString *requestIcon = (self.pkService.liveStatus != NETSPkServicePkLive) ? @"connectMic_able" : @"connectMic_disable"; + [self.livingInputTool scenarioChanged:requestIcon]; + }); + }]; + + [RACObserve(self.pkService, singleRoom) subscribeNext:^(NETSCreateLiveRoomModel* _Nullable room) { + @strongify(self); + if (!room) { return; } + [self.anchorInfo installWithAvatar:room.avatar nickname:room.nickname wealth:0]; + }]; + + RACSignal *signal = [self.joinedPkChannelSubject zipWith:self.serverStartPkSubject]; + [signal subscribeNext:^(RACTuple *tuple) { + @strongify(self); + if (![tuple.first isKindOfClass:[NETSPassThroughHandlePkStartData class]]) { + return; + } + NETSPassThroughHandlePkStartData *data = (NETSPassThroughHandlePkStartData *)tuple.first; + [self _pushPkLiveStreamWithData:data]; + }]; +} + +- (void)startPrevew +{ + NERtcVideoCanvas *canvas = [self setupSingleCanvas]; + int setLocalCanvasRes = [[NERtcEngine sharedEngine] setupLocalVideoCanvas:canvas]; + YXAlogInfo(@"设置本地视频画布, res: %d", setLocalCanvasRes); + + int previewRes = [[NERtcEngine sharedEngine] startPreview]; + YXAlogInfo(@"开启预览, res: %d", previewRes); + if (previewRes == 0) { + self.pkService.liveStatus = NETSPkServicePrevew; + } +} + +/// 布局视频渲染视图 +- (void)layoutRenders +{ + [self.view addSubview:self.localRender]; + [self.view addSubview:self.remoteRender]; + [self.view addSubview:self.singleRender]; + + CGFloat anchorInfoBottom = (kIsFullScreen ? 44 : 20) + 36 + 4; + self.localRender.frame = CGRectMake(0, anchorInfoBottom + 24, kScreenWidth / 2.0, kScreenWidth / 2.0 * 1280 / 720.0); + self.remoteRender.frame = CGRectMake(self.localRender.right, self.localRender.top, self.localRender.width, self.localRender.height); + self.singleRender.frame = self.view.bounds; + + [self.view sendSubviewToBack:self.singleRender]; + [self.view sendSubviewToBack:self.remoteRender]; + [self.view sendSubviewToBack:self.localRender]; +} + +/// 预览布局 +- (void)layoutPreview +{ + [self.anchorInfo removeFromSuperview]; + [self.audienceInfo removeFromSuperview]; + [self.chatView removeFromSuperview]; + [self.pkBtn removeFromSuperview]; + [self.livingInputTool removeFromSuperview]; + [self.pkSuccessIco removeFromSuperview]; + [self.pkFailedIco removeFromSuperview]; + [self.inviteeInfo removeFromSuperview]; + + [self.view addSubview:self.backBtn]; + [self.view addSubview:self.switchCameraBtn]; + [self.view addSubview:self.settingPanel]; + [self.view addSubview:self.bottomPanel]; + [self.view addSubview:self.warnToast]; + + self.backBtn.frame = CGRectMake(20, (kIsFullScreen ? 44 : 20) + 8, 24, 24); + self.switchCameraBtn.frame = CGRectMake(kScreenWidth - 20 - 24, (kIsFullScreen ? 44 : 20) + 8, 24, 24); + self.settingPanel.frame = CGRectMake(20, (kIsFullScreen ? 88 : 64) + 20, kScreenWidth - 40, 88); + self.bottomPanel.frame = CGRectMake(0, kScreenHeight - 128 - (kIsFullScreen ? 54 : 20), kScreenWidth, 128); + self.warnToast.frame = CGRectMake(20, self.bottomPanel.top - 20 - 60, kScreenWidth - 40, 60); + + @weakify(self); + self.warnToast.clickBlock = ^{ + @strongify(self); + [self.warnToast removeFromSuperview]; + }; +} + +/// 单人直播布局 +- (void)layoutSingleLive +{ + [self.pkStatusBar removeFromSuperview]; + [self.backBtn removeFromSuperview]; + [self.switchCameraBtn removeFromSuperview]; + [self.settingPanel removeFromSuperview]; + [self.bottomPanel removeFromSuperview]; + [self.pkSuccessIco removeFromSuperview]; + [self.pkFailedIco removeFromSuperview]; + [self.inviteeInfo removeFromSuperview]; + + [self.view addSubview:self.anchorInfo]; + [self.view addSubview:self.audienceInfo]; + [self.view addSubview:self.chatView]; + [self.view addSubview:self.pkBtn]; + [self.view addSubview:self.livingInputTool]; + [self.view addSubview:self.toolBar]; + + self.singleRender.hidden = NO; + + self.anchorInfo.frame = CGRectMake(8, (kIsFullScreen ? 44 : 20) + 4, 124, 36); + self.audienceInfo.frame = CGRectMake(kScreenWidth - 8 - 195, self.anchorInfo.top + (36 - 28) / 2.0, 195, 28); + CGFloat chatViewHeight = [self chatViewHeight]; + self.chatView.frame = CGRectMake(8, kScreenHeight - (kIsFullScreen ? 34 : 0) - 64 - chatViewHeight, kScreenWidth - 16 - 60 - 20, chatViewHeight); + self.pkBtn.frame = CGRectMake(kScreenWidth - 60 - 8, kScreenHeight - (kIsFullScreen ? 34 : 0) - 64 - 60, 60, 60); + self.livingInputTool.frame = CGRectMake(0, kScreenHeight - (kIsFullScreen ? 34 : 0) - 14 - 36, kScreenWidth, 36); +} + +////多人连麦布局 +//- (void)layoutConnectMic { +// +//} + +/// pk直播布局 +- (void)layoutPkLive +{ + [self.backBtn removeFromSuperview]; + [self.switchCameraBtn removeFromSuperview]; + [self.settingPanel removeFromSuperview]; + [self.bottomPanel removeFromSuperview]; + [self.pkSuccessIco removeFromSuperview]; + [self.pkFailedIco removeFromSuperview]; + + [self.view addSubview:self.pkStatusBar]; + [self.view addSubview:self.anchorInfo]; + [self.view addSubview:self.audienceInfo]; + [self.view addSubview:self.chatView]; + [self.view addSubview:self.pkBtn]; + [self.view addSubview:self.livingInputTool]; + [self.view addSubview:self.inviteeInfo]; + [self.view addSubview:self.toolBar]; + + self.singleRender.hidden = YES; + + self.pkStatusBar.frame = CGRectMake(0, self.localRender.bottom, kScreenWidth, 58); + self.anchorInfo.frame = CGRectMake(8, (kIsFullScreen ? 44 : 20) + 4, 124, 36); + self.audienceInfo.frame = CGRectMake(kScreenWidth - 8 - 195, self.anchorInfo.top + (36 - 28) / 2.0, 195, 28); + CGFloat chatViewHeight = [self chatViewHeight]; + self.chatView.frame = CGRectMake(8, kScreenHeight - (kIsFullScreen ? 34 : 0) - 64 - chatViewHeight, kScreenWidth - 16 - 60 - 20, chatViewHeight); + self.pkBtn.frame = CGRectMake(kScreenWidth - 60 - 8, kScreenHeight - (kIsFullScreen ? 34 : 0) - 64 - 60, 60, 60); + self.livingInputTool.frame = CGRectMake(0, kScreenHeight - (kIsFullScreen ? 34 : 0) - 14 - 36, kScreenWidth, 36); + self.inviteeInfo.frame = CGRectMake(self.remoteRender.right - 8 - 82, self.remoteRender.top + 8, 82, 24); + + [self.pkStatusBar refreshWithLeftRewardCoins:0 leftRewardAvatars:@[] rightRewardCoins:0 rightRewardAvatars:@[]]; +} + +/// 初始化RTC引擎 +- (void)setupRTCEngine +{ + NSAssert(![kAppKey isEqualToString:@"AppKey"], @"请设置AppKey"); + NERtcEngine *coreEngine = [NERtcEngine sharedEngine]; + + NERtcEngineContext *context = [[NERtcEngineContext alloc] init]; + context.engineDelegate = self; + context.appKey = kNertcAppkey; + int res = [coreEngine setupEngineWithContext:context]; + YXAlogInfo(@"初始化设置 NERtcEngine, res: %d", res); + +} + +- (void)clickAction:(UIButton *)sender +{ + if (sender == self.backBtn) { + [self _closeLiveRoom]; + } + else if (sender == self.switchCameraBtn) { + int res = [[NERtcEngine sharedEngine] switchCamera]; + YXAlogInfo(@"切换前后摄像头, res: %d", res); + }else if (sender == self.pkBtn) { + if (self.pkService.liveStatus == NETSPkServiceSingleLive) { + if ([self.pkStatusBar superview]) { + [NETSToast showToast:@"您已经再邀请中,不可再邀请"]; + return; + } + YXAlogInfo(@"打开pk列表面板,开始pk"); + [NETSChoosePKSheet showWithTarget:self]; + }else if (self.pkService.liveStatus == NETSPkServicePkLive) { + YXAlogInfo(@"点击结束pk"); + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"结束PK" message:@"PK尚未结束,强制结束会返回普通直播模式" preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + YXAlogInfo(@"取消强制结束pk"); + }]; + @weakify(self); + UIAlertAction *confirm = [UIAlertAction actionWithTitle:@"立即结束" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [NETSToast showLoading]; + @strongify(self); + [self _manualEndPk]; + }]; + [alert addAction:cancel]; + [alert addAction:confirm]; + [self presenAlert:alert]; + } + else { + YXAlogInfo(@"正在pk链接中,pk按钮无响应"); + } + } +} + +/// 强制关闭pk +- (void)_manualEndPk +{ + [self.pkStatusBar stopCountdown]; + @weakify(self); + [self.pkService endPkWithCompletionBlock:^(NSError * _Nullable error) { + YXAlogInfo(@"强制结束pk完成, error: %@", error); + if (!error) { + ntes_main_async_safe(^{ + @strongify(self); + [NETSToast hideLoading]; + [self layoutSingleLive]; + }); + } + }]; +} + +/// 关闭直播间 +- (void)_closeLiveRoom +{ + // 重置service状态,避免leave channel触发代理方法 + self.pkService.liveStatus = NETSPkServiceInit; + [NETSToast showLoading]; + @weakify(self); + [self.pkService endLiveWithCompletionBlock:^(NSError * _Nullable error) { + @strongify(self); + [NETSToast hideLoading]; + [self.pkStatusBar stopCountdown]; + [[NENavigator shared].navigationController popViewControllerAnimated:YES]; + YXAlogInfo(@"关闭直播间,退出直播间结果, error: %@", error); + }]; + if (_requestConnectMicBar) { + [_requestConnectMicBar dismiss]; + } +} + +- (void)updateLocalConnecterArray:(NSArray *)serverArray { + NSMutableArray *tempArray = [NSMutableArray array]; + for (NETSConnectMicMemberModel *member in serverArray) { + [tempArray addObject:member.accountId]; + } + self.connectMicUidArray = tempArray; +} + +- (void)reachabilityChanged:(NSNotification *)note { + Reachability *currentReach = [note object]; + NSCParameterAssert([currentReach isKindOfClass:[Reachability class]]); + NetworkStatus netStatus = [currentReach currentReachabilityStatus]; + if (netStatus == NotReachable) {//断网 + YXAlogInfo(@"主播检测到断网"); + self.isBrokenNetwork = YES; + }else {//有网络 + if (self.isBrokenNetwork) { + [NETSLiveApi requestMicSeatsResultListWithRoomId:self.liveRoomModel.liveCid type:NETSUserStatusAlreadyOnWheat successBlock:^(NSDictionary * _Nonnull response) { + NSArray *memberList = response[@"/data/seatList"]; + NSMutableArray *currentConnecterArray = [NSMutableArray arrayWithArray:self.connectMicArray]; + for (NETSConnectMicMemberModel *memberModel in memberList) { + if (![self.connectMicUidArray containsObject:memberModel.accountId]) { + [currentConnecterArray addObject:memberModel]; + } + } + //刷新麦位人数 + if (![self.view.subviews containsObject:self.connectMicView]) { + [self.view addSubview:self.connectMicView]; + } + [self.connectMicView reloadDataSource:currentConnecterArray]; + YXAlogInfo(@"请求连麦者列表成功,response = %@",response); + } failedBlock:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { + YXAlogError(@"请求连麦者列表失败,error = %@",error.description); + }]; + } + } + +} + +#pragma mark - 当键盘事件 + +- (void)keyboardWillShow:(NSNotification *)aNotification +{ + NSDictionary *userInfo = [aNotification userInfo]; + CGRect rect = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; + CGFloat keyboardHeight = rect.size.height; + CGFloat chatViewHeight = [self chatViewHeight]; + [UIView animateWithDuration:[userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue] animations:^{ + self.chatView.frame = CGRectMake(8, kScreenHeight - (kIsFullScreen ? 34 : 0) - chatViewHeight - keyboardHeight - 50, kScreenWidth - 16 - 60 - 20, chatViewHeight); + self.toolBar.frame = CGRectMake(0, kScreenHeight - keyboardHeight - 50, kScreenWidth, 50); + }]; + [self.view bringSubviewToFront:self.toolBar]; + +} + +- (void)keyboardWillHide:(NSNotification *)aNotification +{ + CGFloat chatViewHeight = [self chatViewHeight]; + [UIView animateWithDuration:0.1 animations:^{ + self.chatView.frame = CGRectMake(8, kScreenHeight - (kIsFullScreen ? 34 : 0) - 64 - chatViewHeight, kScreenWidth - 16 - 60 - 20, chatViewHeight); + self.toolBar.frame = CGRectMake(0, kScreenHeight + 50, kScreenWidth, 50); + }]; +} + +#pragma mark - NETSAnchorBottomPanelDelegate 底部操作面板代理 + +- (void)clickBeautyBtn +{ + [NETSBeautySettingActionSheet showWithMask:NO]; +} + +- (void)clickFilterBtn +{ + [NETSFilterSettingActionSheet showWithMask:NO]; +} + +- (void)clickSettingBtn +{ + [NETSLiveSettingActionSheet show]; +} + +- (void)clickStartLiveBtn +{ + // 开启直播条件判断 + NSString *topic = [self.settingPanel getTopic]; + NSString *cover = [self.settingPanel getCover]; + if (isEmptyString(topic)) { + [NETSToast showToast:@"直播间主题为空"]; + return; + } + if (isEmptyString(cover)) { + [NETSToast showToast:@"直播间封面为空"]; + return; + } + + [NETSToast showLoading]; + @weakify(self); + [self.pkService startSingleLiveWithTopic:topic coverUrl:cover successBlock:^(NETSCreateLiveRoomModel *_Nonnull room, NERtcLiveStreamTaskInfo *_Nonnull task) { + @strongify(self); + [NETSToast hideLoading]; + self.warnToast.hidden = YES; + self.chatHandle.roomId = room.chatRoomId; + self.anchorMessageHandle.roomId = room.chatRoomId; + self.liveRoomModel = room; + } failedBlock:^(NSError * _Nonnull error) { + @strongify(self); + [NETSToast hideLoading]; + if ([error.domain isEqualToString:@"NIMLocalErrorDomain"] && error.code == 13) { + NSString *msg = error.userInfo[NSLocalizedDescriptionKey] ?: @"IM登录失败"; + [NETSToast showToast:msg]; + + self.pkService.liveStatus = NETSPkServiceInit; + [NETSToast showLoading]; + [self.pkService endLiveWithCompletionBlock:^(NSError * _Nullable error) { + [NETSToast hideLoading]; + ntes_main_async_safe(^{ + NEMenuViewController *vc = [[NEMenuViewController alloc] init]; + [[NENavigator shared].navigationController popToViewController:vc animated:YES]; + }); + }]; + } else { + NSString *msg = error.userInfo[NSLocalizedDescriptionKey] ?: @"开启直播间失败"; + [NETSToast showToast:msg]; + YXAlogInfo(@"开启直播间失败, error: %@", error); + } + }]; +} + +#pragma mark - NETSMoreSettingActionSheetDelegate 点击更多设置代理 + +/// 开启/关闭 摄像头 +- (void)didSelectCameraEnable:(BOOL)enable +{ + if (!enable) { + [self.localCanvas resetCanvas]; + } else { + [self.localCanvas setupCanvas]; + } +} + +/// 关闭直播间 +- (void)didSelectCloseLive +{ + [self _closeLiveRoom]; +} + +#pragma mark - NETSLiveChatViewHandleDelegate 聊天室代理 + +- (void)didShowMessages:(NSArray *)messages +{ + [self.chatView addMessages:messages]; +} + +/// 进入或离开房间 +- (void)didChatroomMember:(NIMChatroomNotificationMember *)member enter:(BOOL)enter sessionId:(NSString *)sessionId +{ + if (enter) { + YXAlogInfo(@"[demo] user %@ enter room.", member.userId); + } else { + YXAlogInfo(@"[demo] user %@ leaved room.", member.userId); + } + // 是否主播离开 + NSString *chatRoomCreator = self.pkService.singleRoom.imAccid; + if (self.pkService.liveStatus == NETSPkServicePkLive) { + chatRoomCreator = self.pkService.pkRoom.imAccid; + } + if ([chatRoomCreator isEqualToString:member.userId]) { + YXAlogInfo(@"聊天室创建者: \"%@\" %@房间", member.userId, (enter ? @"加入":@"离开")); + } else { + // 提示非聊天室创建者 加入/离开 消息 + NIMMessage *message = [[NIMMessage alloc] init]; + message.text = [NSString stringWithFormat:@"\"%@\" %@房间", member.nick, (enter ? @"加入":@"离开")]; + message.remoteExt = @{@"type":@(1)}; + [_chatView addMessages:@[message]]; + } + + // 聊天室信息成员变更 + NSString *roomId = self.pkService.singleRoom.chatRoomId; + [NETSChatroomService fetchMembersRoomId:roomId limit:10 successBlock:^(NSArray * _Nullable members) { + YXAlogInfo(@"members: %@", members); + [self.audienceInfo reloadWithDatas:members]; + } failedBlock:^(NSError * _Nonnull error) { + YXAlogInfo(@"主播端获取IM聊天室成员失败, error: %@", error); + }]; + + YXAlogInfo(@"didChatroomMember:%@ enter:%hhd creator:%@ sessionId:%@", member.userId, enter, chatRoomCreator, sessionId); +} + +/// 房间关闭 +- (void)didChatroomClosedWithRoomId:(NSString *)roomId { + +// [self.liveClosedMask installWithAvatar:self.liveRoomModel.avatar nickname:self.liveRoomModel.nickname]; +// [self.view addSubview:self.liveClosedMask]; + YXAlogInfo(@"主播房间关闭"); +} + +/// 收到文本消息 +- (void)didReceivedTextMessage:(NIMMessage *)message +{ + NSArray *msgs = @[message]; + [self.chatView addMessages:msgs]; +} + +#pragma mark - NETSInputToolBarDelegate 底部工具条代理事件 + +- (void)clickInputToolBarAction:(NETSInputToolBarAction)action +{ + switch (action) { + case NETSInputToolBarInput: { + [self.toolBar becomeFirstResponse]; + } + break; + case NETSInputToolBarBeauty: { + [NETSBeautySettingActionSheet show]; + } + break; + case NETSInputToolBarConnectRequest: {//主播连麦管理 + + if (self.pkService.liveStatus == NETSPkServicePkInviting) { + [NETSToast showToast:@"PK邀请中,不能操作连麦"]; + //预留 + }else if (self.pkService.liveStatus == NETSPkServicePkLive){ + [NETSToast showToast:@"PK中,不能操作连麦"]; + }else { + if (_requestConnectMicBar) { + [self.requestConnectMicBar dismiss]; + } + NETSRequestManageMainController *statusVc = [[NETSRequestManageMainController alloc] initWithRoomId:self.liveRoomModel.liveCid]; + NTESActionSheetNavigationController *nav = [[NTESActionSheetNavigationController alloc] initWithRootViewController:statusVc]; + nav.dismissOnTouchOutside = YES; + [[NENavigator shared].navigationController presentViewController:nav animated:YES completion:nil]; + } + + } + break; + case NETSInputToolBarMusic: { + [NETSAudioMixingActionSheet show]; + } + break; + case NETSInputToolBarMore: { + NSArray *items = [NETSLiveConfig shared].moreSettings; + [NETSMoreSettingActionSheet showWithTarget:self items:items]; + } + break; + + default: + break; + } +} + +#pragma mark - NETSInvitingBarDelegate 取消连麦代理 + +- (void)clickCancelInviting:(NETSInviteBarType)barType { + + if (barType == NETSInviteBarTypeConnectMic) { + [self clickInputToolBarAction:NETSInputToolBarConnectRequest]; + }else {//默认是邀请pk直播的类型 + [self.pkService inviterSendCancelPkWithSuccessBlock:^{ + YXAlogInfo(@"取消pk邀请"); + } failedBlock:^(NSError * _Nonnull error) { + YXAlogInfo(@"取消pk邀请失败, error: %@", error); + }]; + } +} + + +#pragma mark - NETSChoosePKSheetDelegate 选择主播PK代理 + +- (void)choosePkOnSheet:(NETSChoosePKSheet *)sheet withRoom:(NETSLiveRoomModel *)room +{ + [sheet dismiss]; + + @weakify(self); + void (^successBlock)(NETSCreateLiveRoomModel * _Nonnull, NIMSignalingChannelDetailedInfo * _Nonnull) = ^(NETSCreateLiveRoomModel *pkRoom, NIMSignalingChannelDetailedInfo * _Nonnull info) { + @strongify(self); + NSString *title = [NSString stringWithFormat:@"邀请\"%@\"PK连线中...", room.nickname]; + self.pkInvitingBar = [NETSInvitingBar showInvitingWithTarget:self title:title]; + }; + + void (^failedBlock)(NSError * _Nullable) = ^(NSError * _Nullable error) { + NSString *msg = error.userInfo[NSLocalizedDescriptionKey] ?: @"邀请PK失败"; + [NETSToast showToast:msg]; + }; + + NSString *msg = [NSString stringWithFormat:@"确定邀请\"%@\"进行PK?", room.nickname]; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"邀请PK" message:msg preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + YXAlogInfo(@"邀请者取消pk邀请..."); + }]; + UIAlertAction *confirm = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + @strongify(self); + YXAlogInfo(@"邀请者确定邀请%@,进行pk...", room.nickname); + [self.pkService inviterSendPkInviteWithInviteeRoom:room successBlock:successBlock failedBlock:failedBlock]; + }]; + [alert addAction:cancel]; + [alert addAction:confirm]; + [self presenAlert:alert]; +} + +#pragma mark - NERtcEngineDelegateEx G2音视频 + +- (void)onNERtcEngineVideoFrameCaptured:(CVPixelBufferRef)bufferRef rotation:(NERtcVideoRotationType)rotation +{ + [[NETSFUManger shared] renderItemsToPixelBuffer:bufferRef]; +} + +- (void)onNERtcEngineUserDidJoinWithUserID:(uint64_t)userID userName:(NSString *)userName +{ + NERtcVideoCanvas *canvas = [self setupRemoteCanvas]; + [NERtcEngine.sharedEngine setupRemoteVideoCanvas:canvas forUserID:userID]; +} + +- (void)onNERtcEngineUserVideoDidStartWithUserID:(uint64_t)userID videoProfile:(NERtcVideoProfileType)profile +{ + _remoteCanvas.subscribedVideo = YES; + [NERtcEngine.sharedEngine subscribeRemoteVideo:YES forUserID:userID streamType:kNERtcRemoteVideoStreamTypeHigh]; +} + +- (void)onNERtcEngineUserDidLeaveWithUserID:(uint64_t)userID reason:(NERtcSessionLeaveReason)reason +{ + // 如果远端的人离开了,重置远端模型和UI + if (userID == _remoteCanvas.uid) { + [_remoteCanvas resetCanvas]; + _remoteCanvas = nil; + } +} + +/// 离开channel +- (void)onNERtcEngineDidLeaveChannelWithResult:(NERtcError)result +{ + if (result != kNERtcNoError) { + YXAlogInfo(@"离开单人channel失败, error: %d", result); + return; + } + + // 离开channel,重置混音索引 + [NETSLiveConfig shared].mixingIdx = -1; + + @weakify(self); + [self.pkService transformRoomWithSuccessBlock:^(NETSPkServiceStatus status, int64_t uid) { + @strongify(self); + YXAlogInfo(@"转换直播间模式: %zd", status); + if (status == NETSPkServicePkLive) { + NERtcVideoCanvas *canvas = [self setupLocalCanvas]; + [NERtcEngine.sharedEngine setupLocalVideoCanvas:canvas]; + + [self.serverStartPkSubject sendNext:@""]; + } else { + NERtcVideoCanvas *canvas = [self setupSingleCanvas]; + [NERtcEngine.sharedEngine setupLocalVideoCanvas:canvas]; + } + } failedBlock:^(NSError * _Nonnull error) { + YXAlogInfo(@"单人直播间/pk直播间 转换失败, error: %@", error); + }]; +} + +- (void)onNERtcEngineDidDisconnectWithReason:(NERtcError)reason +{ + [NETSToast showToast:@"网络断开"]; + [self _closeLiveRoom]; +} + +/// 直播推流状态回调 +- (void)onNERTCEngineLiveStreamState:(NERtcLiveStreamStateCode)state taskID:(NSString *)taskID url:(NSString *)url +{ + YXAlogInfo(@"直播推流状态回调, state: %ld, taskId: %@, url: %@", state, taskID, url); + if (state == kNERtcLsStatePushFail) { + [NETSPushStreamService removeStreamTask:taskID successBlock:^{ + [NETSPushStreamService addStreamTask:self.pkService.streamTask successBlock:^{ + YXAlogInfo(@"重新推流成功"); + } failedBlock:^(NSError * _Nonnull error, NSString *taskID) { + YXAlogInfo(@"重新推流失败, taskID:%@, error: %@", taskID, error); + }]; + } failedBlock:^(NSError * _Nonnull error) { + YXAlogInfo(@"推流失败, 移除原推流ID: %@, 失败, error: %@", taskID, error); + if (error) { + [self _closeLiveRoom]; + } + }]; + } +} + +/// 音效播放结束回调 +- (void)onAudioEffectFinished:(uint32_t)effectId +{ + NSDictionary *info = @{ @"effectId": @(effectId) }; + [[NSNotificationCenter defaultCenter] postNotificationName:kNetsRtcEffectStopNoti object:nil userInfo:info]; +} + + +/// 点击屏幕收起键盘 +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + [self.toolBar resignFirstResponder]; + [self.livingInputTool resignFirstResponder]; + YXAlogDebug(@"accountId = %@",[NEAccount shared].userModel.accountId); + +} + +#pragma mark - NTESKeyboardToolbarDelegate 键盘顶部工具条代理 + +- (void)didToolBarSendText:(NSString *)text +{ + if (isEmptyString(text)) { + [NETSToast showToast:@"所发消息为空"]; + return; + } + + [self.livingInputTool resignFirstResponder]; + NSError *err = nil; + [self.pkService sendMessageWithText:text errorPtr:&err]; + YXAlogInfo(@"主播端 发送文本消息, error: %@", err); +} + +/// pk推流 +- (void)_pushPkLiveStreamWithData:(NETSPassThroughHandlePkStartData *)data +{ + YXAlogInfo(@"开始推流..."); + NSString *logPtr = (self.pkService.liveRole == NETSPkServiceInviter) ? @"邀请者" : @"被邀请者"; + + @weakify(self); + [self.pkService pushPkStreamWithData:data successBlock:^{ + YXAlogInfo(@"%@添加推流任务成功", logPtr); + } failedBlock:^(NSError * _Nonnull error, NSString * _Nullable taskID) { + @strongify(self); + YXAlogInfo(@"%@添加推流任务失败, error: %@, taskID: %@", logPtr, error, taskID); + if (error) { + [self _closeLiveRoom]; + } + }]; +} + +#pragma mark - private method + +- (void)presenAlert:(UIAlertController *)alert +{ + // 消除顶层视图 + UIView *topmostView = [TopmostView viewForApplicationWindow]; + for (UIView *subview in topmostView.subviews) { + [subview removeFromSuperview]; + } + topmostView.userInteractionEnabled = NO; + + // 弹出alert + if (self.pkAlert) { + [self.pkAlert dismissViewControllerAnimated:NO completion:nil]; + self.pkAlert = nil; + } + [[NENavigator shared].navigationController presentViewController:alert animated:YES completion:nil]; + self.pkAlert = alert; +} + +//更新推流任务 +- (void)updateLiveStreamTask:(NERtcLiveStreamTaskInfo *)taskInfo{ + int ret = [NERtcEngine.sharedEngine updateLiveStreamTask:taskInfo + compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) { + if (errorCode == 0) { + //推流任务添加成功 + }else { + //推流任务添加失败 + YXAlogError(@"推流任务添加失败"); + } + }]; + if (ret != 0) { + //更新失败 + YXAlogError(@"更新推流任务失败"); + } +} + +- (void)updateStreamUserTrans:(uint64_t)uid { + + NERtcLiveStreamTaskInfo *taskInfo = [[NERtcLiveStreamTaskInfo alloc] init]; + taskInfo.taskID = self.pkService.streamTask.taskID; + taskInfo.streamURL = self.pkService.streamTask.streamURL; + taskInfo.lsMode = kNERtcLsModeVideo; + CGFloat width = 720; + CGFloat height = 1280; + + + //设置整体布局 + NERtcLiveStreamLayout *streamLayout = [[NERtcLiveStreamLayout alloc] init]; + streamLayout.width = width; + streamLayout.height = height; + taskInfo.layout = streamLayout; + + NSMutableArray *usersArray = [NSMutableArray array]; + //设置主播布局 + if (self.liveRoomModel.avRoomUid) { + NERtcLiveStreamUserTranscoding *userTranscoding = [[NERtcLiveStreamUserTranscoding alloc] init]; + userTranscoding.uid = self.liveRoomModel.avRoomUid.longLongValue; + userTranscoding.audioPush = YES; + userTranscoding.videoPush = YES; + userTranscoding.width = width; + userTranscoding.height = height; + userTranscoding.adaption = kNERtcLsModeVideoScaleCropFill; + [usersArray addObject:userTranscoding]; + } + + //设置连麦者布局 + for (int i = 0; i 0) { + [self.connectMicView reloadDataSource:self.connectMicArray]; + }else { + [self.connectMicView removeFromSuperview]; + self.connectMicView = nil; + self.pkBtn.hidden = NO; + } + ntes_main_async_safe(^{ + NIMMessage *message = [[NIMMessage alloc] init]; + message.text = [NSString stringWithFormat:@"\"%@\" 成功下麦", msgAttachment.member.nickName]; + message.remoteExt = @{@"type":@(1)}; + [self.chatView addMessages:@[message]]; + }); + [self updateStreamUserTrans:[msgAttachment.member.avRoomUid longLongValue]]; + YXAlogInfo(@"请求连麦管理列表失败,response = %@",response); + } failedBlock:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { + YXAlogError(@"请求连麦管理列表失败,error = %@",error.description); + }]; +} + +//主播收到印音视频变化的信息 +- (void)receivedAudioAndVideoChange:(NETSConnectMicAttachment *)msgAttachment { + for (NETSConnectMicMemberModel *memberModel in self.connectMicArray) { + if ([msgAttachment.member.accountId isEqualToString:memberModel.accountId]) { + memberModel.audio = msgAttachment.member.audio; + memberModel.video = msgAttachment.member.video; + break; + } + } + [self.connectMicView reloadDataSource:self.connectMicArray]; +} + +#pragma mark - NETSMutiConnectViewDelegate +-(void)disconnectRoomWithUserId:(NSString *)userId { + [NETSLiveApi requestSeatManagerWithRoomId:self.liveRoomModel.liveCid userId:userId index:1 action:NETSSeatsOperationAdminKickSeats successBlock:^(NSDictionary * _Nonnull response) { + YXAlogDebug(@"管理员踢下麦成功,response = %@",response); + } failedBlock:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { + YXAlogError(@"管理员踢下麦失败,error = %@",error.description); + }]; +} + +#pragma mark - lazy load + +- (UIView *)singleRender +{ + if (!_singleRender) { + _singleRender = [[UIView alloc] init]; + } + return _singleRender; +} + +- (UIView *)localRender +{ + if (!_localRender) { + _localRender = [[UIView alloc] init]; + } + return _localRender; +} + +- (UIView *)remoteRender +{ + if (!_remoteRender) { + _remoteRender = [[UIView alloc] init]; + } + return _remoteRender; +} + +- (NETSAnchorBottomPanel *)bottomPanel +{ + if (!_bottomPanel) { + _bottomPanel = [[NETSAnchorBottomPanel alloc] init]; + _bottomPanel.delegate = self; + } + return _bottomPanel; +} + +- (NTESKeyboardToolbarView *)toolBar +{ + if (!_toolBar) { + _toolBar = [[NTESKeyboardToolbarView alloc] initWithFrame:CGRectMake(0, kScreenHeight, kScreenWidth, 50)]; + _toolBar.backgroundColor = UIColor.whiteColor; + _toolBar.cusDelegate = self; + } + return _toolBar; +} + +- (UIButton *)backBtn +{ + if (!_backBtn) { + _backBtn = [[UIButton alloc] init]; + [_backBtn setImage:[UIImage imageNamed:@"back_ico"] forState:UIControlStateNormal]; + [_backBtn addTarget:self action:@selector(clickAction:) forControlEvents:UIControlEventTouchUpInside]; + } + return _backBtn; +} + +- (UIButton *)switchCameraBtn +{ + if (!_switchCameraBtn) { + _switchCameraBtn = [[UIButton alloc] init]; + UIImage *img = [[UIImage imageNamed:@"switch_camera_ico"] sd_tintedImageWithColor:[UIColor whiteColor]]; + [_switchCameraBtn setImage:img forState:UIControlStateNormal]; + [_switchCameraBtn addTarget:self action:@selector(clickAction:) forControlEvents:UIControlEventTouchUpInside]; + } + return _switchCameraBtn; +} + +- (NETSAnchorCoverSetting *)settingPanel +{ + if (!_settingPanel) { + _settingPanel = [[NETSAnchorCoverSetting alloc] init]; + } + return _settingPanel; +} + +- (NETSWarnToast *)warnToast +{ + if (!_warnToast) { + _warnToast = [[NETSWarnToast alloc] init]; + _warnToast.toast = @"本应用为测试产品、请勿商用。单次直播最长10分钟,每个频道最多10人"; + } + return _warnToast; +} + +- (NETSAnchorTopInfoView *)anchorInfo +{ + if (!_anchorInfo) { + _anchorInfo = [[NETSAnchorTopInfoView alloc] init]; + } + return _anchorInfo; +} + +- (NETSAudienceNum *)audienceInfo +{ + if (!_audienceInfo) { + _audienceInfo = [[NETSAudienceNum alloc] initWithFrame:CGRectZero]; + } + return _audienceInfo; +} + +- (NETSInputToolBar *)livingInputTool +{ + if (!_livingInputTool) { + _livingInputTool = [[NETSInputToolBar alloc] init]; + _livingInputTool.delegate = self; + _livingInputTool.textField.inputAccessoryView = [[UIView alloc] init]; + } + return _livingInputTool; +} + +- (UIButton *)pkBtn +{ + if (!_pkBtn) { + _pkBtn = [[UIButton alloc] init]; + [_pkBtn setImage:[UIImage imageNamed:@"pk_ico"] forState:UIControlStateNormal]; + [_pkBtn addTarget:self action:@selector(clickAction:) forControlEvents:UIControlEventTouchUpInside]; + } + return _pkBtn; +} + +- (NETSLiveChatView *)chatView { + if (!_chatView) { + CGRect frame = CGRectMake(8, kScreenHeight - (kIsFullScreen ? 34 : 0) - 64 - 204, kScreenWidth - 16 - 60 - 20, 204); + _chatView = [[NETSLiveChatView alloc] initWithFrame:frame]; + } + return _chatView; +} + +- (CGFloat)chatViewHeight +{ + if (kScreenHeight <= 568) { + return 100; + } else if (kScreenHeight <= 736) { + return 130; + } + return 204; +} + +- (NETSPkStatusBar *)pkStatusBar +{ + if (!_pkStatusBar) { + _pkStatusBar = [[NETSPkStatusBar alloc] init]; + } + return _pkStatusBar; +} + +- (UIImageView *)pkSuccessIco +{ + if (!_pkSuccessIco) { + _pkSuccessIco = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + } + return _pkSuccessIco; +} + +- (UIImageView *)pkFailedIco +{ + if (!_pkFailedIco) { + _pkFailedIco = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + } + return _pkFailedIco; +} + +- (NETSInviteeInfoView *)inviteeInfo +{ + if (!_inviteeInfo) { + _inviteeInfo = [[NETSInviteeInfoView alloc] init]; + } + return _inviteeInfo; +} + +/// 建立单人直播canvas模型 +- (NERtcVideoCanvas *)setupSingleCanvas +{ + if (!_singleCanvas) { + _singleCanvas = [[NETSCanvasModel alloc] init]; + _singleCanvas.renderContainer = self.singleRender; + } + [_singleCanvas resetCanvas]; + return [_singleCanvas setupCanvas]; +} + +/// 建立本地canvas模型 +- (NERtcVideoCanvas *)setupLocalCanvas +{ + if (!_localCanvas) { + _localCanvas = [[NETSCanvasModel alloc] init]; + _localCanvas.renderContainer = self.localRender; + } + [_localCanvas resetCanvas]; + return [_localCanvas setupCanvas]; +} + +/// 建立远端canvas模型 +- (NERtcVideoCanvas *)setupRemoteCanvas +{ + if (!_remoteCanvas) { + _remoteCanvas = [[NETSCanvasModel alloc] init]; + _remoteCanvas.renderContainer = self.remoteRender; + } + [_remoteCanvas resetCanvas]; + return [_remoteCanvas setupCanvas]; +} + +- (NETSMutiConnectView *)connectMicView { + if (!_connectMicView) { + _connectMicView = [[NETSMutiConnectView alloc]initWithDataSource:self.connectMicArray frame:CGRectMake(kScreenWidth-88-10, 104, 88, kScreenHeight-2*104)]; + _connectMicView.roleType = NETSUserModeAnchor; + _connectMicView.delegate = self; + } + return _connectMicView; +} + +- (NETSAnchorChatroomMessageHandle *)anchorMessageHandle { + if (!_anchorMessageHandle) { + _anchorMessageHandle = [[NETSAnchorChatroomMessageHandle alloc]init]; + _anchorMessageHandle.delegate = self; + } + return _anchorMessageHandle; +} + +//- (NETSLiveEndView *)liveClosedMask +//{ +// if (!_liveClosedMask) { +// _liveClosedMask = [[NETSLiveEndView alloc] init]; +// } +// return _liveClosedMask; +//} +@end diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/ActionSheet/More/NETSMoreSettingActionSheet.m b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/ActionSheet/More/NETSMoreSettingActionSheet.m index 4e0947d..fb77bb1 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/ActionSheet/More/NETSMoreSettingActionSheet.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/ActionSheet/More/NETSMoreSettingActionSheet.m @@ -106,13 +106,13 @@ - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPa - (void)_didActionWithModel:(NETSMoreSettingModel *)model { + + [self dismiss];//点击后收起sheet NETSMoreSettingStatusModel *statusModel = nil; if ([model isKindOfClass:[NETSMoreSettingStatusModel class]]) { statusModel = (NETSMoreSettingStatusModel *)model; - } else { - [self dismiss]; } - + switch (model.type) { case NETSMoreSettingCamera: { if (!statusModel) { return; } diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/BottomPanel/NETSAnchorBottomPanel.h b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/BottomPanel/NETSAnchorBottomPanel.h index 25af0d0..4f8a195 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/BottomPanel/NETSAnchorBottomPanel.h +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/BottomPanel/NETSAnchorBottomPanel.h @@ -41,9 +41,6 @@ NS_ASSUME_NONNULL_BEGIN /// 触发 滤镜 按钮 代理方法 - (void)clickFilterBtn; -/// 触发 设置 按钮 代理方法 -- (void)clickSettingBtn; - /// 触发 开启直播间 按钮 代理方法 - (void)clickStartLiveBtn; diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/BottomPanel/NETSAnchorBottomPanel.m b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/BottomPanel/NETSAnchorBottomPanel.m index 098d4e4..2c484de 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/BottomPanel/NETSAnchorBottomPanel.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Anchor/view/BottomPanel/NETSAnchorBottomPanel.m @@ -7,6 +7,7 @@ // Use of this source code is governed by a MIT license that can be found in the LICENSE file. #import "NETSAnchorBottomPanel.h" +#import "UIControl+repeatclick.h" @interface NETSCircleBtn () @@ -78,8 +79,7 @@ @interface NETSAnchorBottomPanel () @property (nonatomic, strong) NETSCircleBtn *beautyBtn; /// 滤镜按钮 @property (nonatomic, strong) NETSCircleBtn *filterBtn; -/// 设置按钮 -@property (nonatomic, strong) NETSCircleBtn *settingBtn; + @end @@ -98,10 +98,9 @@ - (void)setupViews { [self addSubview:self.beautyBtn]; [self addSubview:self.filterBtn]; - [self addSubview:self.settingBtn]; [self addSubview:self.liveBtn]; - NSArray *views = @[self.beautyBtn, self.filterBtn, self.settingBtn]; + NSArray *views = @[self.beautyBtn, self.filterBtn]; [views mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedItemLength:64 leadSpacing:52 tailSpacing:52]; for (UIView *view in views) { [view mas_makeConstraints:^(MASConstraintMaker *make) { @@ -128,9 +127,6 @@ - (void)clickAction:(UIButton *)sender else if ([sender isEqual:self.filterBtn] && [self.delegate respondsToSelector:@selector(clickFilterBtn)]) { [self.delegate clickFilterBtn]; } - else if ([sender isEqual:self.settingBtn] && [self.delegate respondsToSelector:@selector(clickSettingBtn)]) { - [self.delegate clickSettingBtn]; - } else if ([sender isEqual:self.liveBtn] && [self.delegate respondsToSelector:@selector(clickStartLiveBtn)]) { [self.delegate clickStartLiveBtn]; } @@ -158,15 +154,6 @@ - (NETSCircleBtn *)filterBtn return _filterBtn; } -- (NETSCircleBtn *)settingBtn -{ - if (!_settingBtn) { - _settingBtn = [[NETSCircleBtn alloc] initWithTitle:NSLocalizedString(@"设置", nil) icon:@"setting_ico"]; - _settingBtn.backgroundColor = [UIColor colorWithWhite:0 alpha:0.6]; - [_settingBtn addTarget:self action:@selector(clickAction:) forControlEvents:UIControlEventTouchUpInside]; - } - return _settingBtn; -} - (UIButton *)liveBtn { @@ -178,6 +165,8 @@ - (UIButton *)liveBtn _liveBtn.titleLabel.font = [UIFont systemFontOfSize:16]; _liveBtn.layer.cornerRadius = 22; _liveBtn.layer.masksToBounds = YES; + _liveBtn.ne_ignoreEvent = NO; + _liveBtn.ne_acceptEventInterval = 3.0;//防重点击 } return _liveBtn; } diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Audience/view/VideoContainerView/NETSAudienceChatRoomCell.m b/OnlinePK-iOS/NLiteAVDemo/Live/Audience/view/VideoContainerView/NETSAudienceChatRoomCell.m index 7b49012..342fc60 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/Audience/view/VideoContainerView/NETSAudienceChatRoomCell.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Audience/view/VideoContainerView/NETSAudienceChatRoomCell.m @@ -25,6 +25,7 @@ #import "NEPkRoomApiService.h" #import "NECreateRoomResponseModel.h" #import "NEPkEnterRoomParams.h" +#import "NETSInvitingBar.h" @interface NETSAudienceChatRoomCell () @@ -54,6 +55,8 @@ @interface NETSAudienceChatRoomCell () #import "NETSGCDTimer.h" @@ -398,7 +399,7 @@ - (void)joinChannelWithData:(NEAvRoomUserDetail *)data { [coreEngine enableLocalAudio:YES]; [coreEngine enableLocalVideo:YES]; - int result = [NERtcEngine.sharedEngine joinChannelWithToken:data.avRoomCheckSum channelName:data.avRoomCName myUid:[data.avRoomUid longLongValue] completion:^(NSError * _Nullable error, uint64_t channelId, uint64_t elapesd) { + int result = [NERtcEngine.sharedEngine joinChannelWithToken:data.avRoomCheckSum channelName:data.avRoomCName myUid:[data.avRoomUid longLongValue] completion:^(NSError * _Nullable error, uint64_t channelId, uint64_t elapesd,uint64_t uid) { if (error) { YXAlogError(@"audience joinChannel failed, error = %@",error); }else{ @@ -460,8 +461,10 @@ - (void)didPlayerFrameChanged:(NSNotification *)notification if (self.delegate && [self.delegate respondsToSelector:@selector(didChangeRoomStatus:)]) { CGFloat height = [userInfo[NELivePlayerVideoHeightKey] floatValue]; + CGFloat width = [userInfo[NELivePlayerVideoWidthKey] floatValue]; + NETSAudienceStreamStatus status = NETSAudienceStreamDefault; - if (height == 640) { + if (height == 640 && width == 720) { status = NETSAudienceStreamMerge; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @@ -542,8 +545,7 @@ -(void)receivePkStartAttachment:(NEPkLiveStartAttachment *)liveStartData { // pk状态栏变更 [self _layoutPkStatusBarWithStatus:_liveStatus]; // pk开始: 启动倒计时,刷新内容 - int32_t countdown = kPkLiveTotalTime - (int32_t)((liveStartData.sendTime - liveStartData.pkStartTime) / 1000); - [self.pkStatusBar countdownWithSeconds:countdown prefix:@"PK "]; + [self.pkStatusBar countdownWithSeconds:liveStartData.pkCountDown prefix:@"PK "]; [self.pkStatusBar refreshWithLeftRewardCoins:0 leftRewardAvatars:@[] rightRewardCoins:0 rightRewardAvatars:@[]]; } @@ -644,6 +646,13 @@ - (void)_liveRoomClosed #pragma mark - NESeatServiceDelegate //主播同意了观众的连麦申请 - (void)onSeatApplyAccepted:(NESeatApplyAcceptEvent *)event { + YXAlogInfo(@"onSeatApplyAccepted success,") + for (UIView *maskSubview in self.subviews) {//断网重连 暂停上麦 + if ([maskSubview isKindOfClass:[NETSPullStreamErrorView class]]) { + return; + } + } + self.bottomBar.buttonType = NETSAudienceBottomRequestTypeAccept; //取消观众申请连麦的bar [self.requestConnectMicBar dismiss]; @@ -665,6 +674,10 @@ - (void)onSeatApplyRejected:(NESeatApplyRejectEvent *)event { - (void)onSeatPickRequest:(NESeatPickRequestEvent *)event { + UIViewController *currentCtrl = [NETSUniversalTool getCurrentActivityViewController]; + if (![currentCtrl isKindOfClass:[NETSAudienceCollectionViewVC class]]) { + return; + } //rtc delegate强引用导致观众控制器无法释放,这里需做去重判断,弹窗避免弹两次 [NETSAlertPrompt showAlert:UIAlertControllerStyleAlert title:NSLocalizedString(@"邀请上麦", nil) message:NSLocalizedString(@"主播邀请你上麦", nil) actionArr:@[NSLocalizedString(@"拒绝", nil),NSLocalizedString(@"上麦", nil)] actionColors:@[HEXCOLOR(0x666666),HEXCOLOR(0x007AFF)] cancel:nil index:^(NSInteger index) { if (index == 1) { @@ -698,6 +711,7 @@ -(void)onSeatPickAccepted:(NESeatPickAcceptEvent *)event { -(void)onSeatEntered:(NESeatEnterEvent *)event { + YXAlogInfo(@"onSeatEntered success,accountId = %@",event.seatInfo.userInfo.accountId); event.seatInfo.avRoomUid = event.avRoomUser.avRoomUid.longLongValue; if (self.delegate && [self.delegate respondsToSelector:@selector(memberSeatStateChanged:seatInfo:)]) { [self.delegate memberSeatStateChanged:YES seatInfo:event.seatInfo]; @@ -719,6 +733,7 @@ -(void)onSeatEntered:(NESeatEnterEvent *)event { - (void)onSeatLeft:(NESeatLeaveEvent *)event { + YXAlogInfo(@"onSeatEntered success,accountId = %@",event.seatInfo.userInfo.accountId); if (self.delegate && [self.delegate respondsToSelector:@selector(memberSeatStateChanged:seatInfo:)]) { [self.delegate memberSeatStateChanged:NO seatInfo:event.seatInfo]; } @@ -938,7 +953,22 @@ - (void)setUpBottomBarButtonType:(NETSAudienceBottomRequestType)buttonType { } } +- (void)clearCurrentLiveRoomData { + [self.chatView clearData]; + if (_liveStatus == NEPkliveStatusPkLiving) { + [self.inviteeInfo removeFromSuperview]; + [self.pkStatusBar removeFromSuperview]; + }else if (_liveStatus == NEPkliveStatusPunish) { + [self.pkSuccessIco removeFromSuperview]; + [self.pkFailedIco removeFromSuperview]; + } +} +-(void)dismissApplySeatBar { + if (_requestConnectMicBar) { + [self.requestConnectMicBar dismiss]; + } +} #pragma mark - NTESAudienceConnectStatusDelegate //设置麦克风开关 - (void)didSetMicOn:(BOOL)micOn { diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Chatroom/NETSLiveChatView.h b/OnlinePK-iOS/NLiteAVDemo/Live/Chatroom/NETSLiveChatView.h index a6ac1a5..1b9ac51 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/Chatroom/NETSLiveChatView.h +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Chatroom/NETSLiveChatView.h @@ -36,6 +36,8 @@ NS_ASSUME_NONNULL_BEGIN /// @param messages - 新增的消息数组 /// - (void)addMessages:(NSArray *)messages; +//清空数据 +- (void)clearData; @end diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Chatroom/NETSLiveChatView.m b/OnlinePK-iOS/NLiteAVDemo/Live/Chatroom/NETSLiveChatView.m index d3e7a53..b87210c 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/Chatroom/NETSLiveChatView.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Chatroom/NETSLiveChatView.m @@ -70,6 +70,13 @@ - (void)addMessages:(NSArray *)messages } } +- (void)clearData { + _tableView.contentInset = UIEdgeInsetsMake(_tableView.height, 0, 0, 0); + [self.messages removeAllObjects]; + [self.pendingMessages removeAllObjects]; + [self.tableView reloadData]; +} + #pragma mark - UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/Config/NETSLiveConfig.m b/OnlinePK-iOS/NLiteAVDemo/Live/Config/NETSLiveConfig.m index ce0c77a..cd33519 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/Config/NETSLiveConfig.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/Config/NETSLiveConfig.m @@ -65,8 +65,9 @@ - (void)resetMoreSetting { - (NERtcVideoEncodeConfiguration *)_defaultVideoConfig { NERtcVideoEncodeConfiguration *config = [[NERtcVideoEncodeConfiguration alloc] init]; - config.maxProfile = kNERtcVideoProfileHD720P; - config.frameRate = kNERtcVideoFrameRateFps30; + config.width = 540; + config.height = 960; + config.frameRate = kNERtcVideoFrameRateFps15; return config; } diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSConnectMicService.m b/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSConnectMicService.m new file mode 100644 index 0000000..38e0a5b --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSConnectMicService.m @@ -0,0 +1,189 @@ +// +// NETSConnectMicService.m +// NLiteAVDemo +// +// Created by vvj on 2021/5/18. +// Copyright © 2021 Netease. All rights reserved. +// + +#import "NETSConnectMicService.h" +#import "SKVObject.h" +#import "TopmostView.h" +#import "NETSLiveApi.h" +#import "NETSConnectMicModel.h" +#import "AppKey.h" +#import "NETSFUManger.h" +#import "NETSAudienceCollectionViewVC.h" +#import "NENavigator.h" +@interface NETSConnectMicService () +//是否已存在弹窗 +@property(nonatomic, assign) BOOL isHaveAlert; +@end + +@implementation NETSConnectMicService + +#pragma mark - NIMPassThroughManagerDelegate + +//收到的透传代理 +-(void)didReceivedPassThroughMsg:(NIMPassThroughMsgData *)recvData { + + NSString *body = recvData.body; + if (isEmptyString(body)) { return; } + + SKVObject *obj = [SKVObject ofJSON:body]; + if (!obj) { return; } + + NSDictionary *dict = [obj dictionaryValue]; + if (!dict) { return; } + + NSInteger type = [dict[@"type"] integerValue]; + + NETSConnectMicModel *data = [NETSConnectMicModel yy_modelWithDictionary:dict]; + + switch (type) { + case NETSSeatsNotificationAdminAcceptJoinSeats: { + YXAlogInfo(@"收到主播同意上麦信令"); + + //解决主播观众同时操作问题,在观众上麦后,清除页面上的弹窗 + if (NENavigator.shared.navigationController.presentedViewController && + [NENavigator.shared.navigationController.presentedViewController isKindOfClass:UIAlertController.class]) { + self.isHaveAlert = NO; + [[NENavigator shared].navigationController dismissViewControllerAnimated:YES completion:nil]; + } + + //判断fromUser和当前主播是不是同一人,解决多端登录进入不同房间问题 + if ([data.fromUser isEqualToString:self.roomModel.accountId]) { + if ([self.delegate respondsToSelector:@selector(adminAcceptJoinSeats)]) { + [self.delegate adminAcceptJoinSeats]; + } + [self initialRtc]; + [self joinChannelWithData:data];//加入频道 + } + } + break; + case NETSSeatsNotificationAdminInviteJoinSeats: { + YXAlogInfo(@"收到主播邀请上麦信令"); + if (!self.isHaveAlert) { + YXAlogDebug(@"所在控制器为%@",[NETSUniversalTool getCurrentActivityViewController]); + Class currentVcClass = [NETSUniversalTool getCurrentActivityViewController].class; + if (currentVcClass == [NETSAudienceCollectionViewVC class] && + [data.fromUser isEqualToString:self.roomModel.accountId]) {//防止观众快速点击关闭房间 + [NETSAlertPrompt showAlert:UIAlertControllerStyleAlert title:@"邀请上麦" message:@"主播邀请你上麦" actionArr:@[@"拒绝",@"上麦"] actionColors:@[HEXCOLOR(0x666666),HEXCOLOR(0x007AFF)] cancel:nil index:^(NSInteger index) { + self.isHaveAlert = NO; + if (index == 1) { + YXAlogDebug(@"观众拒绝上麦"); + [self audienceRejectInvite]; + }else { + YXAlogDebug(@"观众同意主播的上麦邀请"); + [self audienceAcceptInvite:data]; + } + } presentVc:[NETSUniversalTool getCurrentActivityViewController]]; + self.isHaveAlert = YES; + } + + } + + } + break; + case NETSSeatsNotificationAdminRejectAudienceApply:{ + YXAlogInfo(@"收到主播拒绝观众申请连麦信令"); + [self anchorRejectAudienceRequest]; + } + break; + + case NETSSeatsNotificationAudienceApplyJoinSeats: { + ApiLogInfo(@"收到观众申请上麦信令"); + } + break; + + + case NETSSeatsNotificationAdminKickSeats :{ + ApiLogInfo(@"收到主播踢麦信令"); + [NETSToast showToast:@"您已被主播踢下麦位"]; + } + break; + default: + break; + } +} + +#pragma mark - handle PassthroughMessage +//观众拒绝上麦邀请 +- (void)audienceRejectInvite { + YXAlogDebug(@"accountId = %@",[NEAccount shared].userModel.accountId); + [NETSLiveApi requestSeatManagerWithRoomId:self.roomModel.liveCid userId:[NEAccount shared].userModel.accountId index:1 action:NETSSeatsOperationAudienceRejectJoinSeats successBlock:^(NSDictionary * _Nonnull response) { + YXAlogDebug(@"观众拒绝上麦邀请成功,response = %@",response); + } failedBlock:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { + YXAlogError(@"观众拒绝上麦邀请失败,error = %@",error.description); + }]; +} + +//观众同意上麦邀请 +- (void)audienceAcceptInvite:(NETSConnectMicModel *)data { + [NETSLiveApi requestSeatManagerWithRoomId:self.roomModel.liveCid userId:[NEAccount shared].userModel.accountId index:1 action:NETSSeatsOperationAudienceAcceptJoinSeats successBlock:^(NSDictionary * _Nonnull response) { + YXAlogDebug(@"观众同意上麦邀请成功,response = %@",response); + [self initialRtc]; + [self joinChannelWithData:data];//加入频道 + } failedBlock:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { + YXAlogError(@"观众同意上麦邀请失败,error = %@",error.description); + if (error) { + [NETSToast showToast:response[@"msg"]]; + } + }]; +} + +//主播拒绝观众申请连麦信令 +- (void)anchorRejectAudienceRequest{ + + [NETSAlertPrompt showAlert:UIAlertControllerStyleAlert title:@"主播拒绝了你的连麦申请" message:@"" actionArr:@[@"我知道了"] actionColors:@[HEXCOLOR(0x007AFF)] cancel:nil index:^(NSInteger index) {} presentVc:[NETSUniversalTool getCurrentActivityViewController]]; + if ([self.delegate respondsToSelector:@selector(adminRefuseAudienceApplyJoinSeats)]) { + [self.delegate adminRefuseAudienceApplyJoinSeats]; + } +} + + +#pragma mark - privite Method +- (void)joinChannelWithData:(NETSConnectMicModel *)data { + + NERtcEngine *coreEngine = NERtcEngine.sharedEngine; + // 打开推流,回调摄像头采集数据 + NSDictionary *params = @{ + kNERtcKeyPublishSelfStreamEnabled: @YES, // 打开推流 + kNERtcKeyVideoCaptureObserverEnabled: @YES // 将摄像头采集的数据回调给用户 + }; + [coreEngine setClientRole:kNERtcClientRoleBroadcaster]; + [coreEngine setParameters:params]; + // 启用本地音/视频 + [coreEngine enableLocalAudio:YES]; + [coreEngine enableLocalVideo:YES]; + + int result = [NERtcEngine.sharedEngine joinChannelWithToken:data.member.avRoomCheckSum channelName:data.member.avRoomCName myUid:[data.member.avRoomUid longLongValue] completion:^(NSError * _Nullable error, uint64_t channelId, uint64_t elapesd) { + if (error) { + YXAlogError(@"观众加入直播间失败 error:%@",error); + } + }]; + if (result != 0) { + YXAlogError(@"观众joinChannelWithToken失败"); + } +} + +- (void)initialRtc{ + NERtcEngine *coreEngine = [NERtcEngine sharedEngine]; + NERtcEngineContext *context = [[NERtcEngineContext alloc] init]; + context.engineDelegate = self; + context.appKey = kNertcAppkey; + int res = [coreEngine setupEngineWithContext:context]; + YXAlogInfo(@"观众NERtc初始化设置 NERtcEngine, res: %d", res); +} + +#pragma mark - NERtcEngineDelegate + +-(void)onNERtcEngineUserVideoDidStartWithUserID:(uint64_t)userID videoProfile:(NERtcVideoProfileType)profile { + [NERtcEngine.sharedEngine subscribeRemoteVideo:YES forUserID:userID streamType:kNERtcRemoteVideoStreamTypeHigh]; +} + +- (void)onNERtcEngineVideoFrameCaptured:(CVPixelBufferRef)bufferRef rotation:(NERtcVideoRotationType)rotation +{ + [[NETSFUManger shared] renderItemsToPixelBuffer:bufferRef]; +} +@end diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPkEnum.h b/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPkEnum.h index ffdd02c..9358b98 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPkEnum.h +++ b/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPkEnum.h @@ -150,6 +150,15 @@ typedef NS_ENUM(NSUInteger,NEPKStatus) { NEPKStatusPkPunish = 6,//惩罚中 }; +//客户端维护的本地PK状态 +typedef NS_ENUM(NSUInteger,NELocalPkState) { + NELocalPkStateInitial = 0,//未开始 + NELocalPkStateInviting = 1,//邀请中 + NELocalPkStateAgree = 2,//已同意 + NELocalPkStatePkIng = 3,//PK中 + NELocalPkStateEnd = 4,//PK结束 +}; + typedef NS_ENUM(NSUInteger,NESeatFilterType) { NESeatFilterTypeApplying = 1, //观众正在申请 NESeatFilterTypeNormal = 2, //普通观众 diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPkService.m b/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPkService.m new file mode 100644 index 0000000..e14e17c --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPkService.m @@ -0,0 +1,638 @@ +// +// NETSPkService.m +// NLiteAVDemo +// +// Created by Think on 2021/1/6. +// Copyright © 2021 Netease. All rights reserved. +// + +#import "NETSPkService.h" +#import "NETSLiveApi.h" +#import "NETSPushStreamService.h" +#import "SKVObject.h" +#import "NETSChatroomService.h" +#import +#import "NETSToast.h" +#import "NETSPkService+Inviter.h" +#import "NETSPkService+Invitee.h" +#import "NETSPkService+im.h" +#import "NETSConnectMicModel.h" +#import "NETSLiveConfig.h" + +#define kPkServiceTimerQueue "com.netease.pk.service.timer.queue" +#define NETSPkServiceErrorParamDomain @"NETSPkServiceErrorParamDomain" + +@interface NETSPkService () + +@end + +@implementation NETSPkService + +- (instancetype)initWithDelegate:(id)delegate +{ + self = [super init]; + if (self) { + _delegate = delegate; + _timerQueue = dispatch_queue_create(kPkServiceTimerQueue, DISPATCH_QUEUE_SERIAL); + + [[NIMSDK sharedSDK].signalManager addDelegate:self]; + [[NIMSDK sharedSDK].passThroughManager addDelegate:self]; + } + return self; +} + +- (void)dealloc +{ + [[NIMSDK sharedSDK].signalManager removeDelegate:self]; + [[NIMSDK sharedSDK].passThroughManager removeDelegate:self]; + + ApiLogInfo(@"dealloc NETSPkService: %p", self); +} + +#pragma mark - NIMSignalManagerDelegate + +- (void)nimSignalingOnlineNotifyEventType:(NIMSignalingEventType)eventType + response:(NIMSignalingNotifyInfo *)notifyResponse +{ + switch (eventType) { + case NIMSignalingEventTypeContrl: + ApiLogInfo(@"邀请方 收到 被邀请方 发出的pk同步 信息"); + [self _inviterReceivedPkSyncWithInviteeResponse:notifyResponse]; + break; + case NIMSignalingEventTypeReject: + { + ApiLogInfo(@"邀请方 收到 被邀请方 发出的拒绝pk邀请 信息"); + if ([notifyResponse isKindOfClass:[NIMSignalingRejectNotifyInfo class]]) { + NIMSignalingRejectNotifyInfo *response = (NIMSignalingRejectNotifyInfo *)notifyResponse; + [self _inviterReceivedPkRejectWithInviteeResponse:response]; + } + } + break; + case NIMSignalingEventTypeAccept: + { + ApiLogInfo(@"邀请方 收到 被邀请方 发出的接受pk邀请 信息"); + if ([notifyResponse isKindOfClass:[NIMSignalingAcceptNotifyInfo class]]) { + NIMSignalingAcceptNotifyInfo *response = (NIMSignalingAcceptNotifyInfo *)notifyResponse; + [self _inviterReceivedPkAcceptWithInviteeResponse:response]; + } + } + break; + case NIMSignalingEventTypeInvite: + { + ApiLogInfo(@"被邀请方 收到 邀请方 发送pk的邀请 信息"); + if (![notifyResponse isKindOfClass:[NIMSignalingInviteNotifyInfo class]]) { + return; + } + + NIMSignalingInviteNotifyInfo *response = (NIMSignalingInviteNotifyInfo *)notifyResponse; + [self _inviteeReceivedInviterResponse:response]; + } + break; + + case NIMSignalingEventTypeCancelInvite: + { + ApiLogInfo(@"被邀请方 收到 邀请方 取消pk的邀请 信息"); + if (![notifyResponse isKindOfClass:[NIMSignalingCancelInviteNotifyInfo class]]) { + return; + } + NIMSignalingCancelInviteNotifyInfo *response = (NIMSignalingCancelInviteNotifyInfo *)notifyResponse; + [self _inviteeReceivedCancelPkInviteResponse:response]; + } + break; + + default: + break; + } +} + +- (void)startSingleLiveWithTopic:(NSString *)topic + coverUrl:(NSString *)coverUrl + successBlock:(StartSingleLiveSuccess)successBlock + failedBlock:(void(^)(NSError *))failedBlock +{ + // 加入直播间并推流闭包 + void (^joinChannelAndPushStreamBlock)(NETSCreateLiveRoomModel *_Nonnull) = ^(NETSCreateLiveRoomModel *room) { + [NETSPushStreamService joinChannelWithToken:room.avRoomCheckSum channelName:room.avRoomCName uid:[room.avRoomUid longLongValue] streamUrl:room.liveConfig.pushUrl successBlcok:^(NERtcLiveStreamTaskInfo * _Nonnull task) { + ApiLogInfo(@"加入直播间成功,推流成功"); + self.singleRoom = room; + self.streamTask = task; + self.liveStatus = NETSPkServiceSingleLive; + self.liveRole = NETSPkServiceDefault; + if (successBlock) { + successBlock(room, task); + } + } failedBlock:^(NSError * _Nonnull error, NSString *taskID) { + ApiLogInfo(@"操作失败, error: %@", error); + if (failedBlock) { + failedBlock(error); + } + }]; + }; + + // 请求接口,创建直播间 + [NETSLiveApi createLiveRoomWithTopic:topic coverUrl:coverUrl type:NETSLiveTypeNormal parentLiveCid:nil completionHandle:^(NSDictionary * _Nonnull response) { + NETSCreateLiveRoomModel *result = response[@"/data"]; + // 判断直播间数据 + if (!result) { + if (failedBlock) { + NSError *error = [NSError errorWithDomain:NETSRequestErrorParseDomain code:NETSRequestErrorMapping userInfo:@{NSLocalizedDescriptionKey: @"创建直播间失败,返回直播间数据为空"}]; + failedBlock(error); + } + return; + } + [NETSChatroomService enterWithRoomId:result.chatRoomId userMode:NETSUserModeAnchor success:^(NIMChatroom * _Nullable chatroom, NIMChatroomMember * _Nullable me) { + ApiLogInfo(@"开启直播间成功,加入聊天室成功..."); + joinChannelAndPushStreamBlock(result); + } failed:^(NSError * _Nullable error) { + ApiLogInfo(@"开启直播间成功,加入聊天室失败, error: %@", error); + if (failedBlock) { failedBlock(error); } + }]; + } errorHandle:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { + if (failedBlock) { + failedBlock(error); + } + }]; +} + +- (void)endLiveWithCompletionBlock:(void(^)(NSError * _Nullable))completionBlock +{ + void(^popBlock)(NSError * _Nullable) = ^(NSError * _Nullable error) { + int res = [[NERtcEngine sharedEngine] leaveChannel]; + ApiLogInfo(@"离开直播间, res: %d", res); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + int res = [NERtcEngine destroyEngine]; + ApiLogInfo(@"结束直播,销毁音视频引擎, res: %d", res); + + ntes_main_async_safe(^{ + if (completionBlock) { completionBlock(error); } + }); + }); + }; + + if (isEmptyString(self.singleRoom.liveCid)) { + popBlock(nil); + return; + } + + ntes_main_async_safe(^{ [NETSToast showLoading]; }); + [NETSLiveApi closeLiveRoomWithCid:self.singleRoom.liveCid completionHandle:^(NSDictionary * _Nonnull response) { + ntes_main_async_safe(^{ [NETSToast hideLoading]; }); + popBlock(nil); + } errorHandle:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { + ntes_main_async_safe(^{ [NETSToast hideLoading]; }); + ApiLogInfo(@"退出直播间失败: %@", error); + popBlock(error); + }]; +} + +- (void)transformRoomWithSuccessBlock:(void(^)(NETSPkServiceStatus, int64_t))successBlock + failedBlock:(void(^)(NSError *))failedBlock +{ + + if (self.liveStatus == NETSPkServiceSingleLive) { + // 单人直播模式下,邀请者/被邀请者返回各自的单人直播间 + [self _backToSingleLiveRoomWithSuccessBlock:successBlock failedBlock:failedBlock]; + } + else if (self.liveStatus == NETSPkServicePkLive) { + switch (self.liveRole) { + case NETSPkServiceInviter: + { + // pk直播模式下,邀请者 进入pk直播间 + __weak typeof(self) wSelf = self; + [self _inviterJoinPkWithSuccessBlock:^(int64_t uid, uint64_t channelId, uint64_t elapesd) { + __strong __typeof(wSelf) sSelf = wSelf; + if (successBlock) { + successBlock(sSelf.liveStatus, uid); + } + } failedBlock:^(NSError * _Nonnull error) { + if (failedBlock) { failedBlock(error); } + }]; + } + break; + case NETSPkServiceInvitee: + { + // pk直播模式下,被邀请者 进入pk直播间 + __weak typeof(self) wSelf = self; + [self _inviteeJoinPkWithSuccessBlock:^(int64_t uid, uint64_t channelId, uint64_t elapesd) { + __strong __typeof(wSelf) sSelf = wSelf; + if (successBlock) { + successBlock(sSelf.liveStatus, uid); + } + } failedBlock:^(NSError * _Nonnull error) { + if (failedBlock) { failedBlock(error); } + }]; + } + break; + + default: + break; + } + } +} + +/// 返回原单人直播间 +- (void)_backToSingleLiveRoomWithSuccessBlock:(void(^)(NETSPkServiceStatus, int64_t))successBlock + failedBlock:(void(^)(NSError *))failedBlock +{ + NSString *singleRoomCid = self.singleRoom.liveCid; + + if (isEmptyString(singleRoomCid)) { + ApiLogInfo(@"直播间转换失败: 单人直播间cid为空"); + if (failedBlock) { + NSError *error = [NSError errorWithDomain:NETSPkServiceErrorParamDomain code:1000 userInfo:@{NSLocalizedDescriptionKey: @"直播间转换失败: 单人直播间cid为空"}]; + failedBlock(error); + } + return; + } + //joinchannel之前需要设置美颜开关 + NSDictionary *params = @{ + kNERtcKeyVideoCaptureObserverEnabled: @YES // 将摄像头采集的数据回调给用户 + }; + [NERtcEngine.sharedEngine setParameters:params]; + void(^joinSingleChannelAndPushStreamBlock)(NETSCreateLiveRoomModel * _Nonnull) = ^(NETSCreateLiveRoomModel * _Nonnull result) { + [NETSPushStreamService joinChannelWithToken:result.avRoomCheckSum + channelName:result.avRoomCName + uid:[result.avRoomUid longLongValue] + streamUrl:self.singleRoom.liveConfig.pushUrl + successBlcok:^(NERtcLiveStreamTaskInfo * _Nonnull task) { + ApiLogInfo(@"离开pk直播加入原单人直播间成功,推流成功"); + self.streamTask = task; + if (successBlock) { + int64_t uid = [self.pkRoom.accountId longLongValue]; + successBlock(self.liveStatus, uid); + } + } failedBlock:^(NSError * _Nonnull error, NSString *taskID) { + ApiLogInfo(@"离开pk直播加入原单人直播间,推流失败, error: %@, taskID: %@", error, taskID); + if (failedBlock) { + failedBlock(error); + } + }]; + }; + + [NETSLiveApi joinLiveRoomWithLiveId:singleRoomCid parentLiveCid:nil liveType:NETSLiveTypeNormal completionHandle:^(NSDictionary * _Nonnull response) { + NETSCreateLiveRoomModel *result = response[@"/data"]; + if (result) { + joinSingleChannelAndPushStreamBlock(result); + } else { + if (failedBlock) { + NSError *error = [NSError errorWithDomain:NETSPkServiceErrorParamDomain code:1000 userInfo:@{NSLocalizedDescriptionKey: @"返回单人直播间失败: 校验数据为空"}]; + failedBlock(error); + } + } + } errorHandle:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { + ApiLogInfo(@"加入原直播房间失败, error: %@", error); + if (failedBlock) { + failedBlock(error); + } + }]; +} + +- (void)endPkWithCompletionBlock:(nullable void(^)(NSError * _Nullable))completionBlock +{ + NSString *liveCid = self.singleRoom.liveCid; + if (isEmptyString(liveCid)) { + NSError *error = [NSError errorWithDomain:NETSPkServiceErrorParamDomain code:1000 userInfo:@{NSLocalizedDescriptionKey: @"结束pk失败,pk的直播间cid为空"}]; + if (completionBlock) { + completionBlock(error); + } + return; + } + // 重置pk状态 + self.liveStatus = NETSPkServiceSingleLive; + self.liveRole = NETSPkServiceDefault; + + void(^actionBlock)(NSError * _Nullable) = ^(NSError * _Nullable error){ + // 离开pk channel + int res = [[NERtcEngine sharedEngine] leaveChannel]; + ApiLogInfo(@"强制结束pk,离开channel结果, res: %d", res); + + // 强制结束,发送pk惩罚结束im信息 + NSError *punEndErr = nil; + [self _sendPunishEndImMsgWithData:nil errorPtr:&punEndErr]; + ApiLogInfo(@"主播端 发送pk惩罚结束信息, err: %@", punEndErr); + + if (completionBlock) { + completionBlock(error); + } + }; + + [NETSLiveApi endPKWithCid:liveCid completionHandle:^(NSDictionary * _Nonnull response) { + ApiLogInfo(@"强制结束pk 接口请求结束pk成功"); + if (isEmptyString(self.streamTask.taskID)) { + actionBlock(nil); + return; + } + + [NETSPushStreamService removeStreamTask:self.streamTask.taskID successBlock:^{ + ApiLogInfo(@"强制结束pk 移除pk推流 taskId: %@ success", self.streamTask.taskID); + actionBlock(nil); + } failedBlock:^(NSError * _Nonnull error) { + ApiLogInfo(@"强制结束pk 移除pk推流 taskId: %@ error:%@", self.streamTask.taskID, error); + actionBlock(error); + }]; + } errorHandle:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { + ApiLogInfo(@"接口请求结束pk失败"); + actionBlock(error); + }]; +} + +- (void)pushPkStreamWithData:(NETSPassThroughHandlePkStartData *)data + successBlock:(void(^)(void))successBlock + failedBlock:(void(^)(NSError *, NSString * _Nullable))failedBlock +{ + NSArray *uids = @[@(data.inviterRoomUid), @(data.inviteeRoomUid)]; + if (self.liveRole != NETSPkServiceInviter) { + uids = @[@(data.inviteeRoomUid), @(data.inviterRoomUid)]; + } + + if (!self.pkRoom) { + if (failedBlock) { + NSError *error = [NSError errorWithDomain:@"NETSInterfaceErrorParamDomain" code:1000 userInfo:@{NSLocalizedDescriptionKey: @"直播间不存在"}]; + failedBlock(error, nil); + } + return; + } + + NSString *url = self.pkRoom.liveConfig.pushUrl; + NERtcLiveStreamTaskInfo *task = [NETSPushStreamService streamTaskWithUrl:url uids:uids]; + [NETSPushStreamService addStreamTask:task successBlock:^{ + self.streamTask = task; + if (successBlock) { successBlock(); } + + // 超时计时器: 停止 + [self.timer invalidate]; + } failedBlock:failedBlock]; +} + +#pragma mark - private method + +- (void)setLiveStatus:(NETSPkServiceStatus)liveStatus +{ + if (_liveStatus == liveStatus) { + return; + } + _liveStatus = liveStatus; + if (_delegate && [_delegate respondsToSelector:@selector(didPkServiceChangedStatus:)]) { + [_delegate didPkServiceChangedStatus:liveStatus]; + } +} + +#pragma mark - NIMPassThroughManagerDelegate 透传代理 + +- (void)didReceivedPassThroughMsg:(NIMPassThroughMsgData* __nullable)recvData +{ + NSString *body = recvData.body; + if (isEmptyString(body)) { return; } + + SKVObject *obj = [SKVObject ofJSON:body]; + if (!obj) { return; } + + NSDictionary *dict = [obj dictionaryValue]; + if (!dict) { return; } + + NSInteger type = [dict[@"type"] integerValue]; + + NSDictionary * dataDic = nil; + if (type < NETSSeatsNotificationAdminAcceptJoinSeats) { + //只有pk直播相关逻辑才需要取dict中的data,这里为了兼容 + dataDic = dict[@"data"]; + if (!dataDic) { return; } + } + switch (type) { + case NETSLivePassThroughStartPK: + { + ApiLogInfo(@"收到服务端透传 PK开始 信令..."); + // 完成pk链接过程,关闭计时器 + [self.timer invalidate]; + + NETSPassThroughHandlePkStartData *data = [NETSPassThroughHandlePkStartData yy_modelWithDictionary:dataDic]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(receivePassThrourhPkStartData:)]) { + [self.delegate receivePassThrourhPkStartData:data]; + } + + // 发送pk开始im信息 + NSError *err = nil; + [self _sendPkStartImMsgWithData:data errorPtr:&err]; + ApiLogInfo(@"主播端 发送pk开始信息, err: %@", err); + } + break; + case NETSLivePassThroughStartPunish: + { + ApiLogInfo(@"收到服务端透传 PUNISH开始 信令..."); + + if (self.liveStatus != NETSPkServicePkLive) { + ApiLogInfo(@"非PK状态, 不响应服务端透传的 PUNISH开始 信令..."); + return; + } + + NETSPassThroughHandlePunishData *data = [NETSPassThroughHandlePunishData yy_modelWithDictionary:dataDic]; + if (self.liveRole == NETSPkServiceInviter && ![data.roomCid isEqualToString:self.pkRoom.avRoomCid]) { + ApiLogInfo(@"PK邀请者, 收到服务端透传的PK结束信令, roomCid 非法..."); + return; + } + + // 获取pk结果 + NETSPkResult res = NETSPkUnknownResult; + if (data.inviteeRewards == data.inviterRewards) { + res = NETSPkTieResult; + } + else if ((data.inviteeRewards > data.inviterRewards && self.liveRole == NETSPkServiceInvitee) || + (data.inviteeRewards < data.inviterRewards && self.liveRole == NETSPkServiceInviter)) { + res = NETSPkCurrentAnchorWin; + } + else { + res = NETSPkOtherAnchorWin; + } + + if (self.delegate && [self.delegate respondsToSelector:@selector(didPkServiceFetchedPkRestlt:error:)]) { + [self.delegate didPkServiceFetchedPkRestlt:res error:nil]; + } else { + ApiLogInfo(@"未实现获取到pk结果后到代理方法: didPkServiceFetchedPkRestlt:error:"); + } + + // 发送pk结束im信息 + NSError *pkEndErr = nil; + [self _sendPkEndImMsgWithData:data pkResult:res errorPtr:&pkEndErr]; + ApiLogInfo(@"主播端 发送pk结束信息, err: %@", pkEndErr); + + // 发送pk惩罚开始im信息 + NSError *punStartErr = nil; + [self _sendPunishStartImMsgWithData:data pkResult:res errorPtr:&punStartErr]; + ApiLogInfo(@"主播端 发送pk惩罚开始信息, res:%zd err: %@", res, pkEndErr); + + if (self.delegate && [self.delegate respondsToSelector:@selector(receivePassThrourhPunishStartData:pkResult:)]) { + [self.delegate receivePassThrourhPunishStartData:data pkResult:res]; + } + } + break; + case NETSLivePassThroughEndPK: + { + ApiLogInfo(@"收到服务端透传 PK结束 信令..."); + if (self.liveStatus != NETSPkServicePkLive) { + ApiLogInfo(@"非PK状态, 不响应服务端透传的 PK结束 信令..."); + return; + } + + NETSPassThroughHandlePkEndData *data = [NETSPassThroughHandlePkEndData yy_modelWithDictionary:dataDic]; + if (self.liveRole == NETSPkServiceInviter && ![data.roomCid isEqualToString:self.pkRoom.avRoomCid]) { + ApiLogInfo(@"PK邀请者, 收到服务端透传的PK结束信令, roomCid 非法..."); + return; + } + + void(^endPkBlock)(void) = ^(void){ + self.liveStatus = NETSPkServiceSingleLive; + self.liveRole = NETSPkServiceDefault; + + // 收到服务端透传pk结束信令, 离开pk channel + int res = [[NERtcEngine sharedEngine] leaveChannel]; + ApiLogInfo(@"收到服务端透传pk结束信令, 离开pk channel, res:%d", res); + + if (self.delegate && [self.delegate respondsToSelector:@selector(receivePassThrourhPkEndData:)]) { + [self.delegate receivePassThrourhPkEndData:data]; + } else { + ApiLogInfo(@"未实现获取到pk结果后到代理方法: receivePassThrourhPkEndData:"); + } + + // 发送pk惩罚结束im信息 + NSError *punEndErr = nil; + [self _sendPunishEndImMsgWithData:data errorPtr:&punEndErr]; + ApiLogInfo(@"主播端 发送pk惩罚结束信息, err: %@", punEndErr); + }; + + [NETSPushStreamService removeStreamTask:self.streamTask.taskID successBlock:^{ + ApiLogInfo(@"pk结束, 移除推流 taskId: %@ success", self.streamTask.taskID); + endPkBlock(); + } failedBlock:^(NSError * _Nonnull error) { + ApiLogInfo(@"pk结束, 移除推流 taskId: %@ error: %@", self.streamTask.taskID, error); + endPkBlock(); + }]; + } + break; + case NETSLivePassThroughStartLive: + { + ApiLogInfo(@"收到服务端透传 直播开始 信令: %@...", dataDic); + + if (self.delegate && [self.delegate respondsToSelector:@selector(receivePassThrourhLiveStartData:)]) { + NETSPassThroughHandleStartLiveData *data = [NETSPassThroughHandleStartLiveData yy_modelWithDictionary:dataDic]; + [self.delegate receivePassThrourhLiveStartData:data]; + } + } + break; + case NETSLivePassThroughReward: + { + ApiLogInfo(@"收到服务端透传 打赏 信令..."); + + NETSPassThroughHandleRewardData *data = [NETSPassThroughHandleRewardData yy_modelWithDictionary:dataDic]; + + // 发送云币同步IM信息 + NSError *syncCoinsErr = nil; + NIMMessage *rewardMsg = [self _syncCoinMegWithData:data]; + [self _sendSyncCoinsImMsg:rewardMsg errorPtr:&syncCoinsErr]; + ApiLogInfo(@"主播端 发送云币同步IM消息, error: %@", syncCoinsErr); + if (syncCoinsErr) { return; } + + // 执行代理,通知主播端有打赏信息 + if (self.delegate && [self.delegate respondsToSelector:@selector(receivePassThrourhRewardData:rewardMsg:)]) { + [self.delegate receivePassThrourhRewardData:data rewardMsg:rewardMsg]; + } + } + break; + case NETSSeatsNotificationAudienceApplyJoinSeats: { + ApiLogInfo(@"收到观众申请上麦信令"); + NETSConnectMicModel *data = [NETSConnectMicModel yy_modelWithDictionary:dict]; + if (self.delegate && [self.delegate respondsToSelector:@selector(receivePassThrourhApplyJoinSeatsData:)]) { + [self.delegate receivePassThrourhApplyJoinSeatsData:data]; + } + } + break; + + case NETSSeatsNotificationAudienceRejectJoinSeats:{ + ApiLogInfo(@"收到观众拒绝同意上麦信令"); + [NETSToast showToast:@"对方拒绝了你的邀请"]; + } + break; + default: + break; + } + + ApiLogInfo(@" Recv Msg:\n FromAccId: %@\n Body: %@\n Time: %@", recvData.fromAccid, recvData.body, @(recvData.time)); +} + +- (void)_tryToJoinPkRoomWithLiveCid:(NSString *)liveCid + parentLiveCid:(NSString *)parentLiveCid + successBlock:(void(^)(int64_t, uint64_t, uint64_t))successBlock + failedBlock:(void(^)(NSError *))failedBlock +{ + [NETSLiveApi joinLiveRoomWithLiveId:liveCid parentLiveCid:parentLiveCid liveType:NETSLiveTypePK completionHandle:^(NSDictionary * _Nonnull response) { + ApiLogInfo(@"response: %@", response); + NETSCreateLiveRoomModel *data = response[@"/data"]; + if (!isEmptyString(data.avRoomCheckSum) && !isEmptyString(data.avRoomCName) && !isEmptyString(data.avRoomUid)) { + self.pkRoom = data; + //joinchannel之前需要设置美颜开关 + NSDictionary *params = @{ + kNERtcKeyVideoCaptureObserverEnabled: @YES // 将摄像头采集的数据回调给用户 + }; + [NERtcEngine.sharedEngine setParameters:params]; + [self _joinPkChannerAfterTryJoinWithResult:data successBlock:successBlock failedBlock:failedBlock]; + return; + } + + NSError *error = [NSError errorWithDomain:@"NETSInterfaceErrorParamDomain" code:1000 userInfo:@{NSLocalizedDescriptionKey: @"被邀请者进入PK直播间返回数据结构错误"}]; + if (failedBlock) { failedBlock(error); } + } errorHandle:^(NSError * _Nonnull error, NSDictionary * _Nullable response) { + if (failedBlock) { failedBlock(error); } + }]; +} + +/// 加入pk直播间channel, 在join接口请求成功后执行该方法 +- (void)_joinPkChannerAfterTryJoinWithResult:(NETSCreateLiveRoomModel *)result + successBlock:(void(^)(int64_t, uint64_t, uint64_t))successBlock + failedBlock:(void(^)(NSError *))failedBlock +{ + + NERtcEngine *coreEngine = NERtcEngine.sharedEngine; + // 设置直播模式 + [coreEngine setChannelProfile:kNERtcChannelProfileLiveBroadcasting]; + + // 打开推流,回调摄像头采集数据 + NSDictionary *params = @{ + kNERtcKeyPublishSelfStreamEnabled: @YES, // 打开推流 + kNERtcKeyVideoCaptureObserverEnabled: @YES // 将摄像头采集的数据回调给用户 + }; + [coreEngine setParameters:params]; + [coreEngine setClientRole:kNERtcClientRoleBroadcaster]; + + // 设置视频发送配置(帧率/分辨率) + NERtcVideoEncodeConfiguration *config = [NETSLiveConfig shared].videoConfig; + [coreEngine setLocalVideoConfig:config]; + + // 设置音频质量 + NSUInteger quality = [NETSLiveConfig shared].audioQuality; + [coreEngine setAudioProfile:kNERtcAudioProfileDefault scenario:quality]; + // 启用本地音/视频 + [coreEngine enableLocalAudio:YES]; + [coreEngine enableLocalVideo:YES]; + int res = [coreEngine joinChannelWithToken:result.avRoomCheckSum + channelName:result.avRoomCName + myUid:[result.avRoomUid longLongValue] + completion:^(NSError * _Nullable error, uint64_t channelId, uint64_t elapesd) { + if (error) { + ApiLogInfo(@"主播 加入pk直播间失败, error: %@", error); + if (failedBlock) { failedBlock(error); } + } else { + ApiLogInfo(@"主播 加入pk直播间成功"); + if (successBlock) { + int64_t uid = [result.accountId longLongValue]; + successBlock(uid, channelId, elapesd); + } + } + }]; + if (res != 0) { + NSError *error = [NSError errorWithDomain:@"NETSRtcErrorParamDomain" code:res userInfo:@{NSLocalizedDescriptionKey: @"主播 加入PK直播间失败"}]; + if (failedBlock) { failedBlock(error); } + return; + } +} + +@end diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPushStreamService.h b/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPushStreamService.h index d931514..85f7187 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPushStreamService.h +++ b/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPushStreamService.h @@ -53,7 +53,14 @@ NS_ASSUME_NONNULL_BEGIN + (nullable NERtcLiveStreamTaskInfo *)streamTaskWithUrl:(NSString *)url uids:(NSArray *)uids; - +/// 构造推流任务 +/// @param url 推流地址 +/// @param uids 用户ID +/// @param audioPush 是否在直播中混流该用户的对应音频流 ++ (nullable NERtcLiveStreamTaskInfo *)streamTaskWithUrl:(NSString *)url + uids:(NSArray *)uids + audioPush:(BOOL)audioPush + otherAnchorUid:(int64_t)otherUid; /** 解析信令返回自定义字段 diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPushStreamService.m b/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPushStreamService.m index 2bf02c7..5554cf2 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPushStreamService.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/LivePK/NETSPushStreamService.m @@ -59,6 +59,55 @@ + (void)removeStreamTask:(NSString *)taskId } } ++ (nullable NERtcLiveStreamTaskInfo *)streamTaskWithUrl:(NSString *)url + uids:(NSArray *)uids + audioPush:(BOOL)audioPush + otherAnchorUid:(int64_t)otherUid{ + if ([uids count] > 2 || [uids count] == 0) { + ApiLogInfo(@"构建pushStreamTask失败: uid集合元素数量不符合预期"); + return nil; + } + + BOOL isPking = ([uids count] == 2); + + NERtcLiveStreamTaskInfo *taskInfo = [[NERtcLiveStreamTaskInfo alloc] init]; + taskInfo.taskID = [NSString md5ForLower32Bate:url]; + taskInfo.streamURL = url; + taskInfo.lsMode = kNERtcLsModeVideo; + + CGFloat width = 720; + CGFloat height = isPking ? 640 : 1280; + + //设置整体布局 + NERtcLiveStreamLayout *streamLayout = [[NERtcLiveStreamLayout alloc] init]; + streamLayout.width = width; + streamLayout.height = height; + taskInfo.layout = streamLayout; + + NSArray *users = nil; + if (isPking) { + NERtcLiveStreamUserTranscoding *selfTranscoding = [self _streamUserTranscodingWithUid:[uids.firstObject longLongValue] + point:CGPointMake(0, 0) + size:CGSizeMake(360, 640)]; + NERtcLiveStreamUserTranscoding *otherTranscoding = [self _streamUserTranscodingWithUid:[uids.lastObject longLongValue] + point:CGPointMake(360, 0) + size:CGSizeMake(360, 640)]; + + + UInt64 firstUid = [uids.firstObject longLongValue]; + if (firstUid == otherUid) { + selfTranscoding.audioPush = audioPush; + }else{ + otherTranscoding.audioPush = audioPush; + } + users = @[selfTranscoding, otherTranscoding]; + } + taskInfo.layout.users = users; + return taskInfo; +} + + + + (nullable NERtcLiveStreamTaskInfo *)streamTaskWithUrl:(NSString *)url uids:(NSArray *)uids { @@ -91,6 +140,7 @@ + (nullable NERtcLiveStreamTaskInfo *)streamTaskWithUrl:(NSString *)url NERtcLiveStreamUserTranscoding *otherTranscoding = [self _streamUserTranscodingWithUid:[uids.lastObject longLongValue] point:CGPointMake(360, 0) size:CGSizeMake(360, 640)]; + users = @[selfTranscoding, otherTranscoding]; } else { NERtcLiveStreamUserTranscoding *selfTranscoding = [self _streamUserTranscodingWithUid:[uids.firstObject longLongValue] @@ -98,7 +148,6 @@ + (nullable NERtcLiveStreamTaskInfo *)streamTaskWithUrl:(NSString *)url size:CGSizeMake(width, height)]; users = @[selfTranscoding]; } - taskInfo.layout.users = users; return taskInfo; @@ -124,6 +173,7 @@ + (NERtcLiveStreamUserTranscoding *)_streamUserTranscodingWithUid:(int64_t)uid p + (void)updateLiveStreamTask:(NERtcLiveStreamTaskInfo *)taskInfo successBlock:(void(^)(void))successBlock failedBlock:(void(^)(NSError *))failedBlock { + int ret = [NERtcEngine.sharedEngine updateLiveStreamTask:taskInfo compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) { if (errorCode == 0) { @@ -174,7 +224,7 @@ + (void)joinChannelWithToken:(NSString *)token return; } - int res = [NERtcEngine.sharedEngine joinChannelWithToken:token channelName:channelName myUid:uid completion:^(NSError * _Nullable error, uint64_t channelId, uint64_t elapesd) { + int res = [NERtcEngine.sharedEngine joinChannelWithToken:token channelName:channelName myUid:uid completion:^(NSError * _Nullable error, uint64_t channelId, uint64_t elapesd,uint64_t uid) { if (error) { if (failedBlock) { failedBlock(error, nil); } } else { @@ -184,6 +234,7 @@ + (void)joinChannelWithToken:(NSString *)token } failedBlock:failedBlock]; } }]; + if (res != 0) { if (failedBlock) { if (res == kNERtcErrInvalidState) {//30005是因为还未退出rtc导致的 diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/CommonService/NEPkRoomService.m b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/CommonService/NEPkRoomService.m index bfaaae9..2d893c8 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/CommonService/NEPkRoomService.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/CommonService/NEPkRoomService.m @@ -16,7 +16,7 @@ #import "NEPkCreateRoomParams.h" #import "NETSChatroomService.h" #import "NEPkDestroyRoomParams.h" - +#import "NETSLiveConfig.h" @interface NEPkRoomService () @property(nonatomic, strong) NEAudioOption *audioOption; @@ -43,6 +43,8 @@ - (void)createLiveRoomWithTopic:(NSString *)roomTopic successBlock:(NECreateRoomSuccess)successBlock failedBlock:(void(^)(NSError *))failedBlock { + __weak typeof(self) weakSelf = self; + // 加入直播间并推流闭包 void (^joinChannelAndPushStreamBlock)(NECreateRoomResponseModel *_Nonnull) = ^(NECreateRoomResponseModel *responseModel) { [NETSPushStreamService joinChannelWithToken:responseModel.anchor.roomCheckSum channelName:responseModel.live.roomCname uid:responseModel.anchor.roomUid streamUrl:responseModel.live.liveConfig.pushUrl successBlcok:^(NERtcLiveStreamTaskInfo * _Nonnull task) { @@ -68,6 +70,8 @@ - (void)createLiveRoomWithTopic:(NSString *)roomTopic [self.roomApiService createRoomWithParams:params successBlock:^(NSDictionary * _Nonnull response) { NECreateRoomResponseModel *result = response[@"/data"]; [NETSChatroomService enterWithRoomId:result.live.chatRoomId userMode:NETSUserModeAnchor success:^(NIMChatroom * _Nullable chatroom, NIMChatroomMember * _Nullable me) { + + [weakSelf setUpEngineParams]; joinChannelAndPushStreamBlock(result); } failed:^(NSError * _Nullable error) { if (failedBlock) { failedBlock(error); } @@ -137,6 +141,31 @@ - (void)sendTextMessage:(NSString *)text { } } +- (void)setUpEngineParams { + + NERtcEngine *coreEngine = [NERtcEngine sharedEngine]; + // 打开推流,回调摄像头采集数据 + NSDictionary *params = @{ + kNERtcKeyPublishSelfStreamEnabled: @YES, // 打开推流 + kNERtcKeyVideoCaptureObserverEnabled: @YES // 将摄像头采集的数据回调给用户 + }; + [coreEngine setParameters:params]; + [coreEngine setClientRole:kNERtcClientRoleBroadcaster]; + + // 设置视频发送配置(帧率/分辨率) + NERtcVideoEncodeConfiguration *config = [NETSLiveConfig shared].videoConfig; + [coreEngine setLocalVideoConfig:config]; + + // 设置音频质量 + NSUInteger quality = [NETSLiveConfig shared].audioQuality; + [coreEngine setAudioProfile:kNERtcAudioProfileHighQuality scenario:quality]; + [coreEngine setChannelProfile:kNERtcChannelProfileLiveBroadcasting]; + + // 启用本地音/视频 + [coreEngine enableLocalAudio:YES]; + [coreEngine enableLocalVideo:YES]; +} + - (NEAudioOption *)getAudioOption { return self.audioOption; } diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/CommonService/NEPkService.m b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/CommonService/NEPkService.m index c6a5b79..d702e63 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/CommonService/NEPkService.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/CommonService/NEPkService.m @@ -10,6 +10,7 @@ #import "NETSPkEnum.h" #import "NEPKRewardTopParams.h" #import "NERewardTopResponseModel.h" +#import "NEPKInviteConfigModel.h" @implementation NEPkService @@ -69,7 +70,8 @@ - (void)_pkCommonOperation:(NEPkOperation)action @"targetAccountId" : accountId }; options.modelMapping = @[ - [NETSApiModelMapping mappingWith:@"/" mappingClass:[NSDictionary class] isArray:NO] +// [NETSApiModelMapping mappingWith:@"/" mappingClass:[NSDictionary class] isArray:NO] + [NETSApiModelMapping mappingWith:@"/data" mappingClass:[NEPKInviteConfigModel class] isArray:NO], ]; NETSRequest *resuest = [[NETSRequest alloc] initWithOptions:options]; resuest.completionBlock = successBlock; diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKChatRoomMessageHandle/NEPkLiveAttachment.h b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKChatRoomMessageHandle/NEPkLiveAttachment.h index dc90f63..7de53c0 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKChatRoomMessageHandle/NEPkLiveAttachment.h +++ b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKChatRoomMessageHandle/NEPkLiveAttachment.h @@ -37,7 +37,7 @@ typedef NS_ENUM(NSUInteger, NELiveAttachmentType) { //pk开始时间 @property(nonatomic, assign) int64_t pkStartTime; //pk结束时间 -@property(nonatomic, assign) int64_t pkCountDown; +@property(nonatomic, assign) int32_t pkCountDown; @property(nonatomic, strong) NEPkLiveStartSubModel *inviter; @property(nonatomic, strong) NEPkLiveStartSubModel *invitee; @end diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPKInviteConfigModel.h b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPKInviteConfigModel.h new file mode 100644 index 0000000..616afa3 --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPKInviteConfigModel.h @@ -0,0 +1,20 @@ +// +// NEPKInviteConfigModel.h +// NLiteAVDemo +// +// Created by vvj on 2021/11/19. +// Copyright © 2021 Netease. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +@class NEPkConfigModel; +@interface NEPKInviteConfigModel : NSObject +//新增pk 配置。 +@property(nonatomic, strong) NEPkConfigModel *pkConfig; +//pk id +@property(nonatomic, strong) NSString *pkId; +@end + +NS_ASSUME_NONNULL_END diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPKInviteConfigModel.m b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPKInviteConfigModel.m new file mode 100644 index 0000000..01130ed --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPKInviteConfigModel.m @@ -0,0 +1,19 @@ +// +// NEPKInviteConfigModel.m +// NLiteAVDemo +// +// Created by vvj on 2021/11/19. +// Copyright © 2021 Netease. All rights reserved. +// + +#import "NEPKInviteConfigModel.h" +#import "NEPkConfigModel.h" + +@implementation NEPKInviteConfigModel + ++ (NSDictionary *)modelContainerPropertyGenericClass { + + return @{@"pkConfig" : [NEPkConfigModel class]}; +} + +@end diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPkConfigModel.h b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPkConfigModel.h new file mode 100644 index 0000000..5baece8 --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPkConfigModel.h @@ -0,0 +1,20 @@ +// +// NEPkConfigModel.h +// NLiteAVDemo +// +// Created by vvj on 2021/11/19. +// Copyright © 2021 Netease. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NEPkConfigModel : NSObject +//同意后倒计时3S任务 +@property(nonatomic, assign) NSInteger agreeTaskTime; +//邀请后倒计时20S +@property(nonatomic, assign) NSInteger inviteTaskTime; +@end + +NS_ASSUME_NONNULL_END diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPkConfigModel.m b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPkConfigModel.m new file mode 100644 index 0000000..ecb4dd0 --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PKResponseModel/NEPkConfigModel.m @@ -0,0 +1,13 @@ +// +// NEPkConfigModel.m +// NLiteAVDemo +// +// Created by vvj on 2021/11/19. +// Copyright © 2021 Netease. All rights reserved. +// + +#import "NEPkConfigModel.h" + +@implementation NEPkConfigModel + +@end diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PkPassThroughModel/NEPassthroughPkInviteModel.h b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PkPassThroughModel/NEPassthroughPkInviteModel.h index 13c71d8..2f87caf 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PkPassThroughModel/NEPassthroughPkInviteModel.h +++ b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PkPassThroughModel/NEPassthroughPkInviteModel.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN +@class NEPkConfigModel; @interface NEPkInviteActionAnchorModel : NSObject //房间编号 @@ -36,6 +37,8 @@ NS_ASSUME_NONNULL_BEGIN @end + + @interface NEPassthroughPkInviteModel : NSObject @property(nonatomic, strong) NSString *senderAccountId; @@ -49,7 +52,10 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, strong) NEPkInviteActionAnchorModel *actionAnchor; //目标主播 @property(nonatomic, strong) NEPkInviteTargetAnchorModel *targetAnchor; - +//pk Id +@property(nonatomic, strong) NSString *pkId; +//pk 相关配置 +@property(nonatomic, strong) NEPkConfigModel *pkConfig; @end diff --git a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PkPassThroughModel/NEPassthroughPkInviteModel.m b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PkPassThroughModel/NEPassthroughPkInviteModel.m index 16136c1..c030cc3 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PkPassThroughModel/NEPassthroughPkInviteModel.m +++ b/OnlinePK-iOS/NLiteAVDemo/Live/PkLiveBusiness/PKModel/PkPassThroughModel/NEPassthroughPkInviteModel.m @@ -7,6 +7,7 @@ // Use of this source code is governed by a MIT license that can be found in the LICENSE file. #import "NEPassthroughPkInviteModel.h" +#import "NEPkConfigModel.h" @implementation NEPassthroughPkInviteModel @@ -14,6 +15,7 @@ + (NSDictionary *)modelContainerPropertyGenericClass { return @{ @"actionAnchor" : [NEPkInviteActionAnchorModel class], @"targetAnchor" : [NEPkInviteTargetAnchorModel class], + @"pkConfig":[NEPkConfigModel class] }; } @end diff --git a/OnlinePK-iOS/NLiteAVDemo/Service/NEService.h b/OnlinePK-iOS/NLiteAVDemo/Menu/Service/NEService.h similarity index 100% rename from OnlinePK-iOS/NLiteAVDemo/Service/NEService.h rename to OnlinePK-iOS/NLiteAVDemo/Menu/Service/NEService.h diff --git a/OnlinePK-iOS/NLiteAVDemo/Service/NEService.m b/OnlinePK-iOS/NLiteAVDemo/Menu/Service/NEService.m similarity index 100% rename from OnlinePK-iOS/NLiteAVDemo/Service/NEService.m rename to OnlinePK-iOS/NLiteAVDemo/Menu/Service/NEService.m diff --git a/OnlinePK-iOS/NLiteAVDemo/Service/NEServiceTask.h b/OnlinePK-iOS/NLiteAVDemo/Menu/Service/NEServiceTask.h similarity index 100% rename from OnlinePK-iOS/NLiteAVDemo/Service/NEServiceTask.h rename to OnlinePK-iOS/NLiteAVDemo/Menu/Service/NEServiceTask.h diff --git a/OnlinePK-iOS/NLiteAVDemo/Service/NETSRtcConfig.h b/OnlinePK-iOS/NLiteAVDemo/Menu/Service/NETSRtcConfig.h similarity index 100% rename from OnlinePK-iOS/NLiteAVDemo/Service/NETSRtcConfig.h rename to OnlinePK-iOS/NLiteAVDemo/Menu/Service/NETSRtcConfig.h diff --git a/OnlinePK-iOS/NLiteAVDemo/Service/NETSRtcConfig.m b/OnlinePK-iOS/NLiteAVDemo/Menu/Service/NETSRtcConfig.m similarity index 100% rename from OnlinePK-iOS/NLiteAVDemo/Service/NETSRtcConfig.m rename to OnlinePK-iOS/NLiteAVDemo/Menu/Service/NETSRtcConfig.m diff --git a/OnlinePK-iOS/NLiteAVDemo/Service/NETask.h b/OnlinePK-iOS/NLiteAVDemo/Menu/Service/NETask.h similarity index 100% rename from OnlinePK-iOS/NLiteAVDemo/Service/NETask.h rename to OnlinePK-iOS/NLiteAVDemo/Menu/Service/NETask.h diff --git a/OnlinePK-iOS/NLiteAVDemo/Service/NETask.m b/OnlinePK-iOS/NLiteAVDemo/Menu/Service/NETask.m similarity index 100% rename from OnlinePK-iOS/NLiteAVDemo/Service/NETask.m rename to OnlinePK-iOS/NLiteAVDemo/Menu/Service/NETask.m diff --git a/OnlinePK-iOS/NLiteAVDemo/Menu/View/NEMenuViewController.m b/OnlinePK-iOS/NLiteAVDemo/Menu/View/NEMenuViewController.m index 9bad700..9d566a6 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Menu/View/NEMenuViewController.m +++ b/OnlinePK-iOS/NLiteAVDemo/Menu/View/NEMenuViewController.m @@ -20,7 +20,6 @@ #import "NETSToast.h" #import "NEPkLiveAttachment.h" - @interface NEMenuViewController () @property(strong,nonatomic)UITableView *tableView; @property(strong,nonatomic)UIImageView *bgImageView; @@ -118,10 +117,15 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - if (![NEAccount shared].hasLogin) { - [[NENavigator shared] loginWithOptions:nil]; + NSLog(@"[NEAccount shared].hasLogin : %d", [NEAccount shared].hasLogin); + if ([NEAccount shared].hasLogin == NO) { + [LoginManager startEntranceWithCompletion:^(YXUserInfo * _Nullable userinfo, NSError * _Nullable error) { + //[NEAccount imloginWithYXuser:[LoginManager getUserInfo]]; + [NEAccount syncLoginData:userinfo]; + }]; return; } + if ([_datas count] > indexPath.section) { NSArray *array = _datas[indexPath.section]; if ([array count] > indexPath.row) { diff --git a/OnlinePK-iOS/NLiteAVDemo/Person/UI/ViewController/NEPersonInfoVC.m b/OnlinePK-iOS/NLiteAVDemo/Person/UI/ViewController/NEPersonInfoVC.m index 5ee0da7..9f8da37 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Person/UI/ViewController/NEPersonInfoVC.m +++ b/OnlinePK-iOS/NLiteAVDemo/Person/UI/ViewController/NEPersonInfoVC.m @@ -63,7 +63,6 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N NSString *nickname = NEAccount.shared.userModel.nickname.length ? NEAccount.shared.userModel.nickname : @""; cell.personView.detailLabel.text = nickname; - cell.personView.indicatorImageView.image = [UIImage imageNamed:@"menu_arrow"]; } cell.personView.titleLabel.text = content; return cell; @@ -75,17 +74,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) { - if (indexPath.row == 1) { - //修改昵称 - NENicknameVC *nickNameVC = [[NENicknameVC alloc] init]; - WEAK_SELF(weakSelf); - nickNameVC.didModifyNickname = ^(NSString * _Nonnull nickName) { - STRONG_SELF(strongSelf); - strongSelf.nickname = nickName; - [strongSelf.tableView reloadData]; - }; - [self.navigationController pushViewController:nickNameVC animated:YES]; - } + }else { if (indexPath.row == 0) { //退出登录 @@ -95,6 +84,22 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath } - (void)logout { + + __weak typeof(self) weakSelf = self; + [LoginManager logoutWithConfirm:nil withCompletion:^(YXUserInfo * _Nullable userinfo, NSError * _Nullable error) { + if (error == nil) { + [NEAccount localLogoutWithCompletion:^(NSDictionary * _Nullable data, NSError * _Nullable error) { + if (error) { + [weakSelf.view makeToast:error.description]; + }else { + [[NENavigator shared] showRootNavWitnIndex:0]; + } + }]; + }else { + [weakSelf.view makeToast:error.description]; + } + }]; + /* UIAlertController *alerVC = [UIAlertController alertControllerWithTitle:@"" message:[NSString stringWithFormat:NSLocalizedString(@"确认退出登录%@", nil),[NEAccount shared].userModel.mobile] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"取消", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]; @@ -110,5 +115,6 @@ - (void)logout { [alerVC addAction:cancelAction]; [alerVC addAction:okAction]; [self presentViewController:alerVC animated:YES completion:nil]; + */ } @end diff --git a/OnlinePK-iOS/NLiteAVDemo/Person/UI/ViewController/NEPersonVC.m b/OnlinePK-iOS/NLiteAVDemo/Person/UI/ViewController/NEPersonVC.m index f15dae0..adb40ee 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Person/UI/ViewController/NEPersonVC.m +++ b/OnlinePK-iOS/NLiteAVDemo/Person/UI/ViewController/NEPersonVC.m @@ -77,7 +77,10 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath if (indexPath.section == 0) { if (indexPath.row == 0) { if (![NEAccount shared].hasLogin) { - [[NENavigator shared] loginWithOptions:nil]; + //[[NENavigator shared] loginWithOptions:nil]; + [[AuthorManager shareInstance] startEntranceWithCompletion:^(YXUserInfo * _Nullable userinfo, NSError * _Nullable error) { + [NEAccount imloginWithYXuser:[LoginManager getUserInfo]]; + }]; }else { //个人信息 NEPersonInfoVC *vc = [[NEPersonInfoVC alloc] init]; diff --git a/OnlinePK-iOS/NLiteAVDemo/Utils/Account/NEAccount.h b/OnlinePK-iOS/NLiteAVDemo/Utils/Account/NEAccount.h index 740183d..f5f23a9 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Utils/Account/NEAccount.h +++ b/OnlinePK-iOS/NLiteAVDemo/Utils/Account/NEAccount.h @@ -102,6 +102,10 @@ typedef void(^NEAccountActionBlock)(NEAccountAction action); + (void)updateUserInfo:(NEUser *)user; ++ (void)imloginWithYXuser:(YXUserInfo *)yxUser; + ++ (void)syncLoginData:(YXUserInfo *)user; + @end NS_ASSUME_NONNULL_END diff --git a/OnlinePK-iOS/NLiteAVDemo/Utils/Account/NEAccount.m b/OnlinePK-iOS/NLiteAVDemo/Utils/Account/NEAccount.m index 6ade1ea..f43575c 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Utils/Account/NEAccount.m +++ b/OnlinePK-iOS/NLiteAVDemo/Utils/Account/NEAccount.m @@ -71,6 +71,10 @@ + (instancetype)shared return instance; } +- (BOOL)hasLogin { + return [[AuthorManager shareInstance] isLogin]; +} + - (void)_notifyAccountAction:(NEAccountAction)action { @synchronized (self.actionObservers) { @@ -155,6 +159,14 @@ + (void)remoteLogoutWithCompletion:(_Nullable NEAccountComplete)completion }]; } +- (void)setHasLogin:(BOOL)hasLogin { + _hasLogin = hasLogin; +} + +- (BOOL)hasLogin { + return _hasLogin; +} + + (void)localLogoutWithCompletion:(_Nullable NEAccountComplete)completion { NEAccountShared.hasLogin = NO; @@ -212,4 +224,29 @@ + (void)_loginHandleWithResponse:(NSDictionary *)response error:(NSError *)error + (void)updateUserInfo:(NEUser *)user { NEAccountShared.userModel = user; } + ++ (void)imloginWithYXuser:(YXUserInfo *)yxUser { + YXUserInfo *user = yxUser; + [[[NIMSDK sharedSDK] loginManager] login:user.imAccid token:user.imToken completion:^(NSError * _Nullable error) { + NSLog(@"login im error : %@", error); + if (error) { + [[[UIApplication sharedApplication] keyWindow] makeToast:error.description]; + }else{ + [NEAccount shared].accessToken = user.accessToken; + NEUser *neuser = [NEUser yy_modelWithJSON:user.yy_modelToJSONObject]; + [NEAccount shared].userModel = neuser; + [NEAccount shared].hasLogin = YES; + [[[UIApplication sharedApplication] keyWindow] makeToast:@"IM登录成功"]; + [AuthorManager shareInstance].imLoginSuccess = YES; + } + }]; +} + ++ (void)syncLoginData:(YXUserInfo *)user { + [NEAccount shared].accessToken = user.accessToken; + NEUser *neuser = [NEUser yy_modelWithJSON:user.yy_modelToJSONObject]; + [NEAccount shared].userModel = neuser; + [NEAccount shared].hasLogin = YES; +} + @end diff --git a/OnlinePK-iOS/NLiteAVDemo/Utils/Category/UIControl+repeatclick.h b/OnlinePK-iOS/NLiteAVDemo/Utils/Category/UIControl+repeatclick.h new file mode 100755 index 0000000..86848cf --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Utils/Category/UIControl+repeatclick.h @@ -0,0 +1,18 @@ +// +// UIControl+repeatclick.h +// NLiteAVDemo +// +// Created by Ease on 2020/11/10. +// Copyright (c) 2021 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be found in the LICENSE file + + + +#import + +@interface UIControl (repeatclick) +@property (nonatomic, assign) NSTimeInterval ne_acceptEventInterval;//添加点击事件的间隔时间 + +@property (nonatomic, assign) BOOL ne_ignoreEvent;//是否忽略点击事件,不响应点击事件 + +@end diff --git a/OnlinePK-iOS/NLiteAVDemo/Utils/Category/UIControl+repeatclick.m b/OnlinePK-iOS/NLiteAVDemo/Utils/Category/UIControl+repeatclick.m new file mode 100755 index 0000000..8c5c00e --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Utils/Category/UIControl+repeatclick.m @@ -0,0 +1,106 @@ +// +// UIControl+repeatclick.h +// NLiteAVDemo +// +// Created by Ease on 2020/11/10. +// Copyright (c) 2021 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be found in the LICENSE file + +#import "UIControl+repeatclick.h" +#import + +@interface UIControl () + +@property (nonatomic, assign) NSTimeInterval ne_acceptEventTime; + +@end + +@implementation UIControl (repeatclick) + +static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval"; + +static const char *UIcontrol_ignoreEvent = "UIcontrol_ignoreEvent"; + +static const char *UIControl_acceptEventTime = "UIControl_acceptEventTime"; + +- (NSTimeInterval)ne_acceptEventInterval { + + return [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue]; + +} + +- (void)setNe_acceptEventInterval:(NSTimeInterval)ne_acceptEventInterval { + + objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(ne_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + +} + +- (BOOL)ne_ignoreEvent { + + return [objc_getAssociatedObject(self, UIcontrol_ignoreEvent) boolValue]; + +} + +- (void)setNe_ignoreEvent:(BOOL)ne_ignoreEvent { + + objc_setAssociatedObject(self, UIcontrol_ignoreEvent, @(ne_ignoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + +} + +- (NSTimeInterval )ne_acceptEventTime { + return [objc_getAssociatedObject(self, UIControl_acceptEventTime) doubleValue]; +} + + +-(void)setNe_acceptEventTime:(NSTimeInterval)ne_acceptEventTime { + objc_setAssociatedObject(self, UIControl_acceptEventTime, @(ne_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + ++ (void)load { + + Method a = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:)); + + SEL aSEL = @selector(sendAction:to:forEvent:); + + Method b = class_getInstanceMethod(self, @selector(__ne_sendAction:to:forEvent:)); + + SEL bSEL = @selector(__ne_sendAction:to:forEvent:); + + //添加方法 语法:BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) 若添加成功则返回No + // cls:被添加方法的类 name:被添加方法方法名 imp:被添加方法的实现函数 types:被添加方法的实现函数的返回值类型和参数类型的字符串 + BOOL didAddMethod = class_addMethod(self, aSEL, method_getImplementation(b), method_getTypeEncoding(b)); + + //如果系统中该方法已经存在了,则替换系统的方法 语法:IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types) + if (didAddMethod) { + class_replaceMethod(self, bSEL, method_getImplementation(a), method_getTypeEncoding(a)); + }else{ + method_exchangeImplementations(a, b); + } + +} + +- (void)__ne_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event { + + if (self.ne_ignoreEvent) return; + + if (self.ne_acceptEventInterval > 0) { + + // 是否小于设定的时间间隔 + BOOL needSendAction = (NSDate.date.timeIntervalSince1970 - self.ne_acceptEventTime >= self.ne_acceptEventInterval); + + // 更新上一次点击时间戳 + self.ne_acceptEventTime = NSDate.date.timeIntervalSince1970; + + // 两次点击的时间间隔小于设定的时间间隔时,才执行响应事件 + if (needSendAction) { + [self __ne_sendAction:action to:target forEvent:event]; + } + + return; + } + + [self __ne_sendAction:action to:target forEvent:event]; + +} + +@end diff --git a/OnlinePK-iOS/NLiteAVDemo/Utils/Macro/NSMacro.h b/OnlinePK-iOS/NLiteAVDemo/Utils/Macro/NSMacro.h index e5794b1..60c498f 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Utils/Macro/NSMacro.h +++ b/OnlinePK-iOS/NLiteAVDemo/Utils/Macro/NSMacro.h @@ -7,6 +7,7 @@ // Use of this source code is governed by a MIT license that can be found in the LICENSE file. #import +#import // base host #define BASE_HOST @"" @@ -83,3 +84,5 @@ void setupLogger(void); /// 默认PK直播惩罚时长60s(1:00) #define kPkLivePunishTotalTime 60 + +#define LoginManager [AuthorManager shareInstance] diff --git a/OnlinePK-iOS/NLiteAVDemo/Utils/NEGCDTimerManager/NEGCDTimerManager.h b/OnlinePK-iOS/NLiteAVDemo/Utils/NEGCDTimerManager/NEGCDTimerManager.h new file mode 100644 index 0000000..8696144 --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Utils/NEGCDTimerManager/NEGCDTimerManager.h @@ -0,0 +1,53 @@ +// +// NEGCDTimerManager.h +// NEChatroom-iOS-ObjC +// +// Created by vvj on 2021/2/1. +// Copyright (c) 2021 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be found in the LICENSE file. + +#import + +@interface NEGCDTimerManager : NSObject + +typedef NS_ENUM(NSUInteger,ActionOption){ + AbandonPreviousAction = 0, // 废除同一个timer之前的任务 + MergePreviousAction // 将同一个timer之前的任务合并到新的任务中 +}; + ++ (NEGCDTimerManager *)sharedInstance; + +/** + 启动一个timer,默认精度为0.1秒。 + + @param timerName timer的名称,作为唯一标识。 + @param interval 执行的时间间隔。 + @param queue timer将被放入的队列,也就是最终action执行的队列。传入nil将自动放到一个子线程队列中。 + @param repeats timer是否循环调用。 + @param option 多次schedule同一个timer时的操作选项(目前提供将之前的任务废除或合并的选项)。 + @param action 时间间隔到点时执行的block。 + */ +- (void)scheduledDispatchTimerWithName:(NSString *)timerName + timeInterval:(double)interval + queue:(dispatch_queue_t)queue + repeats:(BOOL)repeats + actionOption:(ActionOption)option + action:(dispatch_block_t)action; + +/** + 撤销某个timer。 + + @param timerName timer的名称,作为唯一标识。 + */ +- (void)cancelTimerWithName:(NSString *)timerName; + +/** + * 是否存在某个名称标识的timer。 + * + * @param timerName timer的唯一名称标识。 + * + * @return YES表示存在,反之。 + */ +- (BOOL)existTimer:(NSString *)timerName; + +@end diff --git a/OnlinePK-iOS/NLiteAVDemo/Utils/NEGCDTimerManager/NEGCDTimerManager.m b/OnlinePK-iOS/NLiteAVDemo/Utils/NEGCDTimerManager/NEGCDTimerManager.m new file mode 100644 index 0000000..96c6110 --- /dev/null +++ b/OnlinePK-iOS/NLiteAVDemo/Utils/NEGCDTimerManager/NEGCDTimerManager.m @@ -0,0 +1,150 @@ +// +// NEGCDTimerManager.m +// NEChatroom-iOS-ObjC +// +// Created by vvj on 2021/2/1. +// Copyright (c) 2021 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be found in the LICENSE file. + +#import "NEGCDTimerManager.h" + +@interface NEGCDTimerManager () + +@property (nonatomic, strong) NSMutableDictionary *timerContainer; +@property (nonatomic, strong) NSMutableDictionary *actionBlockCache; +@property (nonatomic, strong) NSTimer *timer; + +@end + +@implementation NEGCDTimerManager + +#pragma mark - Public Method + ++ (NEGCDTimerManager *)sharedInstance { + static NEGCDTimerManager *_gcdTimerManager = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken,^{ + _gcdTimerManager = [[NEGCDTimerManager alloc] init]; + }); + + return _gcdTimerManager; +} + +- (void)scheduledDispatchTimerWithName:(NSString *)timerName + timeInterval:(double)interval + queue:(dispatch_queue_t)queue + repeats:(BOOL)repeats + actionOption:(ActionOption)option + action:(dispatch_block_t)action +{ + if (nil == timerName) + return; + + if (nil == queue) + queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + dispatch_source_t timer = [self.timerContainer objectForKey:timerName]; + if (!timer) { + timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + dispatch_resume(timer); + [self.timerContainer setObject:timer forKey:timerName]; + } + + /* timer精度为0.1秒 */ + dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC); + + __weak typeof(self) weakSelf = self; + + switch (option) { + + case AbandonPreviousAction: + { + /* 移除之前的action */ + [weakSelf removeActionCacheForTimer:timerName]; + + dispatch_source_set_event_handler(timer, ^{ + action(); + + if (!repeats) { + [weakSelf cancelTimerWithName:timerName]; + } + }); + } + break; + + case MergePreviousAction: + { + /* cache本次的action */ + [self cacheAction:action forTimer:timerName]; + + dispatch_source_set_event_handler(timer, ^{ + NSMutableArray *actionArray = [self.actionBlockCache objectForKey:timerName]; + [actionArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + dispatch_block_t actionBlock = obj; + actionBlock(); + }]; + [weakSelf removeActionCacheForTimer:timerName]; + + if (!repeats) { + [weakSelf cancelTimerWithName:timerName]; + } + }); + } + break; + } +} + +- (void)cancelTimerWithName:(NSString *)timerName { + dispatch_source_t timer = [self.timerContainer objectForKey:timerName]; + if (!timer) { + return; + } + [self.timerContainer removeObjectForKey:timerName]; + dispatch_source_cancel(timer); + timer = nil; + [self.actionBlockCache removeObjectForKey:timerName]; +} + +- (BOOL)existTimer:(NSString *)timerName { + if ([self.timerContainer objectForKey:timerName]) { + return YES; + } + return NO; +} + +#pragma mark - Action Cache +- (void)cacheAction:(dispatch_block_t)action forTimer:(NSString *)timerName { + id actionArray = [self.actionBlockCache objectForKey:timerName]; + + if (actionArray && [actionArray isKindOfClass:[NSMutableArray class]]) { + [(NSMutableArray *)actionArray addObject:action]; + }else { + NSMutableArray *array = [NSMutableArray arrayWithObject:action]; + [self.actionBlockCache setObject:array forKey:timerName]; + } +} + +- (void)removeActionCacheForTimer:(NSString *)timerName { + if (![self.actionBlockCache objectForKey:timerName]) + return; + + [self.actionBlockCache removeObjectForKey:timerName]; +} + +#pragma mark ********** Lazy ********** +- (NSMutableDictionary *)timerContainer { + if (!_timerContainer) { + _timerContainer = [[NSMutableDictionary alloc] init]; + } + return _timerContainer; +} + +- (NSMutableDictionary *)actionBlockCache { + if (!_actionBlockCache) { + _actionBlockCache = [[NSMutableDictionary alloc] init]; + } + return _actionBlockCache; +} + +@end diff --git a/OnlinePK-iOS/NLiteAVDemo/Utils/NETSRequest/NETSRequest.m b/OnlinePK-iOS/NLiteAVDemo/Utils/NETSRequest/NETSRequest.m index 41373eb..10a39fa 100644 --- a/OnlinePK-iOS/NLiteAVDemo/Utils/NETSRequest/NETSRequest.m +++ b/OnlinePK-iOS/NLiteAVDemo/Utils/NETSRequest/NETSRequest.m @@ -223,6 +223,8 @@ - (SKVObject *)_fetchValueInDict:(SKVObject *)skvObj pathKeyArr:(NSArray true workspace 'NLiteAVDemo' source 'https://github.com/CocoaPods/Specs.git' # There are no targets called "Shows" in any Xcode projects abstract_target 'NEPkDemo' do - - pod 'NERtcSDK', '4.2.102' - pod 'NIMSDK_LITE', '8.3.1' + + pod 'NERtcSDK', '4.2.115' + pod 'NIMSDK_LITE', '8.6.2' target 'NLiteAVDemo' do + use_frameworks! pod 'Nama-lite', '7.3.2' pod 'Masonry' pod 'SDWebImage', '5.11.1' @@ -24,10 +26,14 @@ abstract_target 'NEPkDemo' do pod 'Reachability' pod 'YXAlog_iOS', '~> 0.1.0' pod 'BlocksKit' + pod 'YXLogin', '0.2.0' + end target 'NELiveRoom' do project './NELiveRoom/NELiveRoom.xcodeproj' end - + + + end diff --git a/OnlinePK-iOS/Podfile.lock b/OnlinePK-iOS/Podfile.lock index 72f2fab..7727853 100644 --- a/OnlinePK-iOS/Podfile.lock +++ b/OnlinePK-iOS/Podfile.lock @@ -14,21 +14,30 @@ PODS: - BlocksKit/UIKit (2.2.5): - BlocksKit/Core - BlocksKit/DynamicDelegate - - IQKeyboardManager (6.5.6) + - IQKeyboardManager (6.5.8) + - LGAlertViewCY (3.0.0) - M80AttributedLabel (1.3.3) - Masonry (1.1.0) - - MJRefresh (3.5.0) + - MJRefresh (3.7.2) - Nama-lite (7.3.2) - NELivePlayer (2.8.0) - - NERtcSDK (4.2.102) - - NIMSDK_LITE (8.3.1) + - NERtcSDK (4.2.115) + - NIMSDK_LITE (8.6.2) - Reachability (3.2) - ReactiveObjC (3.1.1) - SDWebImage (5.11.1): - SDWebImage/Core (= 5.11.1) - SDWebImage/Core (5.11.1) + - SPPageMenu (3.5.0) - Toast (4.0.0) - - YXAlog_iOS (0.1.0) + - YXAlog_iOS (0.1.2) + - YXLogin (0.2.0): + - LGAlertViewCY + - Masonry + - ReactiveObjC + - SPPageMenu + - Toast + - YYModel - YYModel (1.0.4) DEPENDENCIES: @@ -39,19 +48,21 @@ DEPENDENCIES: - MJRefresh - Nama-lite (= 7.3.2) - NELivePlayer (= 2.8.0) - - NERtcSDK (= 4.2.102) - - NIMSDK_LITE (= 8.3.1) + - NERtcSDK (= 4.2.115) + - NIMSDK_LITE (= 8.6.2) - Reachability - ReactiveObjC (~> 3.1.0) - SDWebImage (= 5.11.1) - Toast - YXAlog_iOS (~> 0.1.0) + - YXLogin (= 0.2.0) - YYModel SPEC REPOS: - https://github.com/CocoaPods/Specs.git: + https://github.com/cocoapods/specs.git: - BlocksKit - IQKeyboardManager + - LGAlertViewCY - M80AttributedLabel - Masonry - MJRefresh @@ -62,27 +73,32 @@ SPEC REPOS: - Reachability - ReactiveObjC - SDWebImage + - SPPageMenu - Toast - YXAlog_iOS + - YXLogin - YYModel SPEC CHECKSUMS: BlocksKit: 7f422b971407001178d181a43b99014ea2591fe6 - IQKeyboardManager: 2a6e97afdafc7becf0cb17a9a8d795e3a980717f + IQKeyboardManager: 1943526a2fa1a6ea24f90c845c274b0e80dd1912 + LGAlertViewCY: 3355db1c36b03fbab0d242cae51f4e0adf0e1939 M80AttributedLabel: 3bcacb2d85e6c66e32fb930aac2f04eca3974106 Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 - MJRefresh: 6afc955813966afb08305477dd7a0d9ad5e79a16 + MJRefresh: 30997d30b347c8e9508a4db11e3a690da0c9b85a Nama-lite: 735c7271288664120d3dca3f38418925710e11c2 NELivePlayer: 73b95dedaa50b89d3b649177a3cda58355326598 - NERtcSDK: 84d35e81c41db5f3199f9e7f9301ea52873c6eaf - NIMSDK_LITE: 70dea2d2b6ef91ed19042d12eb4c5b001b858174 + NERtcSDK: 25faaaeb15efed9238790390e4e0b7baeea09236 + NIMSDK_LITE: 4be1326e1b77d6a84070662c4bd8eba8536f7815 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 ReactiveObjC: 011caa393aa0383245f2dcf9bf02e86b80b36040 SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d + SPPageMenu: da182aafcec55719d5c326103cc7716c1e48f311 Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 - YXAlog_iOS: 13d51bd17616f312262a11e91925967bebc80ef5 + YXAlog_iOS: ecbc7f6e1bd2ffac7e5f908b33623ddea2af51be + YXLogin: be6fbfe96149be01bdf0494d23f4f7ab84de2d0a YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30 -PODFILE CHECKSUM: 01c49a8fe7049683b83a54b8656dfc07606b026c +PODFILE CHECKSUM: 1b9bf44eb6479254851ce0c079e199c6bf1113d2 -COCOAPODS: 1.10.1 +COCOAPODS: 1.7.1