From 952d147aad505bb05cb24e7d61319db439b5ed46 Mon Sep 17 00:00:00 2001 From: Dhan Date: Mon, 1 Aug 2022 12:42:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=201.9.5=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Podfile | 8 +- PolyvLiveScenesDemo.xcodeproj/project.pbxproj | 50 +++- .../Resource/PLVHCDemoUtils.h | 1 + .../Chatroom/Cell/PLVLCFileMessageCell.h | 21 ++ .../Chatroom/Cell/PLVLCFileMessageCell.m | 214 ++++++++++++++ .../Chatroom/Cell/PLVLCLandscapeFileCell.h | 28 ++ .../Chatroom/Cell/PLVLCLandscapeFileCell.m | 242 ++++++++++++++++ .../Modules/Chatroom/Cell/PLVLCMessageCell.m | 1 + .../Chatroom/Cell/PLVLCRewardMessageCell.m | 1 + .../Modules/Chatroom/PLVLCChatLandscapeView.h | 2 + .../Modules/Chatroom/PLVLCChatLandscapeView.m | 15 + .../Chatroom/PLVLCChatViewController.h | 6 + .../Chatroom/PLVLCChatViewController.m | 21 ++ .../Modules/Chatroom/PLVLCChatroomViewModel.h | 11 + .../Modules/Chatroom/PLVLCChatroomViewModel.m | 51 +++- .../View/PLVKeyboard/PLVLCKeyboardMoreView.h | 6 + .../View/PLVKeyboard/PLVLCKeyboardMoreView.m | 19 ++ .../View/PLVKeyboard/PLVLCKeyboardTextView.m | 2 +- .../View/PLVKeyboard/PLVLCKeyboardToolView.h | 6 + .../View/PLVKeyboard/PLVLCKeyboardToolView.m | 41 ++- .../Chatroom/View/PLVLCCardPushButtonView.m | 34 +-- .../ViewModel/PLVLCDownloadViewModel.m | 6 - .../SkinView/PLVLCLiveRoomPlayerSkinView.h | 15 +- .../SkinView/PLVLCLiveRoomPlayerSkinView.m | 23 +- .../Modules/Media/PLVLCMediaAreaView.m | 45 +++ .../PageMenu/PLVLCLivePageMenuAreaView.h | 10 + .../PageMenu/PLVLCLivePageMenuAreaView.m | 18 ++ .../PageMenu/buy/PLVLCBuyViewController.h | 3 + .../PageMenu/buy/PLVLCBuyViewController.m | 16 +- .../plvlc_chatroom_file_doc_icon@2x.png | Bin 0 -> 967 bytes .../plvlc_chatroom_file_doc_icon@3x.png | Bin 0 -> 1379 bytes .../plvlc_chatroom_file_pdf_icon@2x.png | Bin 0 -> 971 bytes .../plvlc_chatroom_file_pdf_icon@3x.png | Bin 0 -> 1449 bytes .../plvlc_chatroom_file_ppt_icon@2x.png | Bin 0 -> 603 bytes .../plvlc_chatroom_file_ppt_icon@3x.png | Bin 0 -> 833 bytes .../plvlc_chatroom_file_xls_icon@2x.png | Bin 0 -> 784 bytes .../plvlc_chatroom_file_xls_icon@3x.png | Bin 0 -> 1153 bytes .../Resource/PLVLCUtils.h | 1 + .../Scenes/PLVLCCloudClassViewController.m | 62 ++-- .../PLVCommodity/PLVCommodityCardDetailView.h | 3 + .../PLVCommodity/PLVCommodityCardDetailView.m | 1 + .../PLVReward/View/PLVGiveRewardGoodsButton.m | 1 + .../PLVReward/View/PLVRewardDisplayView.m | 1 + .../Modules/Chatroom/Model/PLVChatModel.m | 12 + .../Modules/Chatroom/PLVChatroomPresenter.h | 8 + .../Modules/Chatroom/PLVChatroomPresenter.m | 84 ++++++ .../Modules/Document/PLVDocumentView.h | 13 +- .../Modules/Document/PLVDocumentView.m | 40 +-- .../Modules/Interact/PLVInteractGenericView.m | 4 +- .../LinkMic/Models/PLVLinkMicOnlineUser.h | 17 ++ .../LinkMic/Models/PLVLinkMicOnlineUser.m | 11 +- .../Modules/Player/PLVPlayerPresenter.m | 57 ++++ .../Modules/Room/Model/PLVRoomData.h | 4 + .../Modules/Room/Model/PLVRoomData.m | 2 + .../Modules/Streamer/PLVStreamerPresenter.h | 10 + .../Modules/Streamer/PLVStreamerPresenter.m | 140 ++++++--- .../Modules/Chatroom/Cell/PLVECChatCell.m | 129 ++++++++- .../Modules/Chatroom/PLVECChatroomViewModel.h | 16 +- .../Modules/Chatroom/PLVECChatroomViewModel.m | 98 +++++-- .../Chatroom/Views/PLVECChatroomView.h | 3 + .../Chatroom/Views/PLVECChatroomView.m | 47 ++- .../Chatroom/Views/PLVECNewMessageView.m | 2 +- .../View/CanvasView/PLVECLinkMicCanvasView.h | 2 +- .../Controller/PLVECPlayerViewController.m | 42 +++ .../Resource/PLVECUtils.h | 1 + .../plvec_chatroom_file_doc_icon@2x.png | Bin 0 -> 967 bytes .../plvec_chatroom_file_doc_icon@3x.png | Bin 0 -> 1379 bytes .../plvec_chatroom_file_pdf_icon@2x.png | Bin 0 -> 971 bytes .../plvec_chatroom_file_pdf_icon@3x.png | Bin 0 -> 1449 bytes .../plvec_chatroom_file_ppt_icon@2x.png | Bin 0 -> 603 bytes .../plvec_chatroom_file_ppt_icon@3x.png | Bin 0 -> 833 bytes .../plvec_chatroom_file_xls_icon@2x.png | Bin 0 -> 784 bytes .../plvec_chatroom_file_xls_icon@3x.png | Bin 0 -> 1153 bytes .../plvec_home_giftbox_btn@2x.png | Bin 0 -> 9572 bytes .../plvec_home_giftbox_btn@3x.png | Bin 0 -> 13069 bytes .../plvec_home_redpack_btn@2x.png | Bin 0 -> 10394 bytes .../plvec_home_redpack_btn@3x.png | Bin 0 -> 17288 bytes .../Scenes/HomePage/PLVECHomePageView.h | 6 + .../Scenes/HomePage/PLVECHomePageView.m | 56 +++- .../View/PLVECCardPushButtonPopupView.h | 21 ++ .../View/PLVECCardPushButtonPopupView.m | 108 +++++++ .../HomePage/View/PLVECCardPushButtonView.h | 41 +++ .../HomePage/View/PLVECCardPushButtonView.m | 270 ++++++++++++++++++ .../Scenes/PLVECWatchRoomViewController.m | 95 ++++-- .../PLVLiveHiClassScene/Resource/PLVHCUtils.h | 1 + .../PLVLSBeautyFilterViewController.m | 15 +- .../Beauty/ViewModel/PLVLSBeautyViewModel.h | 3 + .../Beauty/ViewModel/PLVLSBeautyViewModel.m | 40 +++ .../Cell/PLVLSBaseRemindMessageCell.m | 116 -------- .../Cell/PLVLSRemindImageMessageCell.m | 1 + .../Cell/PLVLSRemindSpeakMessageCell.m | 1 + .../Modules/Chatroom/PLVLSChatroomAreaView.m | 19 +- .../Chatroom/View/PLVLSChatroomToolbar.m | 10 +- .../DocumentArea/PLVLSDocumentAreaView.h | 18 ++ .../DocumentArea/PLVLSDocumentAreaView.m | 94 +++++- .../View/PLVLSDocumentBrushView.m | 10 +- .../DocumentArea/View/PLVLSDocumentToolView.h | 10 + .../DocumentArea/View/PLVLSDocumentToolView.m | 36 ++- .../Modules/LinkMic/PLVLSLinkMicAreaView.h | 30 ++ .../Modules/LinkMic/PLVLSLinkMicAreaView.m | 32 ++- .../View/WindowsView/PLVLSLinkMicWindowCell.h | 6 + .../View/WindowsView/PLVLSLinkMicWindowCell.m | 19 +- .../WindowsView/PLVLSLinkMicWindowsView.h | 30 ++ .../WindowsView/PLVLSLinkMicWindowsView.m | 175 ++++++++++-- .../plvls_ppt_btn_switch@2x.png | Bin 0 -> 766 bytes .../plvls_ppt_btn_switch@3x.png | Bin 0 -> 1139 bytes .../Resource/PLVLSUtils.h | 1 + .../Resource/PLVLSUtils.m | 12 +- .../Scenes/PLVLSStreamerViewController.m | 25 +- .../PLVSABeautyFilterViewController.m | 15 +- .../Beauty/ViewModel/PLVSABeautyViewModel.h | 3 + .../Beauty/ViewModel/PLVSABeautyViewModel.m | 39 +++ .../Modules/LinkMic/PLVSALinkMicAreaView.h | 5 + .../Modules/LinkMic/PLVSALinkMicAreaView.m | 4 + .../View/WindowsView/PLVSALinkMicWindowCell.m | 1 + .../WindowsView/PLVSALinkMicWindowsView.h | 11 + .../WindowsView/PLVSALinkMicWindowsView.m | 124 ++++++-- .../Resource/PLVSAUtils.h | 1 + .../Resource/PLVSAUtils.m | 13 +- .../Scenes/PLVSAStreamerViewController.m | 52 +++- .../PolyvLiveHiClassDemo-Info.plist | 2 +- .../PolyvLiveScenesDemo-Info.plist | 2 +- .../PolyvLiveStreamerDemo-Info.plist | 2 +- .../PolyvLiveWatchDemo-Info.plist | 2 +- 124 files changed, 2904 insertions(+), 400 deletions(-) create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCFileMessageCell.h create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCFileMessageCell.m create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCLandscapeFileCell.h create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCLandscapeFileCell.m create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_doc_icon@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_doc_icon@3x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_pdf_icon@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_pdf_icon@3x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_ppt_icon@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_ppt_icon@3x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_xls_icon@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_xls_icon@3x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_doc_icon@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_doc_icon@3x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_pdf_icon@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_pdf_icon@3x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_ppt_icon@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_ppt_icon@3x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_xls_icon@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_xls_icon@3x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_home_giftbox_btn@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_home_giftbox_btn@3x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_home_redpack_btn@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_home_redpack_btn@3x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonPopupView.h create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonPopupView.m create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonView.h create mode 100644 PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonView.m delete mode 100644 PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSBaseRemindMessageCell.m create mode 100644 PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSDocument.bundle/plvls_ppt_btn_switch@2x.png create mode 100644 PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSDocument.bundle/plvls_ppt_btn_switch@3x.png diff --git a/Podfile b/Podfile index 74fd457a..ac852123 100644 --- a/Podfile +++ b/Podfile @@ -8,11 +8,11 @@ target 'PolyvLiveScenesDemo' do use_frameworks! # 保利威 多场景 SDK - pod 'PLVLiveScenesSDK', '1.9.4' + pod 'PLVLiveScenesSDK', '1.9.5' # 保利威 手机开播场景 需依赖的库 pod 'PLVBytedEffectSDK', '4.3.1' - pod 'PLVBusinessSDK', '1.9.4', :subspecs => ['Beauty'] + pod 'PLVBusinessSDK', '1.9.5', :subspecs => ['Beauty'] # 保利威 UI源码 需依赖的库 pod 'SDWebImage', '4.4.0' @@ -24,7 +24,7 @@ end target 'PLVScreenShareExtension' do use_frameworks! - pod 'PLVBusinessSDK', '1.9.4', :subspecs => ['AbstractBSH','ReplayKitExt'] - pod 'PLVFoundationSDK', '1.9.3' + pod 'PLVBusinessSDK', '1.9.5', :subspecs => ['AbstractBSH','ReplayKitExt'] + pod 'PLVFoundationSDK', '1.9.5' pod 'TXLiteAVSDK_TRTC', '9.3.10763' end diff --git a/PolyvLiveScenesDemo.xcodeproj/project.pbxproj b/PolyvLiveScenesDemo.xcodeproj/project.pbxproj index ac0ef55a..52746682 100644 --- a/PolyvLiveScenesDemo.xcodeproj/project.pbxproj +++ b/PolyvLiveScenesDemo.xcodeproj/project.pbxproj @@ -533,6 +533,7 @@ 04D97C9E26F469750075CAD8 /* PLVHCLoginCustomButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 04D97C9D26F469750075CAD8 /* PLVHCLoginCustomButton.m */; }; 04D97C9F26F469750075CAD8 /* PLVHCLoginCustomButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 04D97C9D26F469750075CAD8 /* PLVHCLoginCustomButton.m */; }; 04EEC0B3281000EC00F111B5 /* PLVLivePictureInPicturePlaceholderView.m in Sources */ = {isa = PBXBuildFile; fileRef = FA561B7327BA48410080AADE /* PLVLivePictureInPicturePlaceholderView.m */; }; + 1E03F7681780EA74BFEB2109 /* Pods_PLVScreenShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40D1CECCA6335A46952F901A /* Pods_PLVScreenShareExtension.framework */; }; 215D04192605875300E86BEF /* PLVRoomLoginClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E48A47258B2EB80068C884 /* PLVRoomLoginClient.m */; }; 215D041A2605875300E86BEF /* PLVInteractQuestionnaire.m in Sources */ = {isa = PBXBuildFile; fileRef = 63976753250F420300D1F16B /* PLVInteractQuestionnaire.m */; }; 215D04252605875300E86BEF /* PLVInteractAnswer.m in Sources */ = {isa = PBXBuildFile; fileRef = 63976743250B190E00D1F16B /* PLVInteractAnswer.m */; }; @@ -587,7 +588,6 @@ 21795DD3260730D4001E3668 /* PLVLSEmojiSelectView.m in Sources */ = {isa = PBXBuildFile; fileRef = 21795DD2260730D4001E3668 /* PLVLSEmojiSelectView.m */; }; 21795DD826073F3B001E3668 /* PLVLSSendMessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 21795DD626073F3A001E3668 /* PLVLSSendMessageTextView.m */; }; 21795DDD26077D7A001E3668 /* PLVLSSendMessageToolView.m in Sources */ = {isa = PBXBuildFile; fileRef = 21795DDC26077D7A001E3668 /* PLVLSSendMessageToolView.m */; }; - 23BDD0703EB32089E8B26735 /* Pods_PLVScreenShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EBFCC5D1FA2228A85E3B91B5 /* Pods_PLVScreenShareExtension.framework */; }; 2937E16E26E5CA5E002D3118 /* PLVLSDocumentWaitLiveView.m in Sources */ = {isa = PBXBuildFile; fileRef = 296A1DA426CDF7FD0076A0CA /* PLVLSDocumentWaitLiveView.m */; }; 296A1DA526CDF7FE0076A0CA /* PLVLSDocumentWaitLiveView.m in Sources */ = {isa = PBXBuildFile; fileRef = 296A1DA426CDF7FD0076A0CA /* PLVLSDocumentWaitLiveView.m */; }; 362D64A926D721270041FAE1 /* PLVHCLinkMicWindowCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 362D64A826D721270041FAE1 /* PLVHCLinkMicWindowCell.m */; }; @@ -596,6 +596,10 @@ 362D659B26D784690041FAE1 /* PLVHCLinkMicWindowsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 362D659926D784690041FAE1 /* PLVHCLinkMicWindowsView.m */; }; 362D65B626D7A1100041FAE1 /* PLVHCLinkMicCanvasView.m in Sources */ = {isa = PBXBuildFile; fileRef = 362D65B526D7A1100041FAE1 /* PLVHCLinkMicCanvasView.m */; }; 362D65B726D7A1100041FAE1 /* PLVHCLinkMicCanvasView.m in Sources */ = {isa = PBXBuildFile; fileRef = 362D65B526D7A1100041FAE1 /* PLVHCLinkMicCanvasView.m */; }; + 362FD735287ED54200565032 /* PLVECCardPushButtonView.m in Sources */ = {isa = PBXBuildFile; fileRef = 362FD734287ED54200565032 /* PLVECCardPushButtonView.m */; }; + 362FD736287ED54200565032 /* PLVECCardPushButtonView.m in Sources */ = {isa = PBXBuildFile; fileRef = 362FD734287ED54200565032 /* PLVECCardPushButtonView.m */; }; + 362FD739287ED58700565032 /* PLVECCardPushButtonPopupView.m in Sources */ = {isa = PBXBuildFile; fileRef = 362FD738287ED58700565032 /* PLVECCardPushButtonPopupView.m */; }; + 362FD73A287ED58700565032 /* PLVECCardPushButtonPopupView.m in Sources */ = {isa = PBXBuildFile; fileRef = 362FD738287ED58700565032 /* PLVECCardPushButtonPopupView.m */; }; 36323F2126E5FFCE0001FF5C /* PLVHCTeacherLoginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 36323F2026E5FFCE0001FF5C /* PLVHCTeacherLoginManager.m */; }; 36323F2226E5FFCE0001FF5C /* PLVHCTeacherLoginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 36323F2026E5FFCE0001FF5C /* PLVHCTeacherLoginManager.m */; }; 36323F2926E601A30001FF5C /* PLVHCTokenLoginModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 36323F2826E601A30001FF5C /* PLVHCTokenLoginModel.m */; }; @@ -797,6 +801,10 @@ 4D02950F28601DE700C7FF4D /* PLVBusinessSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3631485327F15D3700FDF6D7 /* PLVBusinessSDK.framework */; }; 4D02951228601DF500C7FF4D /* PLVBusinessSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3631485327F15D3700FDF6D7 /* PLVBusinessSDK.framework */; }; 4D1E74C6275DB59B008B7D3A /* PLVLCSectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D1E74C5275DB59B008B7D3A /* PLVLCSectionViewController.m */; }; + 4D46919928869D2900043A4E /* PLVLCFileMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D46919828869D2900043A4E /* PLVLCFileMessageCell.m */; }; + 4D46919A28869D2900043A4E /* PLVLCFileMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D46919828869D2900043A4E /* PLVLCFileMessageCell.m */; }; + 4D46919D28869DA000043A4E /* PLVLCLandscapeFileCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D46919C28869DA000043A4E /* PLVLCLandscapeFileCell.m */; }; + 4D46919E28869DA000043A4E /* PLVLCLandscapeFileCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D46919C28869DA000043A4E /* PLVLCLandscapeFileCell.m */; }; 4D4B079A275F11C800A4A883 /* PLVLCSectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D4B0799275F11C800A4A883 /* PLVLCSectionViewCell.m */; }; 4D4C77A42768342C001DB85D /* PLVLCPlaybackListEmptyView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D4C77A32768342C001DB85D /* PLVLCPlaybackListEmptyView.m */; }; 4D8F2BB527B643E200F5BCCC /* PLVLCSectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D1E74C5275DB59B008B7D3A /* PLVLCSectionViewController.m */; }; @@ -908,6 +916,7 @@ 67C43BC22803DD200050EDFA /* PLVLCBuyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 67C43BC02803DD200050EDFA /* PLVLCBuyViewController.m */; }; 67D700C52476807200429B55 /* WatchResource.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 67D700C42476807200429B55 /* WatchResource.bundle */; }; 67D700C92476817500429B55 /* PLVECUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 67D700C82476817500429B55 /* PLVECUtils.m */; }; + 6CD9DC4EE1F9F06132D94E5E /* Pods_PolyvLiveScenesDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B610859BFEC63DBF01B7855 /* Pods_PolyvLiveScenesDemo.framework */; }; 8F119EF4280D10A2008215FF /* PLVSABeautyFilterCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F119EF3280D10A2008215FF /* PLVSABeautyFilterCollectionViewCell.m */; }; 8F119EF5280D10A2008215FF /* PLVSABeautyFilterCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F119EF3280D10A2008215FF /* PLVSABeautyFilterCollectionViewCell.m */; }; 8F119EFC280EC0F0008215FF /* PLVSABeautyBaseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F119EFB280EC0F0008215FF /* PLVSABeautyBaseViewController.m */; }; @@ -1138,7 +1147,6 @@ 8FFC704F26F200A900ED8C28 /* PLVHCAreaCodeChooseTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FFC704D26F200A900ED8C28 /* PLVHCAreaCodeChooseTableViewCell.m */; }; 8FFC705226F200B300ED8C28 /* PLVHCAreaCodeChooseView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FFC705026F200B200ED8C28 /* PLVHCAreaCodeChooseView.m */; }; 8FFC705326F200B300ED8C28 /* PLVHCAreaCodeChooseView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FFC705026F200B200ED8C28 /* PLVHCAreaCodeChooseView.m */; }; - A9472DE30D07CF1577F48D40 /* Pods_PolyvLiveScenesDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9643BA69F981F50FD2DC3520 /* Pods_PolyvLiveScenesDemo.framework */; }; BF574404255D204500283C2D /* PLVECNewMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = BF574403255D204500283C2D /* PLVECNewMessageView.m */; }; BF7CFC6125BA636B0084C160 /* PLVECCommodityCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BF7CFC5725BA636B0084C160 /* PLVECCommodityCell.m */; }; BF7CFC6225BA636B0084C160 /* PLVCommodityDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BF7CFC5925BA636B0084C160 /* PLVCommodityDetailViewController.m */; }; @@ -1584,6 +1592,7 @@ 04D97C9C26F469750075CAD8 /* PLVHCLoginCustomButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVHCLoginCustomButton.h; sourceTree = ""; }; 04D97C9D26F469750075CAD8 /* PLVHCLoginCustomButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVHCLoginCustomButton.m; sourceTree = ""; }; 077C2E48E28AD1CD7FC8C0E8 /* Pods-business-liveScenes-liveScenesDemo-PolyvStreamerAloneDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-business-liveScenes-liveScenesDemo-PolyvStreamerAloneDemo.release.xcconfig"; path = "Target Support Files/Pods-business-liveScenes-liveScenesDemo-PolyvStreamerAloneDemo/Pods-business-liveScenes-liveScenesDemo-PolyvStreamerAloneDemo.release.xcconfig"; sourceTree = ""; }; + 1B610859BFEC63DBF01B7855 /* Pods_PolyvLiveScenesDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PolyvLiveScenesDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 213639DB264C1C56004F66A7 /* plvlw_login_clear@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "plvlw_login_clear@2x.png"; sourceTree = ""; }; 213639DC264C1C56004F66A7 /* plvlw_login_logo@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "plvlw_login_logo@2x.png"; sourceTree = ""; }; 213639DD264C1C56004F66A7 /* plvlw_login_logo@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "plvlw_login_logo@3x.png"; sourceTree = ""; }; @@ -1603,6 +1612,10 @@ 362D659926D784690041FAE1 /* PLVHCLinkMicWindowsView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVHCLinkMicWindowsView.m; sourceTree = ""; }; 362D65B426D7A1100041FAE1 /* PLVHCLinkMicCanvasView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVHCLinkMicCanvasView.h; sourceTree = ""; }; 362D65B526D7A1100041FAE1 /* PLVHCLinkMicCanvasView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVHCLinkMicCanvasView.m; sourceTree = ""; }; + 362FD733287ED54200565032 /* PLVECCardPushButtonView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVECCardPushButtonView.h; sourceTree = ""; }; + 362FD734287ED54200565032 /* PLVECCardPushButtonView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVECCardPushButtonView.m; sourceTree = ""; }; + 362FD737287ED58700565032 /* PLVECCardPushButtonPopupView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVECCardPushButtonPopupView.h; sourceTree = ""; }; + 362FD738287ED58700565032 /* PLVECCardPushButtonPopupView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVECCardPushButtonPopupView.m; sourceTree = ""; }; 3631485327F15D3700FDF6D7 /* PLVBusinessSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PLVBusinessSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36323F1F26E5FFCE0001FF5C /* PLVHCTeacherLoginManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVHCTeacherLoginManager.h; sourceTree = ""; }; 36323F2026E5FFCE0001FF5C /* PLVHCTeacherLoginManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVHCTeacherLoginManager.m; sourceTree = ""; }; @@ -1745,9 +1758,14 @@ 36F4A27026C4B10200143E54 /* PLVHCLiveroomViewModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVHCLiveroomViewModel.m; sourceTree = ""; }; 3AFD93E604C59290FC339D1A /* Pods-PolyvLiveEcommerceDemoUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolyvLiveEcommerceDemoUITests.debug.xcconfig"; path = "Target Support Files/Pods-PolyvLiveEcommerceDemoUITests/Pods-PolyvLiveEcommerceDemoUITests.debug.xcconfig"; sourceTree = ""; }; 3CAD663FB81149230EFFFD29 /* Pods-PLVScreenShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PLVScreenShareExtension.release.xcconfig"; path = "Target Support Files/Pods-PLVScreenShareExtension/Pods-PLVScreenShareExtension.release.xcconfig"; sourceTree = ""; }; + 40D1CECCA6335A46952F901A /* Pods_PLVScreenShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PLVScreenShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 430C9CF57975329ADA06E5C9 /* Pods_PolyvLiveEcommerceDemoUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PolyvLiveEcommerceDemoUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4D1E74C4275DB59B008B7D3A /* PLVLCSectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVLCSectionViewController.h; sourceTree = ""; }; 4D1E74C5275DB59B008B7D3A /* PLVLCSectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVLCSectionViewController.m; sourceTree = ""; }; + 4D46919728869D2900043A4E /* PLVLCFileMessageCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVLCFileMessageCell.h; sourceTree = ""; }; + 4D46919828869D2900043A4E /* PLVLCFileMessageCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVLCFileMessageCell.m; sourceTree = ""; }; + 4D46919B28869DA000043A4E /* PLVLCLandscapeFileCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVLCLandscapeFileCell.h; sourceTree = ""; }; + 4D46919C28869DA000043A4E /* PLVLCLandscapeFileCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVLCLandscapeFileCell.m; sourceTree = ""; }; 4D4B0798275F11C800A4A883 /* PLVLCSectionViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVLCSectionViewCell.h; sourceTree = ""; }; 4D4B0799275F11C800A4A883 /* PLVLCSectionViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVLCSectionViewCell.m; sourceTree = ""; }; 4D4C77A22768342C001DB85D /* PLVLCPlaybackListEmptyView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVLCPlaybackListEmptyView.h; sourceTree = ""; }; @@ -2152,7 +2170,6 @@ 8FFC705026F200B200ED8C28 /* PLVHCAreaCodeChooseView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PLVHCAreaCodeChooseView.m; sourceTree = ""; }; 8FFC705126F200B300ED8C28 /* PLVHCAreaCodeChooseView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PLVHCAreaCodeChooseView.h; sourceTree = ""; }; 91BAD47C9228D092BBD97E25 /* Pods-business-liveScenes-PolyvLiveStreamerDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-business-liveScenes-PolyvLiveStreamerDemo.debug.xcconfig"; path = "Target Support Files/Pods-business-liveScenes-PolyvLiveStreamerDemo/Pods-business-liveScenes-PolyvLiveStreamerDemo.debug.xcconfig"; sourceTree = ""; }; - 9643BA69F981F50FD2DC3520 /* Pods_PolyvLiveScenesDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PolyvLiveScenesDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 992E19E2694CF1F9EE51FC98 /* Pods-business-PLVScreenShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-business-PLVScreenShareExtension.release.xcconfig"; path = "Target Support Files/Pods-business-PLVScreenShareExtension/Pods-business-PLVScreenShareExtension.release.xcconfig"; sourceTree = ""; }; A306B0E8EE71BAB0FC2F9D3F /* Pods-PolyvLiveEcommerceDemoUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolyvLiveEcommerceDemoUITests.release.xcconfig"; path = "Target Support Files/Pods-PolyvLiveEcommerceDemoUITests/Pods-PolyvLiveEcommerceDemoUITests.release.xcconfig"; sourceTree = ""; }; A80F0CB539F9A66B888935AA /* Pods-business-liveScenes-liveScenesDemo-PolyvLiveScenesDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-business-liveScenes-liveScenesDemo-PolyvLiveScenesDemo.debug.xcconfig"; path = "Target Support Files/Pods-business-liveScenes-liveScenesDemo-PolyvLiveScenesDemo/Pods-business-liveScenes-liveScenesDemo-PolyvLiveScenesDemo.debug.xcconfig"; sourceTree = ""; }; @@ -2181,7 +2198,6 @@ D12BFA48BFD11F80E5A2FAA5 /* Pods-business-liveScenes-liveScenesDemo-PolyvStreamerAloneDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-business-liveScenes-liveScenesDemo-PolyvStreamerAloneDemo.debug.xcconfig"; path = "Target Support Files/Pods-business-liveScenes-liveScenesDemo-PolyvStreamerAloneDemo/Pods-business-liveScenes-liveScenesDemo-PolyvStreamerAloneDemo.debug.xcconfig"; sourceTree = ""; }; D9C6F1C98177E8003A3399A9 /* Pods-business-liveScenes-PolyvLiveStreamerDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-business-liveScenes-PolyvLiveStreamerDemo.release.xcconfig"; path = "Target Support Files/Pods-business-liveScenes-PolyvLiveStreamerDemo/Pods-business-liveScenes-PolyvLiveStreamerDemo.release.xcconfig"; sourceTree = ""; }; DDD8A461CB92C616BB85ACDF /* Pods-PLVScreenShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PLVScreenShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-PLVScreenShareExtension/Pods-PLVScreenShareExtension.debug.xcconfig"; sourceTree = ""; }; - EBFCC5D1FA2228A85E3B91B5 /* Pods_PLVScreenShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PLVScreenShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FA4F8BF7283DDF2200DE8C29 /* PLVLCDownloadBottomSheet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVLCDownloadBottomSheet.h; sourceTree = ""; }; FA4F8BF8283DDF2200DE8C29 /* PLVLCDownloadBottomSheet.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PLVLCDownloadBottomSheet.m; sourceTree = ""; }; FA4F8BFB283E046200DE8C29 /* PLVLCBottomSheet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PLVLCBottomSheet.h; sourceTree = ""; }; @@ -2247,7 +2263,7 @@ buildActionMask = 2147483647; files = ( 3660408B27B3AF6000557F90 /* ReplayKit.framework in Frameworks */, - 23BDD0703EB32089E8B26735 /* Pods_PLVScreenShareExtension.framework in Frameworks */, + 1E03F7681780EA74BFEB2109 /* Pods_PLVScreenShareExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2256,7 +2272,7 @@ buildActionMask = 2147483647; files = ( 633E425D257DCD710039D35D /* PLVLiveScenesSDK.framework in Frameworks */, - A9472DE30D07CF1577F48D40 /* Pods_PolyvLiveScenesDemo.framework in Frameworks */, + 6CD9DC4EE1F9F06132D94E5E /* Pods_PolyvLiveScenesDemo.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2357,6 +2373,10 @@ 00476BD72591959000460F8E /* PLVECPlayerContolView.m */, 0077D58025B91D60008A1AE0 /* PLVECLikeButtonView.h */, 0077D58125B91D60008A1AE0 /* PLVECLikeButtonView.m */, + 362FD733287ED54200565032 /* PLVECCardPushButtonView.h */, + 362FD734287ED54200565032 /* PLVECCardPushButtonView.m */, + 362FD737287ED58700565032 /* PLVECCardPushButtonPopupView.h */, + 362FD738287ED58700565032 /* PLVECCardPushButtonPopupView.m */, ); path = View; sourceTree = ""; @@ -2753,6 +2773,8 @@ 36D402182696A51C00FD96F2 /* PLVLCImageEmotionMessageCell.m */, 006862EE25771D0C00E319EA /* PLVLCQuoteMessageCell.h */, 006862E925771D0C00E319EA /* PLVLCQuoteMessageCell.m */, + 4D46919728869D2900043A4E /* PLVLCFileMessageCell.h */, + 4D46919828869D2900043A4E /* PLVLCFileMessageCell.m */, 006862E625771D0C00E319EA /* PLVLCLandscapeSpeakCell.h */, 006862ED25771D0C00E319EA /* PLVLCLandscapeSpeakCell.m */, 006862E725771D0C00E319EA /* PLVLCLandscapeImageCell.h */, @@ -2761,6 +2783,8 @@ 36D4021F2696AEA600FD96F2 /* PLVLCLandscapeImageEmotionCell.m */, 006862E825771D0C00E319EA /* PLVLCLandscapeQuoteCell.h */, 006862EF25771D0C00E319EA /* PLVLCLandscapeQuoteCell.m */, + 4D46919B28869DA000043A4E /* PLVLCLandscapeFileCell.h */, + 4D46919C28869DA000043A4E /* PLVLCLandscapeFileCell.m */, ); path = Cell; sourceTree = ""; @@ -4711,8 +4735,8 @@ 430C9CF57975329ADA06E5C9 /* Pods_PolyvLiveEcommerceDemoUITests.framework */, 6ED5072918A006C94EB83D86 /* Pods_business_liveScenes_liveScenesDemo_PolyvStreamerAloneDemo.framework */, 3660408A27B3AF6000557F90 /* ReplayKit.framework */, - EBFCC5D1FA2228A85E3B91B5 /* Pods_PLVScreenShareExtension.framework */, - 9643BA69F981F50FD2DC3520 /* Pods_PolyvLiveScenesDemo.framework */, + 40D1CECCA6335A46952F901A /* Pods_PLVScreenShareExtension.framework */, + 1B610859BFEC63DBF01B7855 /* Pods_PolyvLiveScenesDemo.framework */, ); name = Frameworks; sourceTree = ""; @@ -5618,6 +5642,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 362FD736287ED54200565032 /* PLVECCardPushButtonView.m in Sources */, 005F174D264BBB410093E874 /* PLVLCLinkMicWindowsView.m in Sources */, 005F174E264BBB410093E874 /* PLVLCLiveRoomPlayerSkinView.m in Sources */, 005F174F264BBB410093E874 /* PLVLCMediaMoreCell.m in Sources */, @@ -5680,11 +5705,14 @@ 4D8F2BB827B6445300F5BCCC /* PLVECPlaybackListView.m in Sources */, 363472D127708FA4007BEFBA /* PLVLinkMicOnlineUser+EC.m in Sources */, 005F177A264BBB410093E874 /* PLVChatModel.m in Sources */, + 4D46919E28869DA000043A4E /* PLVLCLandscapeFileCell.m in Sources */, 364928CB287569D900E3F2D1 /* PLVLCCardPushPopupView.m in Sources */, 36D46AB927B63AA100C4F67F /* PLVBroadcastExtensionLauncher.m in Sources */, 005F177C264BBB410093E874 /* PLVLinkMicOnlineUser.m in Sources */, + 4D46919A28869D2900043A4E /* PLVLCFileMessageCell.m in Sources */, 04B1D1D42791261E007894C5 /* PLVRewardDisplayManager.m in Sources */, 4D8F2BB627B6440E00F5BCCC /* PLVLCPlaybackListViewController.m in Sources */, + 362FD73A287ED58700565032 /* PLVECCardPushButtonPopupView.m in Sources */, 36E8E10426DCD2FB00DAEE02 /* PLVMarqueeLabel.m in Sources */, 36E8E12226DCD2FB00DAEE02 /* PLVPickerController.m in Sources */, 67C43BC22803DD200050EDFA /* PLVLCBuyViewController.m in Sources */, @@ -6310,6 +6338,7 @@ 04504768265F744800D801A2 /* PLVSABottomSheet.m in Sources */, 8F8C670E2814DFEE003BB88D /* PLVLSBeautySheet.m in Sources */, 8F1C83D2266A0E4A00B9374D /* PLVSAChatroomGiftView.m in Sources */, + 4D46919D28869DA000043A4E /* PLVLCLandscapeFileCell.m in Sources */, 00877A0F264BD84200AD9245 /* PLVLiveWatchLoginController.m in Sources */, 8F4C7BF326A6CADC005F968B /* PLVHCBrushColorSelectSheet.m in Sources */, 00E48A48258B2EB80068C884 /* PLVRoomLoginClient.m in Sources */, @@ -6379,6 +6408,7 @@ 8F4C845226AEA93E005F968B /* PLVHCDocumentMinimumSheet.m in Sources */, 04D97C9E26F469750075CAD8 /* PLVHCLoginCustomButton.m in Sources */, 364928C628751B9C00E3F2D1 /* PLVLCCardPushButtonView.m in Sources */, + 362FD739287ED58700565032 /* PLVECCardPushButtonPopupView.m in Sources */, 00476BF22591959000460F8E /* PLVECSwitchView.m in Sources */, 00EF59A1251C985E00A65197 /* PLVLCQuizViewController.m in Sources */, 63C228572546BAFD00410292 /* PLVLCTextViewController.m in Sources */, @@ -6467,6 +6497,7 @@ 55DB27B4280974E5009BEB28 /* PLVSABeautyCellModel.m in Sources */, 04984DA326CCA2E00013A533 /* PLVHCSettingConfigView.m in Sources */, 0450466B265E21D500D801A2 /* PLVSAStreamAlertController.m in Sources */, + 362FD735287ED54200565032 /* PLVECCardPushButtonView.m in Sources */, 8F7C0AE527B6359400C159F8 /* PLVLSRemindChatroomSheet.m in Sources */, 8F6DBA742689672D00AC71AE /* PLVHCNewMessgaeTipView.m in Sources */, 8FC4505D277069B8005AAC7C /* PLVLSProhibitWordTipView.m in Sources */, @@ -6480,6 +6511,7 @@ 8F6F3E392750B79F00E6C4A7 /* PLVHCLinkMicPlaceholderView.m in Sources */, 0070F4A126BD1C1600F0F85B /* PLVHCMemberViewModel.m in Sources */, 005F1873264BBC470093E874 /* PLVLSMemberCellEditView.m in Sources */, + 4D46919928869D2900043A4E /* PLVLCFileMessageCell.m in Sources */, 00877A13264BD9D500AD9245 /* PLVLiveStreamerLoginViewController.m in Sources */, 8FD08F86265FA1D6007F8457 /* PLVSAChatroomListView.m in Sources */, 005F1852264BBC2F0093E874 /* PLVLSImageMessageCell.m in Sources */, @@ -7098,7 +7130,6 @@ baseConfigurationReference = FE0ED85B45376486CC1BFE2E /* Pods-PolyvLiveScenesDemo.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/PolyvLiveScenesDemo/Supporting Files/PLVLiveScenesDemo.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; @@ -7130,7 +7161,6 @@ baseConfigurationReference = C8C0B1BDBA08B5A1387EB520 /* Pods-PolyvLiveScenesDemo.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/PolyvLiveScenesDemo/Supporting Files/PLVLiveScenesDemo.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; diff --git a/PolyvLiveScenesDemo/Demo/Login/PolyvLiveHiClassDemo/Resource/PLVHCDemoUtils.h b/PolyvLiveScenesDemo/Demo/Login/PolyvLiveHiClassDemo/Resource/PLVHCDemoUtils.h index 8766f30d..41c90a77 100644 --- a/PolyvLiveScenesDemo/Demo/Login/PolyvLiveHiClassDemo/Resource/PLVHCDemoUtils.h +++ b/PolyvLiveScenesDemo/Demo/Login/PolyvLiveHiClassDemo/Resource/PLVHCDemoUtils.h @@ -9,6 +9,7 @@ #import #import #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCFileMessageCell.h b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCFileMessageCell.h new file mode 100644 index 00000000..7360ca4a --- /dev/null +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCFileMessageCell.h @@ -0,0 +1,21 @@ +// +// PLVLCFileMessageCell.h +// PolyvLiveScenesDemo +// +// Created by Dhan on 2022/7/19. +// Copyright © 2022 PLV. All rights reserved. +// + +#import "PLVLCMessageCell.h" + +NS_ASSUME_NONNULL_BEGIN + +/* + 云课堂场景,竖屏聊天室消息 cell + 支持文件下载消息 + */ +@interface PLVLCFileMessageCell : PLVLCMessageCell + +@end + +NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCFileMessageCell.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCFileMessageCell.m new file mode 100644 index 00000000..129cbff9 --- /dev/null +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCFileMessageCell.m @@ -0,0 +1,214 @@ +// +// PLVLCFileMessageCell.m +// PolyvLiveScenesDemo +// +// Created by Dhan on 2022/7/19. +// Copyright © 2022 PLV. All rights reserved. +// + +#import "PLVLCFileMessageCell.h" +#import "PLVChatTextView.h" +#import "PLVLCUtils.h" +#import + +@interface PLVLCFileMessageCell () + +#pragma mark 数据 + +/// 消息数据模型 +@property (nonatomic, strong) PLVFileMessage *fileMessage; + +#pragma mark UI + +/// 文件名内容视图 +@property (nonatomic, strong) PLVChatTextView *textView; +/// 背景气泡 +@property (nonatomic, strong) UIView *bubbleView; +/// 文件类型图标 +@property (nonatomic, strong) UIImageView *fileImageView; +/// 手势视图 +@property (nonatomic, strong) UIView *tapGestureView; + +@end + + +@implementation PLVLCFileMessageCell + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + [self.contentView addSubview:self.bubbleView]; + [self.contentView addSubview:self.textView]; + [self.contentView addSubview:self.fileImageView]; + [self.contentView addSubview:self.tapGestureView]; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + if (self.cellWidth == 0) { + return; + } + + CGFloat originX = self.nickLabel.frame.origin.x; + CGFloat originY = self.nickLabel.frame.origin.y + 20; + + CGFloat xPadding = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad ? 20.0 : 16.0; // 头像左间距,气泡右间距 + CGFloat bubblePadding = 12;//textView、fileImageView与bubble的内部间距均为12 + + CGFloat imageViewWidth = 40; + CGFloat imageViewHeight = 48; + + CGFloat maxTextViewWidth = self.cellWidth - originX - xPadding - imageViewWidth - bubblePadding * 3; + CGSize textViewSize = [self.textView sizeThatFits:CGSizeMake(maxTextViewWidth, MAXFLOAT)]; + + self.textView.frame = CGRectMake(originX + bubblePadding, originY + (imageViewHeight - textViewSize.height) / 2 + bubblePadding, textViewSize.width, textViewSize.height); + self.fileImageView.frame = CGRectMake(originX + bubblePadding * 2 + textViewSize.width, originY + bubblePadding, imageViewWidth, imageViewHeight); + CGSize bubbleSize = CGSizeMake(textViewSize.width + imageViewWidth + bubblePadding * 3, imageViewHeight + bubblePadding * 2); + + + self.bubbleView.frame = CGRectMake(originX, originY, bubbleSize.width, bubbleSize.height); + self.tapGestureView.frame = CGRectMake(originX, originY, bubbleSize.width, bubbleSize.height); + + // 绘制气泡外部曲线 + CAShapeLayer *maskLayer = [PLVLCMessageCell bubbleLayerWithSize:bubbleSize]; + self.bubbleView.layer.mask = maskLayer; +} + +#pragma mark - Getter + +- (UIView *)bubbleView { + if (!_bubbleView) { + _bubbleView = [[UIView alloc] init]; + _bubbleView.backgroundColor = [PLVColorUtil colorFromHexString:@"#2B2C35"]; + } + return _bubbleView; +} + +- (PLVChatTextView *)textView { + if (!_textView) { + _textView = [[PLVChatTextView alloc] init]; + _textView.showMenu = YES; + _textView.textContainer.maximumNumberOfLines = 2; + _textView.textContainer.lineBreakMode = NSLineBreakByTruncatingMiddle; + } + return _textView; +} + +- (UIImageView *)fileImageView { + if (!_fileImageView) { + _fileImageView = [[UIImageView alloc] init]; + _fileImageView.layer.masksToBounds = YES; + _fileImageView.userInteractionEnabled = NO; + _fileImageView.contentMode = UIViewContentModeScaleAspectFill; + } + return _fileImageView; +} + +- (UIView *)tapGestureView { + if (!_tapGestureView) { + _tapGestureView = [[UIView alloc] init]; + _tapGestureView.backgroundColor = [UIColor clearColor]; + _tapGestureView.userInteractionEnabled = YES; + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureViewAction)]; + [_tapGestureView addGestureRecognizer:tap]; + } + return _tapGestureView; +} + +#pragma mark - UI + +- (void)updateWithModel:(PLVChatModel *)model loginUserId:(NSString *)loginUserId cellWidth:(CGFloat)cellWidth { + [super updateWithModel:model loginUserId:loginUserId cellWidth:cellWidth]; + + if (self.cellWidth == 0 || ![PLVLCFileMessageCell isModelValid:model]) { + self.cellWidth = 0; + return; + } + + self.fileMessage = model.message; + + NSMutableAttributedString *contentLabelString = [PLVLCFileMessageCell contentLabelAttributedStringWithMessage:model.message + user:model.user]; + [self.textView setContent:contentLabelString showUrl:NO]; + + UIImage *fileImageView = [PLVLCFileMessageCell imageWithMessage:model.message]; + [self.fileImageView setImage:fileImageView]; + +} + +#pragma mark UI - ViewModel + +/// 获取消息多属性文本 ++ (NSMutableAttributedString *)contentLabelAttributedStringWithMessage:(id)message user:(PLVChatUser *)user { + NSString *content = @""; + if ([message isKindOfClass:[PLVFileMessage class]]) { + PLVFileMessage *fileMessage = (PLVFileMessage *)message; + content = fileMessage.name; + } else { + content = (NSString *)message; + } + + NSDictionary *attributeDict = @{ + NSFontAttributeName: [UIFont systemFontOfSize:16.0], + NSForegroundColorAttributeName:[PLVColorUtil colorFromHexString:@"#ADADC0"] + }; + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:content attributes:attributeDict]; + return attributedString; +} + +/// 获取文件类型图标 ++ (UIImage *)imageWithMessage:(PLVFileMessage *)message { + NSString *fileUrl = message.url; + + if (![PLVFdUtil checkStringUseable:fileUrl]) { + return nil; + } + + NSString *fileType = [[[fileUrl pathExtension] lowercaseString] substringToIndex:3]; + NSString *fileImageString = [NSString stringWithFormat:@"plvlc_chatroom_file_%@_icon",fileType]; + + return [PLVLCUtils imageForChatroomResource:fileImageString]; +} + +#pragma mark - 高度计算 + ++ (CGFloat)cellHeightWithModel:(PLVChatModel *)model cellWidth:(CGFloat)cellWidth { + CGFloat cellHeight = [super cellHeightWithModel:model cellWidth:cellWidth]; + if (cellHeight == 0 || ![PLVLCFileMessageCell isModelValid:model]) { + return 0; + } + + CGFloat originY = 28.0; // 64 为气泡初始y值 + CGFloat imageViewHeight = 48; + + return originY + 12 + imageViewHeight + 12 + 16; +} + +#pragma mark - Action + +- (void)tapGestureViewAction { + NSString *url = self.fileMessage.url; + if ([PLVFdUtil checkStringUseable:url]) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; + } +} + + +#pragma mark - Utils + ++ (BOOL)isModelValid:(PLVChatModel *)model { + if (!model || ![model isKindOfClass:[PLVChatModel class]]) { + return NO; + } + + id message = model.message; + if (!message || ![message isKindOfClass:[PLVFileMessage class]]) { + return NO; + } + return YES; +} + +@end diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCLandscapeFileCell.h b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCLandscapeFileCell.h new file mode 100644 index 00000000..f79495d2 --- /dev/null +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCLandscapeFileCell.h @@ -0,0 +1,28 @@ +// +// PLVLCLandscapeFileCell.h +// PolyvLiveScenesDemo +// +// Created by Dhan on 2022/7/19. +// Copyright © 2022 PLV. All rights reserved. +// + +#import +#import "PLVChatModel.h" +#import "PLVChatUser.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface PLVLCLandscapeFileCell : UITableViewCell + +/// 设置消息数据模型,cell宽度 +- (void)updateWithModel:(PLVChatModel *)model loginUserId:(NSString *)loginUserId cellWidth:(CGFloat)cellWidth; + +//// 根据消息数据模型、cell宽度计算cell高度 ++ (CGFloat)cellHeightWithModel:(PLVChatModel *)model loginUserId:(NSString *)loginUserId cellWidth:(CGFloat)cellWidth; + +/// 判断model是否为有效类型,子类可覆写 ++ (BOOL)isModelValid:(PLVChatModel *)model; + +@end + +NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCLandscapeFileCell.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCLandscapeFileCell.m new file mode 100644 index 00000000..b71cf691 --- /dev/null +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCLandscapeFileCell.m @@ -0,0 +1,242 @@ +// +// PLVLCLandscapeFileCell.m +// PolyvLiveScenesDemo +// +// Created by Dan on 2022/7/19. +// Copyright © 2022 PLV. All rights reserved. +// + +#import "PLVLCLandscapeFileCell.h" +#import "PLVChatTextView.h" +#import "PLVLCUtils.h" +#import +#import + +@interface PLVLCLandscapeFileCell () + +#pragma mark 数据 + +/// 消息数据模型 +@property (nonatomic, strong) PLVFileMessage *fileMessage; +/// cell宽度 +@property (nonatomic, assign) CGFloat cellWidth; +/// 登录用户的聊天室userId +@property (nonatomic, strong) NSString *loginUserId; + +#pragma mark UI + +/// 消息文本内容视图 +@property (nonatomic, strong) PLVChatTextView *textView; +/// 文件类型图片 +@property (nonatomic, strong) UIImageView *fileImageView; +/// 背景气泡 +@property (nonatomic, strong) UIView *bubbleView; +/// 手势视图 +@property (nonatomic, strong) UIView *tapGestureView; + +@end + +@implementation PLVLCLandscapeFileCell + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + self.backgroundColor = [UIColor clearColor]; + [self.contentView addSubview:self.bubbleView]; + [self.contentView addSubview:self.textView]; + [self.contentView addSubview:self.fileImageView]; + [self.contentView addSubview:self.tapGestureView]; + } + return self; +} + +- (void)layoutSubviews { + if (self.cellWidth == 0) { + return; + } + + CGFloat xPadding = 12.0; // 气泡与textView的左右内间距 + CGFloat yPadding = 8.0; // textView文本与textView的内部有上下间距8 + CGFloat imageViewWidth = 32; + CGFloat imageViewHeight = 38; + + CGFloat maxTextViewWidth = self.cellWidth - xPadding * 3 - imageViewWidth; + CGSize textViewSize = [self.textView sizeThatFits:CGSizeMake(maxTextViewWidth, MAXFLOAT)]; + + if (textViewSize.height < (imageViewHeight + 2 * yPadding)) { + self.fileImageView.frame = CGRectMake(xPadding * 2 + textViewSize.width, yPadding, imageViewWidth, imageViewHeight); + self.textView.frame = CGRectMake(xPadding, (imageViewHeight - textViewSize.height) / 2 + yPadding, textViewSize.width, textViewSize.height); + } else { + self.textView.frame = CGRectMake(xPadding, 0, textViewSize.width, textViewSize.height); + self.fileImageView.frame = CGRectMake(xPadding * 2 + textViewSize.width, (textViewSize.height - imageViewHeight) / 2, imageViewWidth, imageViewHeight); + } + + CGSize bubbleSize = CGSizeMake(textViewSize.width + imageViewWidth + xPadding + xPadding * 2, MAX(imageViewHeight + 2 * yPadding, textViewSize.height)); + self.bubbleView.frame = CGRectMake(0, 0, bubbleSize.width, bubbleSize.height); + self.tapGestureView.frame = CGRectMake(0, 0, bubbleSize.width, bubbleSize.height); +} + +#pragma mark - Getter + +- (UIView *)bubbleView { + if (!_bubbleView) { + _bubbleView = [[UIView alloc] init]; + _bubbleView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; + _bubbleView.layer.cornerRadius = 14.0; + _bubbleView.layer.masksToBounds = YES; + } + return _bubbleView; +} + +- (PLVChatTextView *)textView { + if (!_textView) { + _textView = [[PLVChatTextView alloc] init]; + _textView.showMenu = YES; + _textView.textContainer.maximumNumberOfLines = 3; + _textView.textContainer.lineBreakMode = NSLineBreakByTruncatingMiddle; + } + return _textView; +} + +- (UIImageView *)fileImageView { + if (!_fileImageView) { + _fileImageView = [[UIImageView alloc] init]; + _fileImageView.layer.masksToBounds = YES; + _fileImageView.userInteractionEnabled = NO; + _fileImageView.contentMode = UIViewContentModeScaleAspectFill; + } + return _fileImageView; +} + +- (UIView *)tapGestureView { + if (!_tapGestureView) { + _tapGestureView = [[UIView alloc] init]; + _tapGestureView.backgroundColor = [UIColor clearColor]; + _tapGestureView.userInteractionEnabled = YES; + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureViewAction)]; + [_tapGestureView addGestureRecognizer:tap]; + } + return _tapGestureView; +} + +#pragma mark - UI + +- (void)updateWithModel:(PLVChatModel *)model loginUserId:(NSString *)loginUserId cellWidth:(CGFloat)cellWidth { + if (![PLVLCLandscapeFileCell isModelValid:model] || cellWidth == 0) { + self.cellWidth = 0; + return; + } + + self.cellWidth = cellWidth; + + if (loginUserId && [loginUserId isKindOfClass:[NSString class]] && loginUserId.length > 0) { + self.loginUserId = loginUserId; + } + + PLVFileMessage *message = (PLVFileMessage *)model.message; + self.fileMessage = message; + NSMutableAttributedString *contentLabelString = [PLVLCLandscapeFileCell contentLabelAttributedStringWithMessage:message user:model.user loginUserId:self.loginUserId]; + [self.textView setContent:contentLabelString showUrl:NO]; + UIImage *fileImageView = [PLVLCLandscapeFileCell imageWithMessage:model.message]; + [self.fileImageView setImage:fileImageView]; +} + +#pragma mark UI - ViewModel + +/// 获取消息多属性文本 ++ (NSMutableAttributedString *)contentLabelAttributedStringWithMessage:(PLVFileMessage *)message + user:(PLVChatUser *)user + loginUserId:(NSString *)loginUserId { + UIFont *font = [UIFont systemFontOfSize:14.0]; + NSString *nickNameColorHexString = [user isUserSpecial] ? @"#FFD36D" : @"#6DA7FF"; + UIColor *nickNameColor = [PLVColorUtil colorFromHexString:nickNameColorHexString]; + UIColor *contentColor = [UIColor whiteColor]; + + NSDictionary *nickNameAttDict = @{NSFontAttributeName: font, + NSForegroundColorAttributeName: nickNameColor}; + NSDictionary *contentAttDict = @{NSFontAttributeName: font, + NSForegroundColorAttributeName:contentColor}; + + NSString *content = user.userName; + if (user.userId && [user.userId isKindOfClass:[NSString class]] && + loginUserId && [loginUserId isKindOfClass:[NSString class]] && [loginUserId isEqualToString:user.userId]) { + content = [content stringByAppendingString:@"(我)"]; + } + if (user.actor && [user.actor isKindOfClass:[NSString class]] && user.actor.length > 0) { + content = [NSString stringWithFormat:@"%@-%@", user.actor, content]; + } + content = [content stringByAppendingString:@":"]; + + NSAttributedString *nickNameString = [[NSAttributedString alloc] initWithString:content attributes:nickNameAttDict]; + NSAttributedString *conentString = [[NSAttributedString alloc] initWithString:message.name attributes:contentAttDict]; + + NSMutableAttributedString *contentLabelString = [[NSMutableAttributedString alloc] init]; + [contentLabelString appendAttributedString:nickNameString]; + [contentLabelString appendAttributedString:conentString]; + return contentLabelString; +} + +/// 获取文件类型图标 ++ (UIImage *)imageWithMessage:(PLVFileMessage *)message { + NSString *fileUrl = message.url; + + if (![PLVFdUtil checkStringUseable:fileUrl]) { + return nil; + } + + NSString *fileType = [[[fileUrl pathExtension] lowercaseString] substringToIndex:3]; + NSString *fileImageString = [NSString stringWithFormat:@"plvlc_chatroom_file_%@_icon",fileType]; + + return [PLVLCUtils imageForChatroomResource:fileImageString]; +} + +#pragma mark - 高度计算 + ++ (CGFloat)cellHeightWithModel:(PLVChatModel *)model loginUserId:(NSString *)loginUserId cellWidth:(CGFloat)cellWidth { + if (![PLVLCLandscapeFileCell isModelValid:model] || cellWidth == 0) { + return 0; + } + + CGFloat xPadding = 12.0; // 气泡与textView的左右内间距 + CGFloat bubbleYPadding = 8.0; // 气泡与textView的上下内间距 + CGFloat imageViewWidth = 40; + CGFloat imageViewHeight = 48; + CGFloat maxTextViewWidth = cellWidth - imageViewWidth - xPadding * 3; + + PLVFileMessage *message = (PLVFileMessage *)model.message; + NSMutableAttributedString *contentLabelString = [PLVLCLandscapeFileCell contentLabelAttributedStringWithMessage:message user:model.user loginUserId:loginUserId]; + + PLVChatTextView *textView = [[PLVChatTextView alloc]init]; + textView.textContainer.maximumNumberOfLines = 3; + textView.attributedText = contentLabelString; + [textView setContent:contentLabelString showUrl:NO]; + CGSize contentLabelSize = [textView sizeThatFits:CGSizeMake(maxTextViewWidth, MAXFLOAT)]; + CGFloat bubbleHeight = MAX(contentLabelSize.height, imageViewHeight + bubbleYPadding * 2); + + return bubbleHeight + 5; // 气泡底部外间距为5 +} + +#pragma mark - Action + +- (void)tapGestureViewAction { + NSString *url = self.fileMessage.url; + if ([PLVFdUtil checkStringUseable:url]) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; + } +} + +#pragma mark - Utils + ++ (BOOL)isModelValid:(PLVChatModel *)model { + if (!model || ![model isKindOfClass:[PLVChatModel class]]) { + return NO; + } + + id message = model.message; + if (!message || ![message isKindOfClass:[PLVFileMessage class]]) { + return NO; + } + return YES; +} + +@end diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCMessageCell.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCMessageCell.m index 4d6a11e5..0b943097 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCMessageCell.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCMessageCell.m @@ -154,6 +154,7 @@ + (BOOL)isModelValid:(PLVChatModel *)model { ![message isKindOfClass:[PLVQuoteMessage class]] && ![message isKindOfClass:[PLVImageMessage class]] && ![message isKindOfClass:[PLVImageEmotionMessage class]] && + ![message isKindOfClass:[PLVFileMessage class]] && ![message isKindOfClass:[NSString class]])) { return NO; } diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCRewardMessageCell.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCRewardMessageCell.m index beaa19b3..887e47f0 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCRewardMessageCell.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/Cell/PLVLCRewardMessageCell.m @@ -9,6 +9,7 @@ #import "PLVLCRewardMessageCell.h" #import #import +#import #import "PLVChatTextView.h" @interface PLVLCRewardMessageCell() diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatLandscapeView.h b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatLandscapeView.h index d81e533a..b1e67cc7 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatLandscapeView.h +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatLandscapeView.h @@ -16,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)updatePlaybackViewModel:(PLVLCChatroomPlaybackViewModel *)playbackViewModel; +- (void)updateChatTableView; + @end NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatLandscapeView.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatLandscapeView.m index 9addc039..f626db40 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatLandscapeView.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatLandscapeView.m @@ -15,6 +15,7 @@ #import "PLVLCLandscapeImageCell.h" #import "PLVLCLandscapeImageEmotionCell.h" #import "PLVLCLandscapeQuoteCell.h" +#import "PLVLCLandscapeFileCell.h" #import "PLVLCChatroomPlaybackViewModel.h" #import #import @@ -240,6 +241,10 @@ - (void)updatePlaybackViewModel:(PLVLCChatroomPlaybackViewModel *)playbackViewMo [self.playbackViewModel addUIDelegate:self delegateQueue:dispatch_get_main_queue()]; } +- (void)updateChatTableView { + [self.tableView reloadData]; +} + #pragma mark - Private Method - (void)scrollsToBottom:(BOOL)animated { @@ -410,6 +415,14 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } [cell updateWithModel:model loginUserId:roomUser.viewerId cellWidth:self.tableView.frame.size.width]; return cell; + } if ([PLVLCLandscapeFileCell isModelValid:model]) { + static NSString *filekMessageCellIdentify = @"PLVLCLandscapeFileCell"; + PLVLCLandscapeFileCell *cell = (PLVLCLandscapeFileCell *)[tableView dequeueReusableCellWithIdentifier:filekMessageCellIdentify]; + if (!cell) { + cell = [[PLVLCLandscapeFileCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:filekMessageCellIdentify]; + } + [cell updateWithModel:model loginUserId:roomUser.viewerId cellWidth:self.tableView.frame.size.width]; + return cell; } else { static NSString *cellIdentify = @"cellIdentify"; UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentify]; @@ -452,6 +465,8 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa cellHeight = [PLVLCLandscapeImageEmotionCell cellHeightWithModel:model cellWidth:self.tableView.frame.size.width]; } else if ([PLVLCLandscapeQuoteCell isModelValid:model]) { cellHeight = [PLVLCLandscapeQuoteCell cellHeightWithModel:model loginUserId:roomUser.viewerId cellWidth:self.tableView.frame.size.width]; + } else if ([PLVLCLandscapeFileCell isModelValid:model]) { + cellHeight = [PLVLCLandscapeFileCell cellHeightWithModel:model loginUserId:roomUser.viewerId cellWidth:self.tableView.frame.size.width]; } else { cellHeight = 0; } diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatViewController.h b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatViewController.h index 79912855..bf721b11 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatViewController.h +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatViewController.h @@ -45,6 +45,12 @@ extern NSString *PLVLCChatroomOpenRewardViewNotification; - (void)resumeCardPushButtonViewLayout; +/// 切换聊天室关闭状态 +- (void)changeCloseRoomStatus:(BOOL)closeRoom; + +/// 切换聊天室专注模式状态 +- (void)changeFocusMode:(BOOL)focusMode; + - (void)updatePlaybackViewModel:(PLVLCChatroomPlaybackViewModel *)playbackViewModel; - (void)leaveLiveRoom; diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatViewController.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatViewController.m index a5c7392d..4a674b7d 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatViewController.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatViewController.m @@ -17,6 +17,7 @@ #import "PLVLCImageEmotionMessageCell.h" #import "PLVLCQuoteMessageCell.h" #import "PLVLCRewardMessageCell.h" +#import "PLVLCFileMessageCell.h" #import "PLVAlbumNavigationController.h" #import "PLVGiveRewardPresenter.h" #import "PLVRewardGoodsModel.h" @@ -362,6 +363,15 @@ - (void)updatePlaybackViewModel:(PLVLCChatroomPlaybackViewModel *)playbackViewMo [self.playbackViewModel addUIDelegate:self delegateQueue:dispatch_get_main_queue()]; } +- (void)changeCloseRoomStatus:(BOOL)closeRoom { + [self.keyboardToolView changeCloseRoomStatus:closeRoom]; +} + +- (void)changeFocusMode:(BOOL)focusMode { + [self.keyboardToolView changeFocusMode:focusMode]; +} + + - (void)leaveLiveRoom { [self.cardPushButtonView leaveLiveRoom]; } @@ -647,6 +657,15 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } [cell updateWithModel:model cellWidth:self.tableView.frame.size.width]; return cell; + } else if ([PLVLCFileMessageCell isModelValid:model]) { + static NSString *fileMessageCellIdentify = @"PLVLCFileMessageCell"; + PLVLCFileMessageCell *cell = (PLVLCFileMessageCell *)[tableView dequeueReusableCellWithIdentifier:fileMessageCellIdentify]; + if (!cell) { + cell = [[PLVLCFileMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:fileMessageCellIdentify]; + } + CGFloat fileMessageCellWidth = self.likeButtonView.frame.origin.x - 8;// 气泡保证不遮挡点赞按钮 + [cell updateWithModel:model loginUserId:roomUser.viewerId cellWidth:fileMessageCellWidth]; + return cell; } else { static NSString *cellIdentify = @"cellIdentify"; UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentify]; @@ -690,6 +709,8 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa cellHeight = [PLVLCQuoteMessageCell cellHeightWithModel:model cellWidth:self.tableView.frame.size.width]; } else if ([PLVLCRewardMessageCell isModelValid:model]) { cellHeight = [PLVLCRewardMessageCell cellHeightWithModel:model cellWidth:self.tableView.frame.size.width]; + } else if ([PLVLCFileMessageCell isModelValid:model]) { + cellHeight = [PLVLCFileMessageCell cellHeightWithModel:model cellWidth:self.tableView.frame.size.width]; } else { cellHeight = 0; } diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatroomViewModel.h b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatroomViewModel.h index 2923b2ef..92da4896 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatroomViewModel.h +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatroomViewModel.h @@ -87,11 +87,22 @@ NS_ASSUME_NONNULL_BEGIN /// @param pointUnit 打赏数据单位 - (void)chatroomManager_loadRewardEnable:(BOOL)enable payWay:(NSString * _Nullable)payWay rewardModelArray:(NSArray * _Nullable)modelArray pointUnit:(NSString * _Nullable)pointUnit; +/// 聊天室登录达到并发限制时触发 +- (void)chatroomManager_didLoginRestrict; + /// 收到卡片推送消息后的回调 /// @param start 是否开启卡片推送(YES 开启 NO 取消) /// @param pushDict 卡片推送的信息 - (void)chatroomManager_startCardPush:(BOOL)start pushInfo:(NSDictionary *)pushDict; +/// 聊天室是否开启关闭时触发 +/// @param closeRoom 是否关闭聊天室,YES-关闭,NO-开启 +- (void)chatroomManager_closeRoom:(BOOL)closeRoom; + +/// 聊天室专注模式是否开启关闭时触发 +/// @param focusMode 是否关闭聊天室,YES-关闭,NO-开启 +- (void)chatroomManager_focusMode:(BOOL)focusMode; + @end /* diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatroomViewModel.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatroomViewModel.m index 4fefa8ab..3d46c2bf 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatroomViewModel.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/PLVLCChatroomViewModel.m @@ -47,10 +47,14 @@ @interface PLVLCChatroomViewModel ()< #pragma mark 数据数组 +/// 是否为专注模式 +@property (nonatomic, assign) BOOL focusMode; /// 公聊全部消息数组 @property (nonatomic, strong) NSMutableArray *publicChatArray; /// 公聊【只看教师与我】消息数组 @property (nonatomic, strong) NSMutableArray *partOfPublicChatArray; +/// 公聊【只看教师】消息数组,用于响应专注模式 +@property (nonatomic, strong) NSMutableArray *partOfSpecialIdentityPublicChatArray; /// 私聊消息数组 @property (nonatomic, strong) NSMutableArray *privateChatArray; @@ -105,6 +109,7 @@ - (void)setup { // 初始化消息数组,预设初始容量 self.publicChatArray = [NSMutableArray arrayWithCapacity:500]; self.partOfPublicChatArray = [NSMutableArray arrayWithCapacity:100]; + self.partOfSpecialIdentityPublicChatArray = [NSMutableArray arrayWithCapacity:100]; self.privateChatArray = [NSMutableArray arrayWithCapacity:20]; // 初始化聊天室Presenter并设置delegate @@ -162,6 +167,7 @@ - (void)clear { [self removeAllPublicChatModels]; self.onlyTeacher = NO; + self.focusMode = NO; } #pragma mark - 加载打赏开关 @@ -274,7 +280,11 @@ - (void)removeAllPrivateChatModels { - (NSMutableArray *)chatArray { if (self.onlyTeacher) { - return self.partOfPublicChatArray; + if (self.focusMode) { + return self.partOfSpecialIdentityPublicChatArray; + } else { + return self.partOfPublicChatArray; + } } else { return self.publicChatArray; } @@ -302,6 +312,7 @@ - (void)addPublicChatModels:(NSArray *)modelArray { [self.publicChatArray addObject:model]; if (model.user.specialIdentity) { [self.partOfPublicChatArray addObject:model]; + [self.partOfSpecialIdentityPublicChatArray addObject:model]; } } } @@ -323,6 +334,7 @@ - (void)deletePublicChatModelWithMsgId:(NSString *)msgId { if (modelMsgId && [modelMsgId isEqualToString:msgId]) { [self.publicChatArray removeObject:model]; [self.partOfPublicChatArray removeObject:model]; + [self.partOfSpecialIdentityPublicChatArray removeObject:model]; break; } } @@ -336,6 +348,7 @@ - (void)removeAllPublicChatModels { dispatch_semaphore_wait(_publicChatArrayLock, DISPATCH_TIME_FOREVER); [self.publicChatArray removeAllObjects]; [self.partOfPublicChatArray removeAllObjects]; + [self.partOfSpecialIdentityPublicChatArray removeAllObjects]; dispatch_semaphore_signal(_publicChatArrayLock); [self notifyDelegatesDidMessageDeleted]; @@ -348,7 +361,10 @@ - (void)insertChatModels:(NSArray *)modelArray noMore:(BOOL)noM if ([model isKindOfClass:[PLVChatModel class]]) { [self.publicChatArray insertObject:model atIndex:0]; PLVChatUser *user = model.user; - if (user.specialIdentity || [self isLoginUser:user.userId]) { + if (user.specialIdentity) { + [self.partOfPublicChatArray insertObject:model atIndex:0]; + [self.partOfSpecialIdentityPublicChatArray insertObject:model atIndex:0]; + } else if ([self isLoginUser:user.userId]) { [self.partOfPublicChatArray insertObject:model atIndex:0]; } } @@ -472,6 +488,24 @@ - (void)notifyDelegatesLoadImageEmotionFailure { }); } +- (void)notifyDelegatesDidLoginRestrict { + dispatch_async(multicastQueue, ^{ + [self->multicastDelegate chatroomManager_didLoginRestrict]; + }); +} + +- (void)notifyDelegatesCloseRoom:(BOOL)closeRoom { + dispatch_async(multicastQueue, ^{ + [self->multicastDelegate chatroomManager_closeRoom:closeRoom]; + }); +} + +- (void)notifyDelegatesFocusMode:(BOOL)focusMode { + dispatch_async(multicastQueue, ^{ + [self->multicastDelegate chatroomManager_focusMode:focusMode]; + }); +} + #pragma mark - 定时上报登录用户 /// 有用户登陆 @@ -668,6 +702,19 @@ - (void)chatroomPresenter_loadImageEmotionsFailure { [self notifyDelegatesLoadImageEmotionFailure]; } +- (void)chatroomPresenter_didLoginRestrict { + [self notifyDelegatesDidLoginRestrict]; +} + +- (void)chatroomPresenter_didChangeCloseRoom:(BOOL)closeRoom { + [self notifyDelegatesCloseRoom:closeRoom]; +} + +- (void)chatroomPresenter_didChangeFocusMode:(BOOL)focusMode { + self.focusMode = focusMode; + [self notifyDelegatesFocusMode:focusMode]; +} + #pragma mark - Utils - (BOOL)isLoginUser:(NSString *)userId { diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardMoreView.h b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardMoreView.h index 8f1c9dcb..f42b00a0 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardMoreView.h +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardMoreView.h @@ -51,4 +51,10 @@ /// 礼物打赏特效开关按钮是否隐藏,默认 YES;YES - 隐藏,NO - 显示 @property (nonatomic, assign) BOOL hideRewardDisplaySwitch; +/// 切换聊天室关闭状态 +- (void)changeCloseRoomStatus:(BOOL)closeRoom; + +/// 切换聊天室专注模式状态 +- (void)changeFocusModeStatus:(BOOL)focusMode; + @end diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardMoreView.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardMoreView.m index 31886a8c..f1442e23 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardMoreView.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardMoreView.m @@ -110,6 +110,8 @@ @interface PLVLCKeyboardMoreView () @property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout; @property (nonatomic, strong) NSArray *dynamicDataArray; +@property (nonatomic, assign) BOOL closeRoom; +@property (nonatomic, assign) BOOL focusMode; @end @@ -171,6 +173,19 @@ - (void)updateChatButtonDataArray:(NSArray *)dataArray { [self.collectionView reloadData]; } +- (void)changeCloseRoomStatus:(BOOL)closeRoom { + self.closeRoom = closeRoom; + [self.collectionView reloadData]; +} + +- (void)changeFocusModeStatus:(BOOL)focusMode { + self.focusMode = focusMode; + if (self.delegate && [self.delegate respondsToSelector:@selector(keyboardMoreView_onlyTeacher:on:)]) { + [self.delegate keyboardMoreView_onlyTeacher:self on:focusMode]; + } + [self changeCloseRoomStatus:focusMode]; +} + #pragma mark - Getterr & Setter - (void)setSendImageEnable:(BOOL)sendImageEnable { @@ -228,6 +243,10 @@ - (UICollectionViewCell *)collectionView:(UICollectionView*)collectionView cellF cell.type = type; if (type == PLVLCKeyboardMoreButtonTypeUnknow) { [self updateMoreButton:cell.moreBtn]; + } else if (type == PLVLCKeyboardMoreButtonTypeOpenCamera || type == PLVLCKeyboardMoreButtonTypeOpenAlbum) { + cell.moreBtn.enabled = !self.closeRoom; + } else if (type == PLVLCKeyboardMoreButtonTypeOnlyTeacher) { + cell.moreBtn.enabled = !self.focusMode; } [cell.moreBtn addTarget:self action:@selector(moreBtnAction:) forControlEvents:UIControlEventTouchUpInside]; diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardTextView.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardTextView.m index beadfaf2..e2a5bd11 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardTextView.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardTextView.m @@ -51,7 +51,7 @@ - (void)setupWithFrame:(CGRect)frame { } UIEdgeInsets oldTextContainerInset = self.textContainerInset; - oldTextContainerInset.right = 24.0; + oldTextContainerInset.right = 12.0; self.textContainerInset = oldTextContainerInset; } diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardToolView.h b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardToolView.h index 40097d3c..d8468007 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardToolView.h +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardToolView.h @@ -86,6 +86,12 @@ NS_ASSUME_NONNULL_BEGIN /// 加载视图,调用该方代替 'addSubview:' - (void)addAtView:(UIView *)parentView frame:(CGRect)rect; +/// 切换聊天室关闭状态,开启/禁用输入框、emoji 选择、查看更多中的部分功能 +- (void)changeCloseRoomStatus:(BOOL)closeRoom; + +/// 切换聊天室专注模式状态,开启/禁用输入框、emoji 选择、查看更多中的部分功能,启用只看讲师功能 +- (void)changeFocusMode:(BOOL)focusMode; + /// 刷新文本和按钮布局,iPad分屏尺寸变动时调用 - (void)updateTextViewAndButton; diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardToolView.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardToolView.m index 53ec98d2..1bc52256 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardToolView.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVKeyboard/PLVLCKeyboardToolView.m @@ -12,6 +12,7 @@ #import "PLVLCKeyboardMoreView.h" #import "PLVEmoticonManager.h" #import "PLVLCUtils.h" +#import #define kScreenWidth ([UIScreen mainScreen].bounds.size.width) #define kScreenHeight ([UIScreen mainScreen].bounds.size.height) @@ -21,7 +22,8 @@ @interface PLVLCKeyboardToolView ()< UITextViewDelegate, PLVLCEmojiSelectViewDelegate, -PLVLCKeyboardMoreViewDelegate +PLVLCKeyboardMoreViewDelegate, +PLVSocketManagerProtocol > /// 不同的 mode,决定不同的 UI @property (nonatomic, assign) PLVLCKeyboardToolMode mode; @@ -66,7 +68,10 @@ @interface PLVLCKeyboardToolView ()< @end -@implementation PLVLCKeyboardToolView +@implementation PLVLCKeyboardToolView { + /// PLVSocketManager回调的执行队列 + dispatch_queue_t socketDelegateQueue; +} #pragma mark - Life Cycle @@ -309,6 +314,10 @@ - (instancetype)initWithMode:(PLVLCKeyboardToolMode)mode { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationDidChange) name:UIDeviceOrientationDidChangeNotification object:nil]; + + // 监听socket消息 + socketDelegateQueue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT); + [[PLVSocketManager sharedManager] addDelegate:self delegateQueue:socketDelegateQueue]; } return self; } @@ -505,6 +514,34 @@ - (BOOL)shouldInteract { } } +/// 切换聊天室关闭状态,开启/禁用输入框、emoji 选择、查看更多中的部分功能 +- (void)changeCloseRoomStatus:(BOOL)closeRoom { + NSString *placeholderText = closeRoom ? @"聊天室已关闭":@"我也来聊几句"; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.textView setEditable:!closeRoom]; + [self changePlaceholderText:placeholderText]; + self.emojiButton.enabled = !closeRoom; + if (self.mode == PLVLCKeyboardToolModeDefault) { + [self.moreboard changeCloseRoomStatus:closeRoom]; + } + [self setToolState:PLVLCKeyboardToolStateNormal]; + }); +} + +/// 切换聊天室专注模式状态,开启/禁用输入框、emoji 选择、查看更多中的部分功能,启用只看讲师功能 +- (void)changeFocusMode:(BOOL)focusMode { + NSString *placeholderText = focusMode ? @"当前为专注模式,无法发言":@"我也来聊几句"; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.textView setEditable:!focusMode]; + [self changePlaceholderText:placeholderText]; + self.emojiButton.enabled = !focusMode; + if (self.mode == PLVLCKeyboardToolModeDefault) { + [self.moreboard changeFocusModeStatus:focusMode]; + } + [self setToolState:PLVLCKeyboardToolStateNormal]; + }); +} + #pragma mark - NSNotification - (void)keyboardWillShow:(NSNotification *)notification { diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVLCCardPushButtonView.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVLCCardPushButtonView.m index 7f709dd9..d4da1405 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVLCCardPushButtonView.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Chatroom/View/PLVLCCardPushButtonView.m @@ -50,13 +50,18 @@ - (instancetype)init { - (void)layoutSubviews { BOOL fullScreen = [UIScreen mainScreen].bounds.size.width > [UIScreen mainScreen].bounds.size.height; + CGSize popupViewSize = [self.popupView.titleLabel sizeThatFits:CGSizeMake(MAXFLOAT, 16)]; + CGFloat popupViewWidth = popupViewSize.width + 16 + (fullScreen ? 0 : 10); + CGFloat popupViewHeight = 34; if (!fullScreen) { // 竖屏 self.cardPushButton.frame = CGRectMake(0, self.countdownLabel.isHidden ? 12 : 0, PLVLCCardPushButtonViewWidth, PLVLCCardPushButtonViewWidth); + self.popupView.frame = CGRectMake(- popupViewWidth - 7, CGRectGetMinY(self.cardPushButton.frame), popupViewWidth, popupViewHeight); }else{ // 横屏 CGFloat viewWidth = CGRectGetWidth(self.bounds); self.cardPushButton.frame = CGRectMake(0, 0, viewWidth, viewWidth); + self.popupView.frame = CGRectMake((CGRectGetWidth(self.frame) - popupViewWidth)/2, - popupViewHeight - 3, popupViewWidth, popupViewHeight); } - [self updatePopupViewLayout]; + [self.popupView setPopupViewDirection:fullScreen ? PLVLCCardPushPopupDirectionTop : PLVLCCardPushPopupDirectionLeft]; self.countdownLabel.frame = CGRectMake(-3, CGRectGetMaxY(self.cardPushButton.frame) + 2, PLVLCCardPushButtonViewWidth + 6, 12); } @@ -132,7 +137,6 @@ - (void)setCardPushButtonViewWithCardInfo:(NSDictionary *)cardDict { self.hidden = NO; self.canOpenCard = NO; self.countdownLabel.hidden = NO; - [self setNeedsLayout]; __weak typeof(self) weakSelf = self; [self startCountdownWithLocalWatchTime:localWatchTime endCallback:^{ [weakSelf countdownEndCallback]; @@ -147,8 +151,9 @@ - (void)setCardPushButtonViewWithCardInfo:(NSDictionary *)cardDict { // 提示弹窗 NSString *countdownMsg = PLV_SafeStringForDictKey(cardDict, @"countdownMsg"); [self.popupView setPopupViewTitle:countdownMsg]; - [self updatePopupViewLayout]; [self showPopupTitleView]; + + [self setNeedsLayout]; } } @@ -199,24 +204,13 @@ - (void)cancelDispatchTimer { } - (void)showPopupTitleView { - self.popupView.hidden = NO; - __weak typeof(self) weakSelf = self; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - weakSelf.popupView.hidden = YES; - }); -} - -- (void)updatePopupViewLayout { - BOOL fullScreen = [UIScreen mainScreen].bounds.size.width > [UIScreen mainScreen].bounds.size.height; - CGSize popupViewSize = [self.popupView.titleLabel sizeThatFits:CGSizeMake(MAXFLOAT, 16)]; - CGFloat popupViewWidth = popupViewSize.width + 16 + (fullScreen ? 0 : 10); - CGFloat popupViewHeight = 34; - if (!fullScreen) { // 竖屏 - self.popupView.frame = CGRectMake(- popupViewWidth - 7, CGRectGetMinY(self.cardPushButton.frame), popupViewWidth, popupViewHeight); - }else{ // 横屏 - self.popupView.frame = CGRectMake((CGRectGetWidth(self.frame) - popupViewWidth)/2, - popupViewHeight - 3, popupViewWidth, popupViewHeight); + if (self.popupView.hidden) { + self.popupView.hidden = NO; + __weak typeof(self) weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.popupView.hidden = YES; + }); } - [self.popupView setPopupViewDirection:fullScreen ? PLVLCCardPushPopupDirectionTop : PLVLCCardPushPopupDirectionLeft]; } #pragma mark - Getter & Setter diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Download/ViewModel/PLVLCDownloadViewModel.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Download/ViewModel/PLVLCDownloadViewModel.m index 9f8841de..e620276c 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Download/ViewModel/PLVLCDownloadViewModel.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Download/ViewModel/PLVLCDownloadViewModel.m @@ -9,7 +9,6 @@ #import "PLVLCDownloadViewModel.h" #import "PLVLCCloudClassViewController.h" #import "PLVRoomDataManager.h" -#import "PLVBugReporter.h" #import "PLVRoomLoginClient.h" @interface PLVLCDownloadViewModel () @@ -205,9 +204,6 @@ - (void)loginRequestWithOfflineVid:(NSString *)vid orRecordFileId:(NSString *)re //push方式的处理,新观看页push进来 [weakSelf.viewProxy.navigationController pushViewController:cloudClassVC animated:YES]; - PLVRoomUser *roomUser = [PLVRoomDataManager sharedManager].roomData.roomUser; - [PLVBugReporter setUserIdentifier:roomUser.viewerId]; - //push方式的处理,kill掉下载列表页 NSMutableArray *vcArray = [NSMutableArray arrayWithArray:cloudClassVC.navigationController.viewControllers]; [vcArray removeObjectAtIndex:vcArray.count - 2]; @@ -217,8 +213,6 @@ - (void)loginRequestWithOfflineVid:(NSString *)vid orRecordFileId:(NSString *)re }else { // model方式的处理,连续dismiss 掉下载页、观看页,然后present新的观看页 - PLVRoomUser *roomUser = [PLVRoomDataManager sharedManager].roomData.roomUser; - [PLVBugReporter setUserIdentifier:roomUser.viewerId]; UIViewController *ingvc = self.viewProxy.navigationController.presentingViewController; [self.viewProxy.navigationController dismissViewControllerAnimated:NO completion:^{ diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/LiveRoom/View/SkinView/PLVLCLiveRoomPlayerSkinView.h b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/LiveRoom/View/SkinView/PLVLCLiveRoomPlayerSkinView.h index c5161fd8..beab294d 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/LiveRoom/View/SkinView/PLVLCLiveRoomPlayerSkinView.h +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/LiveRoom/View/SkinView/PLVLCLiveRoomPlayerSkinView.h @@ -32,6 +32,9 @@ NS_ASSUME_NONNULL_BEGIN /// 弹幕开关按钮是否显示,默认为 NO,只有后台开启了弹幕功能才显示弹幕开关按钮 @property (nonatomic, assign) BOOL danmuButtonShow; +/// 是否需要显示皮肤控件,当通过外部控件 调用 hiddenLiveRoomPlayerSkinView 关闭皮肤时内部会记录此皮肤当前显示状态 +@property (nonatomic, assign) BOOL needShowSkin; + /// 弹幕开关按钮,用于外部读取弹幕开关状态 @property (nonatomic, strong, readonly) UIButton * danmuButton; @@ -41,7 +44,9 @@ NS_ASSUME_NONNULL_BEGIN /// 商品库按钮,用于外部控制是否显示该按钮 @property (nonatomic, strong, readonly) UIButton *commodityButton; -- (void)hiddenLiveRoomPlayerSkinView; +/// 隐藏和显示 直播间播放器皮肤视图 控件 +/// @param isHidden YES 隐藏控件,NO显示控件 +- (void)hiddenLiveRoomPlayerSkinView:(BOOL)isHidden; - (void)displayLikeButtonView:(UIView *)likeButtonView; @@ -51,6 +56,14 @@ NS_ASSUME_NONNULL_BEGIN /// @param show YES显示 NO 不显示 - (void)showCommodityButton:(BOOL)show; +/// 切换聊天室关闭状态,开启/禁用输入框 +/// @param closeRoom YES关闭 NO 不关闭 +- (void)changeCloseRoomStatus:(BOOL)closeRoom; + +/// 切换聊天室专注模式开启/关闭状态,开启/禁用输入框 +/// @param focusMode YES开启 NO 不关闭 +- (void)changeFocusModeStatus:(BOOL)focusMode; + @end @protocol PLVLCLiveRoomPlayerSkinViewDelegate diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/LiveRoom/View/SkinView/PLVLCLiveRoomPlayerSkinView.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/LiveRoom/View/SkinView/PLVLCLiveRoomPlayerSkinView.m index 708b6bad..98f163a4 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/LiveRoom/View/SkinView/PLVLCLiveRoomPlayerSkinView.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/LiveRoom/View/SkinView/PLVLCLiveRoomPlayerSkinView.m @@ -133,8 +133,11 @@ - (void)layoutSubviews{ } #pragma mark - [ Public Methods ] -- (void)hiddenLiveRoomPlayerSkinView { - [self controlsSwitchShowStatusWithAnimation:NO]; +- (void)hiddenLiveRoomPlayerSkinView:(BOOL)isHidden { + if (isHidden) { + self.needShowSkin = self.skinShow; + } + [self controlsSwitchShowStatusWithAnimation:!isHidden]; } - (void)displayLikeButtonView:(UIView *)likeButtonView{ @@ -320,7 +323,22 @@ - (void)refreshGuideChatLabelFrame { CGFloat guideChatLabelOriginX = MAX(middleOriginX,danmuButtonMaxOriginX); self.guideChatLabel.frame = CGRectMake(guideChatLabelOriginX, viewHeight - (bottomPadding - 6) - guideChatLabelHeight, guideChatLabelWidth, guideChatLabelHeight); } +} +/// 切换聊天室关闭状态,开启/禁用输入框 +- (void)changeCloseRoomStatus:(BOOL)closeRoom { + NSString *guideChatLabelText = closeRoom ? @"聊天室已关闭":@"跟大家聊点什么吧~"; + [self.guideChatLabel setText:guideChatLabelText]; + self.guideChatLabel.userInteractionEnabled = !closeRoom; + [self.landscapeInputView showInputView:NO]; +} + +/// 切换聊天室专注模式状态,开启/禁用输入框 +- (void)changeFocusModeStatus:(BOOL)focusMode{ + NSString *guideChatLabelText = focusMode ? @"当前为专注模式,无法发言":@"跟大家聊点什么吧~"; + [self.guideChatLabel setText:guideChatLabelText]; + self.guideChatLabel.userInteractionEnabled = !focusMode; + [self.landscapeInputView showInputView:NO]; } #pragma mark Private Getter @@ -365,7 +383,6 @@ - (UILabel *)guideChatLabel{ UITapGestureRecognizer * guideChatLabelTapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(guideChatLabelTapGestureAction:)]; [_guideChatLabel addGestureRecognizer:guideChatLabelTapGR]; } - } return _guideChatLabel; } diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Media/PLVLCMediaAreaView.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Media/PLVLCMediaAreaView.m index 07f2931a..4a6ecdb3 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Media/PLVLCMediaAreaView.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/Media/PLVLCMediaAreaView.m @@ -143,6 +143,7 @@ @interface PLVLCMediaAreaView () < @property (nonatomic, assign) NSTimeInterval interruptionTime; @property (nonatomic, strong) UILabel *networkQualityMiddleLable; // 网络不佳提示视图 @property (nonatomic, strong) UIView *networkQualityPoorView; // 网络糟糕提示视图 +@property (nonatomic, strong) UILabel *memoryPlayTipLabel; // 记忆播放提示 @end @@ -194,6 +195,7 @@ - (void)layoutSubviews{ self.contentBackgroudView.frame = CGRectMake(0, contentBackgroudViewY, viewWidth, contentBackgroudViewHeight); self.networkQualityMiddleLable.frame = CGRectMake(16, viewHeight - 28 - 36, 219, 28); self.networkQualityPoorView.frame = CGRectMake(viewWidth - 275 - 4, contentBackgroudViewY + 39, 275, 28); + self.memoryPlayTipLabel.frame = CGRectMake(16, CGRectGetMaxY(self.frame) - 44 - 28, 242, 28); } else { // 横屏 CGFloat contentBackgroudViewX = self.limitContentViewInSafeArea ? leftpadding : 0; @@ -201,6 +203,7 @@ - (void)layoutSubviews{ self.contentBackgroudView.frame = CGRectMake(contentBackgroudViewX, 0, contentBackgroudViewWidth, viewHeight); self.networkQualityMiddleLable.frame = CGRectMake(contentBackgroudViewX + 16, viewHeight - 28 - 58, 219, 28); self.networkQualityPoorView.frame = CGRectMake(superviewWidth - 275 - 4 - leftpadding, 50, 275, 28); + self.memoryPlayTipLabel.frame = CGRectMake(contentBackgroudViewX + 16, CGRectGetMaxY(self.frame) - 92 - 28, 242, 28); } [self.danmuView resetFrame:self.contentBackgroudView.frame]; @@ -482,6 +485,8 @@ - (void)setupUI{ [self addSubview:self.retryPlayView]; + [self addSubview:self.memoryPlayTipLabel]; + /// 网络质量提示 [self.skinView.superview addSubview:self.networkQualityMiddleLable]; [self.skinView.superview addSubview:self.networkQualityPoorView]; @@ -725,6 +730,29 @@ - (void)setupWatermark { } } +#pragma mark 记忆播放提示 +- (void)showMemoryPlayTipLabelWithTime:(NSTimeInterval)time { + NSString *playTimeString = [PLVFdUtil secondsToString2:time]; + UIFont *font = [UIFont systemFontOfSize:12]; + NSDictionary *normalAttributes = @{NSFontAttributeName:font, + NSForegroundColorAttributeName:PLV_UIColorFromRGB(@"#FFFFFF")}; + NSDictionary *timeAttributes = @{NSFontAttributeName:font, + NSForegroundColorAttributeName:PLV_UIColorFromRGB(@"#5C9DFF")}; + NSString *textString = [NSString stringWithFormat:@"您上次观看至 %@ ,已为您自动续播", playTimeString]; + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:textString]; + [attributedString addAttributes:normalAttributes range:NSMakeRange(0, attributedString.length)]; + [attributedString addAttributes:timeAttributes range:[textString rangeOfString:playTimeString]]; + self.memoryPlayTipLabel.attributedText = attributedString; + [UIView animateWithDuration:0.5 animations:^{ + self.memoryPlayTipLabel.alpha = 1.0; + } completion:^(BOOL finished) { + __weak typeof(self) weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.memoryPlayTipLabel.alpha = 0; + }); + }]; +} + #pragma mark Getter - (CGFloat)topPaddingBelowiOS11{ /// 仅在 [limitContentViewInSafeArea] 为YES,会使用此值,否则均返回 0 @@ -889,6 +917,18 @@ - (PLVLCDownloadBottomSheet *)downloadSheet { return _downloadSheet; } +- (UILabel *)memoryPlayTipLabel { + if (!_memoryPlayTipLabel) { + _memoryPlayTipLabel = [[UILabel alloc] init]; + _memoryPlayTipLabel.layer.masksToBounds = YES; + _memoryPlayTipLabel.layer.cornerRadius = 14; + _memoryPlayTipLabel.alpha = 0; + _memoryPlayTipLabel.backgroundColor = PLV_UIColorFromRGBA(@"#000000", 0.6); + _memoryPlayTipLabel.textAlignment = NSTextAlignmentCenter; + } + return _memoryPlayTipLabel; +} + - (BOOL)inLinkMic{ if (self.delegate && [self.delegate respondsToSelector:@selector(plvLCMediaAreaViewGetInLinkMic:)]) { return [self.delegate plvLCMediaAreaViewGetInLinkMic:self]; @@ -1210,6 +1250,11 @@ - (void)playerPresenter:(PLVPlayerPresenter *)playerPresenter loadPlayerFailureW /// 播放器 ‘视频大小’ 发生改变 - (void)playerPresenter:(PLVPlayerPresenter *)playerPresenter videoSizeChange:(CGSize)videoSize{ self.canvasView.videoSize = videoSize; + if (self.videoType == PLVChannelVideoType_Playback) { + if (self.currentPlayTime > 0.5) { + [self showMemoryPlayTipLabelWithTime:self.currentPlayTime]; + } + } } /// 播放器 ‘SEI信息’ 发生改变 diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/PLVLCLivePageMenuAreaView.h b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/PLVLCLivePageMenuAreaView.h index 24e6eeae..8dcc6f65 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/PLVLCLivePageMenuAreaView.h +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/PLVLCLivePageMenuAreaView.h @@ -59,6 +59,12 @@ PLVLCLivePageMenuType PLVLCMenuTypeWithMenuTypeString(NSString *menuString); - (void)rollbackProductPageContentView; +/// 切换聊天室关闭状态 +- (void)changeCloseRoomStatus:(BOOL)closeRoom; + +/// 切换聊天室专注模式状态 +- (void)changeFocusMode:(BOOL)focusMode; + - (void)leaveLiveRoom; @end @@ -79,6 +85,10 @@ PLVLCLivePageMenuType PLVLCMenuTypeWithMenuTypeString(NSString *menuString); /// @param linkURL 商品详情的链接url - (void)plvLCLivePageMenuAreaView:(PLVLCLivePageMenuAreaView *)pageMenuAreaView clickProductLinkURL:(NSURL *)linkURL; +/// 关闭商品库视图的回调 +/// @param pageMenuAreaView 菜单视图 +- (void)plvLCLivePageMenuAreaViewCloseProductView:(PLVLCLivePageMenuAreaView *)pageMenuAreaView; + /// 在点击卡片领取按钮或者观看领奖倒计时结束后会执行此回调,需要互动视图打开领取入口 /// @param pageMenuAreaView 菜单视图 /// @param dict 打开视图需要的参数 diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/PLVLCLivePageMenuAreaView.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/PLVLCLivePageMenuAreaView.m index fd46a836..04b6fdbd 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/PLVLCLivePageMenuAreaView.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/PLVLCLivePageMenuAreaView.m @@ -160,6 +160,18 @@ - (BOOL)showCommodityMenu { return commodityMenu; } +- (void)changeCloseRoomStatus:(BOOL)closeRoom { + if (self.chatVctrl) { + [self.chatVctrl changeCloseRoomStatus:closeRoom]; + } +} + +- (void)changeFocusMode:(BOOL)focusMode { + if (self.chatVctrl) { + [self.chatVctrl changeFocusMode:focusMode]; + } +} + #pragma mark - Private Method - (void)updateChannelMenuInfo { @@ -291,6 +303,12 @@ - (void)plvLCClickProductInViewController:(PLVLCBuyViewController *)viewControll } } +- (void)plvLCCloseProductViewInViewController:(PLVLCBuyViewController *)viewController { + if (self.delegate && [self.delegate respondsToSelector:@selector(plvLCLivePageMenuAreaViewCloseProductView:)]) { + [self.delegate plvLCLivePageMenuAreaViewCloseProductView:self]; + } +} + #pragma mark - PLVLCChatViewControllerDelegate - (void)plvLCChatViewController:(PLVLCChatViewController *)chatVC needOpenInteract:(NSDictionary *)dict { diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/buy/PLVLCBuyViewController.h b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/buy/PLVLCBuyViewController.h index 21e912f0..26908b7a 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/buy/PLVLCBuyViewController.h +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/buy/PLVLCBuyViewController.h @@ -19,6 +19,9 @@ NS_ASSUME_NONNULL_BEGIN /// @param linkURL 商品链接url - (void)plvLCClickProductInViewController:(PLVLCBuyViewController *)viewController linkURL:(NSURL *)linkURL; +/// 关闭商品库弹窗页面的回调 +- (void)plvLCCloseProductViewInViewController:(PLVLCBuyViewController *)viewController; + @end /// 边看边买 商品列表 页面 diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/buy/PLVLCBuyViewController.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/buy/PLVLCBuyViewController.m index 8f7bbc61..1f95fef3 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/buy/PLVLCBuyViewController.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Modules/PageMenu/buy/PLVLCBuyViewController.m @@ -35,6 +35,13 @@ - (void)viewDidLoad { [self loadWebView]; } +// 规避左右页面切换时webview布局异常问题 +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self.webView layoutSubviews]; +} + - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; @@ -85,8 +92,8 @@ - (NSDictionary *)getUserInfo { @"pic" : [NSString stringWithFormat:@"%@", roomData.roomUser.viewerAvatar] }; NSDictionary *channelInfo = @{ - @"channelId" : [NSString stringWithFormat:@"%@", [PLVSocketManager sharedManager].roomId], - @"roomId" : [NSString stringWithFormat:@"%@", [PLVSocketManager sharedManager].roomId] + @"channelId" : [NSString stringWithFormat:@"%@", roomData.channelId], + @"roomId" : [NSString stringWithFormat:@"%@", roomData.channelId] }; NSDictionary *sessionDict = @{ @"appId" : [NSString stringWithFormat:@"%@", [PLVLiveVideoConfig sharedInstance].appId], @@ -98,7 +105,7 @@ - (NSDictionary *)getUserInfo { [mutableDict setObject:userInfo forKey:@"userInfo"]; [mutableDict setObject:channelInfo forKey:@"channelInfo"]; [mutableDict addEntriesFromDictionary:sessionDict]; - + return mutableDict; } @@ -163,6 +170,9 @@ - (void)plvProductWebViewBridge:(PLVProductWebViewBridge *)webViewBridge clickPr - (void)tapAction { [self rollbackProductPageContentView]; + if (self.delegate && [self.delegate respondsToSelector:@selector(plvLCCloseProductViewInViewController:)]) { + [self.delegate plvLCCloseProductViewInViewController:self]; + } } @end diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_doc_icon@2x.png b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_doc_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..992c1c1ca4ad53d5391bffe0c006c39ae68bdb94 GIT binary patch literal 967 zcmV;&133JNP)l^xNtD(&GEa+xiEkE#UwF01$LiPE!EtohFpFr0v?qfVmqm_guI(A?^FWR<)AV)i{$G zud_co8Sf8}&RCz(k#sPF=hYiEVAl1IpWd5Qhcfl~+nc~!5GK|@m|tvhjtKy`Do6AUV_rTlcEB+xdPN$Q5hSY@Rh zU;ta0iL@_+AlrAeN(lql4P6swzW;DOtGyJv7EYrgKw^+bQK(VB6_zYSi36NFCSJ^B zpVZsQv2eoz2HHe|U(A7gb}XE*fUqDFW85&t*8y$x4)B0@_?MP#a%%aH(!=mz0&!NM zHu0odzpyb>4IQ#=?FR>F2i;-o**01>CAWC9E)xiSdlm+EblXn!Kw-@Qj;x6te7Dy1 zt%Y+oFv;e%qUGM>-pt$x*uZFNyxY+b??e^_=gj zrm=8b0yxno;`umOX@?o3Eo}3E6e&sKejE(hphZE%1g2;iX3WZsjP($`WdbR-XH7Q- z&JQXE`xMqpAlesBofS19OJU6bQfyNQVm%ophH}8U8Ga%qG0KJuq&3JLiYV05q$Cg) zujaQzr2{2^^vt$)l_4r=Y2cW0CHDkS5*P)l&aadRKHATU3RnRvUY{`~#^zSjCulJWER{@v;Pe600dnetba^3&t{ zu+90Ax%STB`=P}5n!opFpY!ka{hq@2l)LtJsPuED^lPH?Vx05!`Tp4E{HMtHaHRB4 zkn#8W{_FDn=<)rv(D|jt_=~mnfUosUkMYai`^en-tjhT0?)~BH{Kwn+y3_d~Jb{h? z000$qQchC<1HTgP(Y6=~&d98m4F;r0-il~Bx#n4j000CoNklseZTpXurP4{h(n+sUVG-U4+V19cAEIdJEo zr**JnW;^%BH@oGwn;;N`;iPHO2WVyxK}0Hw z=R?(MJ1VgU?4^l1nTGf$D!75ghBxW8}P^eF;{J`!K`r#eSsz>NwOBe3Zraj!3)BC$Jz z#Sj%KSR|-u)fX>OF$aqaDnhV0ArT~ks{VC`9HCmXzpP}(BUIa1!7%9 zD`Vb{)SA;X<|FRO#4tu;!&Up7tKJxiD+t9Q8!}DjUL`$XBkoJZM;)>`op%VCwYEkg zmWmTDtV+}=vX&}BmYq^2c|=$$bU4?rZ=63ndCJuY`_kBK;wi)TpWpz(L5 zFQ*v>}nn=K_V9GYe;1DWC(hMiE;N_!$ZUeBjmI=qIIT!?3wLuf-o3` zuY!Qd1~!5T6Baj@I6gFP^Zu`CnO%8kk?hCL9~1edc@v)2)A#hW_CX7@8gL(oe7;!I zVPsS%S88#a|AL7jvG>!j#>V#QOPnqQU|#p+x%X~}luSQ5FhmJ>^^*}J&6km zm>5zqM*@!-mN*g{378lXkK8!(W8V=?XlnuZ;a6nbIQMHQ5>pd^yOGFs;E4sLXTpa9 zObn?wCgO>uS0a2!z){A@aQ*Y?V^m8i5k6GlofUc5o}2LW{%-RD4yORqpj+2k-K5LS zAaN-J6GIBOJJYF-mpF;{+Uuq-Cvxn)oF>Yh#AhwIaJkRpBjG~{?ybn5m*j^W7s(?{r2|qy}93BQ{Gfb{rLFx%gFGvuHtK8;bmI;?(O^Q>iFK=_R-Gq zy0!1Drt6rI>XeM&VOHEsK-)ge&evh1Lm?4X(KoR;5OPT4>+**-D&+SvBh z()7i_$AKa|}gM8aTZB>7C-Y6Ce>lgn;zvZIRF-r4cWoAd6s-E{;V) zi$G2mRts(~C27?9L?OeeVSke;PAY0F`QKwnz@dpRl~&=tF8AdpI-*CQ0DhhQBB zZfgwsFpx$JHgTY$Jg_9RWCiY%CA+52hQ3%&KOoa zirHz<8}lPYcRDq=eiTt94*=@M-qWU(9$YeY$2X=-=|P`8D^P2i)%4(;kHdBgYRfgH z2X%f@M|mP30BB37W(w7U5MWnCjR*opzZJC!fxx`Q7kWw%kZag_g9rz5XYdT3!83>j tvsf>ixyFK5nH(0(@!!F)=e^2h{sQymQ#3y^QS<-+002ovPDHLkV1fr@*KGg* literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_pdf_icon@3x.png b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_pdf_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4e76b45d116519d5aedb7d474c78bec2c4547837 GIT binary patch literal 1449 zcmV;a1y=frP)?2R_sMF;4~`TIxFWpG1oIC*E1#9G9=hCB-k@0*fJ#8G9=$PDA+P2*fJ#8G9=hF zC)hG1*fS;AG$q(GCEGSA*D@sk|NqxBCHL3V>XnZE`}_X={qeW5-&#-FK{ePmDg5v5 z<8EcyJuuihEdBZT{r2_y>+0fYUi|X$^~}ig!@uyfukWy{?W3LHWLe%d$R^MJz-BCr{Nj>`L=JC3=?x~~QQ%KoA zGWgxw^vA>Prl9DGg5`N|+eJ9~=H&V0;^~lx>5z%$fOq3^X#My1?yIHjpO_$|jE?{S z02p*qPE!ET0|pom3j6KJua>;G6T+yXNblZ?XgN>CSA75g1U^YbK~#9!?c7;+(m)u- zanLF%?(35vI|&73-*+el+gf-1{$EAUWN?BrQ#mAW;syVkbUXcZ(oAN;1(8VPBp!{y zr(QmI6WO=t_uyydt6TfIC(QM;XZy)!$J-k7IM(AkVAllj4+CxgaqSZ zSTJ6P1wT9(UiZ`0+Lt8`?8SkwRWn%0Mh=H{7@X@G+p0R51a26jw@^m`Tc<8~Xh9VT zJT=^XfGQGr#K3&xj2&xGMFMv@_=6o2P(=beBkkiWJ652I1kN*Xij2X6DiZiT^RCc# zU=xZ+;CFliXM={*%sa<5JDx%n2|Q+C4=r~51XU#PjDf{mf&T%jNMJk3#6q7P(ojVL z^Q&vsyJ?jmfv=o%?F@VXWhC&@aJ7Ik5*S_K=AnrMo(Q-KO(gK982AoE+`M=*=pupV zTX#si*^}#Wcm#bU@PNZp=p%v6_YPcuJ`xx;2TqvKM*-(5hSLPvNZ^9O%Wt5I1Qrwd z9J(lATRg?2novgq4-C;q3(82~k#Mzp5DGY(+~c{80{&v`G+aQA1nxNCpK*7xkfMN_ z4p&?xw(>h^ks^WlB(7ocXI9mvA0mLWMF*RMJ*hhNp@`xUhCh*~HG3cmICth+imi?; zo7r}zJor#+}5^rthAHU<6{&3%AJuCq65d^FSz>yu{I#B%C<+VT!(45xwFyezq-%4nQv zzcVyh-r?$YHFBxN>Q&2RcdWNbrF#a?@J#xprTRD9_5Fkj9(#qY&!xZaO8tzq-|k9o z*Y*Z48^hhPUe2MaT-&|Qs~dQM+x6tmjH#DR{qj|AY_EZ{dfC$=vD{_5Qw#sf&NSx> z+uO3ZX%yWv=dy*p^8%Zb-T#^D+)=|2^JcvRN@amTS>*00000NkvXXu0mjf D9Tw7) literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_ppt_icon@2x.png b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_ppt_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f68deff87d1028d414a6ed4b2091e440a265ff7b GIT binary patch literal 603 zcmV-h0;K(kP)sSWE6(P3>4q?^;dmSxoI( zO#f{v_x|Nri8WBu>${q5}d$;I}ts_~42?rB}^Wm*3G`~LX&`{CdE*wy*W$@7|$ z@{Wc1($Dp*rSg)B@O^XcZ)EITPyO`s{qytr(9Q9Md;II^_qnw8vaIx@obPpN?{sOO z+Jb@r000zpQchC<_Uq>1*ghHkA|1mg-7CZ{tC0+tXR?j}00CD?L_t(o!|mD2PJ=)g zhGD1`5!8D4D1~;)`A}->@&2#PIAcjAvJk!ni+Pv);R!<~lMoEUa!vfR`Qww(n`L7_ zmsh>u0^o`!$Ra=%!4|<5F``?i2c(K%ix}73A%mD-cR{uYwg|QeLKo*EDB?{AWQt&m zV2hxMcVrMVC4w!2Dc(zvEP^XKWDpN*aK;9q!LydDPA_qihWTbV=>1)h6G2t7BZFR4 zlR`rFs=SP^d+J&_Kj;aO8oh z98^3okb{f|7HZ${Kwl1O8d!TWK?4u+dJGcSyn3>b1BxtGUL=N|Hrt@>kgU8@CHXu| zZ7vuzV1j-H@W6eVHy8s(VO#m0#2~77(N`o5IpawNKG0+4x p(^!C-Ygjgj*_QepOv-dE;|uw2LgOqi(AfY0002ovPDHLkV1l2tA-n(p literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_ppt_icon@3x.png b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_ppt_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5c7682ccb020d0dfcb0a19b1f0f3a97c1ce9e8d9 GIT binary patch literal 833 zcmeAS@N?(olHy`uVBq!ia0vp^6+k?JgBeJESsTv}q>csngt!9fw-Mg|OS6FF+en|U zalzk`!~Um*|DToqHq!fTgwN;Lzz^{OK-o7y1t2wW-$eWU`~B}ZBp33zkmO}eehISt zzaQTFb?e5L1v5TP==e}m_&vQ~CD11_B|(0{3>R77u9x8DWIC{0MBvYvV=I1bTQq&D z$NPI-8I~=zSw0L5Onja$jv*Dd-rl+CcRN6&HPKpeNx&s1=@(lrU70zjQzb=-S>)G0 z{m{@=k)~5y^&f3j-SYpnZmqc8wP`soSsVj)^5_>Vu#>Ol{!|uvOmTH><>d2gFW3gi ze?D1yz>SYldS@Gx)p^C{%v6!fm7cu8W=(}5L5HV2HAuDfiR2HycKAmCkbBhN=_T{nx3Q^DB*-L9=hz=% zATy>c>^0gF*ygDoX0R{n;PS8!>e>=~=}$Ls-TQOm)$*t(|E7l7h!n5l3Th$$CoSWKB z*#;}cS`0LKUKy-Br!Bo?s`j2GPLV&g9&DA_q<84Fm%tykf<@w*hcab%0aFlzr>mdK II;Vst08166C;$Ke literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_xls_icon@2x.png b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Resource/PLVChatroom.bundle/plvlc_chatroom_file_xls_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9d295c8479b2c37bb9243e92b940ab9fb48315b GIT binary patch literal 784 zcmV+r1MmEaP)XnRXSb zfC!j%|Ns9Lo_!9Rc<}i4{Qdn-wVd_(`C`7OBBF!u`1Zo(-8!w2C#8ns^Y7E`=da$< zpx4Qq*2sy@y&0c>`273l_43y4=*{ZmZ^N%au$8^z+PLA@h0MD%s*NnCiRAR~-tp|* z@av1uzj?>Ba>TH4#IIVqqsHgow&2#N+s>5I!-C4WNwb+;x09s+000woQchC<_T}I{ zEF9_~+SbD+80^HWkqls%X8!;H0qIFZK~z}7?b+#0!ax)S;Gz{56x0L5E=xt00RqAU|Du1g_t?LySI+UhDok&j)FM|izx_;fUpST zT&81y3=z^Iq(!iD8Wq75FCw6{2x$@0BKSE8iI5PlgP^nsX%W&Qq{K5}5a+%KX%W&Q zq(w-J5KT5M4B}QA%u0huz(AVw;wBU9#qow+6&u7bze%ZqP^x-#WKwzTo!bswOReq= zJ{eR#eDVi?=z4#)2lO@)h128fPl3{;?S z6&$eeqDxa@2P_byk}kb1-0w0$EdA28qIXaR7gTLsS{Lqs4Px5M@3K$=AXXZq^}_}6 zz6B?c0cREMxex;JsKGZGkfvpzxF9VYoi7=1Nz3Mk3)0P}TerGQkRA`OdSX8LV8?%` z7Up`UIUqe^E6wduy~Y4(6Ijz~9P>aYu!|I0`rG7yRljhk{>%`hU1S)(1zlwfj;V04 z^u!cgQ{nKoIx+)i6)GGqy--G=ONIAKcTB+VNMS0SFUJQPB`O@f75m+|pc+u&L#3U# zAPQa-B2oICzyB4GfC-p@37CKh`0v1!te3KKvS7xtWkH+%4wmSfv8`Vz1uM+(S8&(> O0000%X$hAzhn=q-2 zC#8q=`uXGY@7?k2%Hmi@U+|g*jt7pHe9iV`)HOx=|000gZuiN+*qmV*w0C`u3)WK&VxP_zC2-;;+_ z&&f=>DIRX$iB)}`sG2E1Jve>4wTdW;iW`M8gz|CiOEhmbOOQ~rgNLMKD=abig zeB=UgE7;hJpVaiMDFlBw1vOY{%AI6&{ZJ4q)Oj#MGyr>OR)`HzKlv7yLhAC^q zl(k{X$}r`kK1><8WF;>cpYwuO@`6iw!3eM`0oH;WTP9;TQf--Ax)V7V&lQsf?CB+U zTuWA6J2PMQpaxrx>gI;;vhQ)DV)E5mbk|%yo5;c#vutb8yebS=yDWP~(PR0?Y0z<& zT|G3Hx>b1?_s%-c&4tbY9^m+vWjl)Q!v~DL9hPk^x)s93%tF>vQtH0f$;(3F;(`t_0jeM zoA*XM+crZ!FXKhHT_vj%H>TmI%Wf&CE#* zOMJlS|Lq>{!xk@a+^R4?V2v=$(QI-FN4&s%g5AGaUf>>^-r0jaqA=GG<~D{c!Z24& z<{p+v!<-XL{Royw!<>xFVHMfye8BhV zqSIyXaOWir4?32gj%7cY7DQq937lp3z(~Sl#|Um$c%JY9H=LV}Q=Xtaz?0;!iWe+8 zTlSR)_}Nl4%Qm?dhp*E`yUHF-h2iOlMb8`^cf#;prf7HBZBe+LE!tgnb0`Q8o6e$H zHdhxhIKD`V9?nrpUkvWsiguUX8A!pCZIhWxd2-jE_3Kst-w6Q@aDW3G-~c-l-~b0W zzyS`hGXV~8fd6B-j9gNV3dkjeXdSs^ogdktno^M;QlXjBX2g%>P)oV6QH-MBiuWKg TO~ #import #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Scenes/PLVLCCloudClassViewController.m b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Scenes/PLVLCCloudClassViewController.m index aaed63d2..19ba999d 100644 --- a/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Scenes/PLVLCCloudClassViewController.m +++ b/PolyvLiveScenesDemo/PLVLiveCloudClassScene/Scenes/PLVLCCloudClassViewController.m @@ -228,7 +228,7 @@ - (void)releaseCurrenrController { - (void)setupUI { self.view.backgroundColor = PLV_UIColorFromRGB(@"#0E141E"); - self.fullScreenDifferent = YES; + self.fullScreenDifferent = YES; //初始化默认值为YES。用于[updateUI]方法中,需要判断该字段的UI更新默认都执行一次 self.currentLandscape = [UIScreen mainScreen].bounds.size.width > [UIScreen mainScreen].bounds.size.height; /// 注意:1. 此处不建议将共同拥有的图层,提炼在 if 判断外,来做“代码简化” @@ -573,6 +573,12 @@ - (PLVCommodityPushView *)pushView { - (PLVCommodityCardDetailView *)cardDetailView { if (!_cardDetailView) { _cardDetailView = [[PLVCommodityCardDetailView alloc] init]; + __weak typeof(self) weakSelf = self; + _cardDetailView.tapActionBlock = ^{ + if (weakSelf.currentLandscape) { + [weakSelf.liveRoomSkinView hiddenLiveRoomPlayerSkinView:!weakSelf.liveRoomSkinView.needShowSkin]; + } + }; } return _cardDetailView; } @@ -732,9 +738,7 @@ - (void)socketMananger_didReceiveMessage:(NSString *)subEvent return; } - if ([subEvent isEqualToString:@"CLOSEROOM"]) { // admin closes or opens the chatroom - [self closeRoomEvent:jsonDict]; - } else if ([subEvent isEqualToString:@"PRODUCT_MESSAGE"]) { + if ([subEvent isEqualToString:@"PRODUCT_MESSAGE"]) { plv_dispatch_main_async_safe(^{ [self productMessageEvent:jsonDict]; }) @@ -743,16 +747,6 @@ - (void)socketMananger_didReceiveMessage:(NSString *)subEvent #pragma mark socket 数据解析 -/// 讲师关闭、打开聊天室 -- (void)closeRoomEvent:(NSDictionary *)jsonDict { - NSDictionary *value = PLV_SafeDictionaryForDictKey(jsonDict, @"value"); - BOOL closeRoom = PLV_SafeBoolForDictKey(value, @"closed"); - NSString *string = closeRoom ? @"聊天室已经关闭" : @"聊天室已经打开"; - plv_dispatch_main_async_safe(^{ - [PLVLCUtils showHUDWithTitle:string detail:@"" view:self.view]; - }) -} - /// 推送商品 - (void)productMessageEvent:(NSDictionary *)jsonDict { NSInteger status = PLV_SafeIntegerForDictKey(jsonDict, @"status"); @@ -799,10 +793,40 @@ - (void)chatroomManager_rewardSuccess:(NSDictionary *)modelDict { } } +- (void)chatroomManager_didLoginRestrict{ + __weak typeof(self)weakSelf = self; + plv_dispatch_main_async_safe(^{ + [PLVLCUtils showHUDWithTitle:nil detail:@"直播间太过火爆了,请稍后再来(2050407)" view:self.view afterDelay:3.0]; + }) + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [weakSelf exitCurrentController]; // 使用weakSelf,不影响self释放内存 + }); +} + - (void)chatroomManager_startCardPush:(BOOL)start pushInfo:(NSDictionary *)pushDict { [self.menuAreaView startCardPush:start cardPushInfo:pushDict]; } +- (void)chatroomManager_closeRoom:(BOOL)closeRoom { + NSString *string = closeRoom ? @"聊天室已经关闭" : @"聊天室已经打开"; + plv_dispatch_main_async_safe(^{ + [PLVLCUtils showHUDWithTitle:string detail:@"" view:self.view]; + [self.liveRoomSkinView changeCloseRoomStatus:closeRoom]; + [self.menuAreaView changeCloseRoomStatus:closeRoom]; + }) +} + +- (void)chatroomManager_focusMode:(BOOL)focusMode { + NSString *string = focusMode ? @"聊天室专注模式已开启" : @"聊天室专注模式已关闭"; + plv_dispatch_main_async_safe(^{ + [PLVLCUtils showHUDWithTitle:string detail:@"" view:self.view]; + [self.liveRoomSkinView changeFocusModeStatus:focusMode]; + [self.menuAreaView changeFocusMode:focusMode]; + [self.chatLandscapeView updateChatTableView]; + }) + +} + #pragma mark PLVLCChatroomPlaybackDelegate - (NSTimeInterval)currentPlaybackTimeForChatroomPlaybackViewModel:(PLVLCChatroomPlaybackViewModel *)viewModel { @@ -1020,7 +1044,7 @@ - (void)plvLCLiveRoomPlayerSkinViewRewardButtonClicked:(PLVLCLiveRoomPlayerSkinV } - (void)plvLCLiveRoomPlayerSkinViewCommodityButtonClicked:(PLVLCLiveRoomPlayerSkinView *)liveRoomPlayerSkinView { - [self.liveRoomSkinView hiddenLiveRoomPlayerSkinView]; + [self.liveRoomSkinView hiddenLiveRoomPlayerSkinView:YES]; // 加载商品库视图 [self.menuAreaView displayProductPageToExternalView:self.view]; } @@ -1167,6 +1191,12 @@ - (void)plvLCLivePageMenuAreaView:(PLVLCLivePageMenuAreaView *)pageMenuAreaView [self plvCommodityPushViewJumpToCommodityDetail:linkURL]; } +- (void)plvLCLivePageMenuAreaViewCloseProductView { + if (self.currentLandscape) { + [self.liveRoomSkinView hiddenLiveRoomPlayerSkinView:!self.liveRoomSkinView.needShowSkin]; + } +} + - (void)plvLCLivePageMenuAreaView:(PLVLCLivePageMenuAreaView *)pageMenuAreaView needOpenInteract:(NSDictionary *)dict { [self.popoverView.interactView openNewPushCardWithDict:dict]; } @@ -1260,7 +1290,7 @@ - (void)plvInteractGenericView:(PLVInteractGenericView *)interactView loadWebVie if (insideLoad) { [self.cardDetailView loadWebviewWithCardURL:url]; if (self.currentLandscape) { - [self.liveRoomSkinView hiddenLiveRoomPlayerSkinView]; + [self.liveRoomSkinView hiddenLiveRoomPlayerSkinView:YES]; [self.cardDetailView showOnView:self.view frame:CGRectMake(self.view.bounds.size.width * 0.6, 0, self.view.bounds.size.width * 0.4, self.view.bounds.size.height)]; } else { [self.cardDetailView showOnView:self.view frame:CGRectMake(0, CGRectGetMinY(self.menuAreaView.frame) + 48, self.menuAreaView.bounds.size.width, self.menuAreaView.bounds.size.height - 48)]; diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVCommodity/PLVCommodityCardDetailView.h b/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVCommodity/PLVCommodityCardDetailView.h index df0b4e04..9b91faf4 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVCommodity/PLVCommodityCardDetailView.h +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVCommodity/PLVCommodityCardDetailView.h @@ -13,6 +13,9 @@ NS_ASSUME_NONNULL_BEGIN /// 当前页面跳转的webview 商品卡片详情视图 @interface PLVCommodityCardDetailView : UIView +/// 商品卡片背景视图点击的回调 +@property (nonatomic, copy) void (^tapActionBlock) (void); + - (void)loadWebviewWithCardURL:(NSURL *)url; - (void)showOnView:(UIView *)superView frame:(CGRect)frame; diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVCommodity/PLVCommodityCardDetailView.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVCommodity/PLVCommodityCardDetailView.m index ea1bcfc0..5c3de1a4 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVCommodity/PLVCommodityCardDetailView.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVCommodity/PLVCommodityCardDetailView.m @@ -130,6 +130,7 @@ - (UIButton *)closeButton { - (void)tapAction { [self hiddenCardDetailView]; + self.tapActionBlock ? self.tapActionBlock() : nil; } - (void)closeButtonAction:(UIButton *)sender { diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVReward/View/PLVGiveRewardGoodsButton.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVReward/View/PLVGiveRewardGoodsButton.m index 135fd70a..c227080e 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVReward/View/PLVGiveRewardGoodsButton.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVReward/View/PLVGiveRewardGoodsButton.m @@ -8,6 +8,7 @@ #import "PLVGiveRewardGoodsButton.h" #import +#import @interface PLVGiveRewardGoodsButton () diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVReward/View/PLVRewardDisplayView.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVReward/View/PLVRewardDisplayView.m index 6da3c34c..0da78ca4 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVReward/View/PLVRewardDisplayView.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/GeneralUI/PLVReward/View/PLVRewardDisplayView.m @@ -8,6 +8,7 @@ #import "PLVRewardDisplayView.h" #import +#import @interface PLVRewardDisplayView () diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/Model/PLVChatModel.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/Model/PLVChatModel.m index 9d3f664f..fa32c8d3 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/Model/PLVChatModel.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/Model/PLVChatModel.m @@ -31,6 +31,9 @@ - (NSString *)msgId { } else if ([messageObject isKindOfClass:[PLVRewardMessage class]]) { PLVRewardMessage *message = (PLVRewardMessage *)messageObject; msgId = message.msgId; + } else if ([messageObject isKindOfClass:[PLVFileMessage class]]) { + PLVFileMessage *message = (PLVFileMessage *)messageObject; + msgId = message.msgId; } return msgId; } @@ -65,6 +68,9 @@ - (NSTimeInterval)time { } else if ([messageObject isKindOfClass:[ PLVImageEmotionMessage class]]) { PLVImageEmotionMessage *message = ( PLVImageEmotionMessage *)messageObject; time = message.time; + } else if ([messageObject isKindOfClass:[PLVFileMessage class]]) { + PLVFileMessage *message = (PLVFileMessage *)messageObject; + time = message.time; } return time; } @@ -84,6 +90,9 @@ - (NSTimeInterval)playbackTime { } else if ([messageObject isKindOfClass:[ PLVImageEmotionMessage class]]) { PLVImageEmotionMessage *message = ( PLVImageEmotionMessage *)messageObject; playbackTime = message.playbackTime; + } else if ([messageObject isKindOfClass:[PLVFileMessage class]]) { + PLVFileMessage *message = (PLVFileMessage *)messageObject; + playbackTime = message.playbackTime; } return playbackTime; } @@ -109,6 +118,9 @@ - (BOOL)isRemindMsg { } else if ([messageObject isKindOfClass:[PLVImageMessage class]]) { PLVImageMessage *message = (PLVImageMessage *)messageObject; source = message.source; + } else if ([messageObject isKindOfClass:[PLVFileMessage class]]) { + PLVFileMessage *message = (PLVFileMessage *)messageObject; + source = message.source; } if ([PLVFdUtil checkStringUseable:source] && diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/PLVChatroomPresenter.h b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/PLVChatroomPresenter.h index 59ce4e51..6878a110 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/PLVChatroomPresenter.h +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/PLVChatroomPresenter.h @@ -76,6 +76,14 @@ NS_ASSUME_NONNULL_BEGIN /// NO:关闭,只允许特殊身份(譬如讲师)发言 - (void)chatroomPresenter_didChangeCloseRoom:(BOOL) closeRoom; +/// 聊天室登录达到并发限制 +- (void)chatroomPresenter_didLoginRestrict; + +/// 聊天室专注模式开启、关闭 +/// @param focusMode 聊天室专注当前状态,YES:只允许特殊身份(譬如讲师)发言; +/// NO:关闭,允许全体人员发言 +- (void)chatroomPresenter_didChangeFocusMode:(BOOL)focusMode; + @end /* diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/PLVChatroomPresenter.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/PLVChatroomPresenter.m index 66479288..73cb475b 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/PLVChatroomPresenter.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Chatroom/PLVChatroomPresenter.m @@ -504,6 +504,8 @@ - (PLVChatModel *)modelWithHistoryDict:(NSDictionary *)dict { return [self modelRewardChatDict:dict]; } else if ([msgType isEqualToString:@"emotion"]) { return [self modelEmotionChatDict:dict]; + } else if ([msgType isEqualToString:@"file"]) { + return [self modelFileChatDict:dict]; } return model; } @@ -514,6 +516,7 @@ - (PLVChatModel *)modelWithHistoryDict:(NSDictionary *)dict { /// 引用消息 @"quote" /// 打赏消息 @"reward" /// 图片表情消息 @"emotion" +/// 文件下载消息 @"file" - (NSString *)messageTypeWithHistoryDict:(NSDictionary *)dict { NSString *msgSource = PLV_SafeStringForDictKey(dict, @"msgSource"); NSString *msgType = PLV_SafeStringForDictKey(dict, @"msgType"); @@ -539,6 +542,9 @@ - (NSString *)messageTypeWithHistoryDict:(NSDictionary *)dict { } else if (msgSource && [msgSource isEqualToString:@"reward"]) { // 打赏消息:红包、礼物 return @"reward"; + } else if (msgSource && + [msgSource isEqualToString:@"file"]) { // 文件下载消息 + return @"file"; } return nil; } @@ -588,6 +594,10 @@ - (PLVChatModel *)modelQuoteChatDict:(NSDictionary *)dict { message.quoteImageSize = CGSizeMake(width, height); } else { message.quoteContent = PLV_SafeStringForDictKey(quoteDict, @"content"); + NSDictionary *fileDict = [PLVDataUtil dictionaryWithJsonString:message.quoteContent]; + if ([PLVFdUtil checkDictionaryUseable:fileDict]) { + message.quoteContent = PLV_SafeStringForDictKey(fileDict, @"name"); + } } PLVChatModel *model = [[PLVChatModel alloc] init]; @@ -688,6 +698,30 @@ - (PLVChatModel *)modelEmotionChatDict:(NSDictionary *)dict { return model; } +- (PLVChatModel *)modelFileChatDict:(NSDictionary *)dict { + NSDictionary *userDict = PLV_SafeDictionaryForDictKey(dict, @"user"); + PLVChatUser *user = [[PLVChatUser alloc] initWithUserInfo:userDict]; + + NSString *msgId = PLV_SafeStringForDictKey(dict, @"id"); + NSTimeInterval time = PLV_SafeIntegerForDictKey(dict, @"time"); + NSString *content = PLV_SafeStringForDictKey(dict, @"content"); + NSDictionary *fileDict = [PLVDataUtil dictionaryWithJsonString:content]; + + PLVFileMessage *message = [[PLVFileMessage alloc] init]; + message.time = time; + message.msgId = msgId; + if ([PLVFdUtil checkDictionaryUseable:fileDict]) { + message.url = PLV_SafeStringForDictKey(fileDict, @"url"); + message.name = PLV_SafeStringForDictKey(fileDict, @"name"); + } + + PLVChatModel *model = [[PLVChatModel alloc] init]; + model.user = user; + model.message = message; + + return model; +} + #pragma mark 获取图片表情数据 ///加载图片表情列表 设置为固定size @@ -795,6 +829,13 @@ - (void)notifyListenerLoadImageEmotionsFailure { } } +- (void)notifyListenerFocusMode:(BOOL)focusMode { + if (self.delegate && + [self.delegate respondsToSelector:@selector(chatroomPresenter_didChangeFocusMode:)]) { + [self.delegate chatroomPresenter_didChangeFocusMode:focusMode]; + } +} + #pragma mark - 更新 RoomData 属性 - (void)updateOnlineCount:(NSInteger)onlineCount { @@ -854,6 +895,13 @@ - (void)socketMananger_didReceiveMessage:(NSString *)subEvent [self rewardMessageEvent:jsonDict]; } else if ([subEvent isEqualToString:@"CLOSEROOM"]) { // admin close chatroom [self closeRoomEvent:jsonDict]; + } else if ([subEvent isEqualToString:@"onSliceID"]) { + NSDictionary *data = PLV_SafeDictionaryForDictKey(jsonDict, @"data"); + NSString *focusSpecialSpeak = PLV_SafeStringForDictKey(data, @"focusSpecialSpeak"); + BOOL focusMode = [focusSpecialSpeak isEqualToString:@"Y"]; + if (focusMode) { + [self notifyListenerFocusMode:focusMode]; + } } } @@ -861,6 +909,10 @@ - (void)socketMananger_didReceiveEvent:(NSString *)event subEvent:(NSString *)su NSDictionary *jsonDict = PLV_SafeDictionaryForValue(object); if ([event isEqualToString:@"emotion"]) {// someone send a image emotion [self imageEmotionMessageEvent:jsonDict]; + } else if ([event isEqualToString:@"focus"]) { + if ([subEvent isEqualToString:@"FOCUS_SPECIAL_SPEAK"]) { + [self focusModeEvent:jsonDict]; + } } } @@ -875,6 +927,14 @@ - (void)loginEvent:(NSDictionary *)data { NSString *userId = PLV_SafeStringForDictKey(user, @"userId"); if (![self isLoginUser:userId]) { [self increaseWatchCount]; // 他人登陆时,观看热度加1 + } else { + PLVRoomData *roomData = [PLVRoomDataManager sharedManager].roomData; + if (roomData.restrictChatEnabled && roomData.maxViewerCount > 0 && onlineCount > roomData.maxViewerCount) { + if (self.delegate && + [self.delegate respondsToSelector:@selector(chatroomPresenter_didLoginRestrict)]) { + [self.delegate chatroomPresenter_didLoginRestrict]; + } + } } } @@ -917,6 +977,7 @@ - (void)speakMessageEvent:(NSDictionary *)data { NSString *content = (NSString *)values.firstObject; NSTimeInterval time = PLV_SafeIntegerForDictKey(data, @"time"); NSString *source = PLV_SafeStringForDictKey(data, @"source"); + NSString *msgSource = PLV_SafeStringForDictKey(data, @"msgSource"); if (quote) { PLVQuoteMessage *message = [[PLVQuoteMessage alloc] init]; message.msgId = msgId; @@ -932,9 +993,26 @@ - (void)speakMessageEvent:(NSDictionary *)data { message.quoteImageSize = CGSizeMake(width, height); } else { message.quoteContent = PLV_SafeStringForDictKey(quote, @"content"); + NSDictionary *fileDict = [PLVDataUtil dictionaryWithJsonString:message.quoteContent]; + if ([PLVFdUtil checkDictionaryUseable:fileDict]) { + message.quoteContent = PLV_SafeStringForDictKey(fileDict, @"name"); + } } model.message = message; [self cachChatModel:model]; + } else if ([msgSource isEqualToString:@"file"]) { + PLVFileMessage *message = [[PLVFileMessage alloc] init]; + NSDictionary *fileDict = [PLVDataUtil dictionaryWithJsonString:content]; + if ([PLVFdUtil checkDictionaryUseable:fileDict]) { + message.url = PLV_SafeStringForDictKey(fileDict, @"url"); + message.name = PLV_SafeStringForDictKey(fileDict, @"name"); + } + + message.msgId = msgId; + message.time = time; + message.source = source; + model.message = message; + [self cachChatModel:model]; } else { PLVSpeakMessage *message = [[PLVSpeakMessage alloc] init]; message.msgId = msgId; @@ -1147,6 +1225,12 @@ - (void)closeRoomEvent:(NSDictionary *)data { } } +- (void)focusModeEvent:(NSDictionary *)data { + NSString *status = PLV_SafeStringForDictKey(data, @"status"); + BOOL focusMode = [status isEqualToString:@"Y"]; + [self notifyListenerFocusMode:focusMode]; +} + #pragma mark socket 数据缓冲 - (void)cachChatModel:(PLVChatModel *)model { diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Document/PLVDocumentView.h b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Document/PLVDocumentView.h index 244a5fd1..0c2240b0 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Document/PLVDocumentView.h +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Document/PLVDocumentView.h @@ -31,6 +31,13 @@ typedef NS_ENUM(NSUInteger, PLVDocumentViewScene) { /// webView加载失败回调 - (void)documentView_webViewLoadFailWithError:(NSError *)error; +/// PPT视图 PPT位置需切换 +/// @note 直播时,收到此回调,表示讲师开播的默认PPT位置,或表示讲师发出切换PPT位置的指令; +/// 回放时,将复现讲师对PPT的位置操作,收到此回调时,外部应根据 pptToMain 值相应切换PPT视图位置。 +/// 推流开播时,收到此回调时,外部应根据 pptToMain 值相应切换PPT视图位置。 +/// @param pptToMain PPT是否需要切换至主窗口 (YES:PPT需要切至主窗口 NO:PPT需要切至小窗,视频需要切至主窗口) +- (void)documentView_changePPTPositionToMain:(BOOL)pptToMain; + #pragma mark 观看场景回调 /// 获取刷新PPT的延迟时间 @@ -38,12 +45,6 @@ typedef NS_ENUM(NSUInteger, PLVDocumentViewScene) { /// @return unsigned int 返回刷新延迟时间 (单位:毫秒) - (unsigned int)documentView_getRefreshDelayTime; -/// PPT视图 PPT位置需切换 -/// @note 直播时,收到此回调,表示讲师开播的默认PPT位置,或表示讲师发出切换PPT位置的指令; -/// 回放时,将复现讲师对PPT的位置操作,收到此回调时,外部应根据 pptToMain 值相应切换PPT视图位置。 -/// @param pptToMain PPT是否需要切换至主窗口 (YES:PPT需要切至主窗口 NO:PPT需要切至小窗,视频需要切至主窗口) -- (void)documentView_changePPTPositionToMain:(BOOL)pptToMain; - /// [回放时] PPT视图需要获取视频播放器的当前播放时间点 /// @return NSTimeInterval 当前播放时间点 (单位:毫秒) - (NSTimeInterval)documentView_getPlayerCurrentTime; diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Document/PLVDocumentView.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Document/PLVDocumentView.m index bc2465ff..a039d5e1 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Document/PLVDocumentView.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Document/PLVDocumentView.m @@ -37,6 +37,7 @@ @interface PLVDocumentView ()< @property (nonatomic, strong) NSDictionary *userInfo; // 登录用户信息 @property (nonatomic, assign) BOOL userInfoHadSeted; // 已设置登录用户信息 @property (nonatomic, assign, readonly) PLVRoomUserType viewerType; +@property (nonatomic, assign, readonly) PLVChannelVideoType videoType; @property (nonatomic, assign, readonly) BOOL liveStatusIsLiving; // 当前直播是否正在进行 /// scene 为 PLVDocumentViewSceneCloudClass 或 PLVDocumentViewSceneEcommerce 的数据 @@ -190,6 +191,10 @@ - (BOOL)liveStatusIsLiving { return [PLVRoomDataManager sharedManager].roomData.liveStatusIsLiving; } +- (PLVChannelVideoType)videoType { + return [PLVRoomDataManager sharedManager].roomData.videoType; +} + #pragma mark - Public Method - (void)setBackgroudImage:(UIImage *)image widthScale:(CGFloat)widthScale { @@ -424,11 +429,13 @@ - (void)socketMananger_didReceiveMessage:(NSString *)subEvent } } + // 讲师也需要监听 "onSliceStart" "onSliceControl" "onSliceDraw" "changeVideoAndPPTPosition" 消息 if ([subEvent isEqualToString:@"onSliceOpen"] || [subEvent isEqualToString:@"onSliceDraw"] || [subEvent isEqualToString:@"onSliceControl"]) { - // 讲师也需要监听 "onSliceStart" "onSliceControl" "onSliceDraw" 消息 [self receiveOnSliceMessageWithjson:jsonString jsonObject:jsonDict]; + } else if ([subEvent isEqualToString:@"changeVideoAndPPTPosition"]) { + [self receiveChangePPTPositionMessageWithjsonObject:jsonDict]; } return; @@ -462,17 +469,13 @@ - (void)socketMananger_didReceiveEvent:(NSString *)event } - (void)receiveChangePPTPositionMessageWithjsonObject:(NSDictionary *)jsonDict { - if (self.scene != PLVDocumentViewSceneCloudClass && - self.scene != PLVDocumentViewSceneEcommerce) { // 目前推流场景不需要用到以下消息监听 - return; - } - - if (self.delegate && - [self.delegate respondsToSelector:@selector(documentView_changePPTPositionToMain:)]) { - BOOL wannaVideoOnMainSite = ((NSNumber *)jsonDict[@"status"]).boolValue; - BOOL pptToMain = !wannaVideoOnMainSite; - self.mainSpeakerPPTOnMain = pptToMain; - [self.delegate documentView_changePPTPositionToMain:pptToMain]; + if (self.videoType != PLVChannelVideoType_Playback) { // 新增条件判断:非直播回放时,才更新PPT位置 + if (self.delegate && [self.delegate respondsToSelector:@selector(documentView_changePPTPositionToMain:)]) { + BOOL wannaVideoOnMainSite = ((NSNumber *)jsonDict[@"status"]).boolValue; + BOOL pptToMain = !wannaVideoOnMainSite; + self.mainSpeakerPPTOnMain = pptToMain; + [self.delegate documentView_changePPTPositionToMain:pptToMain]; + } } } @@ -491,16 +494,17 @@ - (void)receiveOnSliceMessageWithjson:(NSString *)jsonString self.autoId = autoId; self.currPageNum = pageId; [self.jsBridge refreshPPTWithJsonObject:jsonDict delay:0]; - }else{ - if (self.delegate && - [self.delegate respondsToSelector:@selector(documentView_getRefreshDelayTime)]) { - unsigned int delayTime = [self.delegate documentView_getRefreshDelayTime]; - [self.jsBridge refreshPPTWithJsonObject:jsonDict delay:delayTime]; + }else { + if (self.videoType != PLVChannelVideoType_Playback) { // 新增条件判断:非直播回放时,才更新画笔数据 + if ([self.delegate respondsToSelector:@selector(documentView_getRefreshDelayTime)]) { + unsigned int delayTime = [self.delegate documentView_getRefreshDelayTime]; + [self.jsBridge refreshPPTWithJsonObject:jsonDict delay:delayTime]; + } } } BOOL inClass = [jsonDict[@"inClass"] boolValue]; - if (inClass) { + if (inClass && self.videoType != PLVChannelVideoType_Playback) { /// 新增条件判断:非直播回放时,才更新PPT位置 if (self.delegate && [self.delegate respondsToSelector:@selector(documentView_changePPTPositionToMain:)]) { // 从 socket 消息通知获取 ‘PPT与播放器的默认位置’ diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Interact/PLVInteractGenericView.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Interact/PLVInteractGenericView.m index c9ec4f92..c2c1b33d 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Interact/PLVInteractGenericView.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Interact/PLVInteractGenericView.m @@ -196,8 +196,8 @@ - (NSDictionary *)getUserInfo { @"pic" : [NSString stringWithFormat:@"%@", roomData.roomUser.viewerAvatar] }; NSDictionary *channelInfo = @{ - @"channelId" : [NSString stringWithFormat:@"%@", [PLVSocketManager sharedManager].roomId], - @"roomId" : [NSString stringWithFormat:@"%@", [PLVSocketManager sharedManager].roomId] + @"channelId" : [NSString stringWithFormat:@"%@", roomData.channelId], + @"roomId" : [NSString stringWithFormat:@"%@", roomData.channelId] }; NSDictionary *sessionDict = @{ @"appId" : [NSString stringWithFormat:@"%@", [PLVLiveVideoConfig sharedInstance].appId], diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/LinkMic/Models/PLVLinkMicOnlineUser.h b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/LinkMic/Models/PLVLinkMicOnlineUser.h index 746ae074..f8693d67 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/LinkMic/Models/PLVLinkMicOnlineUser.h +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/LinkMic/Models/PLVLinkMicOnlineUser.h @@ -62,6 +62,8 @@ typedef void (^PLVLinkMicOnlineUserWantGrantCupBlock)(PLVLinkMicOnlineUser * onl typedef void (^PLVLinkMicOnlineUserWantAuthUserSpeakerBlock)(PLVLinkMicOnlineUser * onlineUser, BOOL wantAuth); /// [事件] 希望开关该用户的屏幕共享 回调Block typedef void (^PLVLinkMicOnlineUserWantOpenScreenShareBlock)(PLVLinkMicOnlineUser * onlineUser, BOOL wantOpen); +/// [事件] 希望改变该用户PPT是否在主视图 回调Block +typedef void (^PLVLinkMicOnlineUserWantChangePPTToMainBlock)(PLVLinkMicOnlineUser * onlineUser, BOOL wantChange); /// RTC在线用户模型 /// @@ -207,6 +209,13 @@ typedef void (^PLVLinkMicOnlineUserWantOpenScreenShareBlock)(PLVLinkMicOnlineUse /// 将在主线程回调; @property (nonatomic, copy, nullable) PLVLinkMicOnlineUserWantOpenScreenShareBlock wantOpenScreenShareBlock; +/// [事件] 希望改变该用户PPT是否在主视图 回调Block +/// +/// @note 由 [wantChangeUserPPTToMain] 方法直接触发; +/// 将在主线程回调; +@property (nonatomic, copy, nullable) PLVLinkMicOnlineUserWantChangePPTToMainBlock wantChangePPTToMainBlock; + + /// 是否为 本地主讲 (即‘第一画面’;可能是本地点击而成为的主讲) @property (nonatomic, assign) BOOL isLocalMainSpeaker; @@ -443,6 +452,14 @@ typedef void (^PLVLinkMicOnlineUserWantOpenScreenShareBlock)(PLVLinkMicOnlineUse /// @param openScreenShare 是否希望开启该用户的屏幕共享 (YES:开启,NO:关闭) - (void)wantOpenScreenShare:(BOOL)openScreenShare; +/// 希望切换该用户的PPT位置到主视图 +/// +/// @note 不直接改变该模型内部的任何值; +/// 而是作为通知机制,直接触发 [wantChangePPTToMainBlock],由Block实现方去执行相关逻辑 +/// +/// @param pptToMain 是否希望切换该用户的PPT位置到主视图 (YES:在主视图,NO:不切换到主视图) +- (void)wantChangeUserPPTToMain:(BOOL)pptToMain; + #pragma mark 多接收方回调配置 /// 使用 blockKey 添加一个 ’用户模型 即将销毁‘ 回调Block /// diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/LinkMic/Models/PLVLinkMicOnlineUser.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/LinkMic/Models/PLVLinkMicOnlineUser.m index 5633d895..78b2e4ff 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/LinkMic/Models/PLVLinkMicOnlineUser.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/LinkMic/Models/PLVLinkMicOnlineUser.m @@ -492,7 +492,7 @@ - (void)updateUserCurrentSpeakerAuth:(BOOL)isRealMainSpeaker { /// 注: /// _isRealMainSpeaker 默认值为NO, isRealMainSpeaker 值为NO时不需要更新 - if (!_updateUserCurrentSpeakerAuthCallbackBefore && isRealMainSpeaker) { + if (!_updateUserCurrentSpeakerAuthCallbackBefore && needCallBack) { needCallBack = YES; } @@ -620,6 +620,15 @@ - (void)wantOpenScreenShare:(BOOL)openScreenShare { } } +- (void)wantChangeUserPPTToMain:(BOOL)pptToMain { + if (self.wantChangePPTToMainBlock) { + __weak typeof(self) weakSelf = self; + plv_dispatch_main_async_safe(^{ + if (weakSelf) { weakSelf.wantChangePPTToMainBlock(weakSelf, pptToMain);} + }) + } +} + #pragma mark 多接收方回调配置 - (void)addWillDeallocBlock:(PLVLinkMicOnlineUserWillDeallocBlock)strongBlock blockKey:(id)weakBlockKey{ if (!strongBlock) { diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Player/PLVPlayerPresenter.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Player/PLVPlayerPresenter.m index 797651a2..4f98b649 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Player/PLVPlayerPresenter.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Player/PLVPlayerPresenter.m @@ -10,10 +10,13 @@ #import "PLVRoomDataManager.h" #import +#import #import #import #import "PLVPlayerLogoView.h" +static NSString * const kUserDefaultPlaybackLastTimeInfo = @"UserDefaultPlaybackLastTimeInfo"; + @interface PLVPlayerPresenterBackgroundView : UIView /// 仅 PLVPlayerPresenter 内部使用的背景视图类 /// 子视图布局时机回调 @@ -36,6 +39,7 @@ @interface PLVPlayerPresenter ()< @property (nonatomic, assign) BOOL currentNoDelayLiveStart; @property (nonatomic, assign) PLVLivePlayerQuickLiveNetworkQuality networkQuality; @property (nonatomic, assign) NSInteger networkQualityRepeatCount; +@property (nonatomic, assign) BOOL currentLivePlaybackChangingVid; #pragma mark UI /// view hierarchy @@ -272,6 +276,7 @@ - (void)switchLivePlaybackSpeedRate:(CGFloat)toSpeed{ } - (void)changeVid:(NSString *)vid { + self.currentLivePlaybackChangingVid = YES; [self.livePlaybackPlayer changeLivePlaybackVodId:vid]; [self resumePlay]; } @@ -370,6 +375,44 @@ - (void)setupPlayerLogoImage { [self.backgroundView addSubview:self.logoView]; } +- (void)savePlaybackLastTime { + PLVRoomData *roomData = [PLVRoomDataManager sharedManager].roomData; + NSString *viewerId = roomData.roomUser.viewerId; + NSMutableDictionary *infoDict = [[[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultPlaybackLastTimeInfo] mutableCopy]; + NSMutableDictionary *lastTimeDict = [infoDict[viewerId] mutableCopy]; + if (!infoDict) { + infoDict = [NSMutableDictionary dictionary]; + } + if (!lastTimeDict) { + lastTimeDict = [NSMutableDictionary dictionary]; + } + if (roomData.recordEnable && [PLVFdUtil checkStringUseable:roomData.recordFile.fileId]) { + [lastTimeDict setObject:@(self.livePlaybackPlayer.currentPlaybackTime) forKey:roomData.recordFile.fileId]; + } else if ([PLVFdUtil checkStringUseable:roomData.vid]) { + [lastTimeDict setObject:@(self.livePlaybackPlayer.currentPlaybackTime) forKey:roomData.vid]; + } + if ([PLVFdUtil checkStringUseable:viewerId]) { + [infoDict setObject:lastTimeDict forKey:viewerId]; + } + [[NSUserDefaults standardUserDefaults] setObject:infoDict forKey:kUserDefaultPlaybackLastTimeInfo]; +} + +- (void)seekLivePlaybackToLastTime { + PLVRoomData *roomData = [PLVRoomDataManager sharedManager].roomData; + NSString *viewerId = roomData.roomUser.viewerId; + NSMutableDictionary *infoDict = [[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultPlaybackLastTimeInfo]; + NSMutableDictionary *lastTimeDict = infoDict[viewerId]; + NSTimeInterval lastTime = 0; + if (roomData.recordEnable && [PLVFdUtil checkStringUseable:roomData.recordFile.fileId]) { + lastTime = [lastTimeDict[roomData.recordFile.fileId] doubleValue]; + } else if ([PLVFdUtil checkStringUseable:roomData.vid]) { + lastTime = [lastTimeDict[roomData.vid] doubleValue]; + } + if (lastTime != 0 && (self.livePlaybackPlayer.duration - lastTime) > 1) { + [self seekLivePlaybackToTime:lastTime]; + } +} + #pragma mark Getter - (PLVPlayerPresenterBackgroundView *)backgroundView{ if (!_backgroundView) { @@ -499,12 +542,17 @@ - (void)plvPlayer:(PLVPlayer *)player playerSeiDidChanged:(long)timeStamp{ - (void)plvPlayer:(PLVPlayer *)player playerIsPreparedToPlay:(PLVPlayerMainSubType)mainSubType{ [self.activityView stopAnimating]; [self timerEvent:nil]; + self.currentLivePlaybackChangingVid = NO; if (self.keepShowAdvert && self.channelInfo.advertType != PLVChannelAdvertType_None) { self.keepShowAdvert = NO; [self showTitleAdvert]; return; } + + if (self.currentVideoType == PLVChannelVideoType_Playback) { + [self seekLivePlaybackToLastTime]; + } if ([self.delegate respondsToSelector:@selector(playerPresenter:videoSizeChange:)]) { [self.delegate playerPresenter:self videoSizeChange:player.naturalSize]; @@ -801,6 +849,9 @@ - (void)plvLivePlaybackPlayer:(PLVLivePlaybackPlayer *)livePlaybackPlayer downlo if ([self.delegate respondsToSelector:@selector(playerPresenter:downloadProgress:playedProgress:playedTimeString:durationTimeString:)]) { [self.delegate playerPresenter:self downloadProgress:downloadProgress playedProgress:playedProgress playedTimeString:playedTimeString durationTimeString:durationTimeString]; } + if (!self.currentLivePlaybackChangingVid) { + [self savePlaybackLastTime]; + } } /// 直播回放播放器 ‘频道信息’ 发生改变 @@ -834,6 +885,12 @@ - (void)plvAdvertView:(PLVAdvertView *)advertView playStateDidChange:(PLVAdvertV if (!self.keepShowAdvert) { [self.advertView destroyTitleAdvert]; [self resumePlay]; + if (self.currentVideoType == PLVChannelVideoType_Playback) { + [self seekLivePlaybackToLastTime]; + if ([self.delegate respondsToSelector:@selector(playerPresenter:videoSizeChange:)]) { + [self.delegate playerPresenter:self videoSizeChange:self.livePlaybackPlayer.naturalSize]; + } + } } } } diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Room/Model/PLVRoomData.h b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Room/Model/PLVRoomData.h index b116047a..d192efc3 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Room/Model/PLVRoomData.h +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Room/Model/PLVRoomData.h @@ -56,6 +56,10 @@ typedef NS_ENUM (NSInteger, PLVResolutionType) { @property (nonatomic, strong, readonly) PLVViewLogCustomParam *customParam; /// 频道连麦人数,设置为不使用连麦时为0 @property (nonatomic, assign) NSUInteger interactNumLimit; +/// 是否并发限制以聊天室在线人数为准 +@property (nonatomic, assign) BOOL restrictChatEnabled; +/// 最大同时在线人数 +@property (nonatomic, assign) NSUInteger maxViewerCount; #pragma mark 直播独有属性 /// 直播状态 diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Room/Model/PLVRoomData.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Room/Model/PLVRoomData.m index 41d28752..62774716 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Room/Model/PLVRoomData.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Room/Model/PLVRoomData.m @@ -165,6 +165,8 @@ - (void)updateMenuInfo:(PLVLiveVideoChannelMenuInfo *)menuInfo { self.menuInfo = menuInfo; self.likeCount = menuInfo.likes.unsignedIntegerValue; self.watchCount = menuInfo.pageView.unsignedIntegerValue; + self.restrictChatEnabled = menuInfo.restrictChatEnabled; + self.maxViewerCount = menuInfo.maxViewer.unsignedIntegerValue; } #pragma mark Getter & Setter diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Streamer/PLVStreamerPresenter.h b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Streamer/PLVStreamerPresenter.h index eb6055a1..0d423898 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Streamer/PLVStreamerPresenter.h +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Streamer/PLVStreamerPresenter.h @@ -383,6 +383,16 @@ typedef NS_ENUM(NSInteger, PLVStreamerPresenterErrorCode) { /// @param openScreenShare 开启或关闭 屏幕共享 (YES:开启;NO:关闭) - (void)openLocalUserScreenShare:(BOOL)openScreenShare API_AVAILABLE(ios(11.0)); +/// 设置摄像头的变焦倍数 +/// @param zoomRatio zoomRatio 变焦 +- (void)setCameraZoomRatio:(CGFloat)zoomRatio; + +/// 获取摄像头的变焦倍数 +- (CGFloat)getCameraZoomRatio; + +/// 获取摄像头的最大变焦倍数 +- (CGFloat)getMaxCameraZoomRatio; + #pragma mark 连麦事件管理 /// 开启或关闭 ”视频连麦“ /// diff --git a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Streamer/PLVStreamerPresenter.m b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Streamer/PLVStreamerPresenter.m index 2ac9966c..0c4b9e04 100644 --- a/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Streamer/PLVStreamerPresenter.m +++ b/PolyvLiveScenesDemo/PLVLiveCommonModule/Modules/Streamer/PLVStreamerPresenter.m @@ -339,6 +339,33 @@ - (void)openLocalUserScreenShare:(BOOL)openScreenShare API_AVAILABLE(ios(11.0)) [self.rtcStreamerManager switchLocalUserStreamSourceType:streamSourceType]; } +- (void)changeUserPPTPositionToMain:(BOOL)pptToMain { + if (!self.classStarted) { + return; + } + + if (self.viewerType == PLVRoomUserTypeTeacher || (self.viewerType == PLVRoomUserTypeGuest && self.localOnlineUser.isRealMainSpeaker)) { + NSMutableDictionary *messageDict = [NSMutableDictionary dictionary]; + plv_dict_set(messageDict, @"EVENT", PLVSocketLinkMicEventType_changeVideoAndPPTPosition_key); + plv_dict_set(messageDict, @"roomId", self.channelId); + plv_dict_set(messageDict, @"sessionId", self.sessionId); + plv_dict_set(messageDict, @"status", pptToMain ? @(0) : @(1)); + [[PLVSocketManager sharedManager] emitMessage:messageDict]; + } +} + +- (void)setCameraZoomRatio:(CGFloat)zoomRatio { + [self.rtcStreamerManager setCameraZoomRatio:zoomRatio]; +} + +- (CGFloat)getCameraZoomRatio { + return [self.rtcStreamerManager getCameraZoomRatio]; +} + +- (CGFloat)getMaxCameraZoomRatio { + return [self.rtcStreamerManager getMaxCameraZoomRatio]; +} + #pragma mark 连麦事件管理 /// 开启或关闭 ”视频连麦“ - (void)openVideoLinkMic:(BOOL)open emitCompleteBlock:(nullable void (^)(BOOL emitSuccess))emitCompleteBlock{ @@ -946,7 +973,11 @@ - (void)updateMixUserList{ mixUser.inputType = onlineUser.currentCameraOpen ? PLVRTCStreamerMixUserInputType_AudioVideo : PLVRTCStreamerMixUserInputType_Audio; mixUser.streamType = PLVRTCStreamerMixUserStreamType_Camera; } - [mixUserList addObject:mixUser]; + if (onlineUser.isRealMainSpeaker) { + [mixUserList insertObject:mixUser atIndex:0]; + } else { + [mixUserList addObject:mixUser]; + } }else{ continue; } @@ -1059,44 +1090,30 @@ - (void)updateGuestAuthSpeaker:(NSString *)linkMicUserId auth:(BOOL)auth { if (self.arraySafeQueue) { __weak typeof(self) weakSelf = self; dispatch_async(self.arraySafeQueue, ^{ + PLVLinkMicOnlineUser *currentRealMainSpeakerUser = weakSelf.realMainSpeakerUser; // 当前主讲用户 + PLVLinkMicOnlineUser *nextRealMainSpeakerUser; // 下一个将要设置主讲权限的用户 for (int i = 0; i < weakSelf.onlineUserArray.count; i++) { PLVLinkMicOnlineUser *onlineUser = weakSelf.onlineUserArray[i]; if ([onlineUser.linkMicUserId isEqualToString:linkMicUserId]) { - if (auth) { - weakSelf.realMainSpeakerUser = onlineUser; - } else if ([weakSelf.realMainSpeakerUser.linkMicUserId isEqualToString:linkMicUserId]) { - weakSelf.realMainSpeakerUser = nil; - } - - [onlineUser updateUserCurrentSpeakerAuth:auth]; - if (!onlineUser.localUser) { - [weakSelf callbackForLinkMicUser:onlineUser authSpeaker:auth]; - } + nextRealMainSpeakerUser = onlineUser; break; } } - }); - } -} -- (void)authRemoteGuestSpeaker:(PLVLinkMicOnlineUser *)onlineUser auth:(BOOL)auth { - /// 主讲授权时 如果有已授权的主讲 需要先取消授权 然后授权新的嘉宾 主讲权限 - if (auth) { - [self cancelAllGuestSpeakerAuth]; - } - - if ([PLVFdUtil checkStringUseable:onlineUser.userId]) { - [[PLVSocketManager sharedManager] emitPermissionMessageWithUserId:onlineUser.userId type:PLVSocketPermissionTypeSpeaker status:auth]; + // 转移授权时取消上个主讲用户授权 + if (auth && currentRealMainSpeakerUser) { + [currentRealMainSpeakerUser updateUserCurrentSpeakerAuth:NO]; + } - /// 主讲权限的的同时需要 设置第一画面 - [self emitSocketMessge_authFirstSiteUserId:onlineUser.userId auth:auth]; + [nextRealMainSpeakerUser updateUserCurrentSpeakerAuth:auth]; + [weakSelf updateMixUserList]; + }); } } - (void)cancelAllGuestSpeakerAuth { if (self.realMainSpeakerUser && [PLVFdUtil checkStringUseable:self.realMainSpeakerUser.userId]) { - self.realMainSpeakerUser = nil; [[PLVSocketManager sharedManager] emitPermissionMessageWithUserId:self.realMainSpeakerUser.userId type:PLVSocketPermissionTypeSpeaker status:NO]; self.realMainSpeakerUser = nil; } @@ -1144,6 +1161,14 @@ - (PLVLinkMicOnlineUser *)createLocalOnlineUser{ } }; + localOnlineUser.wantChangePPTToMainBlock = ^(PLVLinkMicOnlineUser * _Nonnull onlineUser, BOOL wantChange) { + [weakSelf changeUserPPTPositionToMain:wantChange]; + }; + + localOnlineUser.wantAuthSpeakerBlock = ^(PLVLinkMicOnlineUser * _Nonnull onlineUser, BOOL wantAuth) { + [weakSelf authLinkMicOnlineUser:onlineUser speakerAuth:wantAuth]; + }; + /// 监听 本地用户 状态变化Block [localOnlineUser addMicOpenChangedBlock:^(PLVLinkMicOnlineUser * _Nonnull onlineUser) { [weakSelf callbackForLocalUserMicOpenChanged]; @@ -1398,10 +1423,11 @@ - (BOOL)refreshLinkMicOnlineUserListWithDataDictionary:(NSDictionary *)dataDicti return includeTargetLinkMicUser; } + // 读取当前主位置用户 +// NSString * master = PLV_SafeStringForDictKey(dataDictionary, @"master"); // 读取当前主讲人 - NSString * master = dataDictionary[@"master"]; - master = self.channelId; - + NSString * speaker = PLV_SafeStringForDictKey(dataDictionary, @"speaker"); + // 删除用户 NSArray * currentOnlineUserArray = [self.onlineUserMuArray copy]; for (PLVLinkMicOnlineUser * exsitUser in currentOnlineUserArray) { @@ -1431,7 +1457,7 @@ - (BOOL)refreshLinkMicOnlineUserListWithDataDictionary:(NSDictionary *)dataDicti addToOnlineArray = [self tryAddLinkMicWaitUser:userInfo]; if (addToOnlineArray) { - [self addLinkMicOnlineUser:userInfo mainSpeakerUserId:master]; + [self addLinkMicOnlineUser:userInfo mainSpeakerUserId:speaker]; } if (!includeTargetLinkMicUser) { @@ -1444,7 +1470,7 @@ - (BOOL)refreshLinkMicOnlineUserListWithDataDictionary:(NSDictionary *)dataDicti return includeTargetLinkMicUser; } -- (void)addLinkMicOnlineUser:(NSDictionary *)userInfo mainSpeakerUserId:(NSString *)master{ +- (void)addLinkMicOnlineUser:(NSDictionary *)userInfo mainSpeakerUserId:(NSString *)speaker{ if ([PLVFdUtil checkDictionaryUseable:userInfo]) { PLVLinkMicOnlineUser * onlineUser = [PLVLinkMicOnlineUser modelWithDictionary:userInfo]; @@ -1463,9 +1489,14 @@ - (void)addLinkMicOnlineUser:(NSDictionary *)userInfo mainSpeakerUserId:(NSStrin }; onlineUser.wantAuthSpeakerBlock = ^(PLVLinkMicOnlineUser * _Nonnull onlineUser, BOOL wantAuth) { - [weakSelf authRemoteGuestSpeaker:onlineUser auth:wantAuth]; + [weakSelf authLinkMicOnlineUser:onlineUser speakerAuth:wantAuth]; }; + /// 监听 远端用户 状态变化Block + [onlineUser addCurrentSpeakerAuthChangedBlock:^(PLVLinkMicOnlineUser * _Nonnull onlineUser) { + [weakSelf callbackForLinkMicUser:onlineUser authSpeaker:onlineUser.isRealMainSpeaker]; + } blockKey:self]; + // 若是未上麦嘉宾,则不作添加 if (onlineUser.userType == PLVSocketUserTypeGuest && !onlineUser.currentStatusVoice) { @@ -1483,7 +1514,7 @@ - (void)addLinkMicOnlineUser:(NSDictionary *)userInfo mainSpeakerUserId:(NSStrin } // 设置主讲人标记 - if ([master isEqualToString:onlineUser.linkMicUserId]) { + if ([speaker isEqualToString:onlineUser.linkMicUserId]) { [onlineUser updateUserCurrentSpeakerAuth:YES]; } @@ -1532,6 +1563,10 @@ - (NSComparisonResult)compareLinkMicOnlineUser1:(PLVLinkMicOnlineUser *)user1 us } } +- (void)authLinkMicOnlineUser:(PLVLinkMicOnlineUser *)onlineUser speakerAuth:(BOOL)auth { + [self emitSocketMessge_authOnlineUserId:onlineUser.userId speakerAuth:auth]; +} + #pragma mark LinkMic WaitUser Manage - (BOOL)tryAddLinkMicWaitUser:(NSDictionary *)userInfo{ BOOL addToOnlineArray = YES; @@ -1768,6 +1803,14 @@ - (void)handleSocket_TEACHER_SET_PERMISSION:(NSDictionary *)jsonDict{ } } +- (void)handleSocket_LOGOUT:(NSDictionary *)jsonDict{ + NSString *userId = PLV_SafeStringForDictKey(jsonDict, @"userId"); + /// 主讲用户退出登录时 需要将主讲权限授予讲师 + if ([userId isEqualToString:self.realMainSpeakerUser.userId]) { + [self emitSocketMessge_authOnlineUserId:self.teacherUser.userId speakerAuth:YES]; + } +} + - (void)handleSocket_MUTE_USER_MICRO:(NSDictionary *)jsonDict{ BOOL mute = ((NSNumber *)jsonDict[@"mute"]).boolValue; if (![PLVFdUtil checkStringUseable:jsonDict[@"userId"]]) { /// 全体静音处理 @@ -1815,6 +1858,15 @@ - (void)handleSocket_JOIN_LEAVE:(NSDictionary *)jsonDict{ }]; } +- (void)emitSocketMessge_authOnlineUserId:(NSString *)userId speakerAuth:(BOOL)auth { + if ([PLVFdUtil checkStringUseable:userId]) { + [[PLVSocketManager sharedManager] emitPermissionMessageWithUserId:userId type:PLVSocketPermissionTypeSpeaker status:auth]; + + /// 主讲权限的的同时需要 设置第一画面 + [self emitSocketMessge_authFirstSiteUserId:userId auth:auth]; + } +} + - (void)emitSocketMessge_authFirstSiteUserId:(NSString *)userId auth:(BOOL)auth { /// 在取消主讲授权时 需要 设置讲师为第一画面 NSString *firstSiteUserId = auth ? userId : self.teacherUser.userId; @@ -1907,6 +1959,16 @@ - (void)callbackForLinkMicUser:(PLVLinkMicOnlineUser *)linkMicOnlineUser videoMu } - (void)callbackForLinkMicUser:(PLVLinkMicOnlineUser *)linkMicOnlineUser authSpeaker:(BOOL)authSpeaker{ + if (linkMicOnlineUser.userType == PLVSocketUserTypeTeacher) { + return; + } + + if (authSpeaker) { + _realMainSpeakerUser = linkMicOnlineUser; + } else if ([self.realMainSpeakerUser.userId isEqualToString:linkMicOnlineUser.userId]) { + _realMainSpeakerUser = nil; + } + plv_dispatch_main_async_safe(^{ if ([self.delegate respondsToSelector:@selector(plvStreamerPresenter:linkMicOnlineUser:authSpeaker:)]) { [self.delegate plvStreamerPresenter:self linkMicOnlineUser:linkMicOnlineUser authSpeaker:authSpeaker]; @@ -2139,6 +2201,16 @@ - (PLVChannelClassManager *)channelClassManager{ return _channelClassManager; } +- (PLVLinkMicOnlineUser *)realMainSpeakerUser { + if (!_realMainSpeakerUser) { + NSInteger targetUserIndex = [self findOnlineUserModelIndexWithFiltrateBlock:^BOOL(PLVLinkMicOnlineUser * _Nonnull enumerateUser) { + return enumerateUser.isRealMainSpeaker; + }]; + _realMainSpeakerUser = [self getOnlineUserModelFromOnlineUserArrayWithIndex:targetUserIndex]; + } + return _realMainSpeakerUser; +} + - (PLVLinkMicOnlineUser *)teacherUser { if (!_teacherUser) { NSInteger targetUserIndex = [self findOnlineUserModelIndexWithFiltrateBlock:^BOOL(PLVLinkMicOnlineUser * _Nonnull enumerateUser) { @@ -2190,6 +2262,10 @@ - (void)socketMananger_didReceiveMessage:(NSString *)subEvent [self handleSocket_TEACHER_SET_PERMISSION:jsonDict]; } else if ([subEvent containsString:@"LOGIN"]){ // 登录 + } else if ([subEvent isEqualToString:@"LOGOUT"]) { // 有用户登出 + if (self.viewerType == PLVRoomUserTypeTeacher) { + [self handleSocket_LOGOUT:jsonDict]; + } } else if ([subEvent containsString:@"finishClass"]){ // 下课事件 if (self.viewerType == PLVRoomUserTypeGuest) { self.classStarted = NO; diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Cell/PLVECChatCell.m b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Cell/PLVECChatCell.m index 8da7ccc7..ed9d4f88 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Cell/PLVECChatCell.m +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Cell/PLVECChatCell.m @@ -38,6 +38,10 @@ @interface PLVECChatCell () @property (nonatomic, strong) PLVPhotoBrowser *photoBrowser; +@property (nonatomic, strong) UIView *tapGestureView; + +@property (nonatomic, strong) UIImageView *fileImageView; + @end @implementation PLVECChatCell @@ -52,6 +56,8 @@ - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSStr [self.contentView addSubview:self.bubbleView]; [self.contentView addSubview:self.chatLabel]; [self.contentView addSubview:self.chatImageView]; + [self.contentView addSubview:self.fileImageView]; + [self.contentView addSubview:self.tapGestureView]; } return self; } @@ -63,12 +69,30 @@ - (void)layoutSubviews { CGFloat originX = 8.0; CGFloat originY = 4.0; + CGFloat fileImageWidth = 32; + CGFloat fileImageHeight = 38; + BOOL isFileMessage = self.model.message && [self.model.message isKindOfClass:[PLVFileMessage class]]; // 设置内容文本frame - CGFloat labelWidth = self.cellWidth - originX * 2; + CGFloat labelWidth = isFileMessage ? (self.cellWidth - fileImageWidth - originX * 3) : (self.cellWidth - originX * 2); CGRect chatLabelRect = [self.chatLabel.attributedText boundingRectWithSize:CGSizeMake(labelWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil]; - self.chatLabel.frame = CGRectMake(originX, originY, chatLabelRect.size.width, chatLabelRect.size.height); - originY += chatLabelRect.size.height + 4; + CGFloat chatLabelHeight = ceil(chatLabelRect.size.height) + 8; // 修复可能出现文字显示不全的情况 + CGFloat textViewRealWidth = chatLabelRect.size.width; + // 设置文件下载图标frame + if (isFileMessage) { + if (chatLabelHeight < fileImageHeight) { + self.chatLabel.frame = CGRectMake(originX, (fileImageHeight - chatLabelHeight) / 2 + originY * 2, chatLabelRect.size.width, chatLabelHeight); + self.fileImageView.frame = CGRectMake(originX * 2 + chatLabelRect.size.width, originY * 2, fileImageWidth, fileImageHeight); + } else { + self.chatLabel.frame = CGRectMake(originX, originY , chatLabelRect.size.width, chatLabelHeight); + self.fileImageView.frame = CGRectMake(originX * 2 + chatLabelRect.size.width, (chatLabelHeight - fileImageHeight) / 2 + originY , fileImageWidth, fileImageHeight); + } + originY += MAX(chatLabelRect.size.height,fileImageHeight) + 12; + textViewRealWidth += fileImageWidth + originX; + } else { + self.chatLabel.frame = CGRectMake(originX, 0, chatLabelRect.size.width, chatLabelHeight); + originY += chatLabelHeight + 4; + } // 设置图片frame,如果有的话 CGSize imageViewSize = CGSizeZero; @@ -80,8 +104,13 @@ - (void)layoutSubviews { originY += imageViewSize.height + 4; } - CGFloat bubbleWidth = MIN((MAX(imageViewSize.width, chatLabelRect.size.width) + originX * 2), self.cellWidth); + CGFloat bubbleWidth = MIN((MAX(imageViewSize.width, textViewRealWidth) + originX * 2), self.cellWidth); self.bubbleView.frame = CGRectMake(0, 0, bubbleWidth, originY); + + // 设置文件下载手势视图 + if (isFileMessage) { + self.tapGestureView.frame = CGRectMake(0, 0, bubbleWidth, originY); + } } #pragma mark - Getter @@ -121,6 +150,29 @@ - (UIImageView *)chatImageView { return _chatImageView; } +- (UIImageView *)fileImageView { + if (!_fileImageView) { + _fileImageView = [[UIImageView alloc] init]; + _fileImageView.layer.masksToBounds = YES; + _fileImageView.userInteractionEnabled = NO; + _fileImageView.hidden = YES; + _fileImageView.contentMode = UIViewContentModeScaleAspectFill; + } + return _fileImageView; +} + +- (UIView *)tapGestureView { + if (!_tapGestureView) { + _tapGestureView = [[UIView alloc] init]; + _tapGestureView.backgroundColor = [UIColor clearColor]; + _tapGestureView.userInteractionEnabled = YES; + _tapGestureView.hidden = YES; + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureViewAction)]; + [_tapGestureView addGestureRecognizer:tap]; + } + return _tapGestureView; +} + #pragma mark - Action - (void)tapGestureAction { @@ -136,6 +188,8 @@ - (void)updateWithModel:(PLVChatModel *)model cellWidth:(CGFloat)cellWidth { self.chatImageView.hidden = YES; self.chatLabel.text = @""; self.bubbleView.hidden = YES; + self.tapGestureView.hidden = YES; + self.fileImageView.hidden = YES; return; } @@ -151,6 +205,15 @@ - (void)updateWithModel:(PLVChatModel *)model cellWidth:(CGFloat)cellWidth { [PLVECUtils setImageView:self.chatImageView url:imageURL placeholderImage:placeholderImage options:SDWebImageRetryFailed]; } + UIImage *fileImage = [PLVECChatCell fileImageWithMessage:model.message]; + self.tapGestureView.hidden = !fileImage; + self.fileImageView.hidden = !fileImage; + if (fileImage) { + [self.fileImageView setImage:fileImage]; + } + self.chatLabel.numberOfLines = !fileImage ? 0 : 3; + self.chatLabel.lineBreakMode = !fileImage ? NSLineBreakByTruncatingTail : NSLineBreakByTruncatingMiddle; + // 设置 "昵称:文本(如果有的话)" NSAttributedString *chatLabelString = [PLVECChatCell chatLabelAttributedStringWithModel:model]; self.chatLabel.attributedText = chatLabelString; @@ -261,7 +324,8 @@ + (NSAttributedString *)contentAttributedStringWithChatModel:(PLVChatModel *)cha if (!message && ![message isKindOfClass:[PLVCustomMessage class]] && ![message isKindOfClass:[PLVSpeakMessage class]] && - ![message isKindOfClass:[PLVQuoteMessage class]]) { + ![message isKindOfClass:[PLVQuoteMessage class]] && + ![message isKindOfClass:[PLVFileMessage class]]) { return nil; } @@ -304,6 +368,9 @@ + (NSAttributedString *)contentAttributedStringWithChatModel:(PLVChatModel *)cha } else if([message isKindOfClass:[PLVQuoteMessage class]]){ PLVQuoteMessage *quoteMessage = (PLVQuoteMessage *)message; content = quoteMessage.content; + } else if ([message isKindOfClass:[PLVFileMessage class]]) { + PLVFileMessage *fileMessage = (PLVFileMessage *)message; + content = fileMessage.name; } if (!content || ![content isKindOfClass:[NSString class]] || content.length == 0) { @@ -342,6 +409,24 @@ + (NSURL *)chatImageURLWithMessage:(id)message { return nil; } +/// 获取文件类型图标 ++ (UIImage *)fileImageWithMessage:(id)message { + if ([message isKindOfClass:[PLVFileMessage class]]) { + PLVFileMessage *fileMessage = (PLVFileMessage *)message; + NSString *fileUrl = fileMessage.url; + + if (![PLVFdUtil checkStringUseable:fileUrl]) { + return nil; + } + + NSString *fileType = [[[fileUrl pathExtension] lowercaseString] substringToIndex:3]; + NSString *fileImageString = [NSString stringWithFormat:@"plvec_chatroom_file_%@_icon",fileType]; + + return [PLVECUtils imageForWatchResource:fileImageString]; + } + return nil; +} + #pragma mark - 高度、尺寸计算 /// 计算图片显示宽高 @@ -368,14 +453,27 @@ + (CGFloat)cellHeightWithModel:(PLVChatModel *)model cellWidth:(CGFloat)cellWidt CGFloat originX = 8.0; CGFloat bubbleHeight = 4.0; + CGFloat fileImageWidth = 32; + CGFloat fileImageHeight = 38; // 内容文本高度 NSAttributedString *chatLabelString = [PLVECChatCell chatLabelAttributedStringWithModel:model]; CGRect chatLabelRect = CGRectZero; if (chatLabelString) { - CGFloat labelWidth = cellWidth - originX * 2; - chatLabelRect = [chatLabelString boundingRectWithSize:CGSizeMake(labelWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil]; - bubbleHeight += chatLabelRect.size.height + 4; + BOOL isFileMessage = model.message && [model.message isKindOfClass:[PLVFileMessage class]]; + CGFloat labelWidth = isFileMessage ? (cellWidth - fileImageWidth - originX * 3) : (cellWidth - originX * 2); + CGFloat chatLabelHeight; + if (isFileMessage) { + UILabel *label = [[UILabel alloc]init]; + label.numberOfLines = 3; + label.attributedText = chatLabelString; + chatLabelRect = [label.attributedText boundingRectWithSize:CGSizeMake(labelWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil];; + } else { + chatLabelRect = [chatLabelString boundingRectWithSize:CGSizeMake(labelWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil]; + } + chatLabelHeight = ceil(chatLabelRect.size.height) + 12; + + bubbleHeight += isFileMessage ? (MAX(chatLabelHeight, fileImageHeight + 12)) : (chatLabelHeight); } // 聊天图片高度 @@ -394,6 +492,18 @@ + (CGFloat)cellHeightWithModel:(PLVChatModel *)model cellWidth:(CGFloat)cellWidt return bubbleHeight + 4; } +#pragma mark - Action + +- (void)tapGestureViewAction { + if ([self.model.message isKindOfClass:[PLVFileMessage class]]) { + PLVFileMessage *fileMessage = (PLVFileMessage *)self.model.message; + NSString *url = fileMessage.url; + if ([PLVFdUtil checkStringUseable:url]) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; + } + } +} + #pragma mark - Utils /// 判断model是否为有效类型 @@ -409,7 +519,8 @@ + (BOOL)isModelValid:(PLVChatModel *)model { ![message isKindOfClass:[PLVQuoteMessage class]] && ![message isKindOfClass:[PLVImageMessage class]] && ![message isKindOfClass:[PLVImageEmotionMessage class]] && - ![message isKindOfClass:[PLVCustomMessage class]])) { + ![message isKindOfClass:[PLVCustomMessage class]] && + ![message isKindOfClass:[PLVFileMessage class]])) { return NO; } diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/PLVECChatroomViewModel.h b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/PLVECChatroomViewModel.h index 271dba06..c538b360 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/PLVECChatroomViewModel.h +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/PLVECChatroomViewModel.h @@ -56,6 +56,17 @@ NS_ASSUME_NONNULL_BEGIN /// @param pointUnit 打赏单位 - (void)chatroomManager_loadRewardEnable:(BOOL)rewardEnable payWay:(NSString * _Nullable)payWay rewardModelArray:(NSArray *_Nullable)modelArray pointUnit:(NSString * _Nullable)pointUnit; +/// 聊天室登录达到并发限制时触发 +- (void)chatroomManager_didLoginRestrict; + +/// 聊天室是否开启关闭时触发 +/// @param closeRoom 是否关闭聊天室,YES-关闭,NO-开启 +- (void)chatroomManager_closeRoom:(BOOL)closeRoom; + +/// 聊天室专注模式是否开启关闭时触发 +/// @param focusMode 是否关闭聊天室,YES-关闭,NO-开启 +- (void)chatroomManager_focusMode:(BOOL)focusMode; + @end /* @@ -71,7 +82,10 @@ NS_ASSUME_NONNULL_BEGIN /// 聊天室common层presenter,一个scene层只能初始化一个presenter对象 @property (nonatomic, strong, readonly) PLVChatroomPresenter *presenter; -/// 全部消息数组 +/// 是否打开【只看讲师】开关 +@property (nonatomic, assign) BOOL onlyTeacher; + +/// 公聊消息数组 @property (nonatomic, strong, readonly) NSMutableArray *chatArray; /// 礼物打赏开关 diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/PLVECChatroomViewModel.m b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/PLVECChatroomViewModel.m index 6a3c7390..6fdfdaba 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/PLVECChatroomViewModel.m +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/PLVECChatroomViewModel.m @@ -31,13 +31,15 @@ @interface PLVECChatroomViewModel ()< #pragma mark 数据数组 /// 公聊全部消息数组 -@property (nonatomic, strong) NSMutableArray *chatArray; +@property (nonatomic, strong) NSMutableArray *publicChatArray; +/// 公聊【只看教师】消息数组 +@property (nonatomic, strong) NSMutableArray *partOfPublicChatArray; @end @implementation PLVECChatroomViewModel { /// 操作数组的信号量,防止多线程读写数组 - dispatch_semaphore_t _chatArrayLock; + dispatch_semaphore_t _publicChatArrayLock; dispatch_semaphore_t _loginArrayLock; /// PLVSocketManager回调的执行队列 @@ -65,11 +67,12 @@ - (instancetype)init { - (void)setup { // 初始化信号量 - _chatArrayLock = dispatch_semaphore_create(1); + _publicChatArrayLock = dispatch_semaphore_create(1); _loginArrayLock = dispatch_semaphore_create(1); // 初始化消息数组,预设初始容量 - self.chatArray = [[NSMutableArray alloc] initWithCapacity:500]; + self.publicChatArray = [NSMutableArray arrayWithCapacity:500]; + self.partOfPublicChatArray = [NSMutableArray arrayWithCapacity:100]; // 初始化聊天室Presenter并设置delegate self.presenter = [[PLVChatroomPresenter alloc] initWithLoadingHistoryCount:10 childRoomAllow:YES]; @@ -97,6 +100,8 @@ - (void)clear { [self.loginTimer invalidate]; self.loginTimer = nil; [self removeAllPublicChatModels]; + + self.onlyTeacher = NO; } #pragma mark - 加载消息 @@ -141,27 +146,38 @@ - (void)sendLike { #pragma mark - 消息数组 +- (NSMutableArray *)chatArray { + if (self.onlyTeacher) { + return self.partOfPublicChatArray; + } else { + return self.publicChatArray; + } +} + /// 本地发送公聊消息时 - (void)addPublicChatModel:(PLVChatModel *)model { if (!model || ![model isKindOfClass:[PLVChatModel class]]) { return; } - dispatch_semaphore_wait(_chatArrayLock, DISPATCH_TIME_FOREVER); - [self.chatArray addObject:model]; - dispatch_semaphore_signal(_chatArrayLock); + dispatch_semaphore_wait(_publicChatArrayLock, DISPATCH_TIME_FOREVER); + [self.publicChatArray addObject:model]; + dispatch_semaphore_signal(_publicChatArrayLock); [self notifyListenerDidSendMessage]; } /// 接收到socket的公聊消息时 - (void)addPublicChatModels:(NSArray *)modelArray { - dispatch_semaphore_wait(_chatArrayLock, DISPATCH_TIME_FOREVER); + dispatch_semaphore_wait(_publicChatArrayLock, DISPATCH_TIME_FOREVER); for (PLVChatModel *model in modelArray) { if ([model isKindOfClass:[PLVChatModel class]]) { - [self.chatArray addObject:model]; + [self.publicChatArray addObject:model]; + if (model.user.specialIdentity) { + [self.partOfPublicChatArray addObject:model]; + } } } - dispatch_semaphore_signal(_chatArrayLock); + dispatch_semaphore_signal(_publicChatArrayLock); [self notifyListenerDidReceiveMessages]; } @@ -171,39 +187,45 @@ - (void)deletePublicChatModelWithMsgId:(NSString *)msgId { if (!msgId || ![msgId isKindOfClass:[NSString class]] || msgId.length == 0) { return; } - dispatch_semaphore_wait(_chatArrayLock, DISPATCH_TIME_FOREVER); + dispatch_semaphore_wait(_publicChatArrayLock, DISPATCH_TIME_FOREVER); NSArray *tempChatArray = [self.chatArray copy]; for (PLVChatModel *model in tempChatArray) { NSString *modelMsgId = [model msgId]; if (modelMsgId && [modelMsgId isEqualToString:msgId]) { - [self.chatArray removeObject:model]; + [self.publicChatArray removeObject:model]; + [self.partOfPublicChatArray removeObject:model]; break; } } - dispatch_semaphore_signal(_chatArrayLock); + dispatch_semaphore_signal(_publicChatArrayLock); [self notifyListenerDidMessageDeleted]; } /// 接收到socket删除所有公聊消息的通知时、调用销毁接口时 - (void)removeAllPublicChatModels { - dispatch_semaphore_wait(_chatArrayLock, DISPATCH_TIME_FOREVER); - [self.chatArray removeAllObjects]; - dispatch_semaphore_signal(_chatArrayLock); + dispatch_semaphore_wait(_publicChatArrayLock, DISPATCH_TIME_FOREVER); + [self.publicChatArray removeAllObjects]; + [self.partOfPublicChatArray removeAllObjects]; + dispatch_semaphore_signal(_publicChatArrayLock); [self notifyListenerDidMessageDeleted]; } /// 历史聊天记录接口返回消息数组时 - (void)insertChatModels:(NSArray *)modelArray noMore:(BOOL)noMore { - dispatch_semaphore_wait(_chatArrayLock, DISPATCH_TIME_FOREVER); + dispatch_semaphore_wait(_publicChatArrayLock, DISPATCH_TIME_FOREVER); for (PLVChatModel *model in modelArray) { if ([model isKindOfClass:[PLVChatModel class]]) { - [self.chatArray insertObject:model atIndex:0]; + [self.publicChatArray insertObject:model atIndex:0]; + PLVChatUser *user = model.user; + if (user.specialIdentity) { + [self.partOfPublicChatArray insertObject:model atIndex:0]; + } } } - BOOL first = ([self.chatArray count] <= [modelArray count]); - dispatch_semaphore_signal(_chatArrayLock); + BOOL first = ([self.publicChatArray count] <= [modelArray count]); + dispatch_semaphore_signal(_publicChatArrayLock); [self notifyListenerLoadHistorySuccess:noMore firstTime:first]; } @@ -258,6 +280,30 @@ - (void)notifyListenerRewardSuccess:(NSDictionary *)modelDict { } } +- (void)notifyListenerDidLoginRestrict { + if (self.delegate && [self.delegate respondsToSelector:@selector(chatroomManager_didLoginRestrict)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate chatroomManager_didLoginRestrict]; + }); + } +} + +- (void)notifyListenerCloseRoom:(BOOL)closeRoom { + if (self.delegate && [self.delegate respondsToSelector:@selector(chatroomManager_closeRoom:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate chatroomManager_closeRoom:closeRoom]; + }); + } +} + +- (void)notifyListenerFocusMode:(BOOL)focusMode { + if (self.delegate && [self.delegate respondsToSelector:@selector(chatroomManager_focusMode:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate chatroomManager_focusMode:focusMode]; + }); + } +} + #pragma mark - 加载打赏开关 - (void)loadRewardEnable { __weak typeof(self) weakSelf = self; @@ -417,6 +463,18 @@ - (void)chatroomPresenter_didAllMessageDeleted { [self removeAllPublicChatModels]; } +- (void)chatroomPresenter_didLoginRestrict { + [self notifyListenerDidLoginRestrict]; +} + +- (void)chatroomPresenter_didChangeCloseRoom:(BOOL)closeRoom { + [self notifyListenerCloseRoom:closeRoom]; +} + +- (void)chatroomPresenter_didChangeFocusMode:(BOOL)focusMode { + [self notifyListenerFocusMode:focusMode]; +} + #pragma mark - Utils - (BOOL)isLoginUser:(NSString *)userId { diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECChatroomView.h b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECChatroomView.h index ca02199e..a439a8da 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECChatroomView.h +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECChatroomView.h @@ -23,6 +23,9 @@ NS_ASSUME_NONNULL_BEGIN - (NSTimeInterval)chatroomView_currentPlaybackTime; +/// 聊天室登录达到并发限制时触发 +- (void)chatroomView_didLoginRestrict; + @end /// 聊天室视图 diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECChatroomView.m b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECChatroomView.m index 88b487f5..d12865ab 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECChatroomView.m +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECChatroomView.m @@ -50,6 +50,7 @@ @interface PLVECChatroomView () < @property (nonatomic, assign) BOOL observingTableView; @property (nonatomic, strong) UIView *textAreaView; +@property (nonatomic, strong) UILabel *placeholderLB; @property (nonatomic, strong) UIView *tapView; @property (nonatomic, strong) UITextView *textView; @@ -78,6 +79,8 @@ - (instancetype)init { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; } else { + [[PLVECChatroomViewModel sharedViewModel] setup]; + [[PLVRoomDataManager sharedManager] addDelegate:self delegateQueue:dispatch_get_main_queue()]; PLVRoomData *roomData = [PLVRoomDataManager sharedManager].roomData; @@ -153,11 +156,11 @@ - (instancetype)initWithFrame:(CGRect)frame leftImgView.image = [PLVECUtils imageForWatchResource:@"plv_chat_img"]; [self.textAreaView addSubview:leftImgView]; - UILabel *placeholderLB = [[UILabel alloc] initWithFrame:CGRectMake(30, 9, 130, 14)]; - placeholderLB.text = @"跟大家聊点什么吧~"; - placeholderLB.font = [UIFont systemFontOfSize:14]; - placeholderLB.textColor = [UIColor colorWithWhite:1.0 alpha:0.6]; - [self.textAreaView addSubview:placeholderLB]; + self.placeholderLB= [[UILabel alloc] initWithFrame:CGRectMake(30, 9, 130, 14)]; + self.placeholderLB.text = @"跟大家聊点什么吧~"; + self.placeholderLB.font = [UIFont systemFontOfSize:14]; + self.placeholderLB.textColor = [UIColor colorWithWhite:1.0 alpha:0.6]; + [self.textAreaView addSubview:self.placeholderLB]; } } return self; @@ -416,6 +419,40 @@ - (void)chatroomManager_loadRewardEnable:(BOOL)rewardEnable payWay:(NSString * _ } } +- (void)chatroomManager_didLoginRestrict { + if (self.delegate && [self.delegate respondsToSelector:@selector(chatroomView_didLoginRestrict)]) { + [self.delegate chatroomView_didLoginRestrict]; + } +} + +- (void)chatroomManager_closeRoom:(BOOL)closeRoom { + if (self.videoType != PLVChannelVideoType_Live) { + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ + self.placeholderLB.text = closeRoom ? @"聊天室已关闭" : @"跟大家聊点什么吧~"; + for (UIGestureRecognizer *gestureRecognizer in self.textAreaView.gestureRecognizers) { + gestureRecognizer.enabled = !closeRoom; + [self tapViewAction]; + } + }); +} + +- (void)chatroomManager_focusMode:(BOOL)focusMode { + if (self.videoType != PLVChannelVideoType_Live) { + return; + } + [PLVECChatroomViewModel sharedViewModel].onlyTeacher = focusMode; + [self.tableView reloadData]; + dispatch_async(dispatch_get_main_queue(), ^{ + self.placeholderLB.text = focusMode ? @"聊天室专注模式已开启" : @"跟大家聊点什么吧~"; + for (UIGestureRecognizer *gestureRecognizer in self.textAreaView.gestureRecognizers) { + gestureRecognizer.enabled = !focusMode; + [self tapViewAction]; + } + }); +} + #pragma mark - PLVECChatroomPlaybackViewModelDelegate - (void)clearMessageForPlaybackViewModel:(PLVECChatroomPlaybackViewModel *)viewModel { diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECNewMessageView.m b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECNewMessageView.m index 954637b5..358aad04 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECNewMessageView.m +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Chatroom/Views/PLVECNewMessageView.m @@ -7,7 +7,7 @@ // #import "PLVECNewMessageView.h" -#import "PLVLCUtils.h" +#import "PLVECUtils.h" #import @interface PLVECNewMessageView () diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/LinkMic/View/CanvasView/PLVECLinkMicCanvasView.h b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/LinkMic/View/CanvasView/PLVECLinkMicCanvasView.h index 77b28327..b15f1b53 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/LinkMic/View/CanvasView/PLVECLinkMicCanvasView.h +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/LinkMic/View/CanvasView/PLVECLinkMicCanvasView.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN /// 直播带货场景下 PLVLinkMicOnlineUser 的 rtcview 的容器 /// /// @note 负责承载 PLVLinkMicOnlineUser 的 rtcview;并负责直播带货场景的UI业务; -/// PLVLCLinkMicCanvasView 应仅负责承载,可通过调用 [addRTCView:] 添加 rtcview; +/// PLVECLinkMicCanvasView 应仅负责承载,可通过调用 [addRTCView:] 添加 rtcview; @interface PLVECLinkMicCanvasView : UIView #pragma mark - [ 方法 ] diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Player/Controller/PLVECPlayerViewController.m b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Player/Controller/PLVECPlayerViewController.m index 98edb6a2..284089a2 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Player/Controller/PLVECPlayerViewController.m +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Modules/Player/Controller/PLVECPlayerViewController.m @@ -45,6 +45,7 @@ @interface PLVECPlayerViewController ()< @property (nonatomic, strong) UIButton * playButton; // 播放器暂停、播放按钮 @property (nonatomic, strong) PLVLivePictureInPicturePlaceholderView *pictureInPicturePlaceholderView; // 画中画占位图 @property (nonatomic, strong) PLVMarqueeView * marqueeView; // 跑马灯 (用于显示 ‘用户昵称’,规避非法录屏) +@property (nonatomic, strong) UILabel *memoryPlayTipLabel; // 记忆播放提示 #pragma mark 基本数据 @property (nonatomic, assign) CGRect displayRect; // 播放器区域rect @@ -83,6 +84,7 @@ - (void)viewDidLoad { [self.view addSubview:self.audioAnimalView]; [self.view addSubview:self.playButton]; [self.view addSubview:self.pictureInPicturePlaceholderView]; + [self.view addSubview:self.memoryPlayTipLabel]; [self.playerPresenter setupPlayerWithDisplayView:self.displayView]; if (!self.marqueeView.superview) { @@ -120,6 +122,8 @@ - (void)viewWillLayoutSubviews { // 设置画中画占位图 self.pictureInPicturePlaceholderView.frame = self.contentBackgroudView.frame; + + self.memoryPlayTipLabel.frame = CGRectMake(16, CGRectGetMaxY(self.contentBackgroudView.frame) - 28 - 30, 242, 28); } - (CGRect)getDisplayViewRect { @@ -248,6 +252,29 @@ - (void)setupWatermark { } } +#pragma mark 记忆播放提示 +- (void)showMemoryPlayTipLabelWithTime:(NSTimeInterval)time { + NSString *playTimeString = [PLVFdUtil secondsToString2:time]; + UIFont *font = [UIFont systemFontOfSize:12]; + NSDictionary *normalAttributes = @{NSFontAttributeName:font, + NSForegroundColorAttributeName:PLV_UIColorFromRGB(@"#FFFFFF")}; + NSDictionary *timeAttributes = @{NSFontAttributeName:font, + NSForegroundColorAttributeName:PLV_UIColorFromRGB(@"#5C9DFF")}; + NSString *textString = [NSString stringWithFormat:@"您上次观看至 %@ ,已为您自动续播", playTimeString]; + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:textString]; + [attributedString addAttributes:normalAttributes range:NSMakeRange(0, attributedString.length)]; + [attributedString addAttributes:timeAttributes range:[textString rangeOfString:playTimeString]]; + self.memoryPlayTipLabel.attributedText = attributedString; + [UIView animateWithDuration:0.5 animations:^{ + self.memoryPlayTipLabel.alpha = 1.0; + } completion:^(BOOL finished) { + __weak typeof(self) weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.memoryPlayTipLabel.alpha = 0; + }); + }]; +} + #pragma mark Getter - (UIImageView *)backgroundView { @@ -346,6 +373,18 @@ - (PLVWatermarkView *)watermarkView { return _watermarkView; } +- (UILabel *)memoryPlayTipLabel { + if (!_memoryPlayTipLabel) { + _memoryPlayTipLabel = [[UILabel alloc] init]; + _memoryPlayTipLabel.layer.masksToBounds = YES; + _memoryPlayTipLabel.layer.cornerRadius = 14; + _memoryPlayTipLabel.alpha = 0; + _memoryPlayTipLabel.backgroundColor = PLV_UIColorFromRGBA(@"#000000", 0.6); + _memoryPlayTipLabel.textAlignment = NSTextAlignmentCenter; + } + return _memoryPlayTipLabel; +} + - (PLVRoomData *)roomData { return [PLVRoomDataManager sharedManager].roomData; } @@ -562,6 +601,9 @@ - (void)playerPresenter:(PLVPlayerPresenter *)playerPresenter videoSizeChange:(C self.contentBackgroudView.frame = self.displayRect; self.watermarkView.frame = self.contentBackgroudView.frame; self.pictureInPicturePlaceholderView.frame = self.displayRect; + if (self.playerPresenter.currentPlaybackTime > 0.5) { + [self showMemoryPlayTipLabelWithTime:self.playerPresenter.currentPlaybackTime]; + } } - (void)playerPresenter:(PLVPlayerPresenter *)playerPresenter streamStateUpdate:(PLVChannelLiveStreamState)newestStreamState streamStateDidChanged:(BOOL)streamStateDidChanged{ diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/PLVECUtils.h b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/PLVECUtils.h index 7482d9f8..2792945f 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/PLVECUtils.h +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/PLVECUtils.h @@ -9,6 +9,7 @@ #import #import #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_doc_icon@2x.png b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_doc_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..992c1c1ca4ad53d5391bffe0c006c39ae68bdb94 GIT binary patch literal 967 zcmV;&133JNP)l^xNtD(&GEa+xiEkE#UwF01$LiPE!EtohFpFr0v?qfVmqm_guI(A?^FWR<)AV)i{$G zud_co8Sf8}&RCz(k#sPF=hYiEVAl1IpWd5Qhcfl~+nc~!5GK|@m|tvhjtKy`Do6AUV_rTlcEB+xdPN$Q5hSY@Rh zU;ta0iL@_+AlrAeN(lql4P6swzW;DOtGyJv7EYrgKw^+bQK(VB6_zYSi36NFCSJ^B zpVZsQv2eoz2HHe|U(A7gb}XE*fUqDFW85&t*8y$x4)B0@_?MP#a%%aH(!=mz0&!NM zHu0odzpyb>4IQ#=?FR>F2i;-o**01>CAWC9E)xiSdlm+EblXn!Kw-@Qj;x6te7Dy1 zt%Y+oFv;e%qUGM>-pt$x*uZFNyxY+b??e^_=gj zrm=8b0yxno;`umOX@?o3Eo}3E6e&sKejE(hphZE%1g2;iX3WZsjP($`WdbR-XH7Q- z&JQXE`xMqpAlesBofS19OJU6bQfyNQVm%ophH}8U8Ga%qG0KJuq&3JLiYV05q$Cg) zujaQzr2{2^^vt$)l_4r=Y2cW0CHDkS5*P)l&aadRKHATU3RnRvUY{`~#^zSjCulJWER{@v;Pe600dnetba^3&t{ zu+90Ax%STB`=P}5n!opFpY!ka{hq@2l)LtJsPuED^lPH?Vx05!`Tp4E{HMtHaHRB4 zkn#8W{_FDn=<)rv(D|jt_=~mnfUosUkMYai`^en-tjhT0?)~BH{Kwn+y3_d~Jb{h? z000$qQchC<1HTgP(Y6=~&d98m4F;r0-il~Bx#n4j000CoNklseZTpXurP4{h(n+sUVG-U4+V19cAEIdJEo zr**JnW;^%BH@oGwn;;N`;iPHO2WVyxK}0Hw z=R?(MJ1VgU?4^l1nTGf$D!75ghBxW8}P^eF;{J`!K`r#eSsz>NwOBe3Zraj!3)BC$Jz z#Sj%KSR|-u)fX>OF$aqaDnhV0ArT~ks{VC`9HCmXzpP}(BUIa1!7%9 zD`Vb{)SA;X<|FRO#4tu;!&Up7tKJxiD+t9Q8!}DjUL`$XBkoJZM;)>`op%VCwYEkg zmWmTDtV+}=vX&}BmYq^2c|=$$bU4?rZ=63ndCJuY`_kBK;wi)TpWpz(L5 zFQ*v>}nn=K_V9GYe;1DWC(hMiE;N_!$ZUeBjmI=qIIT!?3wLuf-o3` zuY!Qd1~!5T6Baj@I6gFP^Zu`CnO%8kk?hCL9~1edc@v)2)A#hW_CX7@8gL(oe7;!I zVPsS%S88#a|AL7jvG>!j#>V#QOPnqQU|#p+x%X~}luSQ5FhmJ>^^*}J&6km zm>5zqM*@!-mN*g{378lXkK8!(W8V=?XlnuZ;a6nbIQMHQ5>pd^yOGFs;E4sLXTpa9 zObn?wCgO>uS0a2!z){A@aQ*Y?V^m8i5k6GlofUc5o}2LW{%-RD4yORqpj+2k-K5LS zAaN-J6GIBOJJYF-mpF;{+Uuq-Cvxn)oF>Yh#AhwIaJkRpBjG~{?ybn5m*j^W7s(?{r2|qy}93BQ{Gfb{rLFx%gFGvuHtK8;bmI;?(O^Q>iFK=_R-Gq zy0!1Drt6rI>XeM&VOHEsK-)ge&evh1Lm?4X(KoR;5OPT4>+**-D&+SvBh z()7i_$AKa|}gM8aTZB>7C-Y6Ce>lgn;zvZIRF-r4cWoAd6s-E{;V) zi$G2mRts(~C27?9L?OeeVSke;PAY0F`QKwnz@dpRl~&=tF8AdpI-*CQ0DhhQBB zZfgwsFpx$JHgTY$Jg_9RWCiY%CA+52hQ3%&KOoa zirHz<8}lPYcRDq=eiTt94*=@M-qWU(9$YeY$2X=-=|P`8D^P2i)%4(;kHdBgYRfgH z2X%f@M|mP30BB37W(w7U5MWnCjR*opzZJC!fxx`Q7kWw%kZag_g9rz5XYdT3!83>j tvsf>ixyFK5nH(0(@!!F)=e^2h{sQymQ#3y^QS<-+002ovPDHLkV1fr@*KGg* literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_pdf_icon@3x.png b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_pdf_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4e76b45d116519d5aedb7d474c78bec2c4547837 GIT binary patch literal 1449 zcmV;a1y=frP)?2R_sMF;4~`TIxFWpG1oIC*E1#9G9=hCB-k@0*fJ#8G9=$PDA+P2*fJ#8G9=hF zC)hG1*fS;AG$q(GCEGSA*D@sk|NqxBCHL3V>XnZE`}_X={qeW5-&#-FK{ePmDg5v5 z<8EcyJuuihEdBZT{r2_y>+0fYUi|X$^~}ig!@uyfukWy{?W3LHWLe%d$R^MJz-BCr{Nj>`L=JC3=?x~~QQ%KoA zGWgxw^vA>Prl9DGg5`N|+eJ9~=H&V0;^~lx>5z%$fOq3^X#My1?yIHjpO_$|jE?{S z02p*qPE!ET0|pom3j6KJua>;G6T+yXNblZ?XgN>CSA75g1U^YbK~#9!?c7;+(m)u- zanLF%?(35vI|&73-*+el+gf-1{$EAUWN?BrQ#mAW;syVkbUXcZ(oAN;1(8VPBp!{y zr(QmI6WO=t_uyydt6TfIC(QM;XZy)!$J-k7IM(AkVAllj4+CxgaqSZ zSTJ6P1wT9(UiZ`0+Lt8`?8SkwRWn%0Mh=H{7@X@G+p0R51a26jw@^m`Tc<8~Xh9VT zJT=^XfGQGr#K3&xj2&xGMFMv@_=6o2P(=beBkkiWJ652I1kN*Xij2X6DiZiT^RCc# zU=xZ+;CFliXM={*%sa<5JDx%n2|Q+C4=r~51XU#PjDf{mf&T%jNMJk3#6q7P(ojVL z^Q&vsyJ?jmfv=o%?F@VXWhC&@aJ7Ik5*S_K=AnrMo(Q-KO(gK982AoE+`M=*=pupV zTX#si*^}#Wcm#bU@PNZp=p%v6_YPcuJ`xx;2TqvKM*-(5hSLPvNZ^9O%Wt5I1Qrwd z9J(lATRg?2novgq4-C;q3(82~k#Mzp5DGY(+~c{80{&v`G+aQA1nxNCpK*7xkfMN_ z4p&?xw(>h^ks^WlB(7ocXI9mvA0mLWMF*RMJ*hhNp@`xUhCh*~HG3cmICth+imi?; zo7r}zJor#+}5^rthAHU<6{&3%AJuCq65d^FSz>yu{I#B%C<+VT!(45xwFyezq-%4nQv zzcVyh-r?$YHFBxN>Q&2RcdWNbrF#a?@J#xprTRD9_5Fkj9(#qY&!xZaO8tzq-|k9o z*Y*Z48^hhPUe2MaT-&|Qs~dQM+x6tmjH#DR{qj|AY_EZ{dfC$=vD{_5Qw#sf&NSx> z+uO3ZX%yWv=dy*p^8%Zb-T#^D+)=|2^JcvRN@amTS>*00000NkvXXu0mjf D9Tw7) literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_ppt_icon@2x.png b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_ppt_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f68deff87d1028d414a6ed4b2091e440a265ff7b GIT binary patch literal 603 zcmV-h0;K(kP)sSWE6(P3>4q?^;dmSxoI( zO#f{v_x|Nri8WBu>${q5}d$;I}ts_~42?rB}^Wm*3G`~LX&`{CdE*wy*W$@7|$ z@{Wc1($Dp*rSg)B@O^XcZ)EITPyO`s{qytr(9Q9Md;II^_qnw8vaIx@obPpN?{sOO z+Jb@r000zpQchC<_Uq>1*ghHkA|1mg-7CZ{tC0+tXR?j}00CD?L_t(o!|mD2PJ=)g zhGD1`5!8D4D1~;)`A}->@&2#PIAcjAvJk!ni+Pv);R!<~lMoEUa!vfR`Qww(n`L7_ zmsh>u0^o`!$Ra=%!4|<5F``?i2c(K%ix}73A%mD-cR{uYwg|QeLKo*EDB?{AWQt&m zV2hxMcVrMVC4w!2Dc(zvEP^XKWDpN*aK;9q!LydDPA_qihWTbV=>1)h6G2t7BZFR4 zlR`rFs=SP^d+J&_Kj;aO8oh z98^3okb{f|7HZ${Kwl1O8d!TWK?4u+dJGcSyn3>b1BxtGUL=N|Hrt@>kgU8@CHXu| zZ7vuzV1j-H@W6eVHy8s(VO#m0#2~77(N`o5IpawNKG0+4x p(^!C-Ygjgj*_QepOv-dE;|uw2LgOqi(AfY0002ovPDHLkV1l2tA-n(p literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_ppt_icon@3x.png b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_ppt_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5c7682ccb020d0dfcb0a19b1f0f3a97c1ce9e8d9 GIT binary patch literal 833 zcmeAS@N?(olHy`uVBq!ia0vp^6+k?JgBeJESsTv}q>csngt!9fw-Mg|OS6FF+en|U zalzk`!~Um*|DToqHq!fTgwN;Lzz^{OK-o7y1t2wW-$eWU`~B}ZBp33zkmO}eehISt zzaQTFb?e5L1v5TP==e}m_&vQ~CD11_B|(0{3>R77u9x8DWIC{0MBvYvV=I1bTQq&D z$NPI-8I~=zSw0L5Onja$jv*Dd-rl+CcRN6&HPKpeNx&s1=@(lrU70zjQzb=-S>)G0 z{m{@=k)~5y^&f3j-SYpnZmqc8wP`soSsVj)^5_>Vu#>Ol{!|uvOmTH><>d2gFW3gi ze?D1yz>SYldS@Gx)p^C{%v6!fm7cu8W=(}5L5HV2HAuDfiR2HycKAmCkbBhN=_T{nx3Q^DB*-L9=hz=% zATy>c>^0gF*ygDoX0R{n;PS8!>e>=~=}$Ls-TQOm)$*t(|E7l7h!n5l3Th$$CoSWKB z*#;}cS`0LKUKy-Br!Bo?s`j2GPLV&g9&DA_q<84Fm%tykf<@w*hcab%0aFlzr>mdK II;Vst08166C;$Ke literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_xls_icon@2x.png b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_chatroom_file_xls_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9d295c8479b2c37bb9243e92b940ab9fb48315b GIT binary patch literal 784 zcmV+r1MmEaP)XnRXSb zfC!j%|Ns9Lo_!9Rc<}i4{Qdn-wVd_(`C`7OBBF!u`1Zo(-8!w2C#8ns^Y7E`=da$< zpx4Qq*2sy@y&0c>`273l_43y4=*{ZmZ^N%au$8^z+PLA@h0MD%s*NnCiRAR~-tp|* z@av1uzj?>Ba>TH4#IIVqqsHgow&2#N+s>5I!-C4WNwb+;x09s+000woQchC<_T}I{ zEF9_~+SbD+80^HWkqls%X8!;H0qIFZK~z}7?b+#0!ax)S;Gz{56x0L5E=xt00RqAU|Du1g_t?LySI+UhDok&j)FM|izx_;fUpST zT&81y3=z^Iq(!iD8Wq75FCw6{2x$@0BKSE8iI5PlgP^nsX%W&Qq{K5}5a+%KX%W&Q zq(w-J5KT5M4B}QA%u0huz(AVw;wBU9#qow+6&u7bze%ZqP^x-#WKwzTo!bswOReq= zJ{eR#eDVi?=z4#)2lO@)h128fPl3{;?S z6&$eeqDxa@2P_byk}kb1-0w0$EdA28qIXaR7gTLsS{Lqs4Px5M@3K$=AXXZq^}_}6 zz6B?c0cREMxex;JsKGZGkfvpzxF9VYoi7=1Nz3Mk3)0P}TerGQkRA`OdSX8LV8?%` z7Up`UIUqe^E6wduy~Y4(6Ijz~9P>aYu!|I0`rG7yRljhk{>%`hU1S)(1zlwfj;V04 z^u!cgQ{nKoIx+)i6)GGqy--G=ONIAKcTB+VNMS0SFUJQPB`O@f75m+|pc+u&L#3U# zAPQa-B2oICzyB4GfC-p@37CKh`0v1!te3KKvS7xtWkH+%4wmSfv8`Vz1uM+(S8&(> O0000%X$hAzhn=q-2 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 literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_home_redpack_btn@2x.png b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_home_redpack_btn@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..df610d54fe4f8173b660a8725dec453e82d9c19a GIT binary patch literal 10394 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 Ef{myL{Qv*} literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_home_redpack_btn@3x.png b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Resource/WatchResource.bundle/plvec_home_redpack_btn@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4297ccb2cd705080ad99f9cba5bbbb0141205dbf GIT binary patch literal 17288 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 literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/PLVECHomePageView.h b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/PLVECHomePageView.h index bae41db4..4347948d 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/PLVECHomePageView.h +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/PLVECHomePageView.h @@ -42,6 +42,9 @@ typedef NS_ENUM(NSUInteger, PLVECHomePageType) { /// 打开礼物打赏面板 - (void)homePageViewOpenRewardView:(PLVECHomePageView *)homePageView; +/// 打开卡片推送 +- (void)homePageView:(PLVECHomePageView *)homePageView openCardPush:(NSDictionary *)cardInfo; + /// 按下暂停、播放按钮 - (void)homePageView:(PLVECHomePageView *)homePageView switchPause:(BOOL)pause; @@ -64,6 +67,9 @@ typedef NS_ENUM(NSUInteger, PLVECHomePageType) { /// @param pointUnit 打赏单位 - (void)homePageView_loadRewardEnable:(BOOL)rewardEnable payWay:(NSString * _Nullable)payWay rewardModelArray:(NSArray *_Nullable)modelArray pointUnit:(NSString * _Nullable)pointUnit; +/// 聊天室人数达到并发限制 +- (void)homePageView_didLoginRestrict; + @end @interface PLVECHomePageView : UIView diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/PLVECHomePageView.m b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/PLVECHomePageView.m index 5bac6097..d7a97a20 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/PLVECHomePageView.m +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/PLVECHomePageView.m @@ -21,6 +21,7 @@ #import "PLVECLiveRoomInfoView.h" #import "PLVECChatroomView.h" #import "PLVECLikeButtonView.h" +#import "PLVECCardPushButtonView.h" #import "PLVECPlayerContolView.h" #import "PLVECMoreView.h" #import "PLVECSwitchView.h" @@ -56,7 +57,8 @@ @interface PLVECHomePageView ()< PLVECCommodityViewControllerDelegate, PLVCommodityPushViewDelegate, PLVSocketManagerProtocol, -PLVECChatroomViewDelegate +PLVECChatroomViewDelegate, +PLVECCardPushButtonViewDelegate > #pragma mark 数据 @@ -95,6 +97,7 @@ @interface PLVECHomePageView ()< @property (nonatomic, strong) PLVECLiveRoomInfoView *liveRoomInfoView; // 直播详情视图 @property (nonatomic, strong) PLVECChatroomView *chatroomView; // 聊天室视图 @property (nonatomic, strong) PLVECLikeButtonView *likeButtonView; // 点赞视图 +@property (nonatomic, strong) PLVECCardPushButtonView *cardPushButtonView; // 卡片推送挂件 @property (nonatomic, strong) PLVECPlayerContolView *playerContolView; // 视频播放控制视图 @property (nonatomic, strong) UIButton *moreButton; // 更多按钮 @property (nonatomic, strong) UIButton *giftButton; // 送礼按钮 @@ -157,6 +160,7 @@ - (void)setupUI { [self addSubview:self.playbackListButton]; } } + [self addSubview:self.cardPushButtonView]; [self addSubview:self.moreButton]; [self addSubview:self.shoppingCartButton]; } @@ -172,15 +176,26 @@ - (PLVECLiveRoomInfoView *)liveRoomInfoView { - (PLVECChatroomView *)chatroomView { if (!_chatroomView) { - if (self.type == PLVECHomePageType_Live || - [PLVRoomDataManager sharedManager].roomData.menuInfo.chatInputDisable) { - _chatroomView = [[PLVECChatroomView alloc] init]; + _chatroomView = [[PLVECChatroomView alloc] init]; + if ((self.type == PLVECHomePageType_Live || + [PLVRoomDataManager sharedManager].roomData.menuInfo.chatInputDisable)) { _chatroomView.delegate = self; + } else { + _chatroomView.hidden = YES; } } return _chatroomView; } +- (PLVECCardPushButtonView *)cardPushButtonView { + if (!_cardPushButtonView) { + _cardPushButtonView = [[PLVECCardPushButtonView alloc] init]; + _cardPushButtonView.delegate = self; + _cardPushButtonView.hidden = YES; + } + return _cardPushButtonView; +} + - (PLVECLikeButtonView *)likeButtonView { if (!_likeButtonView) { _likeButtonView = [[PLVECLikeButtonView alloc] init]; @@ -356,6 +371,7 @@ - (void)destroy { [_likeButtonView invalidTimer]; [[PLVECChatroomViewModel sharedViewModel] clear]; } + [self.cardPushButtonView leaveLiveRoom]; } - (void)showShoppingCart:(BOOL)show { @@ -514,6 +530,8 @@ - (void)updateUIFrame { self.shoppingCartButton.frame = CGRectMake(CGRectGetMinX(self.giftButton.frame)-48, CGRectGetMinY(self.moreButton.frame), buttonWidth, buttonWidth); // 点赞按钮 self.likeButtonView.frame = CGRectMake(CGRectGetMinX(self.moreButton.frame), CGRectGetMinY(self.moreButton.frame)-PLVECLikeButtonViewHeight-5, PLVECLikeButtonViewWidth, PLVECLikeButtonViewHeight); + // 卡片推送挂件 + self.cardPushButtonView.frame = CGRectMake(CGRectGetMinX(self.moreButton.frame), CGRectGetHeight(self.frame) * 0.55, PLVECCardPushButtonViewWidth, PLVECCardPushButtonViewHeight); // 网络提示 self.networkQualityMiddleLable.frame = CGRectMake(CGRectGetWidth(self.bounds) - 219 - 16, CGRectGetMinY(self.giftButton.frame) - 28 - 8, 219, 28); self.networkQualityPoorView.frame = CGRectMake(CGRectGetWidth(self.bounds) - 207 - 8, CGRectGetMinY(self.giftButton.frame) - 56 - 8, 207, 56); @@ -526,6 +544,8 @@ - (void)updateUIFrame { self.moreButton.frame = CGRectMake(CGRectGetWidth(self.bounds) - buttonWidth - 15, CGRectGetHeight(self.bounds) - buttonWidth - P_SafeAreaBottomEdgeInsets(), buttonWidth, buttonWidth); self.shoppingCartButton.frame = CGRectMake(CGRectGetMinX(self.moreButton.frame) - 48, CGRectGetMinY(self.moreButton.frame), buttonWidth, buttonWidth); self.playerContolView.frame = CGRectMake(0, CGRectGetMinY(self.moreButton.frame) - 32, CGRectGetMaxX(self.moreButton.frame), 41); + // 卡片推送挂件 + self.cardPushButtonView.frame = CGRectMake(CGRectGetMidX(self.moreButton.frame) - PLVECCardPushButtonViewWidth/2, CGRectGetMinY(self.playerContolView.frame)-PLVECLikeButtonViewHeight, PLVECCardPushButtonViewWidth, PLVECCardPushButtonViewHeight); } } @@ -627,6 +647,10 @@ - (void)socketMananger_didReceiveEvent:(NSString *)event if (![jsonDict isKindOfClass:[NSDictionary class]]) { return; } + + if ([event isEqualToString:@"newsPush"]) { + [self newsPushEvent:jsonDict]; + } } - (void)bulletinEvent:(NSDictionary *)jsonDict { @@ -686,6 +710,14 @@ - (void)productMessageEvent:(NSDictionary *)jsonDict { } } +- (void)newsPushEvent:(NSDictionary *)jsonDict { + NSString *newsPushEvent = PLV_SafeStringForDictKey(jsonDict, @"EVENT"); + if ([PLVFdUtil checkStringUseable:newsPushEvent]) { + BOOL start = [newsPushEvent isEqualToString:@"start"]; + [self.cardPushButtonView startCardPush:start cardPushInfo:jsonDict]; + } +} + #pragma mark PLVPlayerContolViewDelegate - (void)playerContolView:(PLVECPlayerContolView *)playerContolView switchPause:(BOOL)pause { @@ -742,6 +774,14 @@ - (NSTimeInterval)chatroomView_currentPlaybackTime { return self.currentPlaybackTime; } +- (void)chatroomView_didLoginRestrict { + if (self.delegate && [self.delegate respondsToSelector:@selector(homePageView_didLoginRestrict)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate homePageView_didLoginRestrict]; + }); + } +} + #pragma mark PLVECMoreViewDelegate - (NSArray *)dataSourceOfMoreView:(PLVECMoreView *)moreView { @@ -886,4 +926,12 @@ - (void)playerSwitchView:(PLVECSwitchView *)playerSwitchView } } +#pragma mark PLVECCardPushButtonViewDelegate + +- (void)cardPushButtonView:(PLVECCardPushButtonView *)pushButtonView needOpenInteract:(NSDictionary *)dict { + if (self.delegate && [self.delegate respondsToSelector:@selector(homePageView:openCardPush:)]) { + [self.delegate homePageView:self openCardPush:dict]; + } +} + @end diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonPopupView.h b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonPopupView.h new file mode 100644 index 00000000..9d16aa2d --- /dev/null +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonPopupView.h @@ -0,0 +1,21 @@ +// +// PLVECCardPushButtonPopupView.h +// PolyvLiveScenesDemo +// +// Created by Sakya on 2022/7/13. +// Copyright © 2022 PLV. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface PLVECCardPushButtonPopupView : UIView + +@property (nonatomic, strong, readonly) UILabel *titleLabel; + +- (void)setPopupViewTitle:(NSString *)title; + +@end + +NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonPopupView.m b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonPopupView.m new file mode 100644 index 00000000..5493820d --- /dev/null +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonPopupView.m @@ -0,0 +1,108 @@ +// +// PLVECCardPushButtonPopupView.m +// PolyvLiveScenesDemo +// +// Created by Sakya on 2022/7/13. +// Copyright © 2022 PLV. All rights reserved. +// + +#import "PLVECCardPushButtonPopupView.h" +#import + +@interface PLVECCardPushButtonPopupView () + +/// UI +@property (nonatomic, strong) UIBezierPath *bezierPath; +@property (nonatomic, strong) UILabel *titleLabel; +@property (nonatomic, strong) CAGradientLayer *gradientLayer; + +@end + +@implementation PLVECCardPushButtonPopupView + +- (instancetype)init { + self = [super init]; + if (self) { + [self addSubview:self.titleLabel]; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + CGFloat width = self.bounds.size.width; + CGFloat height = self.bounds.size.height; + self.bezierPath = [[self class] leftBezierPathWithSize:CGSizeMake(width, height)]; + self.titleLabel.frame = CGRectMake(0, 0, self.frame.size.width - 6, self.frame.size.height); + CAShapeLayer *shapeLayer = [CAShapeLayer layer]; + shapeLayer.path = self.bezierPath.CGPath; + self.layer.mask = shapeLayer; + + self.gradientLayer.frame = self.layer.bounds; + [self.layer insertSublayer:self.gradientLayer atIndex:0]; +} + +#pragma mark - [ Public Method ] + +- (void)setPopupViewTitle:(NSString *)title { + self.titleLabel.text = [PLVFdUtil checkStringUseable:title] ? title : @"连续观看有奖励哦"; +} + +#pragma mark - [ Private Method ] + +#pragma mark - Getter & Setter + +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [[UILabel alloc] init]; + _titleLabel.textAlignment = NSTextAlignmentCenter; + _titleLabel.textColor = [UIColor whiteColor]; + _titleLabel.font = [UIFont systemFontOfSize:13]; + _titleLabel.text = @"连续观看有奖励哦"; + } + return _titleLabel; +} + +- (CAGradientLayer *)gradientLayer { + if (!_gradientLayer) { + CAGradientLayer *gradientLayer = [CAGradientLayer layer]; + gradientLayer.colors = @[(__bridge id)[PLVColorUtil colorFromHexString:@"#FD8121"].CGColor, (__bridge id)[PLVColorUtil colorFromHexString:@"#F6A125"].CGColor]; + gradientLayer.locations = @[@(0), @(1.0f)]; + gradientLayer.endPoint = CGPointMake(0, 0.5); + _gradientLayer = gradientLayer; + } + return _gradientLayer; +} + ++ (UIBezierPath *)leftBezierPathWithSize:(CGSize)size { + CGFloat conner = 4.0; // 圆角大小 + CGFloat trangleHeight = 6.0; // 箭头高度 + CGFloat trangleWidthForHalf = 6.0; // 箭头宽度的一半 + + UIBezierPath *bezierPath = [UIBezierPath bezierPath]; + + // 从左上角开始,顺时针绘制气泡 + [bezierPath moveToPoint:CGPointMake(conner, 0)]; + [bezierPath addLineToPoint:CGPointMake(size.width-trangleHeight-conner, 0)]; + [bezierPath addQuadCurveToPoint:CGPointMake(size.width-trangleHeight, conner) controlPoint:CGPointMake(size.width-trangleHeight, 0)]; + [bezierPath addLineToPoint:CGPointMake(size.width-trangleHeight, size.height/2 - trangleWidthForHalf)]; + + // 从上向下绘制箭头 + [bezierPath addLineToPoint:CGPointMake(size.width, size.height/2)]; + [bezierPath addLineToPoint:CGPointMake(size.width - trangleHeight, size.height/2 + trangleWidthForHalf)]; + + // 继续顺时针绘制气泡 + [bezierPath addLineToPoint:CGPointMake(size.width-trangleHeight, size.height- conner)]; + [bezierPath addQuadCurveToPoint:CGPointMake(size.width - conner-trangleHeight, size.height) controlPoint:CGPointMake(size.width-trangleHeight, size.height)]; + [bezierPath addLineToPoint:CGPointMake(conner, size.height)]; + [bezierPath addQuadCurveToPoint:CGPointMake(0, size.height - conner) controlPoint:CGPointMake(0, size.height)]; + [bezierPath addLineToPoint:CGPointMake(0, conner)]; + [bezierPath addQuadCurveToPoint:CGPointMake(conner, 0) controlPoint:CGPointMake(0, 0)]; + + // 气泡绘制完毕,关闭贝塞尔曲线 + [bezierPath closePath]; + return bezierPath; +} + +@end diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonView.h b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonView.h new file mode 100644 index 00000000..4d5767d3 --- /dev/null +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonView.h @@ -0,0 +1,41 @@ +// +// PLVECCardPushButtonView.h +// PolyvLiveScenesDemo +// +// Created by Sakya on 2022/7/13. +// Copyright © 2022 PLV. All rights reserved. +// + +#import + +#define PLVECCardPushButtonViewWidth (44) +#define PLVECCardPushButtonViewHeight (44.0 + 12.0) + +NS_ASSUME_NONNULL_BEGIN + +@class PLVECCardPushButtonView; + +@protocol PLVECCardPushButtonViewDelegate + +/// 在点击卡片领取按钮或者观看领奖倒计时结束后会执行此回调,需要互动视图打开领取入口 +/// @param pushButtonView 卡片推送领取按钮挂件 +/// @param dict 打开视图需要的参数 +- (void)cardPushButtonView:(PLVECCardPushButtonView *)pushButtonView needOpenInteract:(NSDictionary *)dict; + +@end + +@interface PLVECCardPushButtonView : UIView + +@property (nonatomic, weak) id delegate; + +/// 开启卡片推送 +/// @param start 是否是开启推送 YES开启 NO取消 +/// @param dict 卡片推送信息 +- (void)startCardPush:(BOOL)start cardPushInfo:(NSDictionary *)dict; + +/// 离开直播房间 +- (void)leaveLiveRoom; + +@end + +NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonView.m b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonView.m new file mode 100644 index 00000000..1cac47c7 --- /dev/null +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/HomePage/View/PLVECCardPushButtonView.m @@ -0,0 +1,270 @@ +// +// PLVECCardPushButtonView.m +// PolyvLiveScenesDemo +// +// Created by Sakya on 2022/7/13. +// Copyright © 2022 PLV. All rights reserved. +// + +#import "PLVECCardPushButtonView.h" +#import "PLVECUtils.h" +#import "PLVECCardPushButtonPopupView.h" +#import +#import + +@interface PLVECCardPushButtonView () + +/// UI +@property (nonatomic, strong) UIButton *cardPushButton; +@property (nonatomic, strong) UILabel *countdownLabel; // 倒计时文本 +@property (nonatomic, strong) PLVECCardPushButtonPopupView *popupView; + +/// 数据 +@property (nonatomic, copy) NSString *channelId; +@property (nonatomic, copy) NSString *cardId; +@property (nonatomic, copy, readonly) NSString *localWatchTimeKey; //本地保存观看时长的 key +@property (nonatomic, strong) dispatch_source_t countdownTimer; // 观看时长计时器 +@property (nonatomic, strong) NSDictionary *cardDict; // 卡片推送的socket消息 +@property (nonatomic, assign) NSInteger conditionTime; // 达到奖励的时间限制 +@property (nonatomic, assign) NSInteger watchTime; // 已经观看直播的时间(包含本地已经保存的时长) +@property (nonatomic, assign) BOOL enterEnabled; // 是否隐藏卡片入口 +@property (nonatomic, assign) BOOL canOpenCard; // 是否能开启卡片弹窗(在倒计时结束前不能打开) + +@end + +@implementation PLVECCardPushButtonView + +#pragma mark - Life Cycle + +- (instancetype)init { + self = [super init]; + if (self) { + [self addSubview:self.cardPushButton]; + [self addSubview:self.countdownLabel]; + [self addSubview:self.popupView]; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + self.cardPushButton.frame = CGRectMake(0, self.countdownLabel.isHidden ? 12 : 0, PLVECCardPushButtonViewWidth, PLVECCardPushButtonViewWidth); + self.countdownLabel.frame = CGRectMake(- 3, CGRectGetMaxY(self.cardPushButton.frame), PLVECCardPushButtonViewWidth + 6, 12); + + CGSize popupViewSize = [self.popupView.titleLabel sizeThatFits:CGSizeMake(MAXFLOAT, 16)]; + CGFloat popupViewWidth = popupViewSize.width + 16 + 10; + CGFloat popupViewHeight = 34; + self.popupView.frame = CGRectMake(CGRectGetMinX(self.cardPushButton.frame) - popupViewWidth, CGRectGetMidY(self.cardPushButton.frame) - popupViewHeight/2, popupViewWidth, popupViewHeight); +} + +#pragma mark - [ Public Method ] + +- (void)startCardPush:(BOOL)start cardPushInfo:(NSDictionary *)dict { + if (start) { //开始推送卡片 + if (![PLVFdUtil checkDictionaryUseable:dict]) { + return; + } + + self.cardDict = dict; + self.channelId = PLV_SafeStringForDictKey(dict, @"roomId"); + self.cardId = PLV_SafeStringForDictKey(dict, @"id"); + __weak typeof(self) weakSelf = self; + [self loadCardPushWithRoomId:self.channelId cardId:self.cardId completion:^(NSDictionary *cardDict) { + if ([PLVFdUtil checkDictionaryUseable:cardDict]) { + [weakSelf setCardPushButtonViewWithCardInfo:cardDict]; + } + }]; + } else { // 取消卡片推送 + self.hidden = YES; + [self saveLocalWatchTime]; + [self cancelDispatchTimer]; + } +} + +- (void)leaveLiveRoom { + [self saveLocalWatchTime]; + [self cancelDispatchTimer]; +} + +#pragma mark - [ Private Method ] + +- (void)loadCardPushWithRoomId:(NSString *)roomId cardId:(NSString *)cardId completion:(void (^)(NSDictionary *cardDict))completion { + [PLVLiveVideoAPI requestCardPushInfoWithChannelId:roomId cardPushId:cardId completion:completion failure:^(NSError * _Nonnull error) {}]; +} + +- (void)setCardPushButtonViewWithCardInfo:(NSDictionary *)cardDict { + self.canOpenCard = YES; + self.countdownLabel.hidden = YES; + // 是否隐藏挂件 + self.enterEnabled = PLV_SafeBoolForDictKey(cardDict, @"enterEnabled"); + self.hidden = !self.enterEnabled; + + // 设置 button 图片 + NSString *imageType = PLV_SafeStringForDictKey(cardDict, @"imageType"); + if ([imageType isEqualToString:@"redpack"] || [imageType isEqualToString:@"giftbox"]) { + NSString *imageName = [imageType isEqualToString:@"redpack"] ? @"plvec_home_redpack_btn" : @"plvec_home_giftbox_btn"; + UIImage *image = [PLVECUtils imageForWatchResource:imageName]; + [self.cardPushButton setImage:image forState:UIControlStateNormal]; + } else if ([imageType isEqualToString:@"custom"]) { + NSString *imageURLString = PLV_SafeStringForDictKey(cardDict, @"enterImage"); + if ([PLVFdUtil checkStringUseable:imageURLString]) { + NSURL *imageURL = [NSURL URLWithString:imageURLString]; + if (imageURL && !imageURL.scheme) { + imageURL = [NSURL URLWithString:[@"https:" stringByAppendingString:imageURL.absoluteString]]; + } + [self.cardPushButton sd_setImageWithURL:imageURL forState:UIControlStateNormal]; + } + } + + // 观看条件 + NSString *showCondition = PLV_SafeStringForDictKey(cardDict, @"showCondition"); + if ([showCondition isEqualToString:@"WATCH"]) { // 需要观看 + // 观看时长(ms) + NSInteger watchConditionTime = PLV_SafeIntegerForDictKey(cardDict, @"conditionValue")/1000; + self.conditionTime = watchConditionTime; + + // 开始观看倒计时 + NSInteger localWatchTime = self.localWatchTime; + if (watchConditionTime > 0 && watchConditionTime > localWatchTime) { + self.hidden = NO; + self.canOpenCard = NO; + self.countdownLabel.hidden = NO; + __weak typeof(self) weakSelf = self; + [self startCountdownWithLocalWatchTime:localWatchTime endCallback:^{ + [weakSelf countdownEndCallback]; + }]; + } else if (watchConditionTime == 0 && localWatchTime == 0){ + // 设置观看时间,记录本地观看记录 + self.watchTime = 1; + [self saveLocalWatchTime]; + [self callbackForNeedOpenInteract]; + } + + // 提示弹窗 + NSString *countdownMsg = PLV_SafeStringForDictKey(cardDict, @"countdownMsg"); + [self.popupView setPopupViewTitle:countdownMsg]; + [self showPopupTitleView]; + + [self setNeedsLayout]; + } +} + +- (void)startCountdownWithLocalWatchTime:(NSInteger)watchTime endCallback:(void (^)(void))callback { + self.watchTime = watchTime; + dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + self.countdownTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, quene); + dispatch_source_set_timer(self.countdownTimer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0); + __weak typeof(self) weakSelf = self; + dispatch_source_set_event_handler(self.countdownTimer, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (weakSelf.watchTime >= weakSelf.conditionTime) { + callback ? callback() : nil; + } else { + NSInteger remainingTime = weakSelf.conditionTime - weakSelf.watchTime; + NSString *watchTimeText = [NSString stringWithFormat:@"%02ld:%02ld:%02ld",lround(floor(remainingTime / 60 / 60)), lround(floor(remainingTime / 60)) % 60, lround(floor(remainingTime)) % 60]; + self.countdownLabel.text = [NSString stringWithFormat:@"%@", watchTimeText]; + weakSelf.watchTime ++; + } + }); + }); + dispatch_resume(self.countdownTimer); +} + +- (void)countdownEndCallback { + self.countdownLabel.hidden = YES; + self.canOpenCard = YES; + self.hidden = !self.enterEnabled; + [self setNeedsLayout]; + + [self saveLocalWatchTime]; + [self cancelDispatchTimer]; + [self callbackForNeedOpenInteract]; +} + +- (void)saveLocalWatchTime { + if (self.countdownTimer && self.watchTime > self.localWatchTime) { + [[NSUserDefaults standardUserDefaults] setInteger:self.watchTime forKey:self.localWatchTimeKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } +} + +- (void)cancelDispatchTimer { + if (self.countdownTimer) { + dispatch_cancel(self.countdownTimer); + self.countdownTimer = nil; + } +} + +- (void)showPopupTitleView { + if (self.popupView.hidden) { + self.popupView.hidden = NO; + __weak typeof(self) weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.popupView.hidden = YES; + }); + } +} + +#pragma mark - Getter & Setter + +- (UIButton *)cardPushButton { + if (!_cardPushButton) { + _cardPushButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_cardPushButton addTarget:self action:@selector(cardPushAction:) forControlEvents:UIControlEventTouchUpInside]; + UIImage *image = [PLVECUtils imageForWatchResource:@"plvec_home_redpack_btn"]; + [_cardPushButton setImage:image forState:UIControlStateNormal]; + } + return _cardPushButton; +} + +- (UILabel *)countdownLabel { + if (!_countdownLabel) { + _countdownLabel = [[UILabel alloc] init]; + _countdownLabel.textAlignment = NSTextAlignmentCenter; + _countdownLabel.textColor = [UIColor whiteColor]; + _countdownLabel.font = [UIFont systemFontOfSize:10]; + _countdownLabel.backgroundColor = [UIColor clearColor]; + _countdownLabel.hidden = YES; + } + return _countdownLabel; +} + +- (PLVECCardPushButtonPopupView *)popupView { + if (!_popupView) { + _popupView = [[PLVECCardPushButtonPopupView alloc] init]; + _popupView.hidden = YES; + } + return _popupView; +} + +- (NSString *)localWatchTimeKey { + return [NSString stringWithFormat:@"PLVECLiveWatchTimeKey%@-%@", self.channelId, self.cardId]; +} + +- (NSInteger)localWatchTime { + NSInteger localWatchTime = [[NSUserDefaults standardUserDefaults] integerForKey:self.localWatchTimeKey]; + return localWatchTime; +} + +#pragma mark - Callback + +- (void)callbackForNeedOpenInteract { + plv_dispatch_main_async_safe(^{ + if (self.delegate && [self.delegate respondsToSelector:@selector(cardPushButtonView:needOpenInteract:)]) { + [self.delegate cardPushButtonView:self needOpenInteract:self.cardDict]; + } + }) +} + +#pragma mark - Action + +- (void)cardPushAction:(UIButton *)sender { + if (self.canOpenCard) { + [self callbackForNeedOpenInteract]; + } else { + [self showPopupTitleView]; + } +} + +@end diff --git a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/PLVECWatchRoomViewController.m b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/PLVECWatchRoomViewController.m index d6b20c8b..8a9c0f8e 100644 --- a/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/PLVECWatchRoomViewController.m +++ b/PolyvLiveScenesDemo/PLVLiveEcommerceScene/Scenes/PLVECWatchRoomViewController.m @@ -23,6 +23,7 @@ #import "PLVECHomePageView.h" #import "PLVECLiveDetailPageView.h" #import "PLVECWatchRoomScrollView.h" +#import "PLVCommodityCardDetailView.h" // 工具 #import "PLVECUtils.h" @@ -31,7 +32,7 @@ #import #import -NSString *PLVLEChatroomOpenBulletinNotification = @"PLVLCChatroomOpenBulletinNotification"; +NSString *PLVLEChatroomOpenBulletinNotification = @"PLVLEChatroomOpenBulletinNotification"; @interface PLVECWatchRoomViewController ()< PLVSocketManagerProtocol, @@ -43,7 +44,8 @@ @interface PLVECWatchRoomViewController ()< PLVECLinkMicAreaViewDelegate, PLVLivePictureInPictureRestoreDelegate, PLVCommodityDetailViewControllerDelegate, -PLVPopoverViewDelegate +PLVPopoverViewDelegate, +PLVInteractGenericViewDelegate > #pragma mark 数据 @@ -61,6 +63,7 @@ @interface PLVECWatchRoomViewController ()< @property (nonatomic, strong) PLVECHomePageView *homePageView; @property (nonatomic, strong) PLVECLiveDetailPageView * liveDetailPageView; @property (nonatomic, strong) UIButton * closeButton; +@property (nonatomic, strong) PLVCommodityCardDetailView *cardDetailView; // 卡片推送加载视图 @end @@ -207,6 +210,10 @@ - (void)setupUI{ self.liveDetailPageView.frame = CGRectMake(0, 0, CGRectGetWidth(scrollViewFrame), CGRectGetHeight(scrollViewFrame)); self.scrollView.contentOffset = CGPointMake(CGRectGetWidth(scrollViewFrame), 0); + + /// 互动 + [self.view addSubview:self.popoverView]; + self.popoverView.frame = self.view.bounds; if (roomData.menuInfo) { [self roomDataManager_didMenuInfoChanged:roomData.menuInfo]; } } @@ -250,6 +257,29 @@ - (void)exitCurrentController { } } +- (void)openCommodityDetailViewControllerWithURL:(NSURL *)commodityURL { + PLVRoomData *roomData = [PLVRoomDataManager sharedManager].roomData; + if (roomData.videoType == PLVChannelVideoType_Live) { // 视频类型为直播 + if (![PLVLivePictureInPictureManager sharedInstance].pictureInPictureActive) { + [[PLVECFloatingWindow sharedInstance] showContentView:self.playerVC.view]; // 打开应用内悬浮窗 + } + } + + // 跳转商品详情页 + self.commodityDetailVC = [[PLVCommodityDetailViewController alloc] initWithCommodityURL:commodityURL]; + self.commodityDetailVC.delegate = self; + if (self.navigationController) { + self.navigationController.navigationBarHidden = NO; + [self.navigationController pushViewController:self.commodityDetailVC animated:YES]; + } else { + [PLVLivePictureInPictureRestoreManager sharedInstance].restoreWithPresent = NO; + PLVBaseNavigationController *nav = [[PLVBaseNavigationController alloc] initWithRootViewController:self.commodityDetailVC]; + nav.navigationBarHidden = NO; + nav.modalPresentationStyle = UIModalPresentationFullScreen; + [self presentViewController:nav animated:YES completion:nil]; + } +} + #pragma mark Getter - (PLVECWatchRoomScrollView *)scrollView{ if (!_scrollView) { @@ -289,10 +319,13 @@ - (PLVECLiveDetailPageView *)liveDetailPageView{ - (PLVPopoverView *)popoverView { PLVChannelVideoType videoType = [PLVRoomDataManager sharedManager].roomData.videoType; - if (!_popoverView && videoType == PLVChannelVideoType_Live) { + if (!_popoverView) { _popoverView = [[PLVPopoverView alloc] initWithLiveType:PLVPopoverViewLiveTypeEC liveRoom:videoType == PLVChannelVideoType_Live]; _popoverView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _popoverView.delegate = self; + _popoverView.interactView.delegate = self; + if (videoType == PLVChannelVideoType_Live) { + _popoverView.delegate = self; + } } return _popoverView; } @@ -314,6 +347,13 @@ - (PLVECLinkMicAreaView *)linkMicAreaView { return _linkMicAreaView; } +- (PLVCommodityCardDetailView *)cardDetailView { + if (!_cardDetailView) { + _cardDetailView = [[PLVCommodityCardDetailView alloc] init]; + } + return _cardDetailView; +} + #pragma mark - [ Event ] #pragma mark Action - (void)closeButtonAction:(UIButton *)button { @@ -624,27 +664,7 @@ - (void)homePageView:(PLVECHomePageView *)homePageView receiveBulletinMessage:(N } - (void)homePageView:(PLVECHomePageView *)homePageView openCommodityDetail:(NSURL *)commodityURL { - PLVRoomData *roomData = [PLVRoomDataManager sharedManager].roomData; - if (roomData.videoType == PLVChannelVideoType_Live) { // 视频类型为直播 - - if (![PLVLivePictureInPictureManager sharedInstance].pictureInPictureActive) { - [[PLVECFloatingWindow sharedInstance] showContentView:self.playerVC.view]; // 打开应用内悬浮窗 - } - } - - // 跳转商品详情页 - self.commodityDetailVC = [[PLVCommodityDetailViewController alloc] initWithCommodityURL:commodityURL]; - self.commodityDetailVC.delegate = self; - if (self.navigationController) { - self.navigationController.navigationBarHidden = NO; - [self.navigationController pushViewController:self.commodityDetailVC animated:YES]; - } else { - [PLVLivePictureInPictureRestoreManager sharedInstance].restoreWithPresent = NO; - PLVBaseNavigationController *nav = [[PLVBaseNavigationController alloc] initWithRootViewController:self.commodityDetailVC]; - nav.navigationBarHidden = NO; - nav.modalPresentationStyle = UIModalPresentationFullScreen; - [self presentViewController:nav animated:YES completion:nil]; - } + [self openCommodityDetailViewControllerWithURL:commodityURL]; } - (void)homePageView:(PLVECHomePageView *)homePageView switchPause:(BOOL)pause { @@ -681,10 +701,24 @@ - (void)homePageView:(PLVECHomePageView *)homePageView switchToNoDelayWatchMode: [self.playerVC switchToNoDelayWatchMode:noDelayWatchMode]; } +- (void)homePageView_didLoginRestrict { + __weak typeof(self) weakSelf = self; + plv_dispatch_main_async_safe(^{ + [PLVECUtils showHUDWithTitle:nil detail:@"直播间太过火爆了,请稍后再来(2050407)" view:self.view afterDelay:3.0]; + }) + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [weakSelf exitCurrentController]; // 使用weakSelf,不影响self释放内存 + }); +} + - (void)homePageViewOpenRewardView:(PLVECHomePageView *)homePageView { [self.popoverView showRewardView]; } +- (void)homePageView:(PLVECHomePageView *)homePageView openCardPush:(NSDictionary *)cardInfo { + [self.popoverView.interactView openNewPushCardWithDict:cardInfo]; +} + - (void)homePageViewClickPictureInPicture:(PLVECHomePageView *)homePageView { if ([PLVLivePictureInPictureManager sharedInstance].pictureInPictureActive) { [self.playerVC stopPictureInPicture]; @@ -722,4 +756,15 @@ - (void)popoverViewDidDonatePointWithError:(NSString *)error { }) } +#pragma mark PLVInteractGenericViewDelegate + +- (void)plvInteractGenericView:(PLVInteractGenericView *)interactView loadWebViewURL:(NSURL *)url insideLoad:(BOOL)insideLoad { + if (insideLoad) { + [self.cardDetailView loadWebviewWithCardURL:url]; + [self.cardDetailView showOnView:self.view frame:CGRectMake(0, CGRectGetHeight(self.view.bounds) * 0.3, self.view.bounds.size.width, self.view.bounds.size.height * 0.7)]; + } else { + [self openCommodityDetailViewControllerWithURL:url]; + } +} + @end diff --git a/PolyvLiveScenesDemo/PLVLiveHiClassScene/Resource/PLVHCUtils.h b/PolyvLiveScenesDemo/PLVLiveHiClassScene/Resource/PLVHCUtils.h index ab08399d..4fe98e89 100644 --- a/PolyvLiveScenesDemo/PLVLiveHiClassScene/Resource/PLVHCUtils.h +++ b/PolyvLiveScenesDemo/PLVLiveHiClassScene/Resource/PLVHCUtils.h @@ -11,6 +11,7 @@ #import "PLVHCHiClassToast.h" #import #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/View/PageView/ViewController/PLVLSBeautyFilterViewController.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/View/PageView/ViewController/PLVLSBeautyFilterViewController.m index 6f277c4d..096e795a 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/View/PageView/ViewController/PLVLSBeautyFilterViewController.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/View/PageView/ViewController/PLVLSBeautyFilterViewController.m @@ -41,7 +41,20 @@ - (void)viewDidLoad { self.view.backgroundColor = [UIColor clearColor]; [self.view addSubview:self.collectionView]; [self.collectionView reloadData]; - self.selectedIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; + + PLVBFilterOption *cacheFilter = [[PLVLSBeautyViewModel sharedViewModel] getCacheSelectFilterOption]; + if (!cacheFilter || !self.dataArray) { + self.selectedIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; + }else { + for (NSInteger i = 0; i < self.dataArray.count; i++) { + PLVLSBeautyCellModel *model = self.dataArray[i]; + if ([model.filerOption.filterSpellName isEqualToString:cacheFilter.filterSpellName]) { + self.selectedIndexPath = [NSIndexPath indexPathForRow:i inSection:0]; + break; + } + } + } + [self.collectionView selectItemAtIndexPath:self.selectedIndexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; [self didSelectItemAtIndexPath:self.selectedIndexPath]; } diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/ViewModel/PLVLSBeautyViewModel.h b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/ViewModel/PLVLSBeautyViewModel.h index 7c2df63b..5ebfdd2a 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/ViewModel/PLVLSBeautyViewModel.h +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/ViewModel/PLVLSBeautyViewModel.h @@ -81,6 +81,9 @@ typedef NS_ENUM(NSInteger, PLVLSBeautyType) { /// @param beautyType 美颜类型 - (void)selectBeautyType:(PLVLSBeautyType)beautyType; +/// 获取缓存选中的滤镜 +- (PLVBFilterOption *)getCacheSelectFilterOption; + @end NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/ViewModel/PLVLSBeautyViewModel.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/ViewModel/PLVLSBeautyViewModel.m index e66e26a4..f83bbd21 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/ViewModel/PLVLSBeautyViewModel.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Beauty/ViewModel/PLVLSBeautyViewModel.m @@ -30,6 +30,7 @@ @interface PLVLSBeautyViewModel() static NSString *kBeautyOptionDictKey = @"kPLVLSBeautyOptionDictKey"; static NSString *kBeautyFilterOptionDictKey = @"kPLVLSBeautyFilterOptionDictKey"; +static NSString *kBeautyFilterSelectKey = @"kPLVLSBeautyFilterSelectKey"; static NSString *kBeautyOpenKey = @"kPLVLSkBeautyOpenKey"; static CGFloat kBeautyFilterOptionDefaultIntensity = 0.5; @@ -130,6 +131,8 @@ - (void)selectBeautyOption:(PLVBBeautyOption)option { - (void)selectBeautyFilterOption:(PLVBFilterOption *)filterOption { // 缓存当前选择的滤镜 self.currentFilterOption = filterOption; + [self saveBeautyFilterSelect:filterOption]; + // 未开启美颜 或 当前选择的不是滤镜 不继续处理 if (!self.beautyIsOpen || !filterOption || @@ -162,6 +165,20 @@ - (BOOL)isSelectedOriginFilter { return isSelectedOriginFilter; } +- (PLVBFilterOption *)getCacheSelectFilterOption { + NSString *spellName = [self getSelectedBeautyFilterSpellName]; + if (![PLVFdUtil checkStringUseable:spellName] || + !self.filterOptionArray) { + return nil; + } + for (PLVBFilterOption *filter in self.filterOptionArray) { + if ([filter.filterSpellName isEqualToString:spellName]) { + return filter; + } + } + return nil; +} + #pragma mark - [ Private Method ] #pragma mark Getter @@ -224,6 +241,12 @@ - (void)initFilterOption { plv_dict_set(self.beautyFilterOptionDict, key, value); }]; } + + PLVBFilterOption *cacheFilter = [self getCacheSelectFilterOption]; + if (cacheFilter) { + [self selectBeautyFilterOption:cacheFilter]; + } + [self saveBeautyFilterOptionDict]; } @@ -263,6 +286,22 @@ - (void)removeBeautyFilterOptionDict { [[NSUserDefaults standardUserDefaults] synchronize]; } +- (void)saveBeautyFilterSelect:(PLVBFilterOption *)filter { + if ([PLVFdUtil checkStringUseable:filter.filterSpellName]) { + [[NSUserDefaults standardUserDefaults] setObject:filter.filterSpellName forKey:kBeautyFilterSelectKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } +} + +- (NSString *)getSelectedBeautyFilterSpellName { + return [[NSUserDefaults standardUserDefaults] objectForKey:kBeautyFilterSelectKey]; +} + +- (void)removeBeautyFilterSelect { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:kBeautyFilterSelectKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + - (void)saveBeautyOpenStatus:(BOOL)open { [[NSUserDefaults standardUserDefaults] setObject:open ? @"1" : @"2" forKey:kBeautyOpenKey]; [[NSUserDefaults standardUserDefaults] synchronize]; @@ -279,6 +318,7 @@ - (BOOL)getBeautyOpenStatus { - (void)resetBeauty { [self removeBeautyOptionDict]; [self removeBeautyFilterOptionDict]; + [self removeBeautyFilterSelect]; [self initBeautyOption]; [self initFilterOption]; } diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSBaseRemindMessageCell.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSBaseRemindMessageCell.m deleted file mode 100644 index 22541d3c..00000000 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSBaseRemindMessageCell.m +++ /dev/null @@ -1,116 +0,0 @@ -// -// PLVLSRemindBaseMessageCell.m -// PolyvLiveScenesDemo -// -// Created by lijingtong on 2022/2/14. -// Copyright © 2022 PLV. All rights reserved. -// - -#import "PLVLSRemindBaseMessageCell.h" - -// 模块 -#import "PLVRoomDataManager.h" -#import "PLVChatModel.h" - -@implementation PLVLSRemindBaseMessageCell - -#pragma mark - [ Life Cycle ] - -- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { - self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; - if (self) { - self.backgroundColor = [UIColor clearColor]; - } - return self; -} - -#pragma mark - [ Public Method ] - -#pragma mark 判断model是否为有效类型 - -/// 判断model是否为有效类型,子类需覆写,默认返回NO -/// @param model 数据模型 -+ (BOOL)isModelValid:(PLVChatModel *)model{ - return NO; -} - -#pragma mark reuseIdentifier -/// 根据身份类型返回不同ID,由子类覆盖实现处理业务。 -+ (NSString *)reuseIdentifierWithUser:(PLVChatUser *)user { - return nil; -} - -#pragma mark 设置身份标签 - -+ (NSString *)actorBgColorHexStringWithUserType:(PLVRoomUserType)userType { - NSString *colorHexString = nil; - switch (userType) { - case PLVRoomUserTypeGuest: - colorHexString = @"#4399FF"; - break; - case PLVRoomUserTypeTeacher: - colorHexString = @"#FFC161"; - break; - case PLVRoomUserTypeAssistant: - colorHexString = @"#33BBC5"; - break; - case PLVRoomUserTypeManager: - colorHexString = @"#33BBC5"; - break; - default: - break; - } - return colorHexString; -} - -+ (BOOL)showActorLabelWithUser:(PLVChatUser *)user { - return (user.userType == PLVRoomUserTypeGuest || - user.userType == PLVRoomUserTypeTeacher || - user.userType == PLVRoomUserTypeAssistant || - user.userType == PLVRoomUserTypeManager); -} - -+ (UIImage*) actorImageWithUser:(PLVChatUser *)user; { - UILabel *label = [[UILabel alloc] init]; - label.font = [UIFont systemFontOfSize:10]; - label.textColor = [UIColor whiteColor]; - label.textAlignment = NSTextAlignmentCenter; - label.layer.cornerRadius = 2; - label.layer.masksToBounds = YES; - label.text = user.actor; - - NSString *backgroundColor = [self actorBgColorHexStringWithUserType:user.userType]; - if (backgroundColor) { - label.backgroundColor = [PLVColorUtil colorFromHexString:backgroundColor]; - } - NSString *actor = user.actor; - CGSize size; - if (actor && - [actor isKindOfClass:[NSString class]] && - actor.length >0) { - NSAttributedString *attr = [[NSAttributedString alloc] initWithString:actor attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:10]}]; - size = [attr boundingRectWithSize:CGSizeMake(100, 14) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil].size; - size.width += 10; - } - label.frame = CGRectMake(0, 0, size.width, 14); - UIGraphicsBeginImageContextWithOptions(label.frame.size, NO, [UIScreen mainScreen].scale); - CGContextRef ctx = UIGraphicsGetCurrentContext(); - [label.layer renderInContext:ctx]; - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return image; -} - -#pragma mark Utils - -+ (BOOL)isLoginUser:(NSString *)userId { - if (!userId || ![userId isKindOfClass:[NSString class]]) { - return NO; - } - - BOOL isLoginUser = [userId isEqualToString:[PLVRoomDataManager sharedManager].roomData.roomUser.viewerId]; - return isLoginUser; -} - -@end diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSRemindImageMessageCell.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSRemindImageMessageCell.m index 8e4d4125..f762a05e 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSRemindImageMessageCell.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSRemindImageMessageCell.m @@ -21,6 +21,7 @@ #import #import #import +#import @interface PLVLSRemindImageMessageCell() diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSRemindSpeakMessageCell.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSRemindSpeakMessageCell.m index e25cbea7..3a65b4c8 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSRemindSpeakMessageCell.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/Cell/PLVLSRemindSpeakMessageCell.m @@ -23,6 +23,7 @@ #import #import #import +#import @interface PLVLSRemindSpeakMessageCell() diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/PLVLSChatroomAreaView.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/PLVLSChatroomAreaView.m index f3e280ef..7ba59223 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/PLVLSChatroomAreaView.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/PLVLSChatroomAreaView.m @@ -83,19 +83,20 @@ - (void)layoutSubviews { BOOL isPad = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad; CGFloat normalPadding = isPad ? 12 : 8; + CGFloat btnWidth = 32; CGFloat areaViewWidth = [UIScreen mainScreen].bounds.size.width * 0.34; // 支持小屏后,聊天室区域保持宽度34%不变 CGFloat areaViewHeight = self.bounds.size.height; - self.chatroomListView.frame = CGRectMake(normalPadding, 0, areaViewWidth - normalPadding, areaViewHeight - 36 - 8 * 2); + self.chatroomListView.frame = CGRectMake(normalPadding, 0, areaViewWidth - normalPadding, areaViewHeight - btnWidth - 8 * 2); BOOL remindEnabled = [PLVRoomDataManager sharedManager].roomData.menuInfo.remindEnabled; - CGFloat leftWidth = 36 * (remindEnabled ? 2 : 1); - self.leftToolView.frame = CGRectMake(normalPadding, areaViewHeight - normalPadding - 36, leftWidth, 36); - self.hideListViewButton.frame = CGRectMake(0, 0, 36, 36); + CGFloat leftWidth = btnWidth * (remindEnabled ? 2 : 1); + self.leftToolView.frame = CGRectMake(normalPadding, areaViewHeight - normalPadding - btnWidth, leftWidth, btnWidth); + self.hideListViewButton.frame = CGRectMake(0, 0, btnWidth, btnWidth); if (remindEnabled) { self.remindButton.hidden = NO; - self.remindButton.frame = CGRectMake(36, 0, 36, 36); - self.remindBadgeView.frame = CGRectMake(36 - 6 * 2, 6, 6, 6); + self.remindButton.frame = CGRectMake(btnWidth, 0, btnWidth, btnWidth); + self.remindBadgeView.frame = CGRectMake(btnWidth - 6 * 2, 6, 6, 6); } else { self.remindButton.hidden = YES; self.remindButton.frame = CGRectZero; @@ -104,9 +105,9 @@ - (void)layoutSubviews { //根据频道是否是音频模式而导致布局不同 if ([PLVRoomDataManager sharedManager].roomData.isOnlyAudio) { - self.toolbarView.frame = CGRectMake(CGRectGetMaxX(self.leftToolView.frame) + 12, CGRectGetMinY(self.leftToolView.frame), 180, 36); + self.toolbarView.frame = CGRectMake(CGRectGetMaxX(self.leftToolView.frame) + 12, CGRectGetMinY(self.leftToolView.frame), 180, btnWidth); } else { - self.toolbarView.frame = CGRectMake(CGRectGetMaxX(self.leftToolView.frame) + 12, CGRectGetMinY(self.leftToolView.frame), 252, 36); + self.toolbarView.frame = CGRectMake(CGRectGetMaxX(self.leftToolView.frame) + 12, CGRectGetMinY(self.leftToolView.frame), 252, btnWidth); } self.receiveNewMessageView.frame = CGRectMake(0, self.chatroomListView.frame.size.height - 28, 86, 28); } @@ -229,7 +230,7 @@ - (UIView *)leftToolView { if (!_leftToolView) { _leftToolView = [[UIView alloc] init]; _leftToolView.backgroundColor = [PLVColorUtil colorFromHexString:@"#1B202D" alpha:0.4]; - _leftToolView.layer.cornerRadius = 18; + _leftToolView.layer.cornerRadius = 16; } return _leftToolView; } diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/View/PLVLSChatroomToolbar.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/View/PLVLSChatroomToolbar.m index ca7e1c91..43c2cef8 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/View/PLVLSChatroomToolbar.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Chatroom/View/PLVLSChatroomToolbar.m @@ -14,8 +14,8 @@ /// 工具类 #import "PLVLSUtils.h" -static CGFloat kToolbarWidth = 36.0; -static CGFloat kToolbarHeight = 36.0; +static CGFloat kToolbarWidth = 32.0; +static CGFloat kToolbarHeight = 32.0; @interface PLVLSChatroomToolbar () @@ -67,7 +67,7 @@ - (void)layoutSubviews { self.foldButton.frame = CGRectMake(0, 0, kToolbarWidth, kToolbarHeight); CGFloat originY = CGRectGetMaxX(self.foldButton.frame); - self.buttonsContainer.frame = CGRectMake(originY, 0, self.bounds.size.width - originY, 36); + self.buttonsContainer.frame = CGRectMake(originY, 0, self.bounds.size.width - originY, 32); self.microphoneButton.frame = CGRectMake(5, 0, kToolbarWidth, kToolbarHeight); if ([PLVRoomDataManager sharedManager].roomData.isOnlyAudio) { originY = CGRectGetMaxX(self.microphoneButton.frame) + 5; @@ -87,7 +87,7 @@ - (UIView *)bgView { if (!_bgView) { _bgView = [[UIView alloc] init]; _bgView.backgroundColor = [UIColor colorWithRed:0x1b/255.0 green:0x20/255.0 blue:0x2d/255.0 alpha:0.4]; - _bgView.layer.cornerRadius = 18; + _bgView.layer.cornerRadius = 16; _bgView.layer.masksToBounds = YES; } return _bgView; @@ -245,7 +245,7 @@ - (void)hideToolbarButton:(BOOL)hide { self.foldButton.selected = !hide; if (hide) { - self.bgView.frame = CGRectMake(0, 0, 36, 36); + self.bgView.frame = CGRectMake(0, 0, 32, 32); } else { self.bgView.frame = self.bounds; } diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/PLVLSDocumentAreaView.h b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/PLVLSDocumentAreaView.h index 3c6935d2..2717120e 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/PLVLSDocumentAreaView.h +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/PLVLSDocumentAreaView.h @@ -31,6 +31,19 @@ NS_ASSUME_NONNULL_BEGIN /// @param whiteboard YES:显示白板,NO:显示文档 - (void)documentAreaView:(PLVLSDocumentAreaView *)documentAreaView didShowWhiteboardOrDocument:(BOOL)whiteboard; +/// 讲师切换摄像头和文档的位置 +/// @param documentAreaView 白板&PPT区域对象 +/// @param pptView 需要同外部交换的 白板&PPT 视图 +/// @param currentDocument 当前是否交换文档 +- (void)documentAreaView:(PLVLSDocumentAreaView *)documentAreaView pptView:(UIView *)pptView exchangeDocument:(BOOL)currentDocument; + +/// 切换文档区域显示的位置,在主视图或者外部显示 +/// @param documentAreaView 白板&PPT区域对象 +/// @param pptView 需要同外部交换的 白板&PPT 视图 +/// @param pptToMain 白板&PPT是否需要显示在主视图 +/// @param needSync 是否需要同步到远端(在本地用户点击切换按钮时则为YES) +- (void)documentAreaView:(PLVLSDocumentAreaView *)documentAreaView pptView:(UIView *)pptView changePPTPositionToMain:(BOOL)pptToMain syncRemoteUser:(BOOL)needSync; + @end @interface PLVLSDocumentAreaView : UIView @@ -66,6 +79,11 @@ NS_ASSUME_NONNULL_BEGIN /// @param show YES: 显示 NO:隐藏 - (void)documentToolViewShow:(BOOL)show; +/// 承载展示外部视图 +/// +/// @param externalView 外部视图 +- (void)displayExternalView:(UIView *)externalView; + @end NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/PLVLSDocumentAreaView.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/PLVLSDocumentAreaView.m index a3fe7035..20c2baa4 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/PLVLSDocumentAreaView.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/PLVLSDocumentAreaView.m @@ -36,6 +36,7 @@ @interface PLVLSDocumentAreaView ()< /// UI @property (nonatomic, strong) UIActivityIndicatorView *viewLoading; // webView加载 +@property (nonatomic, strong) UIView *contentBackgroundView; // 内容背景视图 负责承载PPT 功能模块视图 和 连麦视图 @property (nonatomic, strong) PLVDocumentView *pptView; // PPT 功能模块视图 @property (nonatomic, strong) PLVLSDocumentNumView *pageNum; // 页码 @property (nonatomic, strong) PLVLSDocumentToolView *toolView; // 控制条视图 @@ -50,6 +51,7 @@ @interface PLVLSDocumentAreaView ()< @property (nonatomic, assign, readonly) PLVRoomUserType viewerType; @property (nonatomic, assign) NSInteger lastAutoId; // 直播中断前的文档autoId @property (nonatomic, assign) NSInteger lastPageId; // 直播中断前的文档pageId +@property (nonatomic, assign) BOOL isMainSpeaker; // 本地用户是否是主讲人 @end @@ -64,7 +66,8 @@ - (instancetype)init { self.layer.cornerRadius = 8; self.clipsToBounds = YES; - [self addSubview:self.pptView]; + [self addSubview:self.contentBackgroundView]; + [self displayExternalView:self.pptView]; [self addSubview:self.docPlaceholder]; [self addSubview:self.waitLivePlaceholderView]; [self addSubview:self.brushView]; @@ -74,6 +77,7 @@ - (instancetype)init { [self startLoading]; if (self.viewerType == PLVRoomUserTypeGuest) { + self.toolView.hidden = YES; [self showWaitLivePlaceholderView:YES]; [self.toolView showBtnBrush:NO]; [self.toolView showBtnAddPage:NO]; @@ -87,7 +91,7 @@ - (instancetype)init { - (void)layoutSubviews { [super layoutSubviews]; - self.pptView.frame = self.bounds; + self.contentBackgroundView.frame = self.bounds; self.docPlaceholder.frame = self.bounds; self.waitLivePlaceholderView.frame = self.bounds; @@ -111,12 +115,12 @@ - (void)layoutSubviews { CGFloat maxBrushWidth = bgSize.width - leftPad - rightPad; CGFloat brushWidth = MIN(maxBrushWidth, 504); - CGFloat brushHeight = 36; + CGFloat brushHeight = 32; self.brushView.frame = CGRectMake(bgSize.width - brushWidth - rightPad, bgSize.height - brushHeight - bottomPad, brushWidth, brushHeight); - CGFloat toolViewWidth = 36; + CGFloat toolViewWidth = 32; CGFloat toolViewHeight = bgSize.height - CGRectGetMaxY(self.pageNum.frame) - 12 - bottomPad; self.toolView.frame = CGRectMake(bgSize.width - toolViewWidth - rightPad, CGRectGetMaxY(self.pageNum.frame) + 12, @@ -133,6 +137,13 @@ - (void)layoutSubviews { #pragma mark - Getter +- (UIView *)contentBackgroundView { + if (!_contentBackgroundView) { + _contentBackgroundView = [[UIView alloc] init]; + } + return _contentBackgroundView; +} + - (PLVDocumentView *)pptView { if (! _pptView) { _pptView = [[PLVDocumentView alloc] initWithScene:PLVDocumentViewSceneStreamer]; @@ -280,12 +291,17 @@ - (void)finishClass { [self.toolView setFullScreenButtonSelected:NO]; [self.pageNum setCurrentPage:0 totalPage:0]; [self showWaitLivePlaceholderView:YES]; + [self documentView_changePPTPositionToMain:YES]; } } - (void)updateDocumentSpeakerAuth:(BOOL)auth { + _isMainSpeaker = auth; [self.toolView showBtnNexth:auth]; [self.toolView showBtnPrevious:auth]; + if (auth) { + [self documentView_changePPTPositionToMain:self.pptView.mainSpeakerPPTOnMain]; + } } - (NSDictionary *)getCurrentDocumentInfoDict { @@ -309,6 +325,19 @@ - (void)synchronizeDocumentData { - (void)documentToolViewShow:(BOOL)show { self.toolView.hidden = show; } + +- (void)displayExternalView:(UIView *)externalView { + if (externalView && [externalView isKindOfClass:UIView.class]) { + [self updateControlsWithExternalView:externalView]; + [self removeSubview:self.contentBackgroundView]; + externalView.frame = self.contentBackgroundView.bounds; + externalView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.contentBackgroundView insertSubview:externalView atIndex:0]; + }else{ + NSLog(@"PLVLSDocumentAreaView - displayExternalView failed, externalView:%@",externalView); + } +} + #pragma mark - [ Private Methods ] // 开启webview loading @@ -328,6 +357,51 @@ - (void)stopLoading { _viewLoading = nil; } +- (void)updateControlsWithExternalView:(UIView *)externalView { + if ([externalView isKindOfClass:PLVDocumentView.class]) { + self.pageNum.alpha = 1; + if ([self canManageDocuments]) { + // 有管理文档的权限 + [self.toolView showBtnNexth:YES]; + [self.toolView showBtnPrevious:YES]; + } + if (self.viewerType == PLVRoomUserTypeTeacher) { + // 嘉宾暂无画笔权限 + [self.toolView showBtnAddPage:YES]; + [self.toolView showBtnBrush:YES]; + } + } else { + self.brushView.hidden = YES; + self.pageNum.alpha = 0; + [self.toolView showBtnNexth:NO]; + [self.toolView showBtnPrevious:NO]; + [self.toolView showBtnBrush:NO]; + [self.toolView showBtnAddPage:NO]; + [self.toolView setBrushSelected:NO]; + [self.pptView setPaintStatus:NO]; + } +} + +- (BOOL)canManageDocuments { + if (self.viewerType == PLVRoomUserTypeTeacher || (self.viewerType == PLVRoomUserTypeGuest && self.isMainSpeaker)) { + return YES; + } + + return NO; +} + +- (void)removeSubview:(UIView *)superview{ + for (UIView * subview in superview.subviews) { [subview removeFromSuperview]; } +} + +#pragma mark Callback + +- (void)callbackForChangePPTPositionToMain:(BOOL)pptToMain syncRemoteUser:(BOOL)needSync { + if (self.delegate && [self.delegate respondsToSelector:@selector(documentAreaView:pptView:changePPTPositionToMain:syncRemoteUser:)]) { + [self.delegate documentAreaView:self pptView:self.pptView changePPTPositionToMain:pptToMain syncRemoteUser:needSync]; + } +} + #pragma mark - PLVStreamerPPTView Delegate - (void)documentView_webViewDidFinishLoading { @@ -341,11 +415,17 @@ - (void)documentView_webViewLoadFailWithError:(NSError *)error { [PLVLSUtils showToastInHomeVCWithMessage:@"PPT 加载失败"]; } +- (void)documentView_changePPTPositionToMain:(BOOL)pptToMain { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.toolView setChangeButtonSelected:!pptToMain]; + [self callbackForChangePPTPositionToMain:pptToMain syncRemoteUser:NO]; + }); +} + - (void)documentView_inputWithText:(NSString *)inputText textColor:(NSString *)textColor { [self.inputView presentWithText:inputText textColor:textColor inViewController:[PLVLSUtils sharedUtils].homeVC]; } - - (void)documentView_changeWithAutoId:(NSUInteger)autoId imageUrls:(NSArray *)imageUrls fileName:(NSString *)fileName { if ([PLVFdUtil checkStringUseable:fileName]) { /// 续播时需要直接显示文档详情 @@ -420,6 +500,10 @@ - (void)controlToolsView:(PLVLSDocumentToolView *)controlToolsView turnNextPage: } } +- (void)controlToolsView:(PLVLSDocumentToolView *)controlToolsView changePPTPositionToMain:(BOOL)pptToMain { + [self callbackForChangePPTPositionToMain:pptToMain syncRemoteUser:YES]; +} + #pragma mark - PLVSBrushView Delegate - (void)brushView:(PLVLSDocumentBrushView *)brushView changeType:(PLVLSDocumentBrushViewType)type { diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentBrushView.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentBrushView.m index 3160bdc9..90027005 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentBrushView.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentBrushView.m @@ -47,24 +47,24 @@ - (void)layoutSubviews { CGSize selfSize = self.bounds.size; - CGFloat toolWidth = 36; + CGFloat toolWidth = 32; CGFloat colorWidth = 28; CGFloat toolItemY = 0; CGFloat margin = 8; if (self.bounds.size.width < 504) { // iphone小屏适配 NSInteger subViewCount = self.subviews.count + self.viewColor.subviews.count + 1; - CGFloat subViewWidth = toolWidth * 5 + 1 + colorWidth * self.colors.count + 36 + 4; + CGFloat subViewWidth = toolWidth * 5 + 1 + colorWidth * self.colors.count + 32 + 4; if (self.bounds.size.width < subViewWidth) { // iphone 5s适配 toolWidth = 32; colorWidth = 26; toolItemY = (selfSize.height - toolWidth) / 2.0f; - subViewWidth = toolWidth * 5 + 1 + colorWidth * self.colors.count + 36 + 4; + subViewWidth = toolWidth * 5 + 1 + colorWidth * self.colors.count + 32 + 4; } margin = (self.bounds.size.width - subViewWidth) / subViewCount; } - self.btnClearAll.frame = CGRectMake(selfSize.width - toolWidth - 36 - 2 * margin, toolItemY, toolWidth, toolWidth); + self.btnClearAll.frame = CGRectMake(selfSize.width - toolWidth - 32 - 2 * margin, toolItemY, toolWidth, toolWidth); self.btnClear.frame = CGRectMake(UIViewGetLeft(self.btnClearAll) - toolWidth - margin, toolItemY, toolWidth, toolWidth); self.btnText.frame = CGRectMake(UIViewGetLeft(self.btnClear) - toolWidth - margin, toolItemY, toolWidth, toolWidth); self.btnArrow.frame = CGRectMake(UIViewGetLeft(self.btnText) - toolWidth - margin, toolItemY, toolWidth, toolWidth); @@ -198,7 +198,7 @@ - (UIView *)viewBg { if (! _viewBg) { _viewBg = [[UIView alloc] init]; _viewBg.backgroundColor = PLV_UIColorFromRGBA(@"#1B202D", 0.2f); - _viewBg.layer.cornerRadius = 18; + _viewBg.layer.cornerRadius = 16; _viewBg.clipsToBounds = YES; } return _viewBg; diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentToolView.h b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentToolView.h index d1407c93..ff726c92 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentToolView.h +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentToolView.h @@ -38,6 +38,12 @@ NS_ASSUME_NONNULL_BEGIN /// @param isNextPage YES:下一页,NO:上一页 - (void)controlToolsView:(PLVLSDocumentToolView *)controlToolsView turnNextPage:(BOOL)isNextPage; +/// 交换文档PPT位置到主画面 +/// +/// @param controlToolsView 控制条对象 +/// @param pptToMain 是否改变文档PPT位置到主画面(YES: 文档位于主画面 NO 文档不在主画面)默认为YES +- (void)controlToolsView:(PLVLSDocumentToolView *)controlToolsView changePPTPositionToMain:(BOOL)pptToMain; + @end @interface PLVLSDocumentToolView : UIView @@ -58,6 +64,10 @@ NS_ASSUME_NONNULL_BEGIN /// @param isSelected YES: 选中,NO:不选中 - (void)setFullScreenButtonSelected:(BOOL)isSelected; +/// 设置交换按钮选中 +/// @param isSelected YES: 选中,NO:不选中 +- (void)setChangeButtonSelected:(BOOL)isSelected; + /// 更新页码 /// /// @note 该方法主要目的是对 ‘上一页按钮’、‘下一页按钮’ 进行状态刷新; diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentToolView.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentToolView.m index 6de1230e..66133d56 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentToolView.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/Document/DocumentArea/View/PLVLSDocumentToolView.m @@ -18,6 +18,7 @@ @interface PLVLSDocumentToolView () @property (nonatomic, strong) UIButton *btnFullScreen; // 全屏 @property (nonatomic, strong) UIButton *btnNext; // 下一页 @property (nonatomic, strong) UIButton *btnPrevious; // 上一页 +@property (nonatomic, strong) UIButton *changeButton; // 交换 @end @@ -35,8 +36,8 @@ - (instancetype)init { - (void)layoutSubviews { [super layoutSubviews]; - CGFloat btnWidth = 36; - CGFloat maginTop = 12; + CGFloat btnWidth = 32; + CGFloat maginTop = 8; CGFloat relativeY = UIViewGetHeight(self); self.btnBrush.frame = CGRectMake(0, relativeY - btnWidth, btnWidth, btnWidth); @@ -47,7 +48,10 @@ - (void)layoutSubviews { relativeY = (!self.btnAddPage.hidden && self.btnAddPage.alpha == 1) ? (UIViewGetTop(self.btnAddPage) - maginTop) : relativeY; self.btnFullScreen.frame = CGRectMake(0, relativeY - btnWidth, btnWidth, btnWidth); - relativeY = (self.btnFullScreen.hidden ? relativeY : (UIViewGetTop(self.btnFullScreen) - maginTop)); + relativeY = (!self.btnFullScreen.hidden && self.btnFullScreen.alpha == 1) ? (UIViewGetTop(self.btnFullScreen) - maginTop) : relativeY; + self.changeButton.frame = CGRectMake(0, relativeY - btnWidth, btnWidth, btnWidth); + + relativeY = (self.changeButton.hidden ? relativeY : (UIViewGetTop(self.changeButton) - maginTop)); BOOL isPad = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad; CGFloat btnNextTop = isPad ? (UIViewGetHeight(self) / 2 - btnWidth) : (relativeY - btnWidth); self.btnNext.frame = CGRectMake(0, btnNextTop, btnWidth, btnWidth); @@ -84,13 +88,19 @@ - (void)setFullScreenButtonSelected:(BOOL)isSelected { self.btnFullScreen.selected = isSelected; } +- (void)setChangeButtonSelected:(BOOL)isSelected { + self.changeButton.selected = isSelected; +} + - (void)showBtnBrush:(BOOL)show{ self.btnBrush.hidden = !show; + [self setNeedsLayout]; [self layoutIfNeeded]; } - (void)showBtnAddPage:(BOOL)show{ self.btnAddPage.hidden = !show; + [self setNeedsLayout]; [self layoutIfNeeded]; } @@ -117,6 +127,7 @@ - (void)setupUI { [self addSubview:self.btnFullScreen]; [self addSubview:self.btnNext]; [self addSubview:self.btnPrevious]; + [self addSubview:self.changeButton]; } // 加载图片 @@ -195,6 +206,18 @@ - (UIButton *)btnPrevious { return _btnPrevious; } +- (UIButton *)changeButton { + if (!_changeButton) { + _changeButton = [[UIButton alloc] init]; + [_changeButton setImage:[self getImageWithName:@"plvls_ppt_btn_switch"] + forState:UIControlStateNormal]; + [_changeButton setImage:[self getImageWithName:@"plvls_ppt_btn_switch"] + forState:UIControlStateSelected]; + [_changeButton addTarget:self action:@selector(changeButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + } + return _changeButton; +} + #pragma mark - [ Event ] #pragma mark Action @@ -232,4 +255,11 @@ - (void)pageTurnAction:(UIButton *)button{ } } +- (void)changeButtonAction:(UIButton *)button { + button.selected = !button.selected; + if (self.delegate && [self.delegate respondsToSelector:@selector(controlToolsView:changePPTPositionToMain:)]) { + [self.delegate controlToolsView:self changePPTPositionToMain:!button.selected]; + } +} + @end diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/PLVLSLinkMicAreaView.h b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/PLVLSLinkMicAreaView.h index 011b09bf..af3671dd 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/PLVLSLinkMicAreaView.h +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/PLVLSLinkMicAreaView.h @@ -9,6 +9,7 @@ #import #import "PLVLinkMicOnlineUser.h" +#import "PLVLSLinkMicWindowsView.h" NS_ASSUME_NONNULL_BEGIN @@ -28,6 +29,21 @@ NS_ASSUME_NONNULL_BEGIN /// 外部需实现此代理方法,以让 连麦窗口列表视图 正确获得当前连麦用户数据 - (void)reloadLinkMicUserWindows; +/// 更新在线用户到第一画面 +/// @param linkMicUserId 连麦用户id +/// @param toFirstSite 是否到第一画面 +- (void)updateFirstSiteWindowCellWithUserId:(NSString *)linkMicUserId toFirstSite:(BOOL)toFirstSite; + +/// RTC画面窗口(第一画面连麦窗口)和外部视图交换位置 +/// +/// @note 此方法表示用户希望 第一画面连麦视图与外部视图 externalView交换位置,将触发(plvLSLinkMicAreaView:showFirstSiteWindowCellOnExternal:)此代理方法 +- (void)firstSiteWindowCellExchangeWithExternal:(UIView *)externalView; + +/// 回滚外部视图和第一画面窗口到原来的位置 +/// +///@note 此方法表示用户希望 第一画面连麦视图与外部视图 externalView回滚至原本位置,将触发(plvLSLinkMicAreaView:rollbackExternalView:)此代理方法 +- (void)rollbackFirstSiteWindowCellAndExternalView; + @end /// 连麦区域视图Delegate @@ -55,6 +71,20 @@ NS_ASSUME_NONNULL_BEGIN - (PLVLinkMicOnlineUser *)plvLCLinkMicWindowsView:(PLVLSLinkMicAreaView *)linkMicAreaView getUserModelFromOnlineUserArrayWithIndex:(NSInteger)targetIndex __deprecated_msg("use [plvLSLinkMicAreaView:getUserModelFromOnlineUserArrayWithIndex:] instead."); - (PLVLinkMicOnlineUser *)plvLSLinkMicAreaView:(PLVLSLinkMicAreaView *)linkMicAreaView getUserModelFromOnlineUserArrayWithIndex:(NSInteger)targetIndex; +/// RTC画面窗口 需外部展示 ‘第一画面连麦窗口’ +/// +/// @param linkMicAreaView 连麦区域视图 +/// @param windowCell 第一画面连麦窗口视图 (需外部进行添加展示) +- (void)plvLSLinkMicAreaView:(PLVLSLinkMicAreaView *)linkMicAreaView showFirstSiteWindowCellOnExternal:(UIView *)windowCell; + +/// 恢复外部视图位置 +/// +/// @note 当外部视图需要回归原位时,此回调将被触发;接收到此回调后,可将 externalView 重新布局在原位上 +/// +/// @param linkMicAreaView 连麦区域视图 +/// @param externalView 被添加在连麦区域视图上的外部视图 +- (void)plvLSLinkMicAreaView:(PLVLSLinkMicAreaView *)linkMicAreaView rollbackExternalView:(UIView *)externalView; + @end NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/PLVLSLinkMicAreaView.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/PLVLSLinkMicAreaView.m index e8d575e7..ded9fac4 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/PLVLSLinkMicAreaView.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/PLVLSLinkMicAreaView.m @@ -8,7 +8,6 @@ #import "PLVLSLinkMicAreaView.h" -#import "PLVLSLinkMicWindowsView.h" @interface PLVLSLinkMicAreaView () @@ -19,8 +18,8 @@ @interface PLVLSLinkMicAreaView () /// view hierarchy /// /// (UIView) superview -/// └── (PLVLCLinkMicAreaView) self (lowest) -/// └── (PLVLCLinkMicWindowsView) windowsView +/// └── (PLVLSLinkMicAreaView) self (lowest) +/// └── (PLVLSLinkMicWindowsView) windowsView @property (nonatomic, strong) PLVLSLinkMicWindowsView * windowsView; // 连麦窗口列表视图 (负责展示 多个连麦成员RTC画面窗口,该视图支持左右滑动浏览) @end @@ -44,10 +43,25 @@ - (void)layoutSubviews { self.windowsView.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)); } +#pragma mark - [ Public Methods ] + - (void)reloadLinkMicUserWindows{ [self.windowsView reloadLinkMicUserWindows]; } +- (void)updateFirstSiteWindowCellWithUserId:(NSString *)linkMicUserId toFirstSite:(BOOL)toFirstSite { + [self.windowsView updateFirstSiteWindowCellWithUserId:linkMicUserId toFirstSite:toFirstSite]; + +} + +- (void)firstSiteWindowCellExchangeWithExternal:(UIView *)externalView { + [self.windowsView firstSiteWindowCellExchangeWithExternal:externalView]; +} + +- (void)rollbackFirstSiteWindowCellAndExternalView { + [self.windowsView rollbackFirstSiteWindowCellAndExternalView]; +} + #pragma mark - [ Private Methods ] - (void)setupUI{ // 添加 连麦窗口列表视图 @@ -93,5 +107,17 @@ - (PLVLinkMicOnlineUser *)plvLSLinkMicWindowsView:(PLVLSLinkMicWindowsView *)win } } +- (void)plvLSLinkMicWindowsView:(PLVLSLinkMicWindowsView *)windowsView showFirstSiteWindowCellOnExternal:(UIView *)windowCell { + if ([self.delegate respondsToSelector:@selector(plvLSLinkMicAreaView:showFirstSiteWindowCellOnExternal:)]) { + [self.delegate plvLSLinkMicAreaView:self showFirstSiteWindowCellOnExternal:windowCell]; + } +} + +/// 连麦窗口需要回退外部视图 +- (void)plvLSLinkMicWindowsView:(PLVLSLinkMicWindowsView *)windowsView rollbackExternalView:(UIView *)externalView { + if (self.delegate && [self.delegate respondsToSelector:@selector(plvLSLinkMicAreaView:rollbackExternalView:)]) { + [self.delegate plvLSLinkMicAreaView:self rollbackExternalView:externalView]; + } +} @end diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowCell.h b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowCell.h index 30a1b73e..d946a585 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowCell.h +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowCell.h @@ -17,6 +17,12 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark 方法 - (void)setModel:(PLVLinkMicOnlineUser *)userModel; +/// 切换至 显示默认内容视图 +- (void)switchToShowRtcContentView:(UIView *)rtcCanvasView; + +/// 切换至 显示外部内容视图 +- (void)switchToShowExternalContentView:(UIView *)externalContentView; + @end NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowCell.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowCell.m index 0fe51160..2375274b 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowCell.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowCell.m @@ -121,7 +121,6 @@ - (void)setModel:(PLVLinkMicOnlineUser *)userModel { userModel.cameraShouldShowChangedBlock = ^(PLVLinkMicOnlineUser * _Nonnull onlineUser) { [onlineUser.canvasView rtcViewShow:onlineUser.currentCameraShouldShow]; }; - [self contentBackgroudViewAddView:userModel.canvasView]; /// 音量 [self setMicButtonNormalImageWithVolume:userModel.currentVolume]; @@ -161,6 +160,24 @@ - (void)setModel:(PLVLinkMicOnlineUser *)userModel { [self setNeedsLayout]; } +/// 切换至 显示默认内容视图 +- (void)switchToShowRtcContentView:(UIView *)rtcCanvasView{ + // 移除 contentBackgroudView 上的外部视图 + [self removeSubview:self.contentBackgroudView]; + // contentBackgroudView 移至 contentView 的最底层 + [self.contentView sendSubviewToBack:self.contentBackgroudView]; + // contentBackgroudView 承载 rtcCanvasView + [self contentBackgroudViewAddView:rtcCanvasView]; +} + +/// 切换至 显示外部内容视图 +- (void)switchToShowExternalContentView:(UIView *)externalContentView{ + // contentBackgroudView 移至 contentView 的最顶层 + [self.contentView bringSubviewToFront:self.contentBackgroudView]; + // contentBackgroudView 承载外部未知具体类型的视图 + [self contentBackgroudViewAddView:externalContentView]; +} + #pragma mark - [ Private Methods ] - (UIImage *)getImageWithName:(NSString *)imageName{ return [PLVLSUtils imageForLinkMicResource:imageName]; diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowsView.h b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowsView.h index b1e2f893..86cb2c13 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowsView.h +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowsView.h @@ -9,6 +9,7 @@ #import #import "PLVLinkMicOnlineUser.h" +#import "PLVLSLinkMicWindowCell.h" NS_ASSUME_NONNULL_BEGIN @@ -25,6 +26,21 @@ NS_ASSUME_NONNULL_BEGIN /// 外部需实现此方法,以让 连麦窗口列表视图 正确获得当前连麦用户数据 - (void)reloadLinkMicUserWindows; +/// 更新在线用户到第一画面 +/// @param linkMicUserId 连麦用户id +/// @param toFirstSite 是否到第一画面 +- (void)updateFirstSiteWindowCellWithUserId:(NSString *)linkMicUserId toFirstSite:(BOOL)toFirstSite; + +/// RTC画面窗口(第一画面连麦窗口)和外部视图交换位置 +/// +/// @note 此方法表示用户希望 第一画面连麦视图与外部视图 externalView交换位置,将触发(plvLSLinkMicWindowsView:showFirstSiteWindowCellOnExternal:)此代理方法 +- (void)firstSiteWindowCellExchangeWithExternal:(UIView *)externalView; + +/// 回滚外部视图和第一画面窗口到原来的位置 +/// +///@note 此方法表示用户希望 第一画面连麦视图与外部视图 externalView回滚至原本位置,将触发(plvLSLinkMicWindowsView:rollbackExternalView:)此代理方法 +- (void)rollbackFirstSiteWindowCellAndExternalView; + @end @protocol PLVLSLinkMicWindowsViewDelegate @@ -51,5 +67,19 @@ NS_ASSUME_NONNULL_BEGIN - (PLVLinkMicOnlineUser *)plvLCLinkMicWindowsView:(PLVLSLinkMicWindowsView *)windowsView getUserModelFromOnlineUserArrayWithIndex:(NSInteger)targetIndex __deprecated_msg("use [plvLSLinkMicWindowsView:getUserModelFromOnlineUserArrayWithIndex:] instead."); - (PLVLinkMicOnlineUser *)plvLSLinkMicWindowsView:(PLVLSLinkMicWindowsView *)windowsView getUserModelFromOnlineUserArrayWithIndex:(NSInteger)targetIndex; +/// 连麦窗口列表视图 需外部展示 ‘第一画面连麦窗口’ +/// +/// @param windowsView 连麦窗口列表视图 +/// @param windowCell 第一画面连麦窗口视图 (需外部进行添加展示) +- (void)plvLSLinkMicWindowsView:(PLVLSLinkMicWindowsView *)windowsView showFirstSiteWindowCellOnExternal:(UIView *)windowCell; + +/// 连麦窗口需要回退外部视图 +/// +/// @note 通过此回调,告知外部对象,外部视图将进行位置回退恢复。外部无需关心 windowCell连麦视图 的收尾处理工作,仅需将 externalView外部视图 恢复至正确位置。 +/// +/// @param windowsView 连麦窗口列表视图 +/// @param externalView 正在显示在列表中的外部视图 +- (void)plvLSLinkMicWindowsView:(PLVLSLinkMicWindowsView *)windowsView rollbackExternalView:(UIView *)externalView; + @end NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowsView.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowsView.m index 66edaafc..4b6cd261 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowsView.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Modules/LinkMic/View/WindowsView/PLVLSLinkMicWindowsView.m @@ -9,7 +9,6 @@ #import "PLVLSLinkMicWindowsView.h" #import "PLVLSUtils.h" -#import "PLVLSLinkMicWindowCell.h" #import "PLVLinkMicOnlineUser+LS.h" #import @@ -21,20 +20,21 @@ @interface PLVLSLinkMicWindowsView ()< #pragma mark 数据 @property (nonatomic, readonly) NSArray * dataArray; // 只读,当前连麦在线用户数组 @property (nonatomic, copy) void (^collectionReloadBlock) (void); -@property (nonatomic, copy) NSString * currentMainSpeakerUserId; -@property (nonatomic, copy) NSString * manualSwitchMainSpeakerUserId; // 当前由用户触发的、切去主屏显示的、成为第一画面的用户连麦id +@property (nonatomic, strong) NSIndexPath * showingExternalCellIndexPath; // 正在显示外部视图的Cell,所对应的下标 +@property (nonatomic, copy) NSString * showingExternalCellLinkMicUserId; // 正在显示外部视图的Cell,所对应的用户Id (将用于更新 showingExternalCellIndexPath 属性) +@property (nonatomic, copy) NSString * firstSiteLinkMicUserId; // 当前第一画面用户id(目前第一画面为主讲用户或者讲师) +@property (nonatomic, assign) NSInteger firstSiteUserIndex; // 第一画面用户数据对应的下标 #pragma mark UI -@property (nonatomic, weak) UIView * externalView; // 外部视图 (正在被显示在 PLVLCLinkMicWindowsView 窗口列表中的外部视图;弱引用) -@property (nonatomic, readonly) UICollectionViewFlowLayout * collectionViewLayout; // 集合视图的布局 - /// view hierarchy /// -/// (PLVLCLinkMicWindowsView) self +/// (PLVLSLinkMicWindowsView) self /// └── (UICollectionView) collectionView (lowest) -///    ├── (PLVLCLinkMicWindowCell) windowCell +///    ├── (PLVLSLinkMicWindowCell) windowCell ///    ├── ... -///    └── (PLVLCLinkMicWindowCell) windowCell +///    └── (PLVLSLinkMicWindowCell) windowCell +@property (nonatomic, weak) UIView * externalView; // 外部视图 (正在被显示在 PLVLSLinkMicWindowsView 窗口列表中的外部视图;弱引用) +@property (nonatomic, readonly) UICollectionViewFlowLayout * collectionViewLayout; // 集合视图的布局 @property (nonatomic, strong) UICollectionView * collectionView; // 背景视图 (负责承载 windowCell;负责展示 背景底色;具备宫格样式的改动潜能) @end @@ -75,6 +75,7 @@ - (void)layoutSubviews{ #pragma mark - [ Public Methods ] - (void)reloadLinkMicUserWindows{ NSInteger finalCellNum = self.dataArray.count; + [self setupFirstSiteWindowCellWithUserId:self.firstSiteLinkMicUserId]; if (!CGRectGetHeight(self.bounds) && finalCellNum > 0) { __weak typeof(self) weakSelf = self; @@ -84,13 +85,45 @@ - (void)reloadLinkMicUserWindows{ }else{ [self.collectionView reloadData]; } +} + +- (void)updateFirstSiteWindowCellWithUserId:(NSString *)linkMicUserId toFirstSite:(BOOL)toFirstSite { + if (![PLVFdUtil checkStringUseable:linkMicUserId]) { + return; + } - if ([PLVFdUtil checkArrayUseable:self.dataArray]) { - self.currentMainSpeakerUserId = self.dataArray.firstObject.linkMicUserId; - }else{ - self.currentMainSpeakerUserId = nil; - self.manualSwitchMainSpeakerUserId = nil; + if (!toFirstSite) { + if ([self.firstSiteLinkMicUserId isEqualToString:linkMicUserId]) { + linkMicUserId = nil; + } else { + return; + } + } + + [self setupFirstSiteWindowCellWithUserId:linkMicUserId]; + [self.collectionView reloadData]; +} + +- (void)firstSiteWindowCellExchangeWithExternal:(UIView *)externalView { + // 主副屏切换时会固定 同第一画面 视图切换 + NSInteger targetCellIndex = 0; + PLVLinkMicOnlineUser * linkMicUserModel = [self onlineUserWithIndex:targetCellIndex]; + NSInteger cellNumber = [self.collectionView numberOfItemsInSection:0]; + self.showingExternalCellLinkMicUserId = linkMicUserModel.linkMicUserId; + self.externalView = externalView; + if (cellNumber > targetCellIndex) { + NSIndexPath * targetCellIndexPath = [NSIndexPath indexPathForRow:targetCellIndex inSection:0]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.collectionView reloadItemsAtIndexPaths:@[targetCellIndexPath]]; + }); } + [self callbackForShowFirstSiteUserOnExternal:linkMicUserModel]; +} + +- (void)rollbackFirstSiteWindowCellAndExternalView { + NSIndexPath *oriIndexPath = self.showingExternalCellIndexPath; + [self rollbackExternalView]; + [self rollbackLinkMicCanvasView:oriIndexPath]; } #pragma mark - [ Private Methods ] @@ -120,6 +153,29 @@ - (NSInteger)findCellIndexWithUserId:(NSString *)userId{ return targetUserIndex; } +- (NSInteger)findCellIndexWithMainSpeakerUser{ + NSInteger targetUserIndex = -1; + if ([self.delegate respondsToSelector:@selector(plvLSLinkMicWindowsView:findUserModelIndexWithFiltrateBlock:)]) { + targetUserIndex = [self.delegate plvLSLinkMicWindowsView:self findUserModelIndexWithFiltrateBlock:^BOOL(PLVLinkMicOnlineUser * _Nonnull enumerateUser) { + return enumerateUser.isRealMainSpeaker; + }]; + } + return targetUserIndex; +} + +/// 根据连麦列表视图下标 获取在线用户model [经过业务逻辑处理 与 dataArray 数据并不对应] +- (PLVLinkMicOnlineUser *)onlineUserWithIndex:(NSInteger)targetIndex { + if (self.firstSiteUserIndex > -1) { + if (targetIndex == 0) { + targetIndex = self.firstSiteUserIndex; + } else if (targetIndex <= self.firstSiteUserIndex){ + targetIndex --; + } + } + + return [self readUserModelFromDataArray:targetIndex]; +} + - (PLVLSLinkMicWindowCell *)getWindowCellWithIndex:(NSInteger)cellIndex{ PLVLSLinkMicWindowCell * cell; if (cellIndex >= 0) { cell = (PLVLSLinkMicWindowCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:cellIndex inSection:0]]; } @@ -136,8 +192,14 @@ - (PLVLinkMicOnlineUser *)readUserModelFromDataArray:(NSInteger)targetIndex{ } - (void)cleanLinkMicCellWithLinkMicUser:(PLVLinkMicOnlineUser *)didLeftLinkMicUser{ + __weak typeof(self) weakSelf = self; PLVLSLinkMicCanvasView * canvasView = didLeftLinkMicUser.canvasView; + NSString * didLeftLinkMicUserId = didLeftLinkMicUser.linkMicUserId; dispatch_async(dispatch_get_main_queue(), ^{ + if ([didLeftLinkMicUserId isEqualToString:weakSelf.showingExternalCellLinkMicUserId]) { + /// 此连麦用户对应的rtc小窗,正在展示外部视图,需回滚恢复至原位 + [weakSelf rollbackExternalView]; + } /// 回收资源 [canvasView removeRTCView]; [canvasView removeFromSuperview]; @@ -162,6 +224,51 @@ - (void)setupUserModelWillDeallocBlock:(PLVLinkMicOnlineUser *)linkMicUserModel{ }; } +// 设置第一画面相关数据 +- (void)setupFirstSiteWindowCellWithUserId:(NSString *)linkMicUserId { + NSInteger linkMicUserIndex = [self findCellIndexWithUserId:linkMicUserId]; + PLVLinkMicOnlineUser *firstSiteUserModel; + if (![PLVFdUtil checkStringUseable:linkMicUserId] || linkMicUserIndex < 0) { + linkMicUserIndex = [self findCellIndexWithMainSpeakerUser]; + } + firstSiteUserModel = [self readUserModelFromDataArray:linkMicUserIndex]; + self.firstSiteLinkMicUserId = firstSiteUserModel.linkMicUserId; + self.firstSiteUserIndex = linkMicUserIndex; + + if (!firstSiteUserModel) { + firstSiteUserModel = self.dataArray.firstObject; + } + + if ([PLVFdUtil checkStringUseable:self.showingExternalCellLinkMicUserId]) { + // 替换外部展示的第一画面 + self.showingExternalCellLinkMicUserId = firstSiteUserModel.linkMicUserId; + [self callbackForShowFirstSiteUserOnExternal:firstSiteUserModel]; + } +} + +- (void)rollbackExternalView { + // 告知外部对象,进行视图位置回退、恢复 + if (self.externalView) { + if (self.delegate && [self.delegate respondsToSelector:@selector(plvLSLinkMicWindowsView:rollbackExternalView:)]) { + [self.delegate plvLSLinkMicWindowsView:self rollbackExternalView:self.externalView]; + } + self.showingExternalCellIndexPath = nil; + self.showingExternalCellLinkMicUserId = nil; + self.externalView = nil; + } +} + +- (void)rollbackLinkMicCanvasView:(NSIndexPath *)oriIndexPath{ + PLVLinkMicOnlineUser *oriUserModel = [self onlineUserWithIndex:oriIndexPath.row]; + if (oriUserModel){ + // 将播放画布视图恢复至默认位置 + PLVLSLinkMicWindowCell * showingExternalCell = (PLVLSLinkMicWindowCell *)[self.collectionView cellForItemAtIndexPath:oriIndexPath]; + [showingExternalCell switchToShowRtcContentView:oriUserModel.canvasView]; + } else { + NSLog(@"PLVLSLinkMicWindowsView - rollbackLinkMicCanvasView failed, oriIndexPath %@ can't get userModel",oriIndexPath); + } +} + #pragma mark UI - (void)setupUI{ // 添加 视图 @@ -187,7 +294,7 @@ - (UICollectionView *)collectionView{ _collectionView.alwaysBounceHorizontal = YES; _collectionView.alwaysBounceVertical = NO; - NSString * identifier = [NSString stringWithFormat:@"PLVLCLinkMicWindowCellID"]; + NSString * identifier = [NSString stringWithFormat:@"PLVLSLinkMicWindowCellID"]; [_collectionView registerClass:[PLVLSLinkMicWindowCell class] forCellWithReuseIdentifier:identifier]; } return _collectionView; @@ -204,6 +311,18 @@ - (UICollectionViewFlowLayout *)collectionViewLayout{ return nil; } +#pragma mark Callback + +- (void)callbackForShowFirstSiteUserOnExternal:(PLVLinkMicOnlineUser *)linkMicUser{ + PLVLSLinkMicWindowCell *externalCell = [[PLVLSLinkMicWindowCell alloc] init]; + [self checkUserModelAndSetupLinkMicCanvasView:linkMicUser]; + [externalCell setModel:linkMicUser]; + [externalCell switchToShowRtcContentView:linkMicUser.canvasView]; + if (self.delegate && [self.delegate respondsToSelector:@selector(plvLSLinkMicWindowsView:showFirstSiteWindowCellOnExternal:)]) { + [self.delegate plvLSLinkMicWindowsView:self showFirstSiteWindowCellOnExternal:externalCell]; + } +} + #pragma mark - [ Delegate ] #pragma mark UICollectionViewDataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ @@ -212,18 +331,34 @@ - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSe } - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ - PLVLinkMicOnlineUser * linkMicUserModel = [self readUserModelFromDataArray:indexPath.row]; + BOOL thisCellShowingExternalView = NO; + PLVLinkMicOnlineUser * linkMicUserModel = [self onlineUserWithIndex:indexPath.row]; if (!linkMicUserModel) { NSLog(@"PLVLCLinkMicWindowsView - cellForItemAtIndexPath for %@ error",indexPath); - return [collectionView dequeueReusableCellWithReuseIdentifier:@"PLVLCLinkMicWindowCellID" forIndexPath:indexPath]; + return [collectionView dequeueReusableCellWithReuseIdentifier:@"PLVLSLinkMicWindowCellID" forIndexPath:indexPath]; } [self checkUserModelAndSetupLinkMicCanvasView:linkMicUserModel]; [self setupUserModelWillDeallocBlock:linkMicUserModel]; - PLVLSLinkMicWindowCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"PLVLCLinkMicWindowCellID" forIndexPath:indexPath]; + // 若两值一致,则表示此 Cell 将需要展示外部视图,此时应更新 showingExternalCellIndexPath + if ([self.showingExternalCellLinkMicUserId isEqualToString:linkMicUserModel.linkMicUserId]) { + self.showingExternalCellIndexPath = indexPath; + thisCellShowingExternalView = YES; + } + + PLVLSLinkMicWindowCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"PLVLSLinkMicWindowCellID" forIndexPath:indexPath]; [cell setModel:linkMicUserModel]; + // 在 [selModel:showingExternalCell:] 调用完毕后,再次将所需视图,加载在对应Cell上 + if (thisCellShowingExternalView) { + /// 显示 外部视图 + [cell switchToShowExternalContentView:self.externalView]; + }else{ + /// 显示 rtc画布视图 + [cell switchToShowRtcContentView:linkMicUserModel.canvasView]; + } + return cell; } @@ -244,13 +379,13 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{ if (!indexPath) { - NSLog(@"PLVLCLinkMicWindowsView - didSelectItemAtIndexPath error, indexPath:%@ illegal",indexPath); + NSLog(@"PLVLSLinkMicWindowsView - didSelectItemAtIndexPath error, indexPath:%@ illegal",indexPath); return; } PLVLinkMicOnlineUser * currentTapUserModel = [self readUserModelFromDataArray:indexPath.row]; if (!currentTapUserModel) { - NSLog(@"PLVLCLinkMicWindowsView - didSelectItemAtIndexPath error, indexPath:%@ can't get userModel",indexPath); + NSLog(@"PLVLSLinkMicWindowsView - didSelectItemAtIndexPath error, indexPath:%@ can't get userModel",indexPath); return; } } diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSDocument.bundle/plvls_ppt_btn_switch@2x.png b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSDocument.bundle/plvls_ppt_btn_switch@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1db1622a0eb7a66445710a1791fe11e6fd9ca385 GIT binary patch literal 766 zcmVhF`5ogE}Ek(8YuB{Kj2|CpPiBqT8$ATA&vEiNuD9V0Fr zA}|~xFhD>+93n4dP6000~zE%5R6_)5+a0000dbW%=Jv-FuqnJEFB6*njg zEJ`H=Qj#tXT3r|-1sgj9F9JtkRAgVW!~kZ7jXBf+00I6!oamVz)02H-j_ zY$B@yifm$d|2G_D&=F0bh5UCO>URVoq!D*IJKZ$0BCN`?3X9Az_1(x~K^?I6Sq>k` zv%RGTIR_hv*at@%iqj*EMlR7o_6`G?e6^ zkUXKFs05{y5e{uZIa>^hU`C>EC@JSaW%3CHIm}8P455n)Gvi(c&>3du{0^O9K~DeB zwO9fb*B9i&qWDiJuB6s`9#vK19!jO0x@jmW{ezsNq(eeSr-Kf%PE8&?bdbH+0s8@2 z%N+o#h4u^%>|1Ef;6Qd4yIj@TY>*Q-kOkp5j*0fOvgA9=& zPSa>(4hCfcsUWx4Qp{PQP$9w*2y<$ILZPT=2zB0m@1T^3l7w)FfJz!791@~VjdI9S z!!NcV9Rdo8zkfwK1XN%*&?L_gHbGqVhF*x1pXzT?k>(~>oY+0v&Ws^;WC+qZ01liVj zlFqZOp&Ko-wIUiVw$_iY8D(pa`5Jb7Zs@K?0`jLQvd(}07*qoM6N<$f<%}-+5i9m literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSDocument.bundle/plvls_ppt_btn_switch@3x.png b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSDocument.bundle/plvls_ppt_btn_switch@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..92ae5cc5bdc7070b4a7658cf0941b999bb90196e GIT binary patch literal 1139 zcmV-(1dRKMP)93m|oBP}2xK&!5_A0sdvA}ua1 zE*vB9U(3rAub&uFB>E-93n43KtLTJEg&E;D=R?%|NkB&F&sHL z9wROsAuk;xEgd2+93d`%fPmN8-XbS6%+A#S000~zE%5R6Paeld0000xbW%=J72Bq! z&C`UV4S)wwY|-&g$rczKOfoAwN=XQ-I$8@rXGkDWJyb?GQUp&R3;`+$Up8Q3WCGb} z%m8MX_Z@No00SdQL_t(&-p$%+SL!ej2H?0|7gS{5_g%WTL7D#l|8*-Og4ATvq;q^f z(&afoPnt}IZSk?KO1;r4&U#)HdA(V&)u>l2Tj2%_c^)s}7t~(*A)tkYk)v%nkCy0`~-)Lavz}WBn-OU51^f&JZ!f>mOm=jMm$G9LF8RiQR9^ z4K=Lz2{TuA516lGp}yJYfc$}RPse&6Vovt>9vf>}Z@AOPdu&c%!Tb|zM+6o`?H%S& zSi3PR!I~r%G(RvNl2{P(1S`{60ObU$nX-nr*O#K}Y6(^~WbHn`5?ol_GCMSA#W$iq z&1RNKE8d8%wOB=X@5g_SDwY_lu)65*{d4?$uV9I>I%|jyza-bcC|F{w!CIojo0IG1 zmKbZX0veN8?-w$xz-CxXX1#4>*o^g%nA8F()?*$HlUs7kV-X5NSaK}FVHgX8VSYxG zh4~o~4+ze}JRtH%A87&N(J#ud3#28-it^zqMp|;LB_4=33f9k2J8dAYV=#<$_C88^ z#Z(X&$~u43p1(XTIfk^PvBVRe89#`M{naKfz4o=z z-)_vKZ8;=vrX+3?C2oKvZtbORT0UvVE_}6P7nIwv{{wO`Axs%sI->vp002ovPDHLk FV1gW2>_`9r literal 0 HcmV?d00001 diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSUtils.h b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSUtils.h index 96110342..22cba477 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSUtils.h +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSUtils.h @@ -9,6 +9,7 @@ #import #import #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSUtils.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSUtils.m index 77982feb..b4e2a17d 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSUtils.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Resource/PLVLSUtils.m @@ -69,7 +69,9 @@ + (void)showAlertWithMessage:(NSString *)message cancelActionTitle:(NSString *)cancelActionTitle cancelActionBlock:(void(^)(void))cancelActionBlock { PLVAlertViewController *alert = [PLVAlertViewController alertControllerWithMessage:message cancelActionTitle:cancelActionTitle cancelHandler:cancelActionBlock confirmActionTitle:nil confirmHandler:nil]; - [[PLVLSUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[PLVLSUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + }); } + (void)showAlertWithMessage:(NSString *)message @@ -78,7 +80,9 @@ + (void)showAlertWithMessage:(NSString *)message confirmActionTitle:(NSString *)confirmActionTitle confirmActionBlock:(void(^)(void))confirmActionBlock { PLVAlertViewController *alert = [PLVAlertViewController alertControllerWithMessage:message cancelActionTitle:cancelActionTitle cancelHandler:cancelActionBlock confirmActionTitle:confirmActionTitle confirmHandler:confirmActionBlock]; - [[PLVLSUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[PLVLSUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + }); } + (void)showAlertWithTitle:(NSString *)title @@ -88,7 +92,9 @@ + (void)showAlertWithTitle:(NSString *)title confirmActionTitle:(NSString * _Nullable)confirmActionTitle confirmActionBlock:(void(^ _Nullable)(void))confirmActionBlock { PLVAlertViewController *alert = [PLVAlertViewController alertControllerWithTitle:title message:message cancelActionTitle:cancelActionTitle cancelHandler:cancelActionBlock confirmActionTitle:confirmActionTitle confirmHandler:confirmActionBlock]; - [[PLVLSUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[PLVLSUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + }); } + (UIImage *)imageForStatusResource:(NSString *)imageName { diff --git a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Scenes/PLVLSStreamerViewController.m b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Scenes/PLVLSStreamerViewController.m index 5b11eff7..43fd6654 100644 --- a/PolyvLiveScenesDemo/PLVLiveStreamerScene/Scenes/PLVLSStreamerViewController.m +++ b/PolyvLiveScenesDemo/PLVLiveStreamerScene/Scenes/PLVLSStreamerViewController.m @@ -142,7 +142,7 @@ - (void)viewWillLayoutSubviews { self.linkMicAreaView.frame = CGRectMake(CGRectGetMaxX(self.documentAreaView.frame) + linkMicAreaViewLeftPadding, CGRectGetMaxY(self.statusAreaView.frame), linkMicAreaViewWidth, ducomentViewHeight); // 设置聊天室宽高 - CGFloat chatroomAreaViewWidth = documentAreaViewWidth - 36 - linkMicAreaViewLeftPadding * 2; // 适配小屏输入框无法响应点击事件,chatroomAreaView内部适配聊天宽度 + CGFloat chatroomAreaViewWidth = documentAreaViewWidth - 32 - linkMicAreaViewLeftPadding * 2; // 适配小屏输入框无法响应点击事件,chatroomAreaView内部适配聊天宽度 CGFloat chatroomAreaViewHeigh = [UIScreen mainScreen].bounds.size.height * (isPad ? 0.28 : 0.42) + 44; self.chatroomAreaView.frame = CGRectMake(PLVLSUtils.safeSidePad, screenSize.height - PLVLSUtils.safeBottomPad - chatroomAreaViewHeigh, chatroomAreaViewWidth, chatroomAreaViewHeigh); @@ -742,6 +742,17 @@ - (void)documentAreaView:(PLVLSDocumentAreaView *)documentAreaView didShowWhiteb [self.statusAreaView syncSelectedWhiteboardOrDocument:whiteboard]; } +- (void)documentAreaView:(PLVLSDocumentAreaView *)documentAreaView pptView:(UIView *)pptView changePPTPositionToMain:(BOOL)pptToMain syncRemoteUser:(BOOL)needSync { + if (pptToMain) { + [self.linkMicAreaView rollbackFirstSiteWindowCellAndExternalView]; + } else { + [self.linkMicAreaView firstSiteWindowCellExchangeWithExternal:pptView]; + } + if (needSync) { + [self.streamerPresenter.localOnlineUser wantChangeUserPPTToMain:pptToMain]; + } +} + #pragma mark - PLVSocketManager Protocol - (void)socketMananger_didLoginSuccess:(NSString *)ackString { // 登陆成功 @@ -872,7 +883,7 @@ - (void)plvStreamerPresenter:(PLVStreamerPresenter *)presenter linkMicOnlineUser - (void)plvStreamerPresenter:(PLVStreamerPresenter *)presenter linkMicOnlineUser:(PLVLinkMicOnlineUser *)onlineUser authSpeaker:(BOOL)authSpeaker { - if ([onlineUser.linkMicUserId isEqualToString:self.streamerPresenter.localOnlineUser.linkMicUserId]) { + if (onlineUser.localUser) { NSString *message = authSpeaker ? @"已授予主讲权限" : @"已收回主讲权限"; [PLVLSUtils showToastWithMessage:message inView:self.view]; [self.documentAreaView updateDocumentSpeakerAuth:authSpeaker]; @@ -881,6 +892,8 @@ - (void)plvStreamerPresenter:(PLVStreamerPresenter *)presenter [self.documentAreaView dismissDocument]; } } + + [self.linkMicAreaView updateFirstSiteWindowCellWithUserId:onlineUser.linkMicUserId toFirstSite:onlineUser.isRealMainSpeaker]; } /// ‘是否推流已开始’ 发生变化 @@ -1022,6 +1035,14 @@ - (PLVLinkMicOnlineUser *)plvLSLinkMicAreaView:(PLVLSLinkMicAreaView *)linkMicAr return [self.streamerPresenter getOnlineUserModelFromOnlineUserArrayWithIndex:targetIndex]; } +- (void)plvLSLinkMicAreaView:(PLVLSLinkMicAreaView *)linkMicAreaView showFirstSiteWindowCellOnExternal:(UIView *)windowCell { + [self.documentAreaView displayExternalView:windowCell]; +} + +- (void)plvLSLinkMicAreaView:(PLVLSLinkMicAreaView *)linkMicAreaView rollbackExternalView:(UIView *)externalView { + [self.documentAreaView displayExternalView:externalView]; +} + #pragma mark PLVLSBeautySheetDelegate - (void)beautySheet:(PLVLSBeautySheet *)beautySheet didChangeOn:(BOOL)on { [self.streamerPresenter enableBeautyProcess:on]; diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/View/PageView/ViewController/PLVSABeautyFilterViewController.m b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/View/PageView/ViewController/PLVSABeautyFilterViewController.m index 5d0be833..9f9e154e 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/View/PageView/ViewController/PLVSABeautyFilterViewController.m +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/View/PageView/ViewController/PLVSABeautyFilterViewController.m @@ -38,7 +38,20 @@ - (void)viewDidLoad { self.view.backgroundColor = [UIColor clearColor]; [self.view addSubview:self.collectionView]; [self.collectionView reloadData]; - self.selectedIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; + + PLVBFilterOption *cacheFilter = [[PLVSABeautyViewModel sharedViewModel] getCacheSelectFilterOption]; + if (!cacheFilter || !self.dataArray) { + self.selectedIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]; + }else { + for (NSInteger i = 0; i < self.dataArray.count; i++) { + PLVSABeautyCellModel *model = self.dataArray[i]; + if ([model.filerOption.filterSpellName isEqualToString:cacheFilter.filterSpellName]) { + self.selectedIndexPath = [NSIndexPath indexPathForRow:i inSection:0]; + break; + } + } + } + [self.collectionView selectItemAtIndexPath:self.selectedIndexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; [self didSelectItemAtIndexPath:self.selectedIndexPath]; } diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/ViewModel/PLVSABeautyViewModel.h b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/ViewModel/PLVSABeautyViewModel.h index 7aa3f6ef..e1af18e8 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/ViewModel/PLVSABeautyViewModel.h +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/ViewModel/PLVSABeautyViewModel.h @@ -81,6 +81,9 @@ typedef NS_ENUM(NSInteger, PLVSABeautyType) { /// @param beautyType 美颜类型 - (void)selectBeautyType:(PLVSABeautyType)beautyType; +/// 获取缓存选中的滤镜 +- (PLVBFilterOption *)getCacheSelectFilterOption; + @end NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/ViewModel/PLVSABeautyViewModel.m b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/ViewModel/PLVSABeautyViewModel.m index ab8b47d5..c7b03acc 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/ViewModel/PLVSABeautyViewModel.m +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/Beauty/ViewModel/PLVSABeautyViewModel.m @@ -30,6 +30,7 @@ @interface PLVSABeautyViewModel() static NSString *kBeautyOptionDictKey = @"kPLVSABeautyOptionDictKey"; static NSString *kBeautyFilterOptionDictKey = @"kPLVSABeautyFilterOptionDictKey"; +static NSString *kBeautyFilterSelectKey = @"kPLVSABeautyFilterSelectKey"; static NSString *kBeautyOpenKey = @"kPLVSABeautyopenKey"; static CGFloat kBeautyFilterOptionDefaultIntensity = 0.5; @@ -122,6 +123,7 @@ - (void)selectBeautyOption:(PLVBBeautyOption)option { - (void)selectBeautyFilterOption:(PLVBFilterOption *)filterOption { // 缓存当前选择的滤镜 self.currentFilterOption = filterOption; + [self saveBeautyFilterSelect:filterOption]; // 未开启美颜 或 当前选择的不是滤镜 不继续处理 if (!self.beautyIsOpen || @@ -156,6 +158,20 @@ - (BOOL)isSelectedOriginFilter { return isSelectedOriginFilter; } +- (PLVBFilterOption *)getCacheSelectFilterOption { + NSString *spellName = [self getSelectedBeautyFilterSpellName]; + if (![PLVFdUtil checkStringUseable:spellName] || + !self.filterOptionArray) { + return nil; + } + for (PLVBFilterOption *filter in self.filterOptionArray) { + if ([filter.filterSpellName isEqualToString:spellName]) { + return filter; + } + } + return nil; +} + #pragma mark - [ Private Method ] #pragma mark Getter @@ -219,6 +235,12 @@ - (void)initFilterOption { plv_dict_set(self.beautyFilterOptionDict, key, value); }]; } + + PLVBFilterOption *cacheFilter = [self getCacheSelectFilterOption]; + if (cacheFilter) { + [self selectBeautyFilterOption:cacheFilter]; + } + [self saveBeautyFilterOptionDict]; } @@ -258,6 +280,22 @@ - (void)removeBeautyFilterOptionDict { [[NSUserDefaults standardUserDefaults] synchronize]; } +- (void)saveBeautyFilterSelect:(PLVBFilterOption *)filter { + if ([PLVFdUtil checkStringUseable:filter.filterSpellName]) { + [[NSUserDefaults standardUserDefaults] setObject:filter.filterSpellName forKey:kBeautyFilterSelectKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } +} + +- (NSString *)getSelectedBeautyFilterSpellName { + return [[NSUserDefaults standardUserDefaults] objectForKey:kBeautyFilterSelectKey]; +} + +- (void)removeBeautyFilterSelect { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:kBeautyFilterSelectKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + - (void)saveBeautyOpenStatus:(BOOL)open { [[NSUserDefaults standardUserDefaults] setObject:open ? @"1" : @"2" forKey:kBeautyOpenKey]; [[NSUserDefaults standardUserDefaults] synchronize]; @@ -274,6 +312,7 @@ - (BOOL)getBeautyOpenStatus { - (void)resetBeauty { [self removeBeautyOptionDict]; [self removeBeautyFilterOptionDict]; + [self removeBeautyFilterSelect]; [self initBeautyOption]; [self initFilterOption]; } diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/PLVSALinkMicAreaView.h b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/PLVSALinkMicAreaView.h index dfdda5e9..ef93a92e 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/PLVSALinkMicAreaView.h +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/PLVSALinkMicAreaView.h @@ -24,6 +24,11 @@ NS_ASSUME_NONNULL_BEGIN /// 刷新连麦窗口 - (void)reloadLinkMicUserWindows; +/// 更新在线用户到第一画面 +/// @param linkMicUserId 连麦用户id +/// @param toFirstSite 是否到第一画面 +- (void)updateFirstSiteCanvasViewWithUserId:(NSString *)linkMicUserId toFirstSite:(BOOL)toFirstSite; + /// 退出直播时调用 - (void)clear; diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/PLVSALinkMicAreaView.m b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/PLVSALinkMicAreaView.m index ad05c2c5..4bb9bab1 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/PLVSALinkMicAreaView.m +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/PLVSALinkMicAreaView.m @@ -59,6 +59,10 @@ - (void)reloadLinkMicUserWindows { [self.windowsView reloadLinkMicUserWindows]; } +- (void)updateFirstSiteCanvasViewWithUserId:(NSString *)linkMicUserId toFirstSite:(BOOL)toFirstSite { + [self.windowsView updateFirstSiteCanvasViewWithUserId:linkMicUserId toFirstSite:toFirstSite]; +} + - (void)clear { [self.windowsView removeFromSuperview]; } diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowCell.m b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowCell.m index b86b7650..1563ecc0 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowCell.m +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowCell.m @@ -13,6 +13,7 @@ #import #import +#import @interface PLVSALinkMicWindowCell () diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowsView.h b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowsView.h index 33a1063e..cf8bf10c 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowsView.h +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowsView.h @@ -21,6 +21,11 @@ NS_ASSUME_NONNULL_BEGIN /// 刷新连麦窗口 - (void)reloadLinkMicUserWindows; +/// 更新在线用户到第一画面 +/// @param linkMicUserId 连麦用户id +/// @param toFirstSite 是否到第一画面 +- (void)updateFirstSiteCanvasViewWithUserId:(NSString *)linkMicUserId toFirstSite:(BOOL)toFirstSite; + /// 切换连麦窗口布局和主讲人 /// @note 切换连麦窗口的布局【仅当 speakerMode为YES时linkMicUserId有效】 /// @param speakerMode 连麦的布局模式是否为主讲模式 @@ -84,6 +89,12 @@ NS_ASSUME_NONNULL_BEGIN /// @param windowsView 连麦窗口列表视图 - (BOOL)classStartedInLinkMicWindowsView:(PLVSALinkMicWindowsView *)windowsView; +/// 开启 或者 关闭全屏 +/// @param windowsView 连麦窗口列表视图 +/// @param onlineUser 连麦用户信息 +/// @param isFullScreen 是否开启全屏(YES 开启 NO 关闭) +- (void)linkMicWindowsView:(PLVSALinkMicWindowsView *)windowsView onlineUser:(PLVLinkMicOnlineUser *)onlineUser isFullScreen:(BOOL)isFullScreen; + @end NS_ASSUME_NONNULL_END diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowsView.m b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowsView.m index c068d955..428a8860 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowsView.m +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Modules/LinkMic/View/WindowsView/PLVSALinkMicWindowsView.m @@ -42,7 +42,7 @@ @interface PLVSALinkMicWindowsView ()< @property (nonatomic, assign) PLVSALinkMicLayoutMode linkMicLayoutMode; //当前连麦布局模式 (默认为平铺模式) @property (nonatomic, assign, readonly) PLVRoomUserType viewerType; @property (nonatomic, copy) NSString *currentSpeakerLinkMicUserId; //当前显示主讲用户的连麦id[布局模式切换时缓存此id] -@property (nonatomic, strong) NSIndexPath *showingSpeakerIndexPath; // 主讲显示的画面数据对应本应在collectionView的下标 (仅在讲师角色 PLVSALinkMicLayoutModeSpeaker下有效) +@property (nonatomic, assign) NSInteger currentSpeakerUserIndex; // 当前主讲用户在数据中的下标 @property (nonatomic, copy) NSString *fullScreenUserId; // 全屏用户的Id @property (nonatomic, assign) BOOL delayDisplayToast; // 是否需要延迟显示toast @@ -99,6 +99,9 @@ - (void)reloadLinkMicUserWindows { if (self.linkMicLayoutMode == PLVSALinkMicLayoutModeSpeaker) { [self updateSpeakerViewForLinkMicUserId:self.currentSpeakerLinkMicUserId]; } + + [self setupFirstSiteCanvasViewWithUserId:self.currentSpeakerLinkMicUserId]; + [self.collectionView reloadData]; if (self.viewerType == PLVRoomUserTypeTeacher) { __weak typeof(self) weakSelf = self; @@ -108,6 +111,26 @@ - (void)reloadLinkMicUserWindows { } } +- (void)updateFirstSiteCanvasViewWithUserId:(NSString *)linkMicUserId toFirstSite:(BOOL)toFirstSite { + if (![PLVFdUtil checkStringUseable:linkMicUserId]) { + return; + } + + if (!toFirstSite) { + if ([self.currentSpeakerLinkMicUserId isEqualToString:linkMicUserId]) { + linkMicUserId = nil; + } else { + return; + } + } + + [self setupFirstSiteCanvasViewWithUserId:linkMicUserId]; + if (self.linkMicLayoutMode == PLVSALinkMicLayoutModeSpeaker) { + [self updateSpeakerViewForLinkMicUserId:self.currentSpeakerLinkMicUserId]; + } + [self.collectionView reloadData]; +} + - (void)switchLinkMicWindowsLayoutSpeakerMode:(BOOL)speakerMode linkMicWindowMainSpeaker:(NSString * _Nullable)linkMicUserId { PLVSALinkMicLayoutMode layoutMode = speakerMode ? PLVSALinkMicLayoutModeSpeaker : PLVSALinkMicLayoutModeTiled; if (layoutMode == PLVSALinkMicLayoutModeSpeaker) { @@ -131,20 +154,41 @@ - (void)fullScreenLinkMicUser:(PLVLinkMicOnlineUser *)onlineUser { /// 根据连麦列表视图下标 获取在线用户model [经过业务逻辑处理 与 dataArray 数据并不对应] - (PLVLinkMicOnlineUser *)onlineUserWithIndex:(NSInteger)targetIndex { - if (self.linkMicUserCount == 1 && targetIndex == 0 && - [self isLocalUserPreviewView]) { + if (self.linkMicUserCount == 1 && targetIndex == 0 && [self isLocalUserPreviewView]) { return self.localOnlineUser; } - - if (self.linkMicLayoutMode == PLVSALinkMicLayoutModeSpeaker && - self.showingSpeakerIndexPath && - targetIndex >= self.showingSpeakerIndexPath.row) { - targetIndex ++; - } - + targetIndex = [self indexWithTargetIndex:targetIndex isRealIndex:YES]; return [self readUserModelFromDataArray:targetIndex]; } +/// 通过目标 targetIndex 转换为需要使用的下标 +/// isReal YES 将cell的下标 转换为在dataArray 数据中对应需要显示的坐标; +/// isReal NO 将dataArray 数据中对应的下标转换为cell的下标 +- (NSInteger)indexWithTargetIndex:(NSInteger)targetIndex isRealIndex:(BOOL)isReal { + if (self.currentSpeakerUserIndex > -1) { + if (self.linkMicLayoutMode == PLVSALinkMicLayoutModeSpeaker) { + if (targetIndex >= self.currentSpeakerUserIndex && self.linkMicUserCount != 1) { + isReal ? targetIndex++ : targetIndex--; + } + } else { + if (isReal) { + if (targetIndex == 0) { + targetIndex = self.currentSpeakerUserIndex; + } else if (targetIndex <= self.currentSpeakerUserIndex){ + targetIndex-- ; + } + } else { + if (targetIndex == self.currentSpeakerUserIndex) { + targetIndex = 0; + } else if (targetIndex < self.currentSpeakerUserIndex){ + targetIndex++; + } + } + } + } + return targetIndex; +} + /// 从 dataArray 的对应 targetIndex 获取在线用户 - (PLVLinkMicOnlineUser *)readUserModelFromDataArray:(NSInteger)targetIndex{ PLVLinkMicOnlineUser *onlineUser; @@ -166,6 +210,23 @@ - (NSInteger)findCellIndexWithUserId:(NSString *)userId{ return targetUserIndex; } +- (NSInteger)findCellIndexWithMainSpeakerUser{ + NSInteger targetUserIndex = -1; + if ([self.delegate respondsToSelector:@selector(onlineUserIndexInLinkMicWindowsView:filterBlock:)]) { + targetUserIndex = [self.delegate onlineUserIndexInLinkMicWindowsView:self filterBlock:^BOOL(PLVLinkMicOnlineUser * _Nonnull enumerateUser) { + return enumerateUser.isRealMainSpeaker; + }]; + } + return targetUserIndex; +} + +- (PLVSALinkMicWindowCell *)getWindowCellWithIndex:(NSInteger)cellIndex{ + PLVSALinkMicWindowCell * cell; + if (cellIndex >= 0) { cell = (PLVSALinkMicWindowCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:cellIndex inSection:0]]; } + if (!cell) { NSLog(@"PLVLCLinkMicWindowsView - cell find failed"); } + return cell; +} + - (BOOL)isLocalUserPreviewView { if (self.delegate && [self.delegate respondsToSelector:@selector(localUserPreviewViewInLinkMicWindowsView:)]) { return [self.delegate localUserPreviewViewInLinkMicWindowsView:self]; @@ -218,12 +279,12 @@ - (void)updateSpeakerViewForLinkMicUserId:(NSString * _Nullable)linkMicUserId { linkMicUserId = self.currentSpeakerLinkMicUserId; } - self.showingSpeakerIndexPath = nil; + self.currentSpeakerUserIndex = -1; NSInteger indexRow = [self findCellIndexWithUserId:linkMicUserId]; PLVLinkMicOnlineUser *firstSiteOnlineUser = [self onlineUserWithIndex:indexRow]; if (firstSiteOnlineUser) { self.currentSpeakerLinkMicUserId = linkMicUserId; - self.showingSpeakerIndexPath = [NSIndexPath indexPathForRow:indexRow inSection:0]; + self.currentSpeakerUserIndex = indexRow; [self setupLinkMicCanvasViewWithOnlineUser:firstSiteOnlineUser]; [self setupWillDeallocBlockWithOnlineUser:firstSiteOnlineUser]; [self.speakerView showSpeakerViewWithUserModel:firstSiteOnlineUser delegate:self]; @@ -254,7 +315,6 @@ - (void)showGuideView { - (void)cleanSpeakerView { [self.speakerView hideSpeakerView]; - self.showingSpeakerIndexPath = nil; } - (BOOL)showSpeakerPlaceholderView { @@ -343,20 +403,19 @@ - (void)updateSubviewsLayout { } - (void)fullScreenViewOnlineUser:(PLVLinkMicOnlineUser *)onlineUser didFullScreen:(BOOL)fullScreen { + if (self.delegate && [self.delegate respondsToSelector:@selector(linkMicWindowsView:onlineUser:isFullScreen:)]) { + [self.delegate linkMicWindowsView:self onlineUser:onlineUser isFullScreen:fullScreen]; + } + PLVSALinkMicWindowCell *collectionViewCell; NSInteger indexRow = [self findCellIndexWithUserId:onlineUser.userId]; if (indexRow > -1) { // 主讲模式下,indexPath 会有改变 - if (self.linkMicLayoutMode == PLVSALinkMicLayoutModeSpeaker && - indexRow == self.showingSpeakerIndexPath.row) { + if (self.linkMicLayoutMode == PLVSALinkMicLayoutModeSpeaker && [self.currentSpeakerLinkMicUserId isEqualToString:onlineUser.userId] && self.linkMicUserCount != 1) { collectionViewCell = self.speakerView.linkMicWindowCell; } else { - if (self.linkMicLayoutMode == PLVSALinkMicLayoutModeSpeaker && - indexRow > self.showingSpeakerIndexPath.row) { - indexRow --; - } - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:indexRow inSection:0]; - collectionViewCell = (PLVSALinkMicWindowCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; + indexRow = [self indexWithTargetIndex:indexRow isRealIndex:NO]; + collectionViewCell = [self getWindowCellWithIndex:indexRow]; } } @@ -389,6 +448,18 @@ - (void)fullScreenViewOnlineUser:(PLVLinkMicOnlineUser *)onlineUser didFullScree [[UIApplication sharedApplication] setStatusBarHidden:fullScreen]; } +// 设置第一画面相关数据 +- (void)setupFirstSiteCanvasViewWithUserId:(NSString *)linkMicUserId { + NSInteger linkMicUserIndex = [self findCellIndexWithUserId:linkMicUserId]; + PLVLinkMicOnlineUser *firstSiteUserModel; + if (![PLVFdUtil checkStringUseable:linkMicUserId] || linkMicUserIndex < 0) { + linkMicUserIndex = [self findCellIndexWithMainSpeakerUser]; + } + firstSiteUserModel = [self readUserModelFromDataArray:linkMicUserIndex]; + self.currentSpeakerLinkMicUserId = firstSiteUserModel.linkMicUserId; + self.currentSpeakerUserIndex = linkMicUserIndex; +} + #pragma mark Initialize - (void)setupUI { @@ -434,6 +505,9 @@ - (UICollectionView *)collectionView { _collectionView.delegate = self; _collectionView.showsVerticalScrollIndicator = NO; _collectionView.showsHorizontalScrollIndicator = NO; + if (@available(iOS 11.0, *)) { + _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } [_collectionView registerClass:[PLVSALinkMicWindowCell class] forCellWithReuseIdentifier:kCellIdentifier]; } @@ -608,8 +682,12 @@ - (void)linkMicWindowCellDidSelectCell:(PLVSALinkMicWindowCell *)collectionViewC } NSIndexPath *indexPath = [self.collectionView indexPathForCell:collectionViewCell]; - PLVLinkMicOnlineUser *onlineUser = [self onlineUserWithIndex:indexPath.row]; - if (!indexPath || !onlineUser || onlineUser.localUser) { + PLVLinkMicOnlineUser *onlineUser = indexPath ? [self onlineUserWithIndex:indexPath.row] : nil; + if (self.linkMicLayoutMode == PLVSALinkMicLayoutModeSpeaker && !onlineUser && [collectionViewCell isEqual:self.speakerView.linkMicWindowCell]) { + onlineUser = [self readUserModelFromDataArray:self.currentSpeakerUserIndex]; + } + + if (!onlineUser || onlineUser.localUser) { return; } diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Resource/PLVSAUtils.h b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Resource/PLVSAUtils.h index 32ad2ee7..2c0fa0a1 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Resource/PLVSAUtils.h +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Resource/PLVSAUtils.h @@ -9,6 +9,7 @@ #import #import #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Resource/PLVSAUtils.m b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Resource/PLVSAUtils.m index cb78cd30..87c20a62 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Resource/PLVSAUtils.m +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Resource/PLVSAUtils.m @@ -78,7 +78,9 @@ + (void)showAlertWithMessage:(NSString *)message confirmActionTitle:(NSString *)confirmActionTitle confirmActionBlock:(void(^)(void))confirmActionBlock { PLVSAStreamAlertController *alert = [PLVSAStreamAlertController alertControllerWithTitle:nil Message:message cancelActionTitle:cancelActionTitle cancelHandler:cancelActionBlock confirmActionTitle:confirmActionTitle confirmHandler:confirmActionBlock]; - [[PLVSAUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[PLVSAUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + }); } + (void)showAlertWithTitle:(NSString *)title @@ -87,7 +89,9 @@ + (void)showAlertWithTitle:(NSString *)title confirmActionTitle:(NSString *)confirmActionTitle confirmActionBlock:(void(^)(void))confirmActionBlock { PLVSAStreamAlertController *alert = [PLVSAStreamAlertController alertControllerWithTitle:title Message:nil cancelActionTitle:cancelActionTitle cancelHandler:cancelActionBlock confirmActionTitle:confirmActionTitle confirmHandler:confirmActionBlock]; - [[PLVSAUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[PLVSAUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + }); } + (void)showAlertWithTitle:(NSString *)title @@ -97,8 +101,9 @@ + (void)showAlertWithTitle:(NSString *)title confirmActionTitle:(NSString *)confirmActionTitle confirmActionBlock:(void(^)(void))confirmActionBlock { PLVSAStreamAlertController *alert = [PLVSAStreamAlertController alertControllerWithTitle:title Message:message cancelActionTitle:cancelActionTitle cancelHandler:cancelActionBlock confirmActionTitle:confirmActionTitle confirmHandler:confirmActionBlock]; - - [[PLVSAUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[PLVSAUtils sharedUtils].homeVC presentViewController:alert animated:NO completion:nil]; + }); } + (UIImage *)imageForLiveroomResource:(NSString *)imageName { diff --git a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Scenes/PLVSAStreamerViewController.m b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Scenes/PLVSAStreamerViewController.m index 349d32c4..618985b9 100644 --- a/PolyvLiveScenesDemo/PLVStreamerAloneScene/Scenes/PLVSAStreamerViewController.m +++ b/PolyvLiveScenesDemo/PLVStreamerAloneScene/Scenes/PLVSAStreamerViewController.m @@ -50,7 +50,8 @@ @interface PLVSAStreamerViewController ()< PLVStreamerPresenterDelegate, PLVMemberPresenterDelegate, PLVSAStreamerHomeViewDelegate, -PLVSABeautySheetDelegate +PLVSABeautySheetDelegate, +UIGestureRecognizerDelegate > #pragma mark 模块 @@ -97,12 +98,17 @@ @interface PLVSAStreamerViewController ()< @property (nonatomic, strong) PLVSAStreamerHomeView *homeView; // 开播中的推流页 @property (nonatomic, strong) PLVSAStreamerFinishView *finishView; // 结束开播的结束页 @property (nonatomic, strong) PLVSABeautySheet *beautySheet; // 美颜设置弹层 +@property (nonatomic, strong) UIPinchGestureRecognizer *pinchGesture; //缩放手势 #pragma mark 数据 @property (nonatomic, assign, readonly) PLVRoomUserType viewerType; @property (nonatomic, assign) BOOL socketReconnecting; // socket是否重连中 @property (nonatomic, assign, readonly) NSString * channelId; // 当前频道号 @property (nonatomic, assign) NSTimeInterval showMicTipsTimeInterval; // 显示'请打开麦克风提示'时的时间戳 +@property (nonatomic, assign) CGFloat currentCameraZoomRatio; // 当前摄像头的变焦倍数 +@property (nonatomic, assign) CGFloat maxCameraZoomRatio; // 当前摄像头允许的最大变焦倍数 +@property (nonatomic, assign) BOOL localUserScreenShareOpen; // 本地用户是否开启了屏幕共享 +@property (nonatomic, assign) BOOL otherUserFullScreen; // 非本地用户开启了全屏 @end @@ -216,6 +222,25 @@ - (void)startLinkMic:(BOOL)start { }]; } +/// 缩放手势 +- (void)pinchGesture:(UIPinchGestureRecognizer *)recognizer { + if (self.localUserScreenShareOpen || + self.otherUserFullScreen) { + return; + } + switch (recognizer.state) { + case UIGestureRecognizerStateBegan: + case UIGestureRecognizerStateChanged: { + CGFloat zoomRatio = self.currentCameraZoomRatio * recognizer.scale; + if (zoomRatio >= 1.0 && zoomRatio <= self.maxCameraZoomRatio){ + [self.streamerPresenter setCameraZoomRatio:zoomRatio]; + } + } break; + default: + break; + } +} + #pragma mark Getter & Setter - (PLVSALinkMicAreaView *)linkMicAreaView { @@ -288,12 +313,21 @@ - (PLVSABeautySheet *)beautySheet { return _beautySheet; } +- (UIPinchGestureRecognizer *)pinchGesture { + if (!_pinchGesture) { + _pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchGesture:)]; + _pinchGesture.delegate = self; + } + return _pinchGesture; +} + #pragma mark Initialize - (void)setupUI { [self.view addSubview:self.linkMicAreaView]; [self.view addSubview:self.shadowMaskView]; [self.view addSubview:self.settingView]; + [self.view addGestureRecognizer:self.pinchGesture]; } - (void)setupModule { @@ -742,6 +776,7 @@ - (void)plvStreamerPresenter:(PLVStreamerPresenter *)presenter return; } + [self.linkMicAreaView updateFirstSiteCanvasViewWithUserId:onlineUser.linkMicUserId toFirstSite:onlineUser.isRealMainSpeaker]; NSString *message = nil; if (onlineUser.localUser) { if (authSpeaker) { @@ -820,15 +855,18 @@ - (void)plvStreamerPresenter:(PLVStreamerPresenter *)presenter if (self.viewState == PLVSAStreamerViewStateSteaming) { [PLVSAUtils showToastWithMessage:(currentCameraShouldShow ? @"已开启摄像头" : @"已关闭摄像头") inView:self.view]; } + self.maxCameraZoomRatio = [self.streamerPresenter getMaxCameraZoomRatio]; } /// 本地用户的 ’摄像头前后置状态值‘ 发生变化 - (void)plvStreamerPresenter:(PLVStreamerPresenter *)presenter localUserCameraFrontChanged:(BOOL)currentCameraFront { + self.maxCameraZoomRatio = [self.streamerPresenter getMaxCameraZoomRatio]; } /// 本地用户的 ’屏幕共享开关状态‘ 发生变化 - (void)plvStreamerPresenter:(PLVStreamerPresenter *)presenter localUserScreenShareOpenChanged:(BOOL)currentScreenShareOpen { + self.localUserScreenShareOpen = currentScreenShareOpen; if (self.streamerPresenter.localOnlineUser.isRealMainSpeaker || self.streamerPresenter.localOnlineUser.userType == PLVSocketUserTypeTeacher) { NSString *message = currentScreenShareOpen ? @"其他人现在可以看到你的屏幕" : @"共享已结束"; @@ -933,7 +971,7 @@ - (BOOL)classStartedInLinkMicAreaView:(PLVSALinkMicAreaView *)areaView { } - (void)linkMicAreaView:(PLVSALinkMicAreaView *)areaView onlineUser:(PLVLinkMicOnlineUser *)onlineUser isFullScreen:(BOOL)isFullScreen { - + self.otherUserFullScreen = isFullScreen && !onlineUser.localUser; } #pragma mark PLVSAStreamerSettingViewDelegate @@ -1118,4 +1156,14 @@ - (void)beautySheet:(PLVSABeautySheet *)beautySheet didChangeShow:(BOOL)show { } } +#pragma mark - UIGestureRecognizerDelegate +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] && + !self.localUserScreenShareOpen && + !self.otherUserFullScreen) { + self.currentCameraZoomRatio = [self.streamerPresenter getCameraZoomRatio]; + } + return YES; +} + @end diff --git a/PolyvLiveScenesDemo/Supporting Files/PolyvLiveHiClassDemo-Info.plist b/PolyvLiveScenesDemo/Supporting Files/PolyvLiveHiClassDemo-Info.plist index 5d578aa6..058d47fa 100644 --- a/PolyvLiveScenesDemo/Supporting Files/PolyvLiveHiClassDemo-Info.plist +++ b/PolyvLiveScenesDemo/Supporting Files/PolyvLiveHiClassDemo-Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.9.4 + 1.9.5 CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS diff --git a/PolyvLiveScenesDemo/Supporting Files/PolyvLiveScenesDemo-Info.plist b/PolyvLiveScenesDemo/Supporting Files/PolyvLiveScenesDemo-Info.plist index c97d46fa..1108ef5a 100644 --- a/PolyvLiveScenesDemo/Supporting Files/PolyvLiveScenesDemo-Info.plist +++ b/PolyvLiveScenesDemo/Supporting Files/PolyvLiveScenesDemo-Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.9.4 + 1.9.5 CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS diff --git a/PolyvLiveScenesDemo/Supporting Files/PolyvLiveStreamerDemo-Info.plist b/PolyvLiveScenesDemo/Supporting Files/PolyvLiveStreamerDemo-Info.plist index 5e376b02..3f87ec73 100644 --- a/PolyvLiveScenesDemo/Supporting Files/PolyvLiveStreamerDemo-Info.plist +++ b/PolyvLiveScenesDemo/Supporting Files/PolyvLiveStreamerDemo-Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.9.4 + 1.9.5 CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS diff --git a/PolyvLiveScenesDemo/Supporting Files/PolyvLiveWatchDemo-Info.plist b/PolyvLiveScenesDemo/Supporting Files/PolyvLiveWatchDemo-Info.plist index 20805d3e..ab1796c6 100644 --- a/PolyvLiveScenesDemo/Supporting Files/PolyvLiveWatchDemo-Info.plist +++ b/PolyvLiveScenesDemo/Supporting Files/PolyvLiveWatchDemo-Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.9.4 + 1.9.5 CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS