Merge pull request '完成ios端功能' (#1) from feature-ios into master
Reviewed-on: http://m.shuto.cn:8680/public/cordova-plugin-trtc/pulls/1
152
plugin.xml
@ -20,128 +20,62 @@
|
||||
<clobbers target="cordova.plugin.trtc"/>
|
||||
</js-module>
|
||||
|
||||
<!-- <platform name="ios">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="Trtc">
|
||||
<param name="ios-package" value="TrtcPlugin"/>
|
||||
<platform name="ios">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="Trtc">
|
||||
<param name="ios-package" value="TrtcPlugin"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
</feature>
|
||||
</config-file>
|
||||
<config-file target="*-Info.plist" parent="NSMicrophoneUsageDescription">
|
||||
<string>$PERMISSION_MIC_DESC</string>
|
||||
</config-file>
|
||||
<config-file target="*-Info.plist" parent="NSCameraUsageDescription">
|
||||
<string>$PERMISSION_CAMERA_DESC</string>
|
||||
</config-file>
|
||||
|
||||
<config-file target="*-Info.plist" parent="NSMicrophoneUsageDescription">
|
||||
<string>$PERMISSION_MIC_DESC</string>
|
||||
</config-file>
|
||||
<config-file target="*-Info.plist" parent="NSCameraUsageDescription">
|
||||
<string>$PERMISSION_CAMERA_DESC</string>
|
||||
</config-file>
|
||||
<!-- <hook type="after_platform_add" src="hooks/addEmbedded.js" />-->
|
||||
|
||||
<header-file src="src/ios/TrtcPlugin.h"/>
|
||||
<source-file src="src/ios/TrtcPlugin.m"/>
|
||||
-->
|
||||
<header-file src="src/ios/TrtcPlugin.h"/>
|
||||
<source-file src="src/ios/TrtcPlugin.m"/>
|
||||
|
||||
<!-- <header-file src="src/ios/Trtc/TCLiveConfigDefine.h"/>-->
|
||||
<!-- <header-file src="src/ios/Trtc/TCLiveJoinRoomViewController.h"/>-->
|
||||
<!-- <source-file src="src/ios/Trtc/TCLiveJoinRoomViewController.m"/>-->
|
||||
<!-- <header-file src="src/ios/Trtc/TCLiveRequestManager.h"/>-->
|
||||
<!-- <source-file src="src/ios/Trtc/TCLiveRequestManager.m"/>-->
|
||||
<!-- <header-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveChatTableView.h"/>-->
|
||||
<!-- <source-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveChatTableView.m"/>-->
|
||||
<!-- <header-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveChatTableViewCell.h"/>-->
|
||||
<!-- <source-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveChatTableViewCell.m"/>-->
|
||||
<!-- <header-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveRoomViewController.h"/>-->
|
||||
<!-- <source-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveRoomViewController.m"/>-->
|
||||
<!-- <header-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveUITools/UIColorEX.h"/>-->
|
||||
<!-- <source-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveUITools/UIColorEX.m"/>-->
|
||||
<!-- <header-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveUITools/UIToastView.h"/>-->
|
||||
<!-- <source-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveUITools/UIToastView.m"/>-->
|
||||
<!-- <header-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveVideoControlBar.h"/>-->
|
||||
<!-- <source-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveVideoControlBar.m"/>-->
|
||||
<!-- <header-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveVideoLayoutView.h"/>-->
|
||||
<!-- <source-file src="src/ios/Trtc/TCLiveRoomVC/TCLiveVideoLayoutView.m"/>-->
|
||||
<header-file src="src/ios/CordovaEventKit.h"/>
|
||||
<source-file src="src/ios/CordovaEventKit.m"/>
|
||||
|
||||
<!-- <resource-file src="src/ios/TrtcConfig.plist"/>-->
|
||||
<header-file src="src/ios/Events.h"/>
|
||||
<source-file src="src/ios/Events.m"/>
|
||||
|
||||
<!-- <!– images –>-->
|
||||
<!-- <resource-file src="src/ios/res/beauty-dis.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/beauty.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/bg.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/camera-gray.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/camera.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/chat.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/doubleroom.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/feedback.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/ic_toast_success@2x.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/icon_sign@2x.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/log.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/log2.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/mic-dis.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/mic.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/muti_room_bg.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/role.png"/>-->
|
||||
<!-- <resource-file src="src/ios/res/ui_title_arrow_left.png"/>-->
|
||||
<header-file src="src/ios/Listener.h"/>
|
||||
<source-file src="src/ios/Listener.m"/>
|
||||
|
||||
<!-- <!– lib –>-->
|
||||
<!-- <framework src="Accelerate.framework"/>-->
|
||||
<!-- <framework src="AssetsLibrary.framework"/>-->
|
||||
<!-- <framework src="AVFoundation.framework"/>-->
|
||||
<!-- <framework src="CoreGraphics.framework"/>-->
|
||||
<!-- <framework src="CoreMedia.framework"/>-->
|
||||
<!-- <framework src="CoreTelephony.framework"/>-->
|
||||
<!-- <framework src="CoreVideo.framework"/>-->
|
||||
<!-- <framework src="ImageIO.framework"/>-->
|
||||
<!-- <framework src="JavaScriptCore.framework"/>-->
|
||||
<!-- <framework src="OpenAL.framework"/>-->
|
||||
<!-- <framework src="OpenGLES.framework"/>-->
|
||||
<!-- <framework src="QuartzCore.framework"/>-->
|
||||
<!-- <framework src="SystemConfiguration.framework"/>-->
|
||||
<!-- <framework src="VideoToolbox.framework"/>-->
|
||||
<!-- <framework src="libbz2.tbd"/>-->
|
||||
<!-- <framework src="libc++.tbd"/>-->
|
||||
<!-- <framework src="libiconv.tbd"/>-->
|
||||
<!-- <framework src="libicucore.tbd"/>-->
|
||||
<!-- <framework src="libprotobuf.tbd"/>-->
|
||||
<!-- <framework src="libresolv.tbd"/>-->
|
||||
<!-- <framework src="libsqlite3.tbd"/>-->
|
||||
<!-- <framework src="libstdc++.6.tbd"/>-->
|
||||
<!-- <framework src="libstdc++.tbd"/>-->
|
||||
<!-- <framework src="libz.tbd"/>-->
|
||||
<header-file src="src/ios/UserUpdateListener.h"/>
|
||||
<source-file src="src/ios/UserUpdateListener.m"/>
|
||||
|
||||
<!-- <!– sdk –>-->
|
||||
<!-- <framework src="src/ios/libs/AVSDK/QAVSDK.framework" custom="true"/>-->
|
||||
<header-file src="src/ios/TrtcUserInfo.h"/>
|
||||
<source-file src="src/ios/TrtcUserInfo.m"/>
|
||||
|
||||
<!-- <framework src="src/ios/libs/IMSDK/IMCore.framework" custom="true"/>-->
|
||||
<!-- <framework src="src/ios/libs/IMSDK/ImSDK.framework" custom="true"/>-->
|
||||
<!-- <framework src="src/ios/libs/IMSDK/IMSDKBugly.framework" custom="true"/>-->
|
||||
<!-- <framework src="src/ios/libs/IMSDK/QALSDK.framework" custom="true"/>-->
|
||||
<!-- <framework src="src/ios/libs/IMSDK/TLSSDK.framework" custom="true"/>-->
|
||||
<header-file src="src/ios/UIView+Toast.h"/>
|
||||
<source-file src="src/ios/UIView+Toast.m"/>
|
||||
|
||||
<!-- <framework src="src/ios/libs/ILiveSDK/ILiveSDK.framework" custom="true"/>-->
|
||||
<!-- <framework src="src/ios/libs/ILiveSDK/ILiveLogReport.framework" custom="true"/>-->
|
||||
<!-- <framework src="src/ios/libs/ILiveSDK/TILLiveSDK.framework" custom="true"/>-->
|
||||
<header-file src="src/ios/VideoCallingViewController.h"/>
|
||||
<source-file src="src/ios/VideoCallingViewController.m"/>
|
||||
|
||||
<!-- <framework src="src/ios/libs/BeautySDK/Pitu/GPUImage.framework" custom="true"/>-->
|
||||
<!-- <source-file src="src/ios/libs/BeautySDK/Pitu/libcrypto.a" framework="true"/>-->
|
||||
<!-- <source-file src="src/ios/libs/BeautySDK/Pitu/libssl.a" framework="true"/>-->
|
||||
<!-- <header-file src="src/ios/libs/BeautySDK/include/TXCRenderView.h"/>-->
|
||||
<!-- <header-file src="src/ios/libs/BeautySDK/include/TXCVideoPreprocessor.h"/>-->
|
||||
<!-- <header-file src="src/ios/libs/BeautySDK/include/TXEVideoPreprocessorDef.h"/>-->
|
||||
<!-- <header-file src="src/ios/libs/BeautySDK/include/TXEVideoTypeDef.h"/>-->
|
||||
<!-- <header-file src="src/ios/libs/BeautySDK/include/TXINotifyDelegate.h"/>-->
|
||||
<!-- <header-file src="src/ios/libs/BeautySDK/include/TXIVideoPreprocessorDelegate.h"/>-->
|
||||
<!-- <source-file src="src/ios/libs/BeautySDK/libTXMRenderer.a" framework="true"/>-->
|
||||
<!-- <source-file src="src/ios/libs/BeautySDK/libTXMVideoPreprocessor.a" framework="true"/>-->
|
||||
<!-- <source-file src="src/ios/libs/BeautySDK/libTXMBasic.a" framework="true"/>-->
|
||||
<resource-file src="src/ios/VideoCallingViewController.xib"/>
|
||||
|
||||
<!-- <config-file target="*TrtcConfig.plist" parent="AppId">-->
|
||||
<!-- <string>$APP_ID</string>-->
|
||||
<!-- </config-file>-->
|
||||
<!-- </platform> -->
|
||||
<podspec>
|
||||
<pods use-frameworks="true">
|
||||
<pod name="TXLiteAVSDK_TRTC" options=":podspec => 'https://liteav.sdk.qcloud.com/pod/liteavsdkspec/TXLiteAVSDK_TRTC.podspec'" />
|
||||
</pods>
|
||||
</podspec>
|
||||
|
||||
</platform>
|
||||
|
||||
<platform name="android">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="Trtc">
|
||||
<param name="android-package" value="com.tencent.trtc.TrtcPlugin"/>
|
||||
<!-- <param name="onload" value="true" /> -->
|
||||
<param name="onload" value="true" />
|
||||
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
@ -158,8 +92,8 @@
|
||||
<!-- <uses-feature android:name="android.hardware.camera.autofocus" />-->
|
||||
</config-file>
|
||||
|
||||
<config-file target="res/values/strings.xml" parent="/resources">
|
||||
<string name="common_please_input_roomid_and_userid">用户没有允许需要的权限,加入通话失败</string>
|
||||
<config-file target="res/values/strings.xml" parent="/resources">
|
||||
<string name="common_please_input_roomid_and_userid">用户没有允许需要的权限,加入通话失败</string>
|
||||
<string name="main_trtc_base_funciton">基础功能</string>
|
||||
<string name="main_item_aduio_call">语音通话</string>
|
||||
<string name="main_item_aduio_call_desc">双人/多人语音通话、包含静音/免提等功能</string>
|
||||
@ -211,7 +145,7 @@
|
||||
<resource-file src="src/android/res/values/styles.xml" target="res/values/trtc-styles.xml"/>
|
||||
|
||||
<config-file target="AndroidManifest.xml" parent="/manifest/application">
|
||||
<activity android:name="com.tencent.trtc.TRTCBaseActivity" android:windowSoftInputMode="adjustPan" android:launchMode="singleInstance" android:screenOrientation="portrait">
|
||||
<activity android:name="com.tencent.trtc.TRTCBaseActivity" android:windowSoftInputMode="adjustPan" android:launchMode="singleInstance" android:screenOrientation="portrait">
|
||||
</activity>
|
||||
<activity android:name="com.tencent.trtc.videocall.VideoCallingActivity" android:windowSoftInputMode="adjustPan" android:launchMode="singleInstance" android:screenOrientation="portrait" android:theme="@style/AppTheme">
|
||||
</activity>
|
||||
@ -258,7 +192,7 @@
|
||||
<resource-file src="src/android/res/mipmap-xxhdpi/swap_camera.png" target="res/mipmap-xxhdpi/swap_camera.png"/>
|
||||
<resource-file src="src/android/res/mipmap-xxhdpi/view_close.png" target="res/mipmap-xxhdpi/view_close.png"/>
|
||||
<resource-file src="src/android/res/mipmap-xxhdpi/view_open.png" target="res/mipmap-xxhdpi/view_open.png"/>
|
||||
<resource-file src="src/android/res/mipmap-xxhdpi/rotation.png" target="res/mipmap-xxhdpi/rotation.png"/>
|
||||
<resource-file src="src/android/res/mipmap-xxhdpi/rotation.png" target="res/mipmap-xxhdpi/rotation.png"/>
|
||||
|
||||
|
||||
<framework src="com.tencent.liteav:LiteAVSDK_TRTC:latest.release"/>
|
||||
|
@ -17,6 +17,7 @@ public class CordovaEventKit {
|
||||
}
|
||||
|
||||
public static void init(CordovaPlugin plugin){
|
||||
Log.d(TAG, "init");
|
||||
if(CordovaEventKit.kit == null){
|
||||
CordovaEventKit.kit = new CordovaEventKit(plugin);
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public class CustomVideoView extends RelativeLayout {
|
||||
Log.d(TAG,"TRTC - changeUser: main?:"+mainView+",alwaysHide?:"+alwaysHide+",user:" + (userInfo == null ? null : userInfo.getPersonid()));
|
||||
this.setBackgroundColor(Color.TRANSPARENT);
|
||||
if(userInfo == null){
|
||||
setVisibility(View.GONE);;
|
||||
setVisibility(View.GONE);
|
||||
}else {
|
||||
setVisibility(alwaysHide?INVISIBLE:VISIBLE);
|
||||
}
|
||||
@ -88,7 +88,9 @@ public class CustomVideoView extends RelativeLayout {
|
||||
|
||||
mTRTCCloud.setRemoteRenderParams(this.userInfo.getPersonid(),mainView ? TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_BIG : TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SUB,params);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
if(this.userInfo.isShareUser()){
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -133,12 +135,14 @@ public class CustomVideoView extends RelativeLayout {
|
||||
}
|
||||
|
||||
public CustomVideoView setMainView(boolean mainView) {
|
||||
Log.d(TAG,"TRTC - setMainView mainView:"+mainView);
|
||||
if(this.mTRTCCloud == null){
|
||||
this.mTRTCCloud = TRTCCloud.sharedInstance(mContext);
|
||||
}
|
||||
this.mainView = mainView;
|
||||
if(!mainView){
|
||||
Events.addListener("subview.always.hide",(extra)->{
|
||||
Log.d(TAG,"TRTC - event listener (subview.always.hide):"+extra);
|
||||
this.alwaysHide = extra.getBoolean("alwaysHide",false);
|
||||
if(this.alwaysHide){
|
||||
this.setVisibility(INVISIBLE);
|
||||
@ -161,6 +165,7 @@ public class CustomVideoView extends RelativeLayout {
|
||||
});
|
||||
}
|
||||
Events.addListener("userinfo.update",(extra) -> {
|
||||
Log.d(TAG,"TRTC - event listener (userinfo.update):"+extra + ",userinfo:"+this.userInfo);
|
||||
if(this.userInfo !=null && !this.userInfo.isLocal() && extra.getString("userId").equals(this.userInfo.getPersonid())){
|
||||
this.userInfo.setDisplayName(extra.getString("displayName"));
|
||||
this.titleView.setText(this.userInfo.getDisplayName() + (this.userInfo.isShareUser() ? "的屏幕分享": ""));
|
||||
|
@ -68,4 +68,14 @@ public class UserInfo {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserInfo{" +
|
||||
"personid='" + personid + '\'' +
|
||||
", displayName='" + displayName + '\'' +
|
||||
", local=" + local +
|
||||
", frontCamera=" + frontCamera +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -347,6 +347,7 @@ public class VideoCallingActivity extends TRTCBaseActivity implements View.OnCli
|
||||
|
||||
@Override
|
||||
public void onUserVideoAvailable(String userId, boolean available) {
|
||||
super.onUserVideoAvailable(userId, available);
|
||||
Log.d(TAG, "onUserVideoAvailable userId " + userId + ", mUserCount " + mUserCount + ",available " + available);
|
||||
UserInfo info = new UserInfo().setPersonid(userId);
|
||||
int index = mUserList.indexOf(info);
|
||||
|
22
src/ios/CordovaEventKit.h
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// CordovaEventKit.h
|
||||
// shuto-cne
|
||||
//
|
||||
// Created by 范大德 on 2022/3/18.
|
||||
//
|
||||
#import <Cordova/CDVPlugin.h>
|
||||
|
||||
#ifndef CordovaEventKit_h
|
||||
#define CordovaEventKit_h
|
||||
|
||||
|
||||
#endif /* CordovaEventKit_h */
|
||||
@interface CordovaEventKit
|
||||
{}
|
||||
+ (void)init: (CDVPlugin*)plugin;
|
||||
|
||||
+ (void) fireEvent:(NSString*)event obj:(NSDictionary*) obj;
|
||||
|
||||
+ (void) fireEvent:(NSString*) event msg:(NSString*) msg;
|
||||
|
||||
@end
|
71
src/ios/CordovaEventKit.m
Normal file
@ -0,0 +1,71 @@
|
||||
//
|
||||
// CordovaEventKit.m
|
||||
// 触发Cordova事件
|
||||
//
|
||||
// Created by 范大德 on 2022/3/18.
|
||||
//
|
||||
#import <Cordova/CDVPlugin.h>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CordovaEventKit.h"
|
||||
@interface CordovaEventKit()
|
||||
{}
|
||||
@end
|
||||
@implementation CordovaEventKit
|
||||
|
||||
static CDVPlugin* cdvPlugin;
|
||||
|
||||
+ (void)init: (CDVPlugin*)plugin{
|
||||
cdvPlugin = plugin;
|
||||
}
|
||||
|
||||
+ (void) fireEvent:(NSString*)event obj:(NSDictionary*) obj{
|
||||
NSString* jsonData = [self toJSON:obj];
|
||||
[CordovaEventKit fireEvent:event msg:jsonData];
|
||||
}
|
||||
|
||||
+ (void) fireEvent:(NSString*) event msg:(NSString*) msg{
|
||||
NSLog(@"TRTC - CordovaEventKit::fireEvent --- event:%@,msg:%@",event,msg);
|
||||
|
||||
if (cdvPlugin == nil || event == nil || msg == nil) {
|
||||
NSLog(@"TRTC - CordovaEventKit::fireEvent --- cdvPlugin%@,event:%@,msg:%@",cdvPlugin,event,msg);
|
||||
return;
|
||||
}
|
||||
event = [event stringByReplacingOccurrencesOfString:@"\\" withString:@"_"];
|
||||
|
||||
NSString* js = [[NSString alloc] initWithFormat:@"window.cordova.plugin.trtc.fireEvent('%@',%@)", event, msg];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[cdvPlugin.commandDelegate evalJs:js];
|
||||
});
|
||||
}
|
||||
|
||||
+(NSString*) toJSON:(NSDictionary*)obj{
|
||||
NSError *error = nil;
|
||||
NSData *jsonData = nil;
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
[obj enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
NSString *keyString = nil;
|
||||
NSString *valueString = nil;
|
||||
if ([key isKindOfClass:[NSString class]]) {
|
||||
keyString = key;
|
||||
}else{
|
||||
keyString = [NSString stringWithFormat:@"%@",key];
|
||||
}
|
||||
|
||||
if ([obj isKindOfClass:[NSString class]]) {
|
||||
valueString = obj;
|
||||
}else{
|
||||
valueString = [NSString stringWithFormat:@"%@",obj];
|
||||
}
|
||||
|
||||
[dict setObject:valueString forKey:keyString];
|
||||
}];
|
||||
jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error];
|
||||
if ([jsonData length] == 0 || error != nil) {
|
||||
return nil;
|
||||
}
|
||||
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
@end
|
22
src/ios/Events.h
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Events.h
|
||||
// shuto-cne
|
||||
//
|
||||
// Created by 范大德 on 2022/3/17.
|
||||
//
|
||||
|
||||
#ifndef Events_h
|
||||
#define Events_h
|
||||
|
||||
|
||||
#endif /* Events_h */
|
||||
|
||||
#import "Listener.h"
|
||||
|
||||
@interface Events
|
||||
{}
|
||||
+ (void)fireEvent: (NSString*)event;
|
||||
+ (void)fireEvent: (NSString*)event extra:(NSDictionary*)extra;
|
||||
+ (void)addListener: (NSString*)event listener:(Listener*)listener;
|
||||
+ (void)removeListener: (NSString*)event listener:(Listener*)listener;
|
||||
@end
|
64
src/ios/Events.m
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// Events.m
|
||||
// 插件中事件流转
|
||||
//
|
||||
// Created by 范大德 on 2022/3/17.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Events.h"
|
||||
@interface Events()
|
||||
{}
|
||||
@end
|
||||
@implementation Events
|
||||
|
||||
static NSString* PREFIX = @"com.tencent.trtc.event";
|
||||
static NSMutableDictionary<NSString*,NSMutableOrderedSet<Listener*>*> *events = nil;
|
||||
|
||||
+ (void)fireEvent: (NSString*)event{
|
||||
[Events fireEvent: nil];
|
||||
}
|
||||
+ (void)fireEvent: (NSString*)event extra:(NSDictionary*)extra{
|
||||
[Events init];
|
||||
NSLog(@"TRTC - Events::fireEvent --- event:%@,extra:%@",event,extra);
|
||||
NSMutableOrderedSet* listeners = [self getEventListener:event];
|
||||
if(listeners != nil && listeners.count > 0 ){
|
||||
for (Listener* listener in listeners) {
|
||||
NSLog(@"TRTC - Events::fireEvent --- event:%@,listener:%@",event,listener);
|
||||
[listener on:extra];
|
||||
}
|
||||
}
|
||||
}
|
||||
+ (void)addListener: (NSString*)event listener:(Listener*)listener{
|
||||
[Events init];
|
||||
NSLog(@"TRTC - Events::addListener --- event:%@,listener:%@",event,listener);
|
||||
NSMutableOrderedSet* listeners = [self getEventListener:event];
|
||||
if(![listeners containsObject:listener]){
|
||||
[listeners addObject:listener];
|
||||
}
|
||||
}
|
||||
+ (void)removeListener: (NSString*)event listener:(Listener*)listener{
|
||||
[Events init];
|
||||
NSLog(@"TRTC - Events::removeListener --- event:%@,listener:%@",event,listener);
|
||||
NSMutableOrderedSet* listeners = [self getEventListener:event];
|
||||
if([listeners containsObject:listener]){
|
||||
[listeners removeObject:listener];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)init{
|
||||
if( events == nil){
|
||||
events = [NSMutableDictionary dictionary];
|
||||
}
|
||||
}
|
||||
+ (NSMutableOrderedSet*) getEventListener: (NSString*)event{
|
||||
[Events init];
|
||||
NSString* key = [[NSString alloc] initWithFormat:@"%@%@", PREFIX, event ];
|
||||
NSMutableOrderedSet* listeners =[events valueForKey:key];
|
||||
if(listeners == nil){
|
||||
listeners = [[NSMutableOrderedSet alloc] init];
|
||||
[events setObject:listeners forKey:key];
|
||||
}
|
||||
return [events valueForKey:key];
|
||||
}
|
||||
@end
|
16
src/ios/Listener.h
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// Listener.h
|
||||
// shuto-cne
|
||||
//
|
||||
// Created by 范大德 on 2022/3/17.
|
||||
//
|
||||
|
||||
#ifndef Listener_h
|
||||
#define Listener_h
|
||||
|
||||
#endif /* Listener_h */
|
||||
@interface Listener:NSObject
|
||||
{}
|
||||
- (instancetype)init;
|
||||
- (void)on: (NSDictionary*)extra;
|
||||
@end
|
21
src/ios/Listener.m
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Listener.m
|
||||
// shuto-cne
|
||||
//
|
||||
// Created by 范大德 on 2022/3/30.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Listener.h"
|
||||
@interface Listener()
|
||||
{}
|
||||
- (instancetype)init;
|
||||
- (void)on: (NSDictionary*)extra;
|
||||
@end
|
||||
|
||||
@implementation Listener
|
||||
- (instancetype)init{
|
||||
return self;
|
||||
}
|
||||
- (void)on: (NSDictionary*)extra{}
|
||||
@end
|
@ -1,73 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>trtcdayisheng</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>trtcdayisheng</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string></string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>请点击“好”以允许访问。
|
||||
</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>请点击“好”以允许访问。
|
||||
</string>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
@ -1,15 +0,0 @@
|
||||
//
|
||||
// TCLiveConfigDefine.h
|
||||
// TRTC
|
||||
//
|
||||
// Created by xiaowei li on 2018/6/22.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TCLiveConfigDefine_h
|
||||
#define TCLiveConfigDefine_h
|
||||
|
||||
#define Login_Info_Url @"https://xzb.qcloud.com/webrtc/weapp/webrtc_room/get_login_info" //业务后台登录信息拉取,可替换为自己的业务后台
|
||||
#define AuthBuffer_Info_Url @"https://xzb.qcloud.com/webrtc/weapp/webrtc_room/get_privatemapkey"
|
||||
#define Default_Role @"ed640" //用户角色配置画面参数,可在控制台进行配置 https://cloud.tencent.com/document/product/647/17308
|
||||
#endif /* TCLiveConfigDefine_h */
|
@ -1,17 +0,0 @@
|
||||
//
|
||||
// ViewController.h
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/5/31.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TCLiveJoinRoomViewController : UIViewController
|
||||
|
||||
@property (nonatomic) NSString *defaultRoomId;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -1,112 +0,0 @@
|
||||
//
|
||||
// ViewController.m
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/5/31.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TCLiveJoinRoomViewController.h"
|
||||
#import "UIColorEX.h"
|
||||
#import "TCLiveRoomViewController.h"
|
||||
#import "UIToastView.h"
|
||||
#import "TCLiveConfigDefine.h"
|
||||
|
||||
@interface TCLiveJoinRoomViewController () <UITextFieldDelegate>
|
||||
@property(nonatomic,strong)UITextField *inputTextField;
|
||||
@property(nonatomic,strong)UIButton *joinRoomBtn;
|
||||
@property(nonatomic,strong) UIView *botoomLine;
|
||||
@property(nonatomic,strong) UIImageView *bgImageView;
|
||||
@end
|
||||
|
||||
@implementation TCLiveJoinRoomViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
self.title = @"视频通话";
|
||||
|
||||
[self.navigationController.navigationBar setTranslucent:NO];
|
||||
|
||||
[self.view addSubview:self.bgImageView];
|
||||
|
||||
[self.view addSubview:self.inputTextField];
|
||||
|
||||
[self.view addSubview:self.joinRoomBtn];
|
||||
|
||||
}
|
||||
-(void)viewWillAppear:(BOOL)animated{
|
||||
if (self.defaultRoomId) {
|
||||
self.inputTextField.text = self.defaultRoomId;
|
||||
}
|
||||
[super viewWillAppear:animated];
|
||||
[self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor blackColor]}];
|
||||
}
|
||||
-(UIImageView *)bgImageView{
|
||||
if (!_bgImageView) {
|
||||
_bgImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
|
||||
[_bgImageView setImage:[UIImage imageNamed:@"bg.png"]];
|
||||
_bgImageView.userInteractionEnabled = YES;
|
||||
}
|
||||
return _bgImageView;
|
||||
}
|
||||
|
||||
- (UIButton *)joinRoomBtn{
|
||||
if (!_joinRoomBtn) {
|
||||
_joinRoomBtn = [[UIButton alloc] initWithFrame:CGRectMake(20, _inputTextField.frame.size.height + _inputTextField.frame.origin.y + 50, self.view.frame.size.width - 40, 50)];
|
||||
_joinRoomBtn.layer.cornerRadius = 25;
|
||||
[_joinRoomBtn setTitle:@"加入房间" forState:UIControlStateNormal];
|
||||
_joinRoomBtn.backgroundColor = [UIColor colorWithRGBHex:0x1472fc];
|
||||
[_joinRoomBtn addTarget:self action:@selector(joinRoomBtnClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_joinRoomBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
}
|
||||
return _joinRoomBtn;
|
||||
}
|
||||
- (UITextField *)inputTextField{
|
||||
if (!_inputTextField) {
|
||||
_inputTextField = [[UITextField alloc] initWithFrame:CGRectMake(5, 20 , self.view.frame.size.width-10, 40)];
|
||||
_inputTextField.delegate = self;
|
||||
_inputTextField.backgroundColor= [UIColor clearColor];
|
||||
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:@"请输入房间号码" attributes:@{NSForegroundColorAttributeName:[UIColor grayColor]}];
|
||||
_inputTextField.attributedPlaceholder = str;
|
||||
_inputTextField.textColor = [UIColor grayColor];
|
||||
_inputTextField.returnKeyType = UIReturnKeyDone;
|
||||
_inputTextField.keyboardType = UIKeyboardTypeNumberPad;
|
||||
|
||||
_botoomLine = [[UIView alloc] initWithFrame:CGRectMake(0, 40-1, _inputTextField.frame.size.width, 1)];
|
||||
_botoomLine.backgroundColor = [UIColor colorWithRGBHex:0x1472fc];
|
||||
[_inputTextField addSubview:_botoomLine];
|
||||
}
|
||||
return _inputTextField;
|
||||
}
|
||||
|
||||
- (void)joinRoomBtnClick:(UIButton *)sender{
|
||||
if (self.inputTextField.text.length > 0) {
|
||||
TCLiveRoomViewController *vc = [[TCLiveRoomViewController alloc] initWithRoomID:self.inputTextField.text role:Default_Role];
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
else{
|
||||
[[UIToastView getInstance] showToastWithMessage:@"请输入房间号" toastMode:UIToastShowMode_fail];
|
||||
}
|
||||
}
|
||||
|
||||
// 自动跳转
|
||||
//- (void)autoNav:(NSString *)roomId {
|
||||
// TCLiveRoomViewController *vc = [[TCLiveRoomViewController alloc] initWithRoomID:roomId role:Default_Role];
|
||||
// [self.navigationController pushViewController:vc animated:YES];
|
||||
//}
|
||||
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
|
||||
[textField resignFirstResponder];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
|
||||
{
|
||||
NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
|
||||
NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
|
||||
return [string isEqualToString:filtered];
|
||||
}
|
||||
@end
|
@ -1,21 +0,0 @@
|
||||
//
|
||||
// TCLiveRequestManager.h
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/5/31.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef void (^LiveLoginInfoBlock)(int code);
|
||||
typedef void (^LiveAuthBufferBlock)(NSDictionary *info);
|
||||
@interface TCLiveRequestManager : NSObject
|
||||
@property(nonatomic,assign)int sdkAppID; //app标识,可在实时音视频控制台(https://console.cloud.tencent.com/rav)创建自己的应用生成
|
||||
@property(nonatomic,assign)int accountType; //登录实时音视频应用的帐号类型,在控制台创建应用后分配
|
||||
@property(nonatomic,strong)NSString *userID; //用户id标识(可由业务后台自己管理)
|
||||
@property(nonatomic,strong)NSString *userSig; //用于用户鉴权,生成方法https://cloud.tencent.com/document/product/647/17275 (可由业务后台自己管理)
|
||||
+ (TCLiveRequestManager *)getInstance;
|
||||
- (void)requestLoginInfo:(LiveLoginInfoBlock)block;
|
||||
- (void)reqGetAuthBufferInfoWithParams:(NSDictionary *)params block:(LiveAuthBufferBlock)block;
|
||||
@end
|
@ -1,98 +0,0 @@
|
||||
//
|
||||
// TCLiveRequestManager.m
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/5/31.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TCLiveRequestManager.h"
|
||||
#import "UIToastView.h"
|
||||
#import "TCLiveConfigDefine.h"
|
||||
@implementation TCLiveRequestManager
|
||||
+ (TCLiveRequestManager *)getInstance{
|
||||
static TCLiveRequestManager *singleTon = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
singleTon = [TCLiveRequestManager new];
|
||||
});
|
||||
return singleTon;
|
||||
}
|
||||
|
||||
- (void)requestLoginInfo:(LiveLoginInfoBlock)block{
|
||||
NSString *user = [[NSUserDefaults standardUserDefaults] objectForKey:@"TCLIVE_USER"];
|
||||
if (user.length == 0) {
|
||||
user = @"";
|
||||
}
|
||||
NSDictionary *params = @{@"userID":user};
|
||||
NSMutableURLRequest *request = [self getSendPostRequest:Login_Info_Url body:params];//加备注
|
||||
|
||||
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
[sessionConfig setTimeoutIntervalForRequest:30];
|
||||
|
||||
__weak TCLiveRequestManager *weakself = self;
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
||||
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
if (error|| data == nil) {
|
||||
block(-1);
|
||||
[[UIToastView getInstance] showToastWithMessage:@"登录请求失败" toastMode:UIToastShowMode_fail];
|
||||
}
|
||||
else{
|
||||
//无error data解不出
|
||||
NSDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
if (info) {
|
||||
weakself.sdkAppID = [info[@"sdkAppID"] intValue];
|
||||
weakself.accountType = [info[@"accountType"]intValue];
|
||||
weakself.userID = info[@"userID"];
|
||||
weakself.userSig = info[@"userSig"];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:info[@"userID"] forKey:@"TCLIVE_USER"];
|
||||
block(0);
|
||||
}
|
||||
else{
|
||||
block(-1);
|
||||
[[UIToastView getInstance] showToastWithMessage:@"登录信息解包失败" toastMode:UIToastShowMode_fail];
|
||||
}
|
||||
}
|
||||
}];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
-(void)reqGetAuthBufferInfoWithParams:(NSDictionary *)params block:(LiveAuthBufferBlock)block{
|
||||
NSMutableURLRequest *request = [self getSendPostRequest:AuthBuffer_Info_Url body:params];
|
||||
|
||||
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
[sessionConfig setTimeoutIntervalForRequest:30];
|
||||
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
||||
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
if (error|| data == nil) {
|
||||
[[UIToastView getInstance] showToastWithMessage:@"获取authBuffer请求失败" toastMode:UIToastShowMode_fail];
|
||||
}
|
||||
else{
|
||||
//无error data解不出
|
||||
NSDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
if (info) {
|
||||
block(info);
|
||||
}
|
||||
else{
|
||||
[[UIToastView getInstance] showToastWithMessage:@"获取authBuffer解包失败" toastMode:UIToastShowMode_fail];
|
||||
}
|
||||
}
|
||||
}];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
- (NSMutableURLRequest *)getSendPostRequest:(NSString *)url body:(NSDictionary *)body{
|
||||
|
||||
NSData *dataBody = [NSJSONSerialization dataWithJSONObject:body options:NSJSONWritingPrettyPrinted error:nil];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
|
||||
[request setValue:[NSString stringWithFormat:@"%ld", (long) [dataBody length]] forHTTPHeaderField:@"Content-Length"];
|
||||
[request setHTTPMethod:@"POST"];
|
||||
[request setValue:@"application/json; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
|
||||
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
|
||||
[request setHTTPBody:dataBody];
|
||||
|
||||
return request;
|
||||
}
|
||||
@end
|
||||
|
@ -1,17 +0,0 @@
|
||||
//
|
||||
// TCLiveChatTableView.h
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/3.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <ImSDK/ImSDK.h>
|
||||
|
||||
@interface TCLiveChatTableView : UITableView
|
||||
//添加信息到聊天列表
|
||||
- (void)addChatMessage:(NSArray *)msgList withContentColor:(UIColor *)contentColor nickColor:(UIColor *)nickColor;
|
||||
//发送信息
|
||||
- (void)sendMessage:(NSString *)message;
|
||||
@end
|
@ -1,177 +0,0 @@
|
||||
//
|
||||
// TCLiveChatTableView.m
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/3.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <ImSDK/ImSDK.h>
|
||||
#import <ILiveSDK/ILiveSDK.h>
|
||||
#import <ILiveSDK/ILiveRoomManager.h>
|
||||
#import "TCLiveChatTableView.h"
|
||||
#import "TCLiveChatTableViewCell.h"
|
||||
#import "UIColorEX.h"
|
||||
#import "UIToastView.h"
|
||||
#import "TCLiveRequestManager.h"
|
||||
|
||||
@interface TCLiveChatTableView () <UITableViewDelegate,UITableViewDataSource,TIMMessageListener>
|
||||
@property(nonatomic,strong)NSMutableArray *chatMessageList;
|
||||
@property(nonatomic,strong)UIColor *contentColor;
|
||||
@property(nonatomic,strong)UIColor *nickColor;
|
||||
@end
|
||||
|
||||
@implementation TCLiveChatTableView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style{
|
||||
if (self = [super initWithFrame:frame style:style]) {
|
||||
_chatMessageList = [[NSMutableArray alloc] initWithCapacity:1];
|
||||
self.delegate = self;
|
||||
self.dataSource = self;
|
||||
self.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.transform = CGAffineTransformMakeScale(1, -1);
|
||||
self.layer.cornerRadius = 4;
|
||||
self.layer.masksToBounds = YES;
|
||||
self.scrollEnabled = NO;
|
||||
//设置消息监听
|
||||
[[[ILiveSDK getInstance] getTIMManager] setMessageListener:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
//接收消息
|
||||
-(void)onNewMessage:(NSArray *)msgs{
|
||||
[self addChatMessage:msgs withContentColor:nil nickColor:nil];
|
||||
}
|
||||
//消息处理
|
||||
- (void)addChatMessage:(NSArray *)msgList withContentColor:(UIColor *)contentColor nickColor:(UIColor *)nickColor{
|
||||
self.contentColor = contentColor;
|
||||
self.nickColor = nickColor;
|
||||
for (id item in msgList) {
|
||||
[self.chatMessageList insertObject:item atIndex:0];
|
||||
}
|
||||
//过滤非文本消息
|
||||
NSMutableArray *tempArr = [NSMutableArray array];
|
||||
for (int i = 0; i < msgList.count;i++) {
|
||||
TIMMessage *msg = msgList[i];
|
||||
if (![self isTextMsg:msg]) {
|
||||
[tempArr addObject:msg];
|
||||
}
|
||||
}
|
||||
[self.chatMessageList removeObjectsInArray:tempArr];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self reloadData];
|
||||
});
|
||||
}
|
||||
- (BOOL)isTextMsg:(TIMMessage *)msg {
|
||||
TIMOfflinePushInfo *info = msg.getOfflinePushInfo;
|
||||
if ([info.ext hasPrefix:@"TEXT"]) {
|
||||
return YES;
|
||||
}
|
||||
int count = [msg elemCount];
|
||||
for(int i = 0; i < count; i++) {
|
||||
TIMElem *elem = [msg getElem:i];
|
||||
if ([elem isKindOfClass:[TIMCustomElem class]]){
|
||||
if ([((TIMCustomElem*)elem).ext hasPrefix:@"TEXT"]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
else if ([elem isKindOfClass:[TIMTextElem class]]){
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
//发送消息
|
||||
- (void)sendMessage:(NSString *)message{
|
||||
//消息组装
|
||||
TIMMessage *msge = [[TIMMessage alloc] init];
|
||||
TIMCustomElem *textElem = [[TIMCustomElem alloc] init];
|
||||
textElem.data = [message dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSDictionary *descDic = @{@"nickName":[TCLiveRequestManager getInstance].userID};
|
||||
NSString *desc = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:descDic options:NSJSONReadingAllowFragments error:nil] encoding:NSUTF8StringEncoding];
|
||||
textElem.desc = desc;
|
||||
textElem.ext = @"TEXT";
|
||||
[msge addElem:textElem];
|
||||
//调用发送接口
|
||||
[[ILiveRoomManager getInstance] sendGroupMessage:msge succ:^{
|
||||
NSLog(@"send message succ");
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"send message fail");
|
||||
[[UIToastView getInstance] showToastWithMessage:@"发送消息失败" toastMode:UIToastShowMode_fail];
|
||||
}];
|
||||
[self addChatMessage:@[msge] withContentColor:nil nickColor:nil];
|
||||
}
|
||||
#pragma mark - UITableViewDelegate
|
||||
|
||||
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
|
||||
TCLiveChatTableViewCell *cell = [self createChatTableViewCell:tableView withIndexPath:indexPath];
|
||||
return cell.cellHeight;
|
||||
}
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
|
||||
return self.chatMessageList.count;
|
||||
}
|
||||
|
||||
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
|
||||
TCLiveChatTableViewCell *cell = [self createChatTableViewCell:tableView withIndexPath:indexPath];
|
||||
return cell;
|
||||
}
|
||||
//创建消息cell
|
||||
- (TCLiveChatTableViewCell *)createChatTableViewCell:(UITableView *)tableView withIndexPath:(NSIndexPath *)indexPath{
|
||||
TCLiveChatTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ChatTableviewCell"];
|
||||
if (!cell) {
|
||||
cell = [[TCLiveChatTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"ChatTableviewCell"];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
cell.contentView.transform = CGAffineTransformMakeScale (1,-1);
|
||||
}
|
||||
TIMMessage *msg = self.chatMessageList[indexPath.row];
|
||||
int count = [msg elemCount];
|
||||
for(int i = 0; i < count; i++) {
|
||||
TIMElem *elem = [msg getElem:i];
|
||||
//收到消息展示
|
||||
NSMutableAttributedString *msgInfo = [[NSMutableAttributedString alloc] initWithString:@""];
|
||||
if([elem isKindOfClass:[TIMTextElem class]]){
|
||||
msgInfo = [self getContentWithNick:msg.sender andContentTex:((TIMTextElem *)elem).text];
|
||||
[cell setModel:msgInfo];
|
||||
break;
|
||||
}
|
||||
else if ([elem isKindOfClass:[TIMCustomElem class]]){
|
||||
NSString *nick = msg.sender;
|
||||
NSString *dataStr = [[NSString alloc] initWithData:((TIMCustomElem *)elem).data encoding:NSUTF8StringEncoding];
|
||||
NSDictionary *descDic = [NSJSONSerialization JSONObjectWithData:[((TIMCustomElem *)elem).desc dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
|
||||
NSString *nickNmae = descDic[@"nickName"];
|
||||
if (nickNmae.length > 0) {
|
||||
nick = nickNmae;
|
||||
}
|
||||
msgInfo = [self getContentWithNick:nick andContentTex:dataStr];
|
||||
[cell setModel:msgInfo];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
- (NSMutableAttributedString *)getContentWithNick:(NSString *)nick andContentTex:(NSString *)contentText{
|
||||
NSString *content = [NSString stringWithFormat:@"%@:%@",nick, contentText];
|
||||
NSMutableAttributedString *msgInfo = [[NSMutableAttributedString alloc] initWithString:content];
|
||||
UIColor *contentColor = [UIColor whiteColor];
|
||||
UIColor *nickColor = [UIColor colorWithRGBHex:0xFF4081];
|
||||
if (self.contentColor) {
|
||||
contentColor = self.contentColor;
|
||||
}
|
||||
if(self.nickColor){
|
||||
nickColor = self.nickColor;
|
||||
}
|
||||
[msgInfo addAttribute:NSForegroundColorAttributeName value:contentColor range:[content rangeOfString:contentText]];
|
||||
[msgInfo addAttribute:NSForegroundColorAttributeName value:nickColor range:[content rangeOfString:nick]];
|
||||
|
||||
return msgInfo;
|
||||
}
|
||||
//点击事件透传
|
||||
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
|
||||
return nil;
|
||||
}
|
||||
@end
|
@ -1,14 +0,0 @@
|
||||
//
|
||||
// TCLiveChatTableViewCell.h
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/8.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TCLiveChatTableViewCell : UITableViewCell
|
||||
@property(nonatomic,assign)CGFloat cellHeight;
|
||||
-(void)setModel:(NSMutableAttributedString *)model;
|
||||
@end
|
@ -1,59 +0,0 @@
|
||||
//
|
||||
// TCLiveChatTableViewCell.m
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/8.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import "TCLiveChatTableViewCell.h"
|
||||
|
||||
@interface TCLiveChatTableViewCell ()
|
||||
@property(nonatomic,strong) UILabel *contentLabel;
|
||||
@property(nonatomic,strong) UIView *backMaskView;
|
||||
@end
|
||||
|
||||
@implementation TCLiveChatTableViewCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
|
||||
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||
|
||||
[self.contentView addSubview:self.backMaskView];
|
||||
[_backMaskView addSubview:self.contentLabel];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(UILabel *)contentLabel{
|
||||
if (!_contentLabel) {
|
||||
_contentLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
_contentLabel.backgroundColor = [UIColor clearColor];
|
||||
_contentLabel.textAlignment = NSTextAlignmentLeft;
|
||||
_contentLabel.font = [UIFont systemFontOfSize:13];
|
||||
_contentLabel.textColor = [UIColor whiteColor];
|
||||
_contentLabel.numberOfLines = 0;
|
||||
}
|
||||
return _contentLabel;
|
||||
}
|
||||
|
||||
-(UIView *)backMaskView{
|
||||
if (!_backMaskView) {
|
||||
_backMaskView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
_backMaskView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
|
||||
_backMaskView.layer.cornerRadius = 10;
|
||||
_backMaskView.layer.masksToBounds = YES;
|
||||
}
|
||||
return _backMaskView;
|
||||
}
|
||||
|
||||
-(void)setModel:(NSMutableAttributedString *)model{
|
||||
_contentLabel.attributedText = model;
|
||||
CGSize size = [_contentLabel sizeThatFits:CGSizeMake(230, 10000)];
|
||||
|
||||
_contentLabel.frame = CGRectMake(5, 5, size.width, size.height );
|
||||
_backMaskView.frame = CGRectMake(5, 5, size.width + 10, size.height + 10);
|
||||
self.cellHeight = size.height+20;
|
||||
}
|
||||
|
||||
@end
|
@ -1,15 +0,0 @@
|
||||
//
|
||||
// TCLiveRoomViewController.h
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/5/31.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TCLiveVideoLayoutView.h"
|
||||
|
||||
@interface TCLiveRoomViewController : UIViewController
|
||||
//传入roomid(房间号)进入指定房间 设置role配置画面参数
|
||||
-(instancetype)initWithRoomID:(NSString *)roomid role:(NSString *)role;
|
||||
@end
|
@ -1,275 +0,0 @@
|
||||
//
|
||||
// TCLiveRoomViewController.m
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/5/31.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TCLiveRoomViewController.h"
|
||||
#import <ILiveSDK/ILiveRoomOption.h>
|
||||
#import <ILiveSDK/ILiveRoomManager.h>
|
||||
#import <ILiveSDK/ILiveRenderView.h>
|
||||
#import <ILiveSDK/ILiveFrameDispatcher.h>
|
||||
#import <TILLiveSDK/TILLiveManager.h>
|
||||
#import <QAVSDK/QAVCommon.h>
|
||||
#import <ImSDK/ImSDK.h>
|
||||
#import "TCLiveRequestManager.h"
|
||||
#import "TCLiveVideoControlBar.h"
|
||||
#import "TCLiveChatTableView.h"
|
||||
#import "UIToastView.h"
|
||||
|
||||
#define LIVE_VIEW_HEIGHT 370
|
||||
#define LIVE_CONTROL_BAR_HEIGHT 70
|
||||
#define LIVE_INPUTTEXTFIELD_HEIGHT 40
|
||||
|
||||
@interface TCLiveRoomViewController ()<UITextFieldDelegate,TCLiveVideoControlBarDelegate>
|
||||
@property(nonatomic,strong) TCLiveVideoLayoutView *videoLayoutView;
|
||||
@property(nonatomic,strong) TCLiveVideoControlBar *controlBar;
|
||||
@property(nonatomic,strong) TCLiveChatTableView *chatTableView;
|
||||
@property(nonatomic,strong) UITextField *inputTextField;
|
||||
@property(nonatomic,strong) NSString *roomID;
|
||||
@property(nonatomic,strong) UIImageView *bgImageView;
|
||||
@property(nonatomic,strong) NSTimer *logTimer;
|
||||
@property(nonatomic,strong) NSTimer *heartBeatTimer;
|
||||
@property(nonatomic,assign) CGRect origInputTextFieldFrame;
|
||||
@property(nonatomic,assign) CGRect origChatTableViewFrame;
|
||||
@property(nonatomic,strong) NSString *role;
|
||||
@end
|
||||
|
||||
@implementation TCLiveRoomViewController
|
||||
|
||||
-(instancetype)initWithRoomID:(NSString *)roomid role:(NSString *)role{
|
||||
if (self = [super init]) {
|
||||
self.roomID = roomid;
|
||||
self.role = role;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
self.title = self.roomID;
|
||||
[self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor]}];
|
||||
|
||||
[self enterRoom];
|
||||
|
||||
[self customLeftButton];
|
||||
|
||||
[self.view addSubview:self.bgImageView];
|
||||
|
||||
[_bgImageView addSubview:self.videoLayoutView];
|
||||
|
||||
[_bgImageView addSubview:self.chatTableView];
|
||||
|
||||
[_bgImageView addSubview:self.controlBar];
|
||||
|
||||
[_bgImageView addSubview:self.inputTextField];
|
||||
|
||||
//监听键盘变化
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
|
||||
}
|
||||
-(void)viewWillAppear:(BOOL)animated{
|
||||
[super viewWillAppear:animated];
|
||||
//导航栏透明化
|
||||
[self setNavigationBarTransparent];
|
||||
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated{
|
||||
[super viewWillDisappear:animated];
|
||||
[self.navigationController.navigationBar setTranslucent:NO];
|
||||
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
|
||||
}
|
||||
- (void)setNavigationBarTransparent{
|
||||
[self.navigationController.navigationBar setTranslucent:YES];
|
||||
[self.navigationController.navigationBar setBackgroundImage:[[UIImage alloc]init] forBarMetrics:UIBarMetricsDefault];
|
||||
[self.navigationController.navigationBar setShadowImage:[[UIImage alloc]init]];
|
||||
[self.navigationController.navigationBar setBackgroundColor:[UIColor clearColor]];
|
||||
}
|
||||
|
||||
- (void)enterRoom{
|
||||
[[TCLiveRequestManager getInstance] reqGetAuthBufferInfoWithParams:@{@"roomID":self.roomID,@"userID":[TCLiveRequestManager getInstance].userID} block:^(NSDictionary *info) {
|
||||
ILiveRoomOption *option = [ILiveRoomOption defaultHostLiveOption];
|
||||
option.imOption.imSupport = YES;
|
||||
option.memberStatusListener = self.videoLayoutView;
|
||||
option.roomDisconnectListener = self;
|
||||
option.controlRole = self.role;
|
||||
option.avOption.privateMapKey = [info[@"privateMapKey"] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
[[ILiveRoomManager getInstance] createRoom:[self.roomID intValue] option:option succ:^{
|
||||
NSLog(@"-----> create room succ");
|
||||
[[UIToastView getInstance] showToastWithMessage:@"创建房间成功" toastMode:UIToastShowMode_Succ];
|
||||
[self.controlBar enableBeauty:YES];//进入房间默认开启美颜
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
if(errId == 10021){
|
||||
//表示房间已存在直接加入房间
|
||||
[[ILiveRoomManager getInstance] joinRoom:[self.roomID intValue] option:option succ:^{
|
||||
NSLog(@"-----> join room succ");
|
||||
[[UIToastView getInstance] showToastWithMessage:@"加入房间成功" toastMode:UIToastShowMode_Succ];
|
||||
[self.controlBar enableBeauty:YES];//进入房间默认开启美颜
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"-----> join room fail,%@ %d %@",module, errId, errMsg);
|
||||
[[UIToastView getInstance] showToastWithMessage:errMsg toastMode:UIToastShowMode_fail];
|
||||
}];
|
||||
}
|
||||
else{
|
||||
NSLog(@"-----> create room fail,%@ %d %@",module, errId, errMsg);
|
||||
[[UIToastView getInstance] showToastWithMessage:errMsg toastMode:UIToastShowMode_fail];
|
||||
}
|
||||
}];
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (void)customLeftButton{
|
||||
UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
backBtn.frame = CGRectMake(0, 0, 44, 44);
|
||||
[backBtn setImage:[UIImage imageNamed:@"ui_title_arrow_left.png"] forState:UIControlStateNormal];
|
||||
[backBtn addTarget:self action:@selector(backBtnClicked:) forControlEvents:UIControlEventTouchUpInside];
|
||||
backBtn.frame = CGRectMake(0, 0, 44, 44);
|
||||
[backBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
||||
backBtn.imageEdgeInsets = UIEdgeInsetsMake(10, 0, 10, 20);
|
||||
UIBarButtonItem *item = [[UIBarButtonItem alloc]initWithCustomView:backBtn];
|
||||
self.navigationItem.leftBarButtonItem = item;
|
||||
}
|
||||
|
||||
- (UIImageView *)bgImageView{
|
||||
if (!_bgImageView) {
|
||||
_bgImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
|
||||
[_bgImageView setImage:[UIImage imageNamed:@"bg.png"]];
|
||||
_bgImageView.userInteractionEnabled = YES;
|
||||
}
|
||||
return _bgImageView;
|
||||
}
|
||||
|
||||
//视频区域
|
||||
-(TCLiveVideoLayoutView *)videoLayoutView{
|
||||
if (!_videoLayoutView) {
|
||||
_videoLayoutView = [[TCLiveVideoLayoutView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
|
||||
}
|
||||
return _videoLayoutView;
|
||||
}
|
||||
//控制bar
|
||||
- (TCLiveVideoControlBar *)controlBar{
|
||||
if (!_controlBar) {
|
||||
_controlBar = [[TCLiveVideoControlBar alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - LIVE_CONTROL_BAR_HEIGHT, self.view.frame.size.width, LIVE_CONTROL_BAR_HEIGHT)];
|
||||
_controlBar.delegate = self;
|
||||
}
|
||||
return _controlBar;
|
||||
}
|
||||
//消息列表
|
||||
- (UITableView *)chatTableView{
|
||||
if (!_chatTableView) {
|
||||
_chatTableView = [[TCLiveChatTableView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - LIVE_CONTROL_BAR_HEIGHT - 400, 250, 400) style:UITableViewStylePlain];
|
||||
_chatTableView.backgroundColor = [UIColor clearColor];
|
||||
self.origChatTableViewFrame = self.chatTableView.frame;
|
||||
}
|
||||
return _chatTableView;
|
||||
}
|
||||
//输入框
|
||||
- (UITextField *)inputTextField{
|
||||
if (!_inputTextField) {
|
||||
_inputTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height , self.view.frame.size.width, LIVE_INPUTTEXTFIELD_HEIGHT)];
|
||||
_inputTextField.delegate = self;
|
||||
_inputTextField.backgroundColor= [[UIColor whiteColor] colorWithAlphaComponent:0.9];
|
||||
_inputTextField.placeholder = @"请输入内容";
|
||||
_inputTextField.returnKeyType = UIReturnKeySend;
|
||||
self.origInputTextFieldFrame = self.inputTextField.frame;
|
||||
}
|
||||
return _inputTextField;
|
||||
}
|
||||
//关闭界面退出房间
|
||||
- (void)backBtnClicked:(UIButton *)sender{
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
[[ILiveRoomManager getInstance] quitRoom:^{
|
||||
NSLog(@"-----> quit room succ");
|
||||
[[UIToastView getInstance] showToastWithMessage:@"退出房间成功" toastMode:UIToastShowMode_Succ];
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"-----> quit room fail,%@ %d %@",module, errId, errMsg);
|
||||
[[UIToastView getInstance] showToastWithMessage:@"退出房间失败" toastMode:UIToastShowMode_fail];
|
||||
}];
|
||||
[_logTimer invalidate];
|
||||
_logTimer = nil;
|
||||
|
||||
}
|
||||
#pragma mark - ILiveRoomDisconnectListener
|
||||
- (BOOL)onRoomDisconnect:(int)reason;{
|
||||
[self backBtnClicked:nil];
|
||||
return YES;
|
||||
}
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField;{
|
||||
[textField resignFirstResponder];
|
||||
NSString *text = textField.text;
|
||||
if (text.length > 0) {
|
||||
[self.chatTableView sendMessage:text];
|
||||
textField.text = nil;
|
||||
}
|
||||
else{
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
#pragma mark - TCLiveVideoControlBarDelegate
|
||||
-(void)logBtnClick:(UIButton *)sender{
|
||||
if (sender.selected) {
|
||||
if (!_logTimer) {
|
||||
[self logUpdate];
|
||||
_logTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(logUpdate) userInfo:nil repeats:YES];
|
||||
}
|
||||
}
|
||||
else{
|
||||
[_logTimer invalidate];
|
||||
_logTimer = nil;
|
||||
[_videoLayoutView closeLogView];
|
||||
}
|
||||
}
|
||||
- (void)chatBtnClick:(UIButton *)sender{
|
||||
[self.inputTextField becomeFirstResponder];
|
||||
}
|
||||
- (void)logUpdate{
|
||||
QAVContext *avContext = [[ILiveSDK getInstance] getAVContext];
|
||||
NSString *qualityStr = [avContext.room getQualityTips];
|
||||
[_videoLayoutView showLogView:qualityStr];
|
||||
}
|
||||
|
||||
- (void)beautyBtnClick:(UIButton *)sender{
|
||||
if (sender.selected) {
|
||||
[self.videoLayoutView setBeautyLevel:9];
|
||||
}
|
||||
else{
|
||||
[self.videoLayoutView setBeautyLevel:0];
|
||||
}
|
||||
}
|
||||
#pragma mark - 键盘事件
|
||||
- (void)keybaordAnimationWithDuration:(CGFloat)duration keyboardOriginY:(CGFloat)keyboardOriginY{
|
||||
|
||||
__block TCLiveRoomViewController *blockSelf = self;
|
||||
//作为视图的键盘,弹出动画也是UIViewAnimationOptionCurveEaseIn的方式
|
||||
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
|
||||
//text field
|
||||
if(keyboardOriginY == blockSelf.view.frame.size.height){
|
||||
blockSelf.inputTextField.frame = blockSelf.origInputTextFieldFrame;
|
||||
blockSelf.chatTableView.frame = blockSelf.origChatTableViewFrame;
|
||||
}
|
||||
else{
|
||||
blockSelf.inputTextField.frame = CGRectMake(blockSelf.inputTextField.frame.origin.x,keyboardOriginY - blockSelf.inputTextField.frame.size.height, blockSelf.inputTextField.frame.size.width, blockSelf.inputTextField.frame.size.height);
|
||||
blockSelf.chatTableView.frame = CGRectMake(blockSelf.chatTableView.frame.origin.x,keyboardOriginY - blockSelf.chatTableView.frame.size.height - LIVE_INPUTTEXTFIELD_HEIGHT, blockSelf.chatTableView.frame.size.width, blockSelf.chatTableView.frame.size.height);
|
||||
}
|
||||
|
||||
} completion:nil];
|
||||
}
|
||||
- (void)keyboardWillChangeFrame:(NSNotification *)notify{
|
||||
NSDictionary * info = notify.userInfo;
|
||||
//动画时间
|
||||
CGFloat animationDuration = [info[UIKeyboardAnimationDurationUserInfoKey] floatValue];
|
||||
//键盘目标位置
|
||||
CGRect keyboardAimFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
if ([self.inputTextField isFirstResponder]) {
|
||||
[self keybaordAnimationWithDuration:animationDuration keyboardOriginY:keyboardAimFrame.origin.y];
|
||||
}
|
||||
}
|
||||
@end
|
@ -1,13 +0,0 @@
|
||||
//
|
||||
// UIColorEX.h
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/5.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIColor (EX)
|
||||
+ (UIColor *)colorWithRGBHex: (unsigned int)hex;
|
||||
@end
|
@ -1,25 +0,0 @@
|
||||
//
|
||||
// UIColorEX.m
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/5.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIColorEX.h"
|
||||
|
||||
@implementation UIColor (EX)
|
||||
|
||||
+ (UIColor *)colorWithRGBHex: (unsigned int)hex
|
||||
{
|
||||
int r = (hex >> 16) & 0xFF;
|
||||
int g = (hex >> 8) & 0xFF;
|
||||
int b = (hex) & 0xFF;
|
||||
|
||||
return [UIColor colorWithRed:r / 255.0f
|
||||
green:g / 255.0f
|
||||
blue:b / 255.0f
|
||||
alpha:1.0f];
|
||||
}
|
||||
|
||||
@end
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// UIToastView.h
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/9.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef enum {
|
||||
UIToastShowMode_Default = 1,
|
||||
UIToastShowMode_Succ,
|
||||
UIToastShowMode_fail,
|
||||
}UIToastShowMode;
|
||||
|
||||
@interface UIToastView : UIView
|
||||
+ (UIToastView *)getInstance;
|
||||
- (void)showToastWithMessage:(NSString *)text toastMode:(UIToastShowMode )mode;
|
||||
@end
|
@ -1,96 +0,0 @@
|
||||
//
|
||||
// UIToastView.m
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/9.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIToastView.h"
|
||||
|
||||
#define UITOAST_HEIGHT 30
|
||||
#define UITOAST_IMAGE_HEIGHT 15
|
||||
@interface UIToastView ()
|
||||
@property(nonatomic,strong)UILabel *toastLabel;
|
||||
@property(nonatomic,strong)UIImageView *toastImageView;
|
||||
@end
|
||||
|
||||
@implementation UIToastView
|
||||
|
||||
+ (UIToastView *)getInstance{
|
||||
static UIToastView *singleTon = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
singleTon = [UIToastView new];
|
||||
});
|
||||
return singleTon;
|
||||
}
|
||||
|
||||
-(instancetype)initWithFrame:(CGRect)frame{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
|
||||
self.layer.cornerRadius = 4;
|
||||
self.layer.masksToBounds = YES;
|
||||
|
||||
[self addSubview:self.toastImageView];
|
||||
[self addSubview:self.toastLabel];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (UILabel *)toastLabel{
|
||||
if (!_toastLabel) {
|
||||
_toastLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
_toastLabel.numberOfLines = 1;
|
||||
_toastLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_toastLabel.textColor = [UIColor whiteColor];
|
||||
_toastLabel.font = [UIFont systemFontOfSize:16];
|
||||
}
|
||||
return _toastLabel;
|
||||
}
|
||||
|
||||
-(UIImageView *)toastImageView{
|
||||
if (!_toastImageView) {
|
||||
_toastImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
|
||||
|
||||
}
|
||||
return _toastImageView;
|
||||
}
|
||||
|
||||
- (void)showToastWithMessage:(NSString *)text toastMode:(UIToastShowMode )mode{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIToastView *view = [UIToastView new];
|
||||
CGRect rect = [text boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width - 80, 20) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil];
|
||||
view.frame = CGRectMake(0, 0, rect.size.width + 10 + 20, UITOAST_HEIGHT);
|
||||
view.toastImageView.frame = CGRectMake(5, 0, UITOAST_IMAGE_HEIGHT, UITOAST_IMAGE_HEIGHT);
|
||||
view.toastImageView.center = CGPointMake(view.toastImageView.center.x, view.frame.size.height/2);
|
||||
view.toastLabel.frame = CGRectMake(25, 0, rect.size.width, UITOAST_HEIGHT);
|
||||
view.toastLabel.text = text;
|
||||
if (UIToastShowMode_Succ == mode){
|
||||
view.toastImageView.image = [UIImage imageNamed:@"ic_toast_success@2x"];
|
||||
}
|
||||
else if(UIToastShowMode_fail == mode){
|
||||
view.toastImageView.image = [UIImage imageNamed:@"icon_sign@2x"];
|
||||
}
|
||||
else{
|
||||
view.toastImageView.frame = CGRectZero;
|
||||
view.frame = CGRectMake(0, 0, rect.size.width + 10 , UITOAST_HEIGHT);
|
||||
view.toastLabel.frame = CGRectMake(5, 0, rect.size.width, UITOAST_HEIGHT);
|
||||
}
|
||||
view.center = [[UIApplication sharedApplication] keyWindow].center;
|
||||
[[[UIApplication sharedApplication] keyWindow] addSubview:view];
|
||||
[UIView animateWithDuration:0.5 animations:^{
|
||||
view.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.5 animations:^{
|
||||
view.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
[view removeFromSuperview];
|
||||
}];
|
||||
});
|
||||
}];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@end
|
@ -1,34 +0,0 @@
|
||||
//
|
||||
// TCLiveVideoControlBar.h
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/3.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol TCLiveVideoControlBarDelegate <NSObject>
|
||||
@optional
|
||||
//切换摄像头
|
||||
- (void)switchCameraBtnClick:(UIButton *)sender;
|
||||
//开关美颜
|
||||
- (void)beautyBtnClick:(UIButton *)sender;
|
||||
//开关麦克风
|
||||
- (void)voiceBtnClick:(UIButton *)sender;
|
||||
//展示日志
|
||||
- (void)logBtnClick:(UIButton *)sender;
|
||||
//反馈
|
||||
- (void)feedBackBtnClick:(UIButton *)sender;
|
||||
//切换配置
|
||||
- (void)changeRoleBtnClick:(UIButton *)sender;
|
||||
//聊天
|
||||
- (void)chatBtnClick:(UIButton *)sender;
|
||||
@end
|
||||
|
||||
@interface TCLiveVideoControlBar : UIView
|
||||
@property(nonatomic,weak)id<TCLiveVideoControlBarDelegate> delegate;
|
||||
@property(nonatomic,strong) UIButton *logBtn;
|
||||
- (void)enableLog:(BOOL)endable;
|
||||
- (void)enableBeauty:(BOOL)enable;
|
||||
@end
|
@ -1,298 +0,0 @@
|
||||
//
|
||||
// TCLiveVideoControlBar.m
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/3.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
#import "TCLiveVideoControlBar.h"
|
||||
#import <ILiveSDK/ILiveRoomManager.h>
|
||||
#import <ILiveSDK/ILiveRenderView.h>
|
||||
#import <ILiveSDK/ILiveFrameDispatcher.h>
|
||||
#import "TCLiveRequestManager.h"
|
||||
#import "UIToastView.h"
|
||||
#import "TCLiveRoomViewController.h"
|
||||
|
||||
#define CONTROLBAR_BUTTON_WIDTH 50
|
||||
|
||||
@interface TCLiveVideoControlBar ()
|
||||
@property(nonatomic,strong) UIButton *chatBtn;
|
||||
@property(nonatomic,strong) UIButton *switchCamera;
|
||||
@property(nonatomic,strong) UIButton *beautyBtn;
|
||||
@property(nonatomic,strong) UIButton *voiceBtn;
|
||||
@property(nonatomic,strong) UIButton *changeRoleBtn;
|
||||
@property(nonatomic,strong) UIButton *feedBackBtn;
|
||||
@end
|
||||
|
||||
@implementation TCLiveVideoControlBar
|
||||
|
||||
-(instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
// [self addSubview:self.chatBtn];
|
||||
// [self addSubview:self.beautyBtn];
|
||||
[self addSubview:self.voiceBtn];
|
||||
// [self addSubview:self.logBtn];
|
||||
[self addSubview:self.switchCamera];
|
||||
// [self addSubview:self.feedBackBtn];
|
||||
// [self addSubview:self.changeRoleBtn];
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(UIButton *)chatBtn{
|
||||
if(!_chatBtn){
|
||||
_chatBtn = [self createCustomControlBtn:@"聊天" withImage:[UIImage imageNamed:@"chat.png"] selectedImage:nil];
|
||||
[_chatBtn addTarget:self action:@selector(chatBtnClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
_chatBtn.frame = CGRectMake(self.frame.size.width / 2 - CONTROLBAR_BUTTON_WIDTH * 3.5, 0, CONTROLBAR_BUTTON_WIDTH, self.frame.size.height);
|
||||
return _chatBtn;
|
||||
}
|
||||
-(UIButton *)switchCamera{
|
||||
if (!_switchCamera) {
|
||||
_switchCamera = [self createCustomControlBtn:@"翻转" withImage:[UIImage imageNamed:@"camera.png"] selectedImage:[UIImage imageNamed:@"camera-gray.png"]];
|
||||
[_switchCamera addTarget:self action:@selector(switchCameraClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
// _switchCamera.frame = CGRectMake(self.frame.size.width/2 - CONTROLBAR_BUTTON_WIDTH * 2.5,0,CONTROLBAR_BUTTON_WIDTH,self.frame.size.height);
|
||||
_switchCamera.frame = CGRectMake(self.frame.size.width/2 - CONTROLBAR_BUTTON_WIDTH * 1.5,0,CONTROLBAR_BUTTON_WIDTH,self.frame.size.height);
|
||||
return _switchCamera;
|
||||
}
|
||||
|
||||
- (UIButton *)beautyBtn{
|
||||
if (!_beautyBtn) {
|
||||
_beautyBtn = [self createCustomControlBtn:@"美颜" withImage:[UIImage imageNamed:@"beauty.png"] selectedImage:[UIImage imageNamed:@"beauty-dis.png"]];
|
||||
[_beautyBtn addTarget:self action:@selector(beautyBtnClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
_beautyBtn.frame = CGRectMake(self.frame.size.width/2 - CONTROLBAR_BUTTON_WIDTH *1.5, 0, CONTROLBAR_BUTTON_WIDTH, self.frame.size.height);
|
||||
return _beautyBtn;
|
||||
}
|
||||
|
||||
- (UIButton *)voiceBtn{
|
||||
if (!_voiceBtn) {
|
||||
_voiceBtn = [self createCustomControlBtn:@"声音" withImage:[UIImage imageNamed:@"mic-dis.png"] selectedImage:[UIImage imageNamed:@"mic.png"]];
|
||||
[_voiceBtn addTarget:self action:@selector(voiceBtnClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
// _voiceBtn.frame = CGRectMake(self.frame.size.width/2 - CONTROLBAR_BUTTON_WIDTH * 0.5, 0, CONTROLBAR_BUTTON_WIDTH, self.frame.size.height);
|
||||
_voiceBtn.frame = CGRectMake(self.frame.size.width/2 + CONTROLBAR_BUTTON_WIDTH * 0.5, 0, CONTROLBAR_BUTTON_WIDTH, self.frame.size.height);
|
||||
return _voiceBtn;
|
||||
}
|
||||
|
||||
-(UIButton *)changeRoleBtn{
|
||||
if (!_changeRoleBtn) {
|
||||
_changeRoleBtn = [self createCustomControlBtn:@"配置" withImage:[UIImage imageNamed:@"role.png"] selectedImage:[UIImage imageNamed:@"role.png"]];
|
||||
[_changeRoleBtn addTarget:self action:@selector(changeRoleBtnClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
_changeRoleBtn.frame = CGRectMake(self.frame.size.width/2 + CONTROLBAR_BUTTON_WIDTH * 0.5, 0, CONTROLBAR_BUTTON_WIDTH, self.frame.size.height);
|
||||
return _changeRoleBtn;
|
||||
}
|
||||
|
||||
- (UIButton *)feedBackBtn{
|
||||
if (!_feedBackBtn) {
|
||||
_feedBackBtn = [self createCustomControlBtn:@"反馈" withImage:[UIImage imageNamed:@"feedback.png"] selectedImage:[UIImage imageNamed:@"feedback.png"]];
|
||||
[_feedBackBtn addTarget:self action:@selector(feedBackBtnClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
_feedBackBtn.frame = CGRectMake(self.frame.size.width/2 + CONTROLBAR_BUTTON_WIDTH * 1.5, 0, CONTROLBAR_BUTTON_WIDTH, self.frame.size.height);
|
||||
return _feedBackBtn;
|
||||
}
|
||||
|
||||
- (UIButton *)logBtn{
|
||||
if (!_logBtn) {
|
||||
_logBtn = [self createCustomControlBtn:@"信息" withImage:[UIImage imageNamed:@"log.png"] selectedImage:[UIImage imageNamed:@"log2.png"]];
|
||||
[_logBtn addTarget:self action:@selector(logBtnClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
_logBtn.frame = CGRectMake(self.frame.size.width/2 + CONTROLBAR_BUTTON_WIDTH * 2.5, 0, CONTROLBAR_BUTTON_WIDTH, self.frame.size.height);
|
||||
return _logBtn;
|
||||
}
|
||||
|
||||
- (UIButton *)createCustomControlBtn:(NSString *)wording withImage:(UIImage *)image selectedImage:(UIImage *)highlightImage{
|
||||
UIButton *customButton = [[UIButton alloc] initWithFrame:CGRectZero];
|
||||
[customButton setTitle:wording forState:UIControlStateNormal];
|
||||
customButton.titleLabel.font = [UIFont systemFontOfSize:11];
|
||||
[customButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
[customButton setImage:image forState:UIControlStateNormal];
|
||||
[customButton setImage:highlightImage forState:UIControlStateSelected];
|
||||
CGFloat imageHeight = image.size.height;
|
||||
CGFloat imageWidth = image.size.width;
|
||||
[customButton setTitleEdgeInsets:UIEdgeInsetsMake(15, -60, -15, 0)];
|
||||
[customButton setImageEdgeInsets:UIEdgeInsetsMake(10, 10, self.frame.size.height - (CONTROLBAR_BUTTON_WIDTH - 20)/imageWidth *imageHeight -10, 10)];
|
||||
return customButton;
|
||||
}
|
||||
#pragma mark - Handle Event
|
||||
//聊天按钮
|
||||
- (void)chatBtnClick:(UIButton *)sender{
|
||||
if (!sender.selected) {
|
||||
sender.selected = YES;
|
||||
}
|
||||
else{
|
||||
sender.selected = NO;
|
||||
}
|
||||
if ([_delegate respondsToSelector:@selector(chatBtnClick:)]) {
|
||||
[_delegate chatBtnClick:sender];
|
||||
}
|
||||
}
|
||||
//切换摄像头
|
||||
- (void)switchCameraClick:(UIButton *)sender{
|
||||
ILiveFrameDispatcher *frameDispatcher = [[ILiveRoomManager getInstance] getFrameDispatcher];
|
||||
ILiveRenderView *renderView = [frameDispatcher getRenderView:[TCLiveRequestManager getInstance].userID srcType:QAVVIDEO_SRC_TYPE_CAMERA];
|
||||
if (!sender.selected) {
|
||||
sender.selected = YES;
|
||||
renderView.isMirror = NO;
|
||||
}
|
||||
else{
|
||||
sender.selected = NO;
|
||||
renderView.isMirror = YES;
|
||||
}
|
||||
[[ILiveRoomManager getInstance] switchCamera:^{
|
||||
NSLog(@"switch camera succ");
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"switch camera fail");
|
||||
[[UIToastView getInstance] showToastWithMessage:@"切换摄像头失败" toastMode:UIToastShowMode_fail];
|
||||
}];
|
||||
if ([_delegate respondsToSelector:@selector(switchCameraClick:)]) {
|
||||
[_delegate switchCameraBtnClick:sender];
|
||||
}
|
||||
}
|
||||
//美颜
|
||||
- (void)beautyBtnClick:(UIButton *)sender{
|
||||
if (!sender.selected) {
|
||||
sender.selected = YES;
|
||||
}
|
||||
else{
|
||||
sender.selected = NO;
|
||||
}
|
||||
if ([_delegate respondsToSelector:@selector(beautyBtnClick:)]) {
|
||||
[_delegate beautyBtnClick:sender];
|
||||
}
|
||||
}
|
||||
- (void)enableBeauty:(BOOL)enable{
|
||||
if (enable) {
|
||||
_beautyBtn.selected = YES;
|
||||
}
|
||||
else{
|
||||
_beautyBtn.selected = NO;
|
||||
}
|
||||
if ([_delegate respondsToSelector:@selector(beautyBtnClick:)]) {
|
||||
[_delegate beautyBtnClick:_beautyBtn];
|
||||
}
|
||||
}
|
||||
//语音开关
|
||||
- (void)voiceBtnClick:(UIButton *)sender{
|
||||
if (!sender.selected) {
|
||||
sender.selected = YES;
|
||||
}
|
||||
else{
|
||||
sender.selected = NO;
|
||||
}
|
||||
[[ILiveRoomManager getInstance] enableMic:!sender.selected succ:^{
|
||||
NSLog(@"enable mic succ");
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"enable mic fail");
|
||||
[[UIToastView getInstance] showToastWithMessage:@"关麦失败" toastMode:UIToastShowMode_fail];
|
||||
}];
|
||||
if ([_delegate respondsToSelector:@selector(voiceBtnClick:)]) {
|
||||
[_delegate voiceBtnClick:sender];
|
||||
}
|
||||
|
||||
}
|
||||
//配置角色
|
||||
- (void)changeRoleBtnClick:(UIButton *)sender{
|
||||
if (!sender.selected) {
|
||||
sender.selected = YES;
|
||||
}
|
||||
else{
|
||||
sender.selected = NO;
|
||||
}
|
||||
if ([_delegate respondsToSelector:@selector(changeRoleBtnClick:)]) {
|
||||
[_delegate changeRoleBtnClick:sender];
|
||||
}
|
||||
if ([_delegate isKindOfClass:[UIViewController class]]) {
|
||||
[self showChangeRoleMenuOnVC:(UIViewController *)_delegate];
|
||||
}
|
||||
}
|
||||
//日志
|
||||
- (void)logBtnClick:(UIButton *)sender{
|
||||
if (!sender.selected) {
|
||||
sender.selected = YES;
|
||||
}
|
||||
else{
|
||||
sender.selected = NO;
|
||||
}
|
||||
if ([_delegate respondsToSelector:@selector(logBtnClick:)]) {
|
||||
[_delegate logBtnClick:sender];
|
||||
}
|
||||
}
|
||||
//问题反馈
|
||||
- (void)feedBackBtnClick:(UIButton *)sender{
|
||||
if (!sender.selected) {
|
||||
sender.selected = YES;
|
||||
}
|
||||
else{
|
||||
sender.selected = NO;
|
||||
}
|
||||
if ([_delegate respondsToSelector:@selector(feedBackBtnClick:)]) {
|
||||
[_delegate feedBackBtnClick:sender];
|
||||
}
|
||||
if ([_delegate isKindOfClass:[UIViewController class]]) {
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"若您在接入过程中有疑问可直接反馈给我们" message:@"邮箱联系地址:trtcfb@qq.com" preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:nil]];
|
||||
[((UIViewController *)self.delegate) presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
//log展示开关
|
||||
- (void)enableLog:(BOOL)endable{
|
||||
self.logBtn.selected = endable;
|
||||
if ([_delegate respondsToSelector:@selector(logBtnClick:)]) {
|
||||
[_delegate logBtnClick:self.logBtn];
|
||||
}
|
||||
}
|
||||
//弹出配置菜单
|
||||
-(void)showChangeRoleMenuOnVC:(UIViewController *)vc{
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"请选择配置分辨率" message:nil preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"1280x720 1000-1800kbps 20fps" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[[ILiveRoomManager getInstance] changeRole:@"ed1280" succ:^{
|
||||
NSLog(@"更换ed1280 succ");
|
||||
[self enableLog:YES];
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"更换ed1280 fail");
|
||||
}];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"960x540 500-800kbps 15fps" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[[ILiveRoomManager getInstance] changeRole:@"ed960" succ:^{
|
||||
NSLog(@"更换ed960 succ");
|
||||
[self enableLog:YES];
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"更换ed960 fail");
|
||||
}];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"640x480 400-800kbps 15fps" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[[ILiveRoomManager getInstance] changeRole:@"ed640" succ:^{
|
||||
NSLog(@"更换ed640 succ");
|
||||
[self enableLog:YES];
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"更换ed640 fail");
|
||||
}];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"480x360 300-600kbps 15fps" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[[ILiveRoomManager getInstance] changeRole:@"ed480" succ:^{
|
||||
NSLog(@"更换ed480 succ");
|
||||
[self enableLog:YES];
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"更换ed480 fail");
|
||||
}];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"320x240 200-400kbps 15fps" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[[ILiveRoomManager getInstance] changeRole:@"ed320" succ:^{
|
||||
NSLog(@"更换ed320 succ");
|
||||
[self enableLog:YES];
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"更换ed320 fail");
|
||||
}];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[vc presentViewController:alertController animated:YES completion:nil];
|
||||
}
|
||||
@end
|
@ -1,42 +0,0 @@
|
||||
//
|
||||
// TCLiveVideoLayoutView.h
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/3.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <ILiveSDK/ILiveRenderView.h>
|
||||
|
||||
@class TCLiveVideoElementView;
|
||||
|
||||
typedef enum {
|
||||
TCLiveRoomVideoLayoutStyle_1v3,
|
||||
TCLiveRoomVideoLayoutStyle_4_geizi,
|
||||
}TCLiveRoomVideoLayoutStyle;
|
||||
|
||||
@protocol TCLiveVideoElementViewDelegate
|
||||
- (void)tapHandle:(TCLiveVideoElementView *)view;
|
||||
@end
|
||||
|
||||
@interface TCLiveVideoLayoutView : UIView
|
||||
//画面布局样式
|
||||
@property(nonatomic,assign) TCLiveRoomVideoLayoutStyle layoutStyle;
|
||||
//显示日志信息
|
||||
- (void)showLogView:(NSString *)qualityParams;
|
||||
//关闭日志信息
|
||||
- (void)closeLogView;
|
||||
//设置美颜
|
||||
- (void)setBeautyLevel:(NSInteger)level;
|
||||
@end
|
||||
|
||||
@interface TCLiveVideoElementView: UIView
|
||||
//展示userID
|
||||
@property(nonatomic,strong)UILabel *userIdLabel;
|
||||
//展示视频分辨率、帧率等信息
|
||||
@property(nonatomic,strong)UILabel *videoInfoLable;
|
||||
@property(nonatomic,weak)TCLiveVideoLayoutView<TCLiveVideoElementViewDelegate> *delegate;
|
||||
//可拖动开关
|
||||
- (void)ennableDraggable:(BOOL)draggable;
|
||||
@end
|
@ -1,403 +0,0 @@
|
||||
//
|
||||
// TCLiveVideoLayoutView.m
|
||||
// TRTC
|
||||
//
|
||||
// Created by Tencent on 2018/6/3.
|
||||
// Copyright © 2018年 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TCLiveVideoLayoutView.h"
|
||||
#import <ILiveSDK/ILiveRoomManager.h>
|
||||
#import <ILiveSDK/ILiveQualityData.h>
|
||||
#import <TILLiveSDK/TILLiveManager.h>
|
||||
#import "TCLiveRequestManager.h"
|
||||
#import "UIColorEX.h"
|
||||
#import "TXCVideoPreprocessor.h"
|
||||
#define LIVE_VIDEO_NUM 4
|
||||
|
||||
@interface TCLiveVideoLayoutView () <QAVRemoteVideoDelegate,QAVLocalVideoDelegate,ILiveScreenVideoDelegate,ILVLiveAVListener,ILiveMemStatusListener,TCLiveVideoElementViewDelegate,TXIVideoPreprocessorDelegate>
|
||||
@property(nonatomic,strong)NSMutableArray *liveVideos;
|
||||
@property(nonatomic,strong)NSMutableArray *liveRnederView;
|
||||
@property(nonatomic,strong)UITextView *logView;
|
||||
@property(nonatomic,assign) BOOL isShowLogInfo;
|
||||
@property(nonatomic,assign) NSDate *startTime;
|
||||
@property (nonatomic, strong) TXCVideoPreprocessor *preProcessor;
|
||||
@property (nonatomic, assign) Byte *processorBytes;
|
||||
@end
|
||||
|
||||
@implementation TCLiveVideoLayoutView
|
||||
|
||||
-(instancetype)initWithFrame:(CGRect)frame{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.layoutStyle = TCLiveRoomVideoLayoutStyle_1v3;
|
||||
[self initialVideoViews];
|
||||
[[TILLiveManager getInstance] setAVListener:self];
|
||||
[[ILiveRoomManager getInstance] setLocalVideoDelegate:self];
|
||||
[[ILiveRoomManager getInstance] setRemoteVideoDelegate:self];
|
||||
[[ILiveRoomManager getInstance] setScreenVideoDelegate:self];
|
||||
//美颜处理器
|
||||
self.preProcessor = [[TXCVideoPreprocessor alloc] init];
|
||||
[self.preProcessor setDelegate:self];
|
||||
//记录进入时间
|
||||
self.startTime = [NSDate date];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
//初始化每路视频view
|
||||
- (void)initialVideoViews{
|
||||
_liveVideos = [NSMutableArray array];
|
||||
//自定义视窗数组
|
||||
NSArray *frames = [self customLayoutFrames];
|
||||
for (int i = 0; i < frames.count; i++) {
|
||||
TCLiveVideoElementView *view = [[TCLiveVideoElementView alloc] initWithFrame:CGRectZero];
|
||||
view.delegate = self;
|
||||
[self addSubview:view];
|
||||
[_liveVideos addObject:view];
|
||||
}
|
||||
}
|
||||
-(UITextView *)logView{
|
||||
if(!_logView){
|
||||
_logView = [[UITextView alloc] initWithFrame:CGRectMake(0, self.frame.size.height/5, self.frame.size.width/3*2, self.frame.size.height/2)];
|
||||
_logView.textColor = [UIColor colorWithRGBHex:0xFF4081];
|
||||
_logView.font = [UIFont systemFontOfSize:14];
|
||||
_logView.backgroundColor = [UIColor clearColor];
|
||||
_logView.editable = NO;
|
||||
}
|
||||
return _logView;
|
||||
}
|
||||
|
||||
//自定义布局(开发者可自定义每个视窗的frame来实现,视窗个数即为frames数组个数)
|
||||
- (NSArray *)customLayoutFrames{
|
||||
NSMutableArray *frames = [NSMutableArray array];
|
||||
if (TCLiveRoomVideoLayoutStyle_1v3 == _layoutStyle) {
|
||||
[frames removeAllObjects];
|
||||
int smallViewWidth = ((self.frame.size.height - 20 - 150 - 10 *2)/3)*(3.0/4.0);
|
||||
int smallViewHeight = (self.frame.size.height - 20 - 150 - 10 *2)/3;
|
||||
CGRect frame1 = self.bounds;
|
||||
CGRect frame2 = CGRectMake(self.bounds.size.width - 10 - smallViewWidth, 84 , smallViewWidth, smallViewHeight);
|
||||
CGRect frame3 = CGRectMake(self.bounds.size.width - 10 - smallViewWidth, frame2.origin.y + frame2.size.height + 10, smallViewWidth, smallViewHeight);
|
||||
CGRect frame4 = CGRectMake(self.bounds.size.width - 10 - smallViewWidth, frame3.origin.y + frame3.size.height + 10, smallViewWidth, smallViewHeight);
|
||||
|
||||
[frames addObject:[NSValue valueWithCGRect:frame1]];
|
||||
[frames addObject:[NSValue valueWithCGRect:frame2]];
|
||||
[frames addObject:[NSValue valueWithCGRect:frame3]];
|
||||
[frames addObject:[NSValue valueWithCGRect:frame4]];
|
||||
}
|
||||
else if (TCLiveRoomVideoLayoutStyle_4_geizi == _layoutStyle) {
|
||||
[frames removeAllObjects];
|
||||
CGRect frame1 = CGRectMake(20, 50, (self.frame.size.width -40)/2, (self.frame.size.height - 100)/2);
|
||||
CGRect frame2 = CGRectMake(20 + (self.frame.size.width -40)/2 +1, 50, (self.frame.size.width -40)/2, (self.frame.size.height - 100)/2);
|
||||
CGRect frame3 = CGRectMake(20, 50 + (self.frame.size.height - 100)/2 +1, (self.frame.size.width -40)/2, (self.frame.size.height - 100)/2);
|
||||
CGRect frame4 = CGRectMake(20 + (self.frame.size.width -40)/2 +1, 50 + (self.frame.size.height - 100)/2 +1,(self.frame.size.width -40)/2, (self.frame.size.height - 100)/2);
|
||||
[frames addObject:[NSValue valueWithCGRect:frame1]];
|
||||
[frames addObject:[NSValue valueWithCGRect:frame2]];
|
||||
[frames addObject:[NSValue valueWithCGRect:frame3]];
|
||||
[frames addObject:[NSValue valueWithCGRect:frame4]];
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
//添加视频画面
|
||||
-(void)addLiveRenderView:(ILiveRenderView *)renderView{
|
||||
NSArray *frames = [self customLayoutFrames];
|
||||
for (int i = 0; i < _liveVideos.count;i++) {
|
||||
TCLiveVideoElementView *videoView = _liveVideos[i];
|
||||
videoView.frame = [frames[i] CGRectValue];
|
||||
if (![self getLiveRenderViewOnElementView:videoView]) {
|
||||
renderView.frame = videoView.bounds;
|
||||
[videoView insertSubview:renderView atIndex:0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//移除视频画面
|
||||
- (void)removeLiverRenderVeiw:(ILiveRenderView *)renderView{
|
||||
if ([[renderView superview] isKindOfClass:[TCLiveVideoElementView class]]) {
|
||||
TCLiveVideoElementView *videoView = (TCLiveVideoElementView *)[renderView superview];
|
||||
videoView.frame = CGRectZero;
|
||||
[renderView removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
//画面切换
|
||||
- (void)tapHandle:(TCLiveVideoElementView *)view{
|
||||
NSUInteger index = [_liveVideos indexOfObject:view];
|
||||
TCLiveVideoElementView *bigView = _liveVideos[0];
|
||||
ILiveRenderView *bigRenderView = [self getLiveRenderViewOnElementView:bigView];
|
||||
ILiveRenderView *renderView = [self getLiveRenderViewOnElementView:view];
|
||||
if (index > 0) {
|
||||
[UIView animateWithDuration:0.5 animations:^{
|
||||
bigView.frame = view.frame;
|
||||
view.frame = [[self customLayoutFrames][0] CGRectValue];
|
||||
bigRenderView.frame = bigView.bounds;
|
||||
renderView.frame = view.bounds;
|
||||
[self.liveVideos exchangeObjectAtIndex:0 withObjectAtIndex:index];
|
||||
[self exchangeSubviewAtIndex:0 withSubviewAtIndex:index];
|
||||
} completion:^(BOOL finished) {
|
||||
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//获取指定视窗的渲染view
|
||||
- (ILiveRenderView *)getLiveRenderViewOnElementView:(TCLiveVideoElementView *)elementView{
|
||||
ILiveRenderView *renderView = nil;
|
||||
for (id view in [elementView subviews]) {
|
||||
if ([view isKindOfClass:[ILiveRenderView class]]) {
|
||||
renderView = view;
|
||||
}
|
||||
}
|
||||
return renderView;
|
||||
}
|
||||
-(void)setBeautyLevel:(NSInteger)level{
|
||||
[self.preProcessor setBeautyLevel:level];
|
||||
}
|
||||
#pragma mark - ILiveMemStatusListener
|
||||
//房间成员状态变化事件
|
||||
- (BOOL)onEndpointsUpdateInfo:(QAVUpdateEvent)event updateList:(NSArray *)endpoints{
|
||||
if (endpoints.count <= 0) {
|
||||
return NO;
|
||||
}
|
||||
for (QAVEndpoint *endoption in endpoints) {
|
||||
switch (event) {
|
||||
case QAV_EVENT_ID_ENDPOINT_HAS_CAMERA_VIDEO:
|
||||
{
|
||||
ILiveFrameDispatcher *frameDispatcher = [[ILiveRoomManager getInstance] getFrameDispatcher];
|
||||
ILiveRenderView *renderView = [frameDispatcher addRenderAt:CGRectZero forIdentifier:endoption.identifier srcType:QAVVIDEO_SRC_TYPE_CAMERA];
|
||||
renderView.isRotate = NO;
|
||||
renderView.autoRotate = NO;
|
||||
renderView.isMirror = YES;
|
||||
renderView.identifier = endoption.identifier;
|
||||
renderView.diffDirectionRenderMode = ILIVERENDERMODE_SCALEASPECTFILL;
|
||||
if ([[TCLiveRequestManager getInstance].userID isEqualToString:endoption.identifier]) {
|
||||
renderView.rotateAngle = ILIVEROTATION_90;
|
||||
}
|
||||
[self addLiveRenderView:renderView];
|
||||
}
|
||||
break;
|
||||
case QAV_EVENT_ID_ENDPOINT_NO_CAMERA_VIDEO:
|
||||
{
|
||||
ILiveFrameDispatcher *frameDispatcher = [[ILiveRoomManager getInstance] getFrameDispatcher];
|
||||
ILiveRenderView *renderView = [frameDispatcher removeRenderViewFor:endoption.identifier srcType:QAVVIDEO_SRC_TYPE_CAMERA];
|
||||
[self removeLiverRenderVeiw:renderView];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
/***本地画面帧数据回调***/
|
||||
#pragma mark - QAVLocalVideoDelegate
|
||||
- (void)OnLocalVideoPreview:(QAVVideoFrame *)frameData{
|
||||
[self showElementVideoInfoWithVideoFrame:frameData];
|
||||
}
|
||||
|
||||
- (void)OnLocalVideoPreProcess:(QAVVideoFrame *)frameData{
|
||||
|
||||
|
||||
[self.preProcessor setOutputSize:CGSizeMake(frameData.frameDesc.width, frameData.frameDesc.height)];
|
||||
[self.preProcessor setCropRect:CGRectMake(0, 0,frameData.frameDesc.width, frameData.frameDesc.height)];
|
||||
[self.preProcessor processFrame:frameData.data width:frameData.frameDesc.width height:frameData.frameDesc.height orientation:TXE_ROTATION_0 inputFormat:TXE_FRAME_FORMAT_NV12 outputFormat:TXE_FRAME_FORMAT_NV12];
|
||||
//将处理完的数据拷贝到原来的地址空间,如果是同步处理,此时会先执行didProcessFrame回调
|
||||
if(self.processorBytes){
|
||||
memcpy(frameData.data, self.processorBytes, frameData.frameDesc.width * frameData.frameDesc.height * 3 / 2);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)OnLocalVideoRawSampleBuf:(CMSampleBufferRef)buf result:(CMSampleBufferRef *)ret{
|
||||
|
||||
}
|
||||
|
||||
/***远端视频帧数据回调***/
|
||||
#pragma mark - QAVRemoteVideoDelegate
|
||||
- (void)OnVideoPreview:(QAVVideoFrame *)frameData{
|
||||
[self showElementVideoInfoWithVideoFrame:frameData];
|
||||
}
|
||||
/***远端屏幕分享帧数据回调***/
|
||||
#pragma mark - ILiveScreenVideoDelegate
|
||||
|
||||
-(void)onScreenVideoPreview:(QAVVideoFrame *)frameData{
|
||||
[self showElementVideoInfoWithVideoFrame:frameData];
|
||||
}
|
||||
/***首帧回调***/
|
||||
#pragma mark - ILVLiveAVListener
|
||||
- (void)onFirstFrameRecved:(int)width height:(int)height identifier:(NSString *)identifier srcType:(avVideoSrcType)srcType;{
|
||||
|
||||
}
|
||||
/***美颜相关处理***/
|
||||
#pragma mark - TXIVideoPreprocessorDelegate
|
||||
- (void)didProcessFrame:(Byte *)bytes width:(NSInteger)width height:(NSInteger)height format:(TXEFrameFormat)format timeStamp:(UInt64)timeStamp
|
||||
{
|
||||
self.processorBytes = bytes;
|
||||
}
|
||||
#pragma mark - LOG相关
|
||||
- (void)showLogView:(NSString *)qualityParams{
|
||||
NSString *role = [[[[qualityParams componentsSeparatedByString:@"ControlRole="] lastObject] componentsSeparatedByString:@","] firstObject];
|
||||
|
||||
self.logView.text = [NSString stringWithFormat:@"发送速率:%ldkbps 丢包率:%.1f%%\n接收速率:%ldkbps 丢包率:%.1f%%\n应用CPU:%.1f%% 系统CPU:%.1f%%\n角色:%@\nSDKAPPID:%d\nSDKVersion:%@",(long)[[ILiveRoomManager getInstance] getQualityData].sendRate,[[ILiveRoomManager getInstance] getQualityData].sendLossRate/100.0,(long)[[ILiveRoomManager getInstance] getQualityData].recvRate,[[ILiveRoomManager getInstance] getQualityData].recvLossRate/100.0,[[ILiveRoomManager getInstance] getQualityData].appCPURate/100.0,[[ILiveRoomManager getInstance] getQualityData].sysCPURate/100.0,role,[TCLiveRequestManager getInstance].sdkAppID,[[ILiveSDK getInstance] getVersion]];
|
||||
if(![_logView superview]){
|
||||
[self addSubview:_logView];
|
||||
}
|
||||
self.isShowLogInfo = YES;
|
||||
}
|
||||
|
||||
- (void)showElementVideoInfoWithVideoFrame:(QAVVideoFrame *)frame{
|
||||
if(!self.isShowLogInfo){
|
||||
return;
|
||||
}
|
||||
NSString *userId = frame.identifier;
|
||||
NSString *fps = @"";
|
||||
if (userId.length == 0){
|
||||
userId = [TCLiveRequestManager getInstance].userID;
|
||||
}
|
||||
else{
|
||||
NSString *qualityParams = [[[ILiveSDK getInstance] getAVContext].room getQualityTips];
|
||||
NSString *decode = [[qualityParams componentsSeparatedByString:@"音频部分:========"] firstObject];
|
||||
NSString *itemDecode = [[[[decode componentsSeparatedByString:[NSString stringWithFormat:@"成员:%@",userId]] lastObject] componentsSeparatedByString:@"接收参数"] firstObject];
|
||||
fps = [[[[itemDecode componentsSeparatedByString:@"FPS="] lastObject] componentsSeparatedByString:@","] firstObject];
|
||||
}
|
||||
int width = frame.frameDesc.width;
|
||||
int height = frame.frameDesc.height;
|
||||
for (int i = 0; i < _liveVideos.count;i++) {
|
||||
TCLiveVideoElementView *videoView = _liveVideos[i];
|
||||
ILiveRenderView *renderView = [self getLiveRenderViewOnElementView:videoView];
|
||||
if ([renderView.identifier isEqualToString:userId]) {
|
||||
if ([userId isEqualToString:[TCLiveRequestManager getInstance].userID]) {
|
||||
videoView.videoInfoLable.text = [NSString stringWithFormat:@"%dx%d",width,height];
|
||||
}
|
||||
else{
|
||||
videoView.videoInfoLable.text = [NSString stringWithFormat:@"%dx%d fps:%d",width,height,[fps intValue]/10];
|
||||
}
|
||||
videoView.userIdLabel.text = [NSString stringWithFormat:@"%@",userId];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)closeLogView{
|
||||
[_logView removeFromSuperview];
|
||||
_logView = nil;
|
||||
for (int i = 0; i < _liveVideos.count;i++) {
|
||||
TCLiveVideoElementView *videoView = _liveVideos[i];
|
||||
[videoView.userIdLabel removeFromSuperview];
|
||||
videoView.userIdLabel = nil;
|
||||
[videoView.videoInfoLable removeFromSuperview];
|
||||
videoView.videoInfoLable = nil;
|
||||
}
|
||||
self.isShowLogInfo = NO;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface TCLiveVideoElementView() <UIGestureRecognizerDelegate>
|
||||
@property(nonatomic,strong) UIPanGestureRecognizer *panGesture;
|
||||
@end
|
||||
|
||||
@implementation TCLiveVideoElementView
|
||||
|
||||
-(instancetype)initWithFrame:(CGRect)frame{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self ennableDraggable:YES];
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestuer:)];
|
||||
[self addGestureRecognizer:tap];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (UILabel *)userIdLabel{
|
||||
if (!_userIdLabel) {
|
||||
_userIdLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 12, self.bounds.size.width, 8)];
|
||||
_userIdLabel.textColor = [UIColor colorWithRGBHex:0xFF4081];
|
||||
_userIdLabel.textAlignment = NSTextAlignmentLeft;
|
||||
_userIdLabel.font = [UIFont systemFontOfSize:9];
|
||||
[self addSubview:_userIdLabel];
|
||||
}
|
||||
return _userIdLabel;
|
||||
}
|
||||
-(UILabel *)videoInfoLable{
|
||||
if (!_videoInfoLable) {
|
||||
_videoInfoLable = [[UILabel alloc] initWithFrame:CGRectMake(0, 0 , self.bounds.size.width, 12)];
|
||||
_videoInfoLable.textColor = [UIColor colorWithRGBHex:0xFF4081];
|
||||
_videoInfoLable.textAlignment = NSTextAlignmentLeft;
|
||||
_videoInfoLable.font = [UIFont systemFontOfSize:13];
|
||||
[self addSubview:_videoInfoLable];
|
||||
}
|
||||
return _videoInfoLable;
|
||||
}
|
||||
- (void)layoutSubviews{
|
||||
if (self.bounds.size.width == [UIScreen mainScreen].bounds.size.width) {
|
||||
_videoInfoLable.frame = CGRectMake(0, 84 , self.bounds.size.width, 12);
|
||||
_userIdLabel.frame = CGRectMake(0, 12 + 84, self.bounds.size.width, 8);
|
||||
}
|
||||
else{
|
||||
_videoInfoLable.frame = CGRectMake(0, 0 , self.bounds.size.width, 12);
|
||||
_userIdLabel.frame = CGRectMake(0, 12, self.bounds.size.width, 8);
|
||||
}
|
||||
}
|
||||
//可拖动能力
|
||||
-(void)ennableDraggable:(BOOL)draggable {
|
||||
[self setUserInteractionEnabled:YES];
|
||||
[self removeConstraints:self.constraints];
|
||||
for (NSLayoutConstraint *constraint in self.superview.constraints) {
|
||||
if ([constraint.firstItem isEqual:self]) {
|
||||
[self.superview removeConstraint:constraint];
|
||||
}
|
||||
}
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:YES];
|
||||
if (draggable) {
|
||||
if (!_panGesture) {
|
||||
_panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
|
||||
_panGesture.delegate = self;
|
||||
[self addGestureRecognizer:_panGesture];
|
||||
}
|
||||
}else{
|
||||
if (_panGesture) {
|
||||
_panGesture = nil;
|
||||
[self removeGestureRecognizer:_panGesture];
|
||||
}
|
||||
}
|
||||
}
|
||||
- (void)tapGestuer:(UITapGestureRecognizer *)gesture{
|
||||
if ([_delegate respondsToSelector:@selector(tapHandle:)]) {
|
||||
[_delegate tapHandle:self];
|
||||
}
|
||||
}
|
||||
- (void)pan:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
switch (panGestureRecognizer.state) {
|
||||
case UIGestureRecognizerStateBegan: {
|
||||
[self dragging:panGestureRecognizer];
|
||||
}
|
||||
break;
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
[self dragging:panGestureRecognizer];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
-(void)dragging:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
UIView *view = panGestureRecognizer.view;
|
||||
CGPoint translation = [panGestureRecognizer translationInView:view.superview];
|
||||
CGPoint center = CGPointMake(view.center.x + translation.x, view.center.y + translation.y);
|
||||
//不能拖过边界
|
||||
CGSize size = view.frame.size;
|
||||
CGSize superSize = view.superview.frame.size;
|
||||
CGFloat width = size.width;
|
||||
CGFloat height = size.height;
|
||||
CGFloat superWidth = superSize.width;
|
||||
CGFloat superHeight = superSize.height;
|
||||
center.x = (center.x<width/2)?width/2:center.x;
|
||||
center.x = (center.x+width/2>superWidth)?superWidth-width/2:center.x;
|
||||
center.y = (center.y<height/2)?height/2:center.y;
|
||||
center.y = (center.y+height/2>superHeight)?superHeight-height/2:center.y;
|
||||
|
||||
[view setCenter:center];
|
||||
[panGestureRecognizer setTranslation:CGPointZero inView:view.superview];
|
||||
}
|
||||
@end
|
||||
|
@ -1,13 +1,15 @@
|
||||
//
|
||||
// TrtcPlugin.h
|
||||
//
|
||||
// Created by 布丁丸子酱 on 2018/12/26.
|
||||
//
|
||||
|
||||
#import <Cordova/CDVPlugin.h>
|
||||
|
||||
@interface TrtcPlugin : CDVPlugin
|
||||
{
|
||||
}
|
||||
|
||||
- (void) showCreatePage:(CDVInvokedUrlCommand*)command;
|
||||
- (void)joinChannel:(CDVInvokedUrlCommand *)command;
|
||||
- (void)userInfoChange:(CDVInvokedUrlCommand *)command;
|
||||
|
||||
@end
|
||||
|
@ -1,16 +1,21 @@
|
||||
//
|
||||
// TrtcPlugin.m
|
||||
//
|
||||
// Created by 布丁丸子酱 on 2018/12/26.
|
||||
//
|
||||
|
||||
#import "TrtcPlugin.h"
|
||||
#import "TCLiveRequestManager.h"
|
||||
#import <ILiveSDK/ILiveSDK.h>
|
||||
#import "TCLiveJoinRoomViewController.h"
|
||||
#import <ILiveSDK/ILiveLoginManager.h>
|
||||
#import "UIToastView.h"
|
||||
#import <ILiveLogReport/ILiveLogReport.h>
|
||||
|
||||
#import "VideoCallingViewController.h"
|
||||
// #import "TCLiveRequestManager.h"
|
||||
// #import <ILiveSDK/ILiveSDK.h>
|
||||
// #import "TCLiveJoinRoomViewController.h"
|
||||
// #import <ILiveSDK/ILiveLoginManager.h>
|
||||
// #import "UIToastView.h"
|
||||
// #import <ILiveLogReport/ILiveLogReport.h>
|
||||
#import <TXLiteAVSDK_TRTC/TRTCCloud.h>
|
||||
|
||||
#import "Events.h";
|
||||
#import "CordovaEventKit.h";
|
||||
|
||||
@interface TrtcPlugin()
|
||||
{}
|
||||
@ -18,32 +23,56 @@
|
||||
|
||||
@implementation TrtcPlugin
|
||||
|
||||
- (void) showCreatePage:(CDVInvokedUrlCommand*)command {
|
||||
NSLog(@"showCreatePage");
|
||||
[[TCLiveRequestManager getInstance] requestLoginInfo:^(int code) {
|
||||
if (code == 0) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
int retCode = [[ILiveSDK getInstance] initSdk:[TCLiveRequestManager getInstance].sdkAppID accountType:[TCLiveRequestManager getInstance].accountType];
|
||||
NSLog(@"initSdk success %d",retCode);
|
||||
if (retCode == 0) {
|
||||
NSLog(@"userId & sig:");
|
||||
NSLog(@"%@", [TCLiveRequestManager getInstance].userID);
|
||||
NSLog(@"%@", [TCLiveRequestManager getInstance].userSig);
|
||||
[[ILiveLoginManager getInstance] iLiveLogin:[TCLiveRequestManager getInstance].userID sig:[TCLiveRequestManager getInstance].userSig succ:^{
|
||||
NSLog(@"-----> login succ");
|
||||
[[UIToastView getInstance] showToastWithMessage:@"登录成功" toastMode:UIToastShowMode_Succ];
|
||||
} failed:^(NSString *module, int errId, NSString *errMsg) {
|
||||
NSLog(@"-----> login fail,%@ %d %@",module, errId, errMsg);
|
||||
[[UIToastView getInstance] showToastWithMessage:@"登录失败" toastMode:UIToastShowMode_fail];
|
||||
}];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
-(void)joinChannel:(CDVInvokedUrlCommand*)command {
|
||||
NSDictionary* params = [command.arguments objectAtIndex:0];
|
||||
NSLog(@"TRTC - joinChannel::%@",params);
|
||||
CDVPluginResult* pluginResult = nil;
|
||||
VideoCallingViewController *vc = [[VideoCallingViewController alloc]
|
||||
initWithRoomId:[params[@"ROOM_ID"] intValue]
|
||||
userId:params[@"USER_ID"]
|
||||
appId:[params[@"SDK_APP_ID"] intValue]
|
||||
userSig:params[@"USER_SIG"]];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (@available(iOS 13.0, *)){
|
||||
vc.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
}
|
||||
}];
|
||||
TCLiveJoinRoomViewController *vc = [TCLiveJoinRoomViewController new];
|
||||
// vc.defaultRoomId = self.defaultRoomId;
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
|
||||
[self.viewController presentViewController:nav animated:YES completion:nil];
|
||||
[self.viewController presentViewController:vc animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
-(void)userInfoChange:(CDVInvokedUrlCommand*)command {
|
||||
NSDictionary* params = [command.arguments objectAtIndex:0];
|
||||
NSLog(@"TRTC - userInfoChange::%@",params);
|
||||
[Events fireEvent:@"userinfo.update" extra:params];
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef __CORDOVA_4_0_0
|
||||
|
||||
- (void)pluginInitialize {
|
||||
NSLog(@"TRTC - pluginInitialize ");
|
||||
[self initPlugin];
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
- (CDVPlugin*)initWithWebView:(WKWebView*)theWebView{
|
||||
NSLog(@"TRTC - initWithWebView ");
|
||||
if (self=[super initWithWebView:theWebView]) {
|
||||
}
|
||||
[self initPlugin];
|
||||
return self;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
-(void)initPlugin{
|
||||
NSLog(@"TRTC - initPlugin ");
|
||||
[CordovaEventKit init:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
31
src/ios/TrtcUserInfo.h
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// TrtcUserInfo.h
|
||||
// shuto-cne
|
||||
//
|
||||
// Created by 范大德 on 2022/3/30.
|
||||
//
|
||||
|
||||
#ifndef TrtcUserInfo_h
|
||||
#define TrtcUserInfo_h
|
||||
|
||||
|
||||
#endif /* TrtcUserInfo_h */
|
||||
@interface TrtcUserInfo: NSObject
|
||||
{}
|
||||
// 自定义类的初始化方法
|
||||
|
||||
// 过去类型instancetype 是 id 类型
|
||||
|
||||
- (instancetype)initWithPersonid:(NSString *)personid;
|
||||
|
||||
- (NSString*) personid;
|
||||
-(NSString *)displayName;
|
||||
|
||||
-(BOOL)isShareUser;
|
||||
|
||||
-(BOOL)isLocalUser;
|
||||
|
||||
-(void)setLocal:(BOOL) value;
|
||||
-(void)setDisplayName:(NSString *)value;
|
||||
|
||||
@end
|
68
src/ios/TrtcUserInfo.m
Normal file
@ -0,0 +1,68 @@
|
||||
//
|
||||
// TrtcUserInfo.m
|
||||
// shuto-cne
|
||||
//
|
||||
// Created by 范大德 on 2022/3/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "TrtcUserInfo.h"
|
||||
@interface TrtcUserInfo()
|
||||
{
|
||||
NSString* personid;
|
||||
NSString* displayName;
|
||||
BOOL local;
|
||||
}
|
||||
|
||||
@end
|
||||
@implementation TrtcUserInfo
|
||||
|
||||
- (instancetype)initWithPersonid:(NSString *)userId
|
||||
{
|
||||
personid = userId;
|
||||
return self; // 返回类本身不能忘
|
||||
}
|
||||
|
||||
- (NSString*) personid{
|
||||
return personid;
|
||||
}
|
||||
|
||||
|
||||
-(NSString *)displayName{
|
||||
return displayName;
|
||||
}
|
||||
|
||||
-(BOOL)isShareUser{
|
||||
return personid != nil && [personid hasPrefix:@"share_"];
|
||||
}
|
||||
|
||||
-(BOOL)isLocalUser{
|
||||
return local;
|
||||
}
|
||||
|
||||
-(void)setLocal:(BOOL) value{
|
||||
local = value;
|
||||
}
|
||||
-(void)setDisplayName:(NSString *)value{
|
||||
displayName = value;
|
||||
}
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return [personid hash];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual: (id)obj
|
||||
{
|
||||
if( obj == nil){
|
||||
return NO;
|
||||
}
|
||||
if([obj isKindOfClass: [TrtcUserInfo class]]){
|
||||
TrtcUserInfo *user = (TrtcUserInfo*)obj;
|
||||
return personid == nil ? [user personid] == nil : [personid isEqual: [user personid]];
|
||||
} else if([obj isKindOfClass: [NSString class]]){
|
||||
return personid == nil ? obj == nil : [personid isEqual: obj];
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
@end
|
446
src/ios/UIView+Toast.h
Normal file
@ -0,0 +1,446 @@
|
||||
//
|
||||
// UIView+Toast.h
|
||||
// Toast
|
||||
//
|
||||
// Copyright (c) 2011-2017 Charles Scalesse.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
extern const NSString * CSToastPositionTop;
|
||||
extern const NSString * CSToastPositionCenter;
|
||||
extern const NSString * CSToastPositionBottom;
|
||||
|
||||
@class CSToastStyle;
|
||||
|
||||
/**
|
||||
Toast is an Objective-C category that adds toast notifications to the UIView
|
||||
object class. It is intended to be simple, lightweight, and easy to use. Most
|
||||
toast notifications can be triggered with a single line of code.
|
||||
|
||||
The `makeToast:` methods create a new view and then display it as toast.
|
||||
|
||||
The `showToast:` methods display any view as toast.
|
||||
|
||||
*/
|
||||
@interface UIView (Toast)
|
||||
|
||||
/**
|
||||
Creates and presents a new toast view with a message and displays it with the
|
||||
default duration and position. Styled using the shared style.
|
||||
|
||||
@param message The message to be displayed
|
||||
*/
|
||||
- (void)makeToast:(NSString *)message;
|
||||
|
||||
/**
|
||||
Creates and presents a new toast view with a message. Duration and position
|
||||
can be set explicitly. Styled using the shared style.
|
||||
|
||||
@param message The message to be displayed
|
||||
@param duration The toast duration
|
||||
@param position The toast's center point. Can be one of the predefined CSToastPosition
|
||||
constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
*/
|
||||
- (void)makeToast:(NSString *)message
|
||||
duration:(NSTimeInterval)duration
|
||||
position:(id)position;
|
||||
|
||||
/**
|
||||
Creates and presents a new toast view with a message. Duration, position, and
|
||||
style can be set explicitly.
|
||||
|
||||
@param message The message to be displayed
|
||||
@param duration The toast duration
|
||||
@param position The toast's center point. Can be one of the predefined CSToastPosition
|
||||
constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
@param style The style. The shared style will be used when nil
|
||||
*/
|
||||
- (void)makeToast:(NSString *)message
|
||||
duration:(NSTimeInterval)duration
|
||||
position:(id)position
|
||||
style:(CSToastStyle *)style;
|
||||
|
||||
/**
|
||||
Creates and presents a new toast view with a message, title, and image. Duration,
|
||||
position, and style can be set explicitly. The completion block executes when the
|
||||
toast view completes. `didTap` will be `YES` if the toast view was dismissed from
|
||||
a tap.
|
||||
|
||||
@param message The message to be displayed
|
||||
@param duration The toast duration
|
||||
@param position The toast's center point. Can be one of the predefined CSToastPosition
|
||||
constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
@param title The title
|
||||
@param image The image
|
||||
@param style The style. The shared style will be used when nil
|
||||
@param completion The completion block, executed after the toast view disappears.
|
||||
didTap will be `YES` if the toast view was dismissed from a tap.
|
||||
*/
|
||||
- (void)makeToast:(NSString *)message
|
||||
duration:(NSTimeInterval)duration
|
||||
position:(id)position
|
||||
title:(NSString *)title
|
||||
image:(UIImage *)image
|
||||
style:(CSToastStyle *)style
|
||||
completion:(void(^)(BOOL didTap))completion;
|
||||
|
||||
/**
|
||||
Creates a new toast view with any combination of message, title, and image.
|
||||
The look and feel is configured via the style. Unlike the `makeToast:` methods,
|
||||
this method does not present the toast view automatically. One of the showToast:
|
||||
methods must be used to present the resulting view.
|
||||
|
||||
@warning if message, title, and image are all nil, this method will return nil.
|
||||
|
||||
@param message The message to be displayed
|
||||
@param title The title
|
||||
@param image The image
|
||||
@param style The style. The shared style will be used when nil
|
||||
@return The newly created toast view
|
||||
*/
|
||||
- (UIView *)toastViewForMessage:(NSString *)message
|
||||
title:(NSString *)title
|
||||
image:(UIImage *)image
|
||||
style:(CSToastStyle *)style;
|
||||
|
||||
/**
|
||||
Hides the active toast. If there are multiple toasts active in a view, this method
|
||||
hides the oldest toast (the first of the toasts to have been presented).
|
||||
|
||||
@see `hideAllToasts` to remove all active toasts from a view.
|
||||
|
||||
@warning This method has no effect on activity toasts. Use `hideToastActivity` to
|
||||
hide activity toasts.
|
||||
*/
|
||||
- (void)hideToast;
|
||||
|
||||
/**
|
||||
Hides an active toast.
|
||||
|
||||
@param toast The active toast view to dismiss. Any toast that is currently being displayed
|
||||
on the screen is considered active.
|
||||
|
||||
@warning this does not clear a toast view that is currently waiting in the queue.
|
||||
*/
|
||||
- (void)hideToast:(UIView *)toast;
|
||||
|
||||
/**
|
||||
Hides all active toast views and clears the queue.
|
||||
*/
|
||||
- (void)hideAllToasts;
|
||||
|
||||
/**
|
||||
Hides all active toast views, with options to hide activity and clear the queue.
|
||||
|
||||
@param includeActivity If `true`, toast activity will also be hidden. Default is `false`.
|
||||
@param clearQueue If `true`, removes all toast views from the queue. Default is `true`.
|
||||
*/
|
||||
- (void)hideAllToasts:(BOOL)includeActivity clearQueue:(BOOL)clearQueue;
|
||||
|
||||
/**
|
||||
Removes all toast views from the queue. This has no effect on toast views that are
|
||||
active. Use `hideAllToasts` to hide the active toasts views and clear the queue.
|
||||
*/
|
||||
- (void)clearToastQueue;
|
||||
|
||||
/**
|
||||
Creates and displays a new toast activity indicator view at a specified position.
|
||||
|
||||
@warning Only one toast activity indicator view can be presented per superview. Subsequent
|
||||
calls to `makeToastActivity:` will be ignored until hideToastActivity is called.
|
||||
|
||||
@warning `makeToastActivity:` works independently of the showToast: methods. Toast activity
|
||||
views can be presented and dismissed while toast views are being displayed. `makeToastActivity:`
|
||||
has no effect on the queueing behavior of the showToast: methods.
|
||||
|
||||
@param position The toast's center point. Can be one of the predefined CSToastPosition
|
||||
constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
*/
|
||||
- (void)makeToastActivity:(id)position;
|
||||
|
||||
/**
|
||||
Dismisses the active toast activity indicator view.
|
||||
*/
|
||||
- (void)hideToastActivity;
|
||||
|
||||
/**
|
||||
Displays any view as toast using the default duration and position.
|
||||
|
||||
@param toast The view to be displayed as toast
|
||||
*/
|
||||
- (void)showToast:(UIView *)toast;
|
||||
|
||||
/**
|
||||
Displays any view as toast at a provided position and duration. The completion block
|
||||
executes when the toast view completes. `didTap` will be `YES` if the toast view was
|
||||
dismissed from a tap.
|
||||
|
||||
@param toast The view to be displayed as toast
|
||||
@param duration The notification duration
|
||||
@param position The toast's center point. Can be one of the predefined CSToastPosition
|
||||
constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
@param completion The completion block, executed after the toast view disappears.
|
||||
didTap will be `YES` if the toast view was dismissed from a tap.
|
||||
*/
|
||||
- (void)showToast:(UIView *)toast
|
||||
duration:(NSTimeInterval)duration
|
||||
position:(id)position
|
||||
completion:(void(^)(BOOL didTap))completion;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
`CSToastStyle` instances define the look and feel for toast views created via the
|
||||
`makeToast:` methods as well for toast views created directly with
|
||||
`toastViewForMessage:title:image:style:`.
|
||||
|
||||
@warning `CSToastStyle` offers relatively simple styling options for the default
|
||||
toast view. If you require a toast view with more complex UI, it probably makes more
|
||||
sense to create your own custom UIView subclass and present it with the `showToast:`
|
||||
methods.
|
||||
*/
|
||||
@interface CSToastStyle : NSObject
|
||||
|
||||
/**
|
||||
The background color. Default is `[UIColor blackColor]` at 80% opacity.
|
||||
*/
|
||||
@property (strong, nonatomic) UIColor *backgroundColor;
|
||||
|
||||
/**
|
||||
The title color. Default is `[UIColor whiteColor]`.
|
||||
*/
|
||||
@property (strong, nonatomic) UIColor *titleColor;
|
||||
|
||||
/**
|
||||
The message color. Default is `[UIColor whiteColor]`.
|
||||
*/
|
||||
@property (strong, nonatomic) UIColor *messageColor;
|
||||
|
||||
/**
|
||||
A percentage value from 0.0 to 1.0, representing the maximum width of the toast
|
||||
view relative to it's superview. Default is 0.8 (80% of the superview's width).
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat maxWidthPercentage;
|
||||
|
||||
/**
|
||||
A percentage value from 0.0 to 1.0, representing the maximum height of the toast
|
||||
view relative to it's superview. Default is 0.8 (80% of the superview's height).
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat maxHeightPercentage;
|
||||
|
||||
/**
|
||||
The spacing from the horizontal edge of the toast view to the content. When an image
|
||||
is present, this is also used as the padding between the image and the text.
|
||||
Default is 10.0.
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat horizontalPadding;
|
||||
|
||||
/**
|
||||
The spacing from the vertical edge of the toast view to the content. When a title
|
||||
is present, this is also used as the padding between the title and the message.
|
||||
Default is 10.0.
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat verticalPadding;
|
||||
|
||||
/**
|
||||
The corner radius. Default is 10.0.
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat cornerRadius;
|
||||
|
||||
/**
|
||||
The title font. Default is `[UIFont boldSystemFontOfSize:16.0]`.
|
||||
*/
|
||||
@property (strong, nonatomic) UIFont *titleFont;
|
||||
|
||||
/**
|
||||
The message font. Default is `[UIFont systemFontOfSize:16.0]`.
|
||||
*/
|
||||
@property (strong, nonatomic) UIFont *messageFont;
|
||||
|
||||
/**
|
||||
The title text alignment. Default is `NSTextAlignmentLeft`.
|
||||
*/
|
||||
@property (assign, nonatomic) NSTextAlignment titleAlignment;
|
||||
|
||||
/**
|
||||
The message text alignment. Default is `NSTextAlignmentLeft`.
|
||||
*/
|
||||
@property (assign, nonatomic) NSTextAlignment messageAlignment;
|
||||
|
||||
/**
|
||||
The maximum number of lines for the title. The default is 0 (no limit).
|
||||
*/
|
||||
@property (assign, nonatomic) NSInteger titleNumberOfLines;
|
||||
|
||||
/**
|
||||
The maximum number of lines for the message. The default is 0 (no limit).
|
||||
*/
|
||||
@property (assign, nonatomic) NSInteger messageNumberOfLines;
|
||||
|
||||
/**
|
||||
Enable or disable a shadow on the toast view. Default is `NO`.
|
||||
*/
|
||||
@property (assign, nonatomic) BOOL displayShadow;
|
||||
|
||||
/**
|
||||
The shadow color. Default is `[UIColor blackColor]`.
|
||||
*/
|
||||
@property (strong, nonatomic) UIColor *shadowColor;
|
||||
|
||||
/**
|
||||
A value from 0.0 to 1.0, representing the opacity of the shadow.
|
||||
Default is 0.8 (80% opacity).
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat shadowOpacity;
|
||||
|
||||
/**
|
||||
The shadow radius. Default is 6.0.
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat shadowRadius;
|
||||
|
||||
/**
|
||||
The shadow offset. The default is `CGSizeMake(4.0, 4.0)`.
|
||||
*/
|
||||
@property (assign, nonatomic) CGSize shadowOffset;
|
||||
|
||||
/**
|
||||
The image size. The default is `CGSizeMake(80.0, 80.0)`.
|
||||
*/
|
||||
@property (assign, nonatomic) CGSize imageSize;
|
||||
|
||||
/**
|
||||
The size of the toast activity view when `makeToastActivity:` is called.
|
||||
Default is `CGSizeMake(100.0, 100.0)`.
|
||||
*/
|
||||
@property (assign, nonatomic) CGSize activitySize;
|
||||
|
||||
/**
|
||||
The fade in/out animation duration. Default is 0.2.
|
||||
*/
|
||||
@property (assign, nonatomic) NSTimeInterval fadeDuration;
|
||||
|
||||
/**
|
||||
Creates a new instance of `CSToastStyle` with all the default values set.
|
||||
*/
|
||||
- (instancetype)initWithDefaultStyle NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
@warning Only the designated initializer should be used to create
|
||||
an instance of `CSToastStyle`.
|
||||
*/
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
`CSToastManager` provides general configuration options for all toast
|
||||
notifications. Backed by a singleton instance.
|
||||
*/
|
||||
@interface CSToastManager : NSObject
|
||||
|
||||
/**
|
||||
Sets the shared style on the singleton. The shared style is used whenever
|
||||
a `makeToast:` method (or `toastViewForMessage:title:image:style:`) is called
|
||||
with with a nil style. By default, this is set to `CSToastStyle`'s default
|
||||
style.
|
||||
|
||||
@param sharedStyle the shared style
|
||||
*/
|
||||
+ (void)setSharedStyle:(CSToastStyle *)sharedStyle;
|
||||
|
||||
/**
|
||||
Gets the shared style from the singlton. By default, this is
|
||||
`CSToastStyle`'s default style.
|
||||
|
||||
@return the shared style
|
||||
*/
|
||||
+ (CSToastStyle *)sharedStyle;
|
||||
|
||||
/**
|
||||
Enables or disables tap to dismiss on toast views. Default is `YES`.
|
||||
|
||||
@param tapToDismissEnabled YES or NO
|
||||
*/
|
||||
+ (void)setTapToDismissEnabled:(BOOL)tapToDismissEnabled;
|
||||
|
||||
/**
|
||||
Returns `YES` if tap to dismiss is enabled, otherwise `NO`.
|
||||
Default is `YES`.
|
||||
|
||||
@return BOOL YES or NO
|
||||
*/
|
||||
+ (BOOL)isTapToDismissEnabled;
|
||||
|
||||
/**
|
||||
Enables or disables queueing behavior for toast views. When `YES`,
|
||||
toast views will appear one after the other. When `NO`, multiple Toast
|
||||
views will appear at the same time (potentially overlapping depending
|
||||
on their positions). This has no effect on the toast activity view,
|
||||
which operates independently of normal toast views. Default is `NO`.
|
||||
|
||||
@param queueEnabled YES or NO
|
||||
*/
|
||||
+ (void)setQueueEnabled:(BOOL)queueEnabled;
|
||||
|
||||
/**
|
||||
Returns `YES` if the queue is enabled, otherwise `NO`.
|
||||
Default is `NO`.
|
||||
|
||||
@return BOOL
|
||||
*/
|
||||
+ (BOOL)isQueueEnabled;
|
||||
|
||||
/**
|
||||
Sets the default duration. Used for the `makeToast:` and
|
||||
`showToast:` methods that don't require an explicit duration.
|
||||
Default is 3.0.
|
||||
|
||||
@param duration The toast duration
|
||||
*/
|
||||
+ (void)setDefaultDuration:(NSTimeInterval)duration;
|
||||
|
||||
/**
|
||||
Returns the default duration. Default is 3.0.
|
||||
|
||||
@return duration The toast duration
|
||||
*/
|
||||
+ (NSTimeInterval)defaultDuration;
|
||||
|
||||
/**
|
||||
Sets the default position. Used for the `makeToast:` and
|
||||
`showToast:` methods that don't require an explicit position.
|
||||
Default is `CSToastPositionBottom`.
|
||||
|
||||
@param position The default center point. Can be one of the predefined
|
||||
CSToastPosition constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
*/
|
||||
+ (void)setDefaultPosition:(id)position;
|
||||
|
||||
/**
|
||||
Returns the default toast position. Default is `CSToastPositionBottom`.
|
||||
|
||||
@return position The default center point. Will be one of the predefined
|
||||
CSToastPosition constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
*/
|
||||
+ (id)defaultPosition;
|
||||
|
||||
@end
|
586
src/ios/UIView+Toast.m
Normal file
@ -0,0 +1,586 @@
|
||||
//
|
||||
// UIView+Toast.m
|
||||
// Toast
|
||||
//
|
||||
// Copyright (c) 2011-2017 Charles Scalesse.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#import "UIView+Toast.h"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
// Positions
|
||||
NSString * CSToastPositionTop = @"CSToastPositionTop";
|
||||
NSString * CSToastPositionCenter = @"CSToastPositionCenter";
|
||||
NSString * CSToastPositionBottom = @"CSToastPositionBottom";
|
||||
|
||||
// Keys for values associated with toast views
|
||||
static const NSString * CSToastTimerKey = @"CSToastTimerKey";
|
||||
static const NSString * CSToastDurationKey = @"CSToastDurationKey";
|
||||
static const NSString * CSToastPositionKey = @"CSToastPositionKey";
|
||||
static const NSString * CSToastCompletionKey = @"CSToastCompletionKey";
|
||||
|
||||
// Keys for values associated with self
|
||||
static const NSString * CSToastActiveKey = @"CSToastActiveKey";
|
||||
static const NSString * CSToastActivityViewKey = @"CSToastActivityViewKey";
|
||||
static const NSString * CSToastQueueKey = @"CSToastQueueKey";
|
||||
|
||||
@interface UIView (ToastPrivate)
|
||||
|
||||
/**
|
||||
These private methods are being prefixed with "cs_" to reduce the likelihood of non-obvious
|
||||
naming conflicts with other UIView methods.
|
||||
|
||||
@discussion Should the public API also use the cs_ prefix? Technically it should, but it
|
||||
results in code that is less legible. The current public method names seem unlikely to cause
|
||||
conflicts so I think we should favor the cleaner API for now.
|
||||
*/
|
||||
- (void)cs_showToast:(UIView *)toast duration:(NSTimeInterval)duration position:(id)position;
|
||||
- (void)cs_hideToast:(UIView *)toast;
|
||||
- (void)cs_hideToast:(UIView *)toast fromTap:(BOOL)fromTap;
|
||||
- (void)cs_toastTimerDidFinish:(NSTimer *)timer;
|
||||
- (void)cs_handleToastTapped:(UITapGestureRecognizer *)recognizer;
|
||||
- (CGPoint)cs_centerPointForPosition:(id)position withToast:(UIView *)toast;
|
||||
- (NSMutableArray *)cs_toastQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIView (Toast)
|
||||
|
||||
#pragma mark - Make Toast Methods
|
||||
|
||||
- (void)makeToast:(NSString *)message {
|
||||
[self makeToast:message duration:[CSToastManager defaultDuration] position:[CSToastManager defaultPosition] style:nil];
|
||||
}
|
||||
|
||||
- (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position {
|
||||
[self makeToast:message duration:duration position:position style:nil];
|
||||
}
|
||||
|
||||
- (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position style:(CSToastStyle *)style {
|
||||
UIView *toast = [self toastViewForMessage:message title:nil image:nil style:style];
|
||||
[self showToast:toast duration:duration position:position completion:nil];
|
||||
}
|
||||
|
||||
- (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position title:(NSString *)title image:(UIImage *)image style:(CSToastStyle *)style completion:(void(^)(BOOL didTap))completion {
|
||||
UIView *toast = [self toastViewForMessage:message title:title image:image style:style];
|
||||
[self showToast:toast duration:duration position:position completion:completion];
|
||||
}
|
||||
|
||||
#pragma mark - Show Toast Methods
|
||||
|
||||
- (void)showToast:(UIView *)toast {
|
||||
[self showToast:toast duration:[CSToastManager defaultDuration] position:[CSToastManager defaultPosition] completion:nil];
|
||||
}
|
||||
|
||||
- (void)showToast:(UIView *)toast duration:(NSTimeInterval)duration position:(id)position completion:(void(^)(BOOL didTap))completion {
|
||||
// sanity
|
||||
if (toast == nil) return;
|
||||
|
||||
// store the completion block on the toast view
|
||||
objc_setAssociatedObject(toast, &CSToastCompletionKey, completion, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
if ([CSToastManager isQueueEnabled] && [self.cs_activeToasts count] > 0) {
|
||||
// we're about to queue this toast view so we need to store the duration and position as well
|
||||
objc_setAssociatedObject(toast, &CSToastDurationKey, @(duration), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
objc_setAssociatedObject(toast, &CSToastPositionKey, position, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
// enqueue
|
||||
[self.cs_toastQueue addObject:toast];
|
||||
} else {
|
||||
// present
|
||||
[self cs_showToast:toast duration:duration position:position];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Hide Toast Methods
|
||||
|
||||
- (void)hideToast {
|
||||
[self hideToast:[[self cs_activeToasts] firstObject]];
|
||||
}
|
||||
|
||||
- (void)hideToast:(UIView *)toast {
|
||||
// sanity
|
||||
if (!toast || ![[self cs_activeToasts] containsObject:toast]) return;
|
||||
|
||||
[self cs_hideToast:toast];
|
||||
}
|
||||
|
||||
- (void)hideAllToasts {
|
||||
[self hideAllToasts:NO clearQueue:YES];
|
||||
}
|
||||
|
||||
- (void)hideAllToasts:(BOOL)includeActivity clearQueue:(BOOL)clearQueue {
|
||||
if (clearQueue) {
|
||||
[self clearToastQueue];
|
||||
}
|
||||
|
||||
for (UIView *toast in [self cs_activeToasts]) {
|
||||
[self hideToast:toast];
|
||||
}
|
||||
|
||||
if (includeActivity) {
|
||||
[self hideToastActivity];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearToastQueue {
|
||||
[[self cs_toastQueue] removeAllObjects];
|
||||
}
|
||||
|
||||
#pragma mark - Private Show/Hide Methods
|
||||
|
||||
- (void)cs_showToast:(UIView *)toast duration:(NSTimeInterval)duration position:(id)position {
|
||||
toast.center = [self cs_centerPointForPosition:position withToast:toast];
|
||||
toast.alpha = 0.0;
|
||||
|
||||
if ([CSToastManager isTapToDismissEnabled]) {
|
||||
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cs_handleToastTapped:)];
|
||||
[toast addGestureRecognizer:recognizer];
|
||||
toast.userInteractionEnabled = YES;
|
||||
toast.exclusiveTouch = YES;
|
||||
}
|
||||
|
||||
[[self cs_activeToasts] addObject:toast];
|
||||
|
||||
[self addSubview:toast];
|
||||
|
||||
[UIView animateWithDuration:[[CSToastManager sharedStyle] fadeDuration]
|
||||
delay:0.0
|
||||
options:(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction)
|
||||
animations:^{
|
||||
toast.alpha = 1.0;
|
||||
} completion:^(BOOL finished) {
|
||||
NSTimer *timer = [NSTimer timerWithTimeInterval:duration target:self selector:@selector(cs_toastTimerDidFinish:) userInfo:toast repeats:NO];
|
||||
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
||||
objc_setAssociatedObject(toast, &CSToastTimerKey, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)cs_hideToast:(UIView *)toast {
|
||||
[self cs_hideToast:toast fromTap:NO];
|
||||
}
|
||||
|
||||
- (void)cs_hideToast:(UIView *)toast fromTap:(BOOL)fromTap {
|
||||
NSTimer *timer = (NSTimer *)objc_getAssociatedObject(toast, &CSToastTimerKey);
|
||||
[timer invalidate];
|
||||
|
||||
[UIView animateWithDuration:[[CSToastManager sharedStyle] fadeDuration]
|
||||
delay:0.0
|
||||
options:(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
|
||||
animations:^{
|
||||
toast.alpha = 0.0;
|
||||
} completion:^(BOOL finished) {
|
||||
[toast removeFromSuperview];
|
||||
|
||||
// remove
|
||||
[[self cs_activeToasts] removeObject:toast];
|
||||
|
||||
// execute the completion block, if necessary
|
||||
void (^completion)(BOOL didTap) = objc_getAssociatedObject(toast, &CSToastCompletionKey);
|
||||
if (completion) {
|
||||
completion(fromTap);
|
||||
}
|
||||
|
||||
if ([self.cs_toastQueue count] > 0) {
|
||||
// dequeue
|
||||
UIView *nextToast = [[self cs_toastQueue] firstObject];
|
||||
[[self cs_toastQueue] removeObjectAtIndex:0];
|
||||
|
||||
// present the next toast
|
||||
NSTimeInterval duration = [objc_getAssociatedObject(nextToast, &CSToastDurationKey) doubleValue];
|
||||
id position = objc_getAssociatedObject(nextToast, &CSToastPositionKey);
|
||||
[self cs_showToast:nextToast duration:duration position:position];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - View Construction
|
||||
|
||||
- (UIView *)toastViewForMessage:(NSString *)message title:(NSString *)title image:(UIImage *)image style:(CSToastStyle *)style {
|
||||
// sanity
|
||||
if (message == nil && title == nil && image == nil) return nil;
|
||||
|
||||
// default to the shared style
|
||||
if (style == nil) {
|
||||
style = [CSToastManager sharedStyle];
|
||||
}
|
||||
|
||||
// dynamically build a toast view with any combination of message, title, & image
|
||||
UILabel *messageLabel = nil;
|
||||
UILabel *titleLabel = nil;
|
||||
UIImageView *imageView = nil;
|
||||
|
||||
UIView *wrapperView = [[UIView alloc] init];
|
||||
wrapperView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
|
||||
wrapperView.layer.cornerRadius = style.cornerRadius;
|
||||
|
||||
if (style.displayShadow) {
|
||||
wrapperView.layer.shadowColor = style.shadowColor.CGColor;
|
||||
wrapperView.layer.shadowOpacity = style.shadowOpacity;
|
||||
wrapperView.layer.shadowRadius = style.shadowRadius;
|
||||
wrapperView.layer.shadowOffset = style.shadowOffset;
|
||||
}
|
||||
|
||||
wrapperView.backgroundColor = style.backgroundColor;
|
||||
|
||||
if(image != nil) {
|
||||
imageView = [[UIImageView alloc] initWithImage:image];
|
||||
imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
imageView.frame = CGRectMake(style.horizontalPadding, style.verticalPadding, style.imageSize.width, style.imageSize.height);
|
||||
}
|
||||
|
||||
CGRect imageRect = CGRectZero;
|
||||
|
||||
if(imageView != nil) {
|
||||
imageRect.origin.x = style.horizontalPadding;
|
||||
imageRect.origin.y = style.verticalPadding;
|
||||
imageRect.size.width = imageView.bounds.size.width;
|
||||
imageRect.size.height = imageView.bounds.size.height;
|
||||
}
|
||||
|
||||
if (title != nil) {
|
||||
titleLabel = [[UILabel alloc] init];
|
||||
titleLabel.numberOfLines = style.titleNumberOfLines;
|
||||
titleLabel.font = style.titleFont;
|
||||
titleLabel.textAlignment = style.titleAlignment;
|
||||
titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
titleLabel.textColor = style.titleColor;
|
||||
titleLabel.backgroundColor = [UIColor clearColor];
|
||||
titleLabel.alpha = 1.0;
|
||||
titleLabel.text = title;
|
||||
|
||||
// size the title label according to the length of the text
|
||||
CGSize maxSizeTitle = CGSizeMake((self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, self.bounds.size.height * style.maxHeightPercentage);
|
||||
CGSize expectedSizeTitle = [titleLabel sizeThatFits:maxSizeTitle];
|
||||
// UILabel can return a size larger than the max size when the number of lines is 1
|
||||
expectedSizeTitle = CGSizeMake(MIN(maxSizeTitle.width, expectedSizeTitle.width), MIN(maxSizeTitle.height, expectedSizeTitle.height));
|
||||
titleLabel.frame = CGRectMake(0.0, 0.0, expectedSizeTitle.width, expectedSizeTitle.height);
|
||||
}
|
||||
|
||||
if (message != nil) {
|
||||
messageLabel = [[UILabel alloc] init];
|
||||
messageLabel.numberOfLines = style.messageNumberOfLines;
|
||||
messageLabel.font = style.messageFont;
|
||||
messageLabel.textAlignment = style.messageAlignment;
|
||||
messageLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
messageLabel.textColor = style.messageColor;
|
||||
messageLabel.backgroundColor = [UIColor clearColor];
|
||||
messageLabel.alpha = 1.0;
|
||||
messageLabel.text = message;
|
||||
|
||||
CGSize maxSizeMessage = CGSizeMake((self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, self.bounds.size.height * style.maxHeightPercentage);
|
||||
CGSize expectedSizeMessage = [messageLabel sizeThatFits:maxSizeMessage];
|
||||
// UILabel can return a size larger than the max size when the number of lines is 1
|
||||
expectedSizeMessage = CGSizeMake(MIN(maxSizeMessage.width, expectedSizeMessage.width), MIN(maxSizeMessage.height, expectedSizeMessage.height));
|
||||
messageLabel.frame = CGRectMake(0.0, 0.0, expectedSizeMessage.width, expectedSizeMessage.height);
|
||||
}
|
||||
|
||||
CGRect titleRect = CGRectZero;
|
||||
|
||||
if(titleLabel != nil) {
|
||||
titleRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding;
|
||||
titleRect.origin.y = style.verticalPadding;
|
||||
titleRect.size.width = titleLabel.bounds.size.width;
|
||||
titleRect.size.height = titleLabel.bounds.size.height;
|
||||
}
|
||||
|
||||
CGRect messageRect = CGRectZero;
|
||||
|
||||
if(messageLabel != nil) {
|
||||
messageRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding;
|
||||
messageRect.origin.y = titleRect.origin.y + titleRect.size.height + style.verticalPadding;
|
||||
messageRect.size.width = messageLabel.bounds.size.width;
|
||||
messageRect.size.height = messageLabel.bounds.size.height;
|
||||
}
|
||||
|
||||
CGFloat longerWidth = MAX(titleRect.size.width, messageRect.size.width);
|
||||
CGFloat longerX = MAX(titleRect.origin.x, messageRect.origin.x);
|
||||
|
||||
// Wrapper width uses the longerWidth or the image width, whatever is larger. Same logic applies to the wrapper height.
|
||||
CGFloat wrapperWidth = MAX((imageRect.size.width + (style.horizontalPadding * 2.0)), (longerX + longerWidth + style.horizontalPadding));
|
||||
CGFloat wrapperHeight = MAX((messageRect.origin.y + messageRect.size.height + style.verticalPadding), (imageRect.size.height + (style.verticalPadding * 2.0)));
|
||||
|
||||
wrapperView.frame = CGRectMake(0.0, 0.0, wrapperWidth, wrapperHeight);
|
||||
|
||||
if(titleLabel != nil) {
|
||||
titleLabel.frame = titleRect;
|
||||
[wrapperView addSubview:titleLabel];
|
||||
}
|
||||
|
||||
if(messageLabel != nil) {
|
||||
messageLabel.frame = messageRect;
|
||||
[wrapperView addSubview:messageLabel];
|
||||
}
|
||||
|
||||
if(imageView != nil) {
|
||||
[wrapperView addSubview:imageView];
|
||||
}
|
||||
|
||||
return wrapperView;
|
||||
}
|
||||
|
||||
#pragma mark - Storage
|
||||
|
||||
- (NSMutableArray *)cs_activeToasts {
|
||||
NSMutableArray *cs_activeToasts = objc_getAssociatedObject(self, &CSToastActiveKey);
|
||||
if (cs_activeToasts == nil) {
|
||||
cs_activeToasts = [[NSMutableArray alloc] init];
|
||||
objc_setAssociatedObject(self, &CSToastActiveKey, cs_activeToasts, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
return cs_activeToasts;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)cs_toastQueue {
|
||||
NSMutableArray *cs_toastQueue = objc_getAssociatedObject(self, &CSToastQueueKey);
|
||||
if (cs_toastQueue == nil) {
|
||||
cs_toastQueue = [[NSMutableArray alloc] init];
|
||||
objc_setAssociatedObject(self, &CSToastQueueKey, cs_toastQueue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
return cs_toastQueue;
|
||||
}
|
||||
|
||||
#pragma mark - Events
|
||||
|
||||
- (void)cs_toastTimerDidFinish:(NSTimer *)timer {
|
||||
[self cs_hideToast:(UIView *)timer.userInfo];
|
||||
}
|
||||
|
||||
- (void)cs_handleToastTapped:(UITapGestureRecognizer *)recognizer {
|
||||
UIView *toast = recognizer.view;
|
||||
NSTimer *timer = (NSTimer *)objc_getAssociatedObject(toast, &CSToastTimerKey);
|
||||
[timer invalidate];
|
||||
|
||||
[self cs_hideToast:toast fromTap:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Activity Methods
|
||||
|
||||
- (void)makeToastActivity:(id)position {
|
||||
// sanity
|
||||
UIView *existingActivityView = (UIView *)objc_getAssociatedObject(self, &CSToastActivityViewKey);
|
||||
if (existingActivityView != nil) return;
|
||||
|
||||
CSToastStyle *style = [CSToastManager sharedStyle];
|
||||
|
||||
UIView *activityView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, style.activitySize.width, style.activitySize.height)];
|
||||
activityView.center = [self cs_centerPointForPosition:position withToast:activityView];
|
||||
activityView.backgroundColor = style.backgroundColor;
|
||||
activityView.alpha = 0.0;
|
||||
activityView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
|
||||
activityView.layer.cornerRadius = style.cornerRadius;
|
||||
|
||||
if (style.displayShadow) {
|
||||
activityView.layer.shadowColor = style.shadowColor.CGColor;
|
||||
activityView.layer.shadowOpacity = style.shadowOpacity;
|
||||
activityView.layer.shadowRadius = style.shadowRadius;
|
||||
activityView.layer.shadowOffset = style.shadowOffset;
|
||||
}
|
||||
|
||||
UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
|
||||
activityIndicatorView.center = CGPointMake(activityView.bounds.size.width / 2, activityView.bounds.size.height / 2);
|
||||
[activityView addSubview:activityIndicatorView];
|
||||
[activityIndicatorView startAnimating];
|
||||
|
||||
// associate the activity view with self
|
||||
objc_setAssociatedObject (self, &CSToastActivityViewKey, activityView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
[self addSubview:activityView];
|
||||
|
||||
[UIView animateWithDuration:style.fadeDuration
|
||||
delay:0.0
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^{
|
||||
activityView.alpha = 1.0;
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
- (void)hideToastActivity {
|
||||
UIView *existingActivityView = (UIView *)objc_getAssociatedObject(self, &CSToastActivityViewKey);
|
||||
if (existingActivityView != nil) {
|
||||
[UIView animateWithDuration:[[CSToastManager sharedStyle] fadeDuration]
|
||||
delay:0.0
|
||||
options:(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
|
||||
animations:^{
|
||||
existingActivityView.alpha = 0.0;
|
||||
} completion:^(BOOL finished) {
|
||||
[existingActivityView removeFromSuperview];
|
||||
objc_setAssociatedObject (self, &CSToastActivityViewKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (CGPoint)cs_centerPointForPosition:(id)point withToast:(UIView *)toast {
|
||||
CSToastStyle *style = [CSToastManager sharedStyle];
|
||||
|
||||
UIEdgeInsets safeInsets = UIEdgeInsetsZero;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
safeInsets = self.safeAreaInsets;
|
||||
}
|
||||
|
||||
CGFloat topPadding = style.verticalPadding + safeInsets.top;
|
||||
CGFloat bottomPadding = style.verticalPadding + safeInsets.bottom;
|
||||
|
||||
if([point isKindOfClass:[NSString class]]) {
|
||||
if([point caseInsensitiveCompare:CSToastPositionTop] == NSOrderedSame) {
|
||||
return CGPointMake(self.bounds.size.width / 2.0, (toast.frame.size.height / 2.0) + topPadding);
|
||||
} else if([point caseInsensitiveCompare:CSToastPositionCenter] == NSOrderedSame) {
|
||||
return CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0);
|
||||
}
|
||||
} else if ([point isKindOfClass:[NSValue class]]) {
|
||||
return [point CGPointValue];
|
||||
}
|
||||
|
||||
// default to bottom
|
||||
return CGPointMake(self.bounds.size.width / 2.0, (self.bounds.size.height - (toast.frame.size.height / 2.0)) - bottomPadding);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSToastStyle
|
||||
|
||||
#pragma mark - Constructors
|
||||
|
||||
- (instancetype)initWithDefaultStyle {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8];
|
||||
self.titleColor = [UIColor whiteColor];
|
||||
self.messageColor = [UIColor whiteColor];
|
||||
self.maxWidthPercentage = 0.8;
|
||||
self.maxHeightPercentage = 0.8;
|
||||
self.horizontalPadding = 10.0;
|
||||
self.verticalPadding = 10.0;
|
||||
self.cornerRadius = 10.0;
|
||||
self.titleFont = [UIFont boldSystemFontOfSize:16.0];
|
||||
self.messageFont = [UIFont systemFontOfSize:16.0];
|
||||
self.titleAlignment = NSTextAlignmentLeft;
|
||||
self.messageAlignment = NSTextAlignmentLeft;
|
||||
self.titleNumberOfLines = 0;
|
||||
self.messageNumberOfLines = 0;
|
||||
self.displayShadow = NO;
|
||||
self.shadowOpacity = 0.8;
|
||||
self.shadowRadius = 6.0;
|
||||
self.shadowOffset = CGSizeMake(4.0, 4.0);
|
||||
self.imageSize = CGSizeMake(80.0, 80.0);
|
||||
self.activitySize = CGSizeMake(100.0, 100.0);
|
||||
self.fadeDuration = 0.2;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setMaxWidthPercentage:(CGFloat)maxWidthPercentage {
|
||||
_maxWidthPercentage = MAX(MIN(maxWidthPercentage, 1.0), 0.0);
|
||||
}
|
||||
|
||||
- (void)setMaxHeightPercentage:(CGFloat)maxHeightPercentage {
|
||||
_maxHeightPercentage = MAX(MIN(maxHeightPercentage, 1.0), 0.0);
|
||||
}
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE {
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface CSToastManager ()
|
||||
|
||||
@property (strong, nonatomic) CSToastStyle *sharedStyle;
|
||||
@property (assign, nonatomic, getter=isTapToDismissEnabled) BOOL tapToDismissEnabled;
|
||||
@property (assign, nonatomic, getter=isQueueEnabled) BOOL queueEnabled;
|
||||
@property (assign, nonatomic) NSTimeInterval defaultDuration;
|
||||
@property (strong, nonatomic) id defaultPosition;
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSToastManager
|
||||
|
||||
#pragma mark - Constructors
|
||||
|
||||
+ (instancetype)sharedManager {
|
||||
static CSToastManager *_sharedManager = nil;
|
||||
static dispatch_once_t oncePredicate;
|
||||
dispatch_once(&oncePredicate, ^{
|
||||
_sharedManager = [[self alloc] init];
|
||||
});
|
||||
|
||||
return _sharedManager;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.sharedStyle = [[CSToastStyle alloc] initWithDefaultStyle];
|
||||
self.tapToDismissEnabled = YES;
|
||||
self.queueEnabled = NO;
|
||||
self.defaultDuration = 3.0;
|
||||
self.defaultPosition = CSToastPositionBottom;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Singleton Methods
|
||||
|
||||
+ (void)setSharedStyle:(CSToastStyle *)sharedStyle {
|
||||
[[self sharedManager] setSharedStyle:sharedStyle];
|
||||
}
|
||||
|
||||
+ (CSToastStyle *)sharedStyle {
|
||||
return [[self sharedManager] sharedStyle];
|
||||
}
|
||||
|
||||
+ (void)setTapToDismissEnabled:(BOOL)tapToDismissEnabled {
|
||||
[[self sharedManager] setTapToDismissEnabled:tapToDismissEnabled];
|
||||
}
|
||||
|
||||
+ (BOOL)isTapToDismissEnabled {
|
||||
return [[self sharedManager] isTapToDismissEnabled];
|
||||
}
|
||||
|
||||
+ (void)setQueueEnabled:(BOOL)queueEnabled {
|
||||
[[self sharedManager] setQueueEnabled:queueEnabled];
|
||||
}
|
||||
|
||||
+ (BOOL)isQueueEnabled {
|
||||
return [[self sharedManager] isQueueEnabled];
|
||||
}
|
||||
|
||||
+ (void)setDefaultDuration:(NSTimeInterval)duration {
|
||||
[[self sharedManager] setDefaultDuration:duration];
|
||||
}
|
||||
|
||||
+ (NSTimeInterval)defaultDuration {
|
||||
return [[self sharedManager] defaultDuration];
|
||||
}
|
||||
|
||||
+ (void)setDefaultPosition:(id)position {
|
||||
if ([position isKindOfClass:[NSString class]] || [position isKindOfClass:[NSValue class]]) {
|
||||
[[self sharedManager] setDefaultPosition:position];
|
||||
}
|
||||
}
|
||||
|
||||
+ (id)defaultPosition {
|
||||
return [[self sharedManager] defaultPosition];
|
||||
}
|
||||
|
||||
@end
|
18
src/ios/UserUpdateListener.h
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// UserUpdateListener.h
|
||||
// shuto-cne
|
||||
//
|
||||
// Created by 范大德 on 2022/3/30.
|
||||
//
|
||||
|
||||
#ifndef UserUpdateListener_h
|
||||
#define UserUpdateListener_h
|
||||
|
||||
|
||||
#endif /* UserUpdateListener_h */
|
||||
|
||||
#import "Listener.h"
|
||||
|
||||
@interface UserUpdateListener:Listener
|
||||
{}
|
||||
@end
|
24
src/ios/UserUpdateListener.m
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Listener.m
|
||||
// shuto-cne
|
||||
//
|
||||
// Created by 范大德 on 2022/3/29.
|
||||
//
|
||||
|
||||
#import "UserUpdateListener.h"
|
||||
#import "VideoCallingViewController.h"
|
||||
@interface UserUpdateListener()
|
||||
{}
|
||||
@end
|
||||
@implementation UserUpdateListener
|
||||
|
||||
- (instancetype)init {
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
-(void)on: (NSDictionary*)extra{
|
||||
[[VideoCallingViewController viewController]updateUser:extra];
|
||||
}
|
||||
|
||||
@end
|
23
src/ios/VideoCallingViewController.h
Executable file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// VideoCallingViewController.h
|
||||
// TRTC-API-Example-OC
|
||||
//
|
||||
// Created by bluedang on 2021/4/12.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <TXLiteAVSDK_TRTC/TRTCCloud.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
//MARK: 视频通话示例 - 通话界面
|
||||
@interface VideoCallingViewController : UIViewController
|
||||
- (instancetype)initWithRoomId:(UInt32)roomId userId:(NSString *)userId appId:(UInt32 *)appId userSig:(NSString *) userSig;
|
||||
- (NSMutableOrderedSet *)remoteUidSet;
|
||||
+(VideoCallingViewController*)viewController;
|
||||
- (void)refreshRemoteVideoViews;
|
||||
- (void) updateUser:(NSDictionary*)extra;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
402
src/ios/VideoCallingViewController.m
Executable file
@ -0,0 +1,402 @@
|
||||
//
|
||||
// VideoCallingViewController.m
|
||||
// TRTC-API-Example-OC
|
||||
//
|
||||
// Created by bluedang on 2021/4/12.
|
||||
//
|
||||
|
||||
/*
|
||||
实时视频通话功能
|
||||
TRTC APP 实时视频通话功能
|
||||
本文件展示如何集成实时视频通话功能
|
||||
1、切换摄像头 API:[[_trtcCloud getDeviceManager] switchCamera:_isFrontCamera];
|
||||
2、打开关闭摄像头 API: [self.trtcCloud startLocalPreview:_isFrontCamera view:_localVideoView];
|
||||
[self.trtcCloud stopLocalPreview];
|
||||
3、切换听筒与扬声器 API:[[_trtcCloud getDeviceManager] setAudioRoute:TXAudioRouteEarpiece];
|
||||
[[_trtcCloud getDeviceManager] setAudioRoute:TXAudioRouteSpeakerphone];
|
||||
4、静音当前设备,其他人将无法听到该设备的声音 API: [_trtcCloud muteLocalAudio:YES];
|
||||
参考文档:https://cloud.tencent.com/document/product/647/42044
|
||||
*/
|
||||
|
||||
/*
|
||||
Real-Time Audio Call
|
||||
TRTC Audio Call
|
||||
This document shows how to integrate the real-time audio call feature.
|
||||
1. Switch between the speaker and receiver: [[_trtcCloud getDeviceManager] setAudioRoute:TXAudioRouteSpeakerphone]
|
||||
2. Mute the device so that others won’t hear the audio of the device: [_trtcCloud muteLocalAudio:YES]
|
||||
3. Display other network and volume information: delegate -> onNetworkQuality, onUserVoiceVolume
|
||||
Documentation: https://cloud.tencent.com/document/product/647/42046
|
||||
*/
|
||||
|
||||
#import "VideoCallingViewController.h"
|
||||
|
||||
#import "TrtcUserInfo.h"
|
||||
#import "CordovaEventKit.h"
|
||||
#import "UIView+Toast.h"
|
||||
#import "Events.h"
|
||||
#import "UserUpdateListener.h"
|
||||
|
||||
|
||||
static const NSInteger maxRemoteUserNum = 7;
|
||||
|
||||
@interface VideoCallingViewController ()<TRTCCloudDelegate>
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIButton *backButton;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *subVisibleButton;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *viewRotateButton;
|
||||
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *remoteViewArr;
|
||||
@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *subViewBtnArr;
|
||||
@property (strong, nonatomic) IBOutletCollection(UILabel) NSArray *displayLabelArr;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *displayLabel;
|
||||
|
||||
@property (assign, nonatomic) UInt32 roomId;
|
||||
@property (strong, nonatomic) NSString* userId;
|
||||
@property(nonatomic, assign) UInt32 sdkAppId;
|
||||
@property (strong, nonatomic) NSString* userSig;
|
||||
@property (strong, nonatomic) TRTCCloud *trtcCloud;
|
||||
@property (strong, nonatomic) NSMutableOrderedSet *remoteUidSet;
|
||||
@property (strong, nonatomic) NSMutableArray<TrtcUserInfo*> *viewUsers;
|
||||
|
||||
@property (assign, nonatomic) BOOL isFrontCamera;
|
||||
@property (assign, nonatomic) BOOL *hasShareUser;
|
||||
@property (assign, nonatomic) BOOL *hiddenAllSubView;
|
||||
|
||||
@property (assign, nonatomic) NSInteger rotation;
|
||||
@end
|
||||
|
||||
@implementation VideoCallingViewController
|
||||
|
||||
static VideoCallingViewController* _self;
|
||||
|
||||
+(VideoCallingViewController*)viewController{
|
||||
return _self;
|
||||
}
|
||||
- (TRTCCloud*)trtcCloud {
|
||||
if (!_trtcCloud) {
|
||||
_trtcCloud = [TRTCCloud sharedInstance];
|
||||
}
|
||||
return _trtcCloud;
|
||||
}
|
||||
|
||||
- (NSMutableOrderedSet<TrtcUserInfo*> *)remoteUidSet {
|
||||
if (!_remoteUidSet) {
|
||||
_remoteUidSet = [[NSMutableOrderedSet alloc] initWithCapacity:maxRemoteUserNum];
|
||||
}
|
||||
return _remoteUidSet;
|
||||
}
|
||||
|
||||
- (NSMutableArray<TrtcUserInfo*> *)viewUsers {
|
||||
if (!_viewUsers) {
|
||||
_viewUsers = [[NSMutableArray alloc] initWithCapacity:maxRemoteUserNum];
|
||||
}
|
||||
return _viewUsers;
|
||||
}
|
||||
|
||||
- (instancetype)initWithRoomId:(UInt32)roomId userId:(NSString *)userId appId:(UInt32)appId userSig:(NSString *)userSig{
|
||||
NSLog(@"TRTC - initWithRoomId:::::");
|
||||
self = [super initWithNibName:NSStringFromClass([self class]) bundle:nil];
|
||||
if (self) {
|
||||
_roomId = roomId;
|
||||
_userId = userId;
|
||||
_sdkAppId = appId;
|
||||
_userSig = userSig;
|
||||
}
|
||||
NSLog(@"TRTC - roomid:%d,userID:%@,sdkAppid:%d,userSig:%@",_roomId,_userId,_sdkAppId,_userSig);
|
||||
_self = self;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) updateUser:(NSDictionary*)extra{
|
||||
NSLog(@"TRTC - userinfo.update -- userID:%@,displayname:%@",extra[@"userId"],extra[@"displayName"]);
|
||||
TrtcUserInfo *user = [[TrtcUserInfo alloc]initWithPersonid:extra[@"userId"]];
|
||||
NSInteger index = [[self remoteUidSet] indexOfObject: user];
|
||||
NSLog(@"TRTC - userinfo.update -- userId:%@,index:%ld",extra[@"userId"],index);
|
||||
if (index == NSNotFound) { return; }
|
||||
TrtcUserInfo *obj = [self remoteUidSet][index];
|
||||
[obj setDisplayName: extra[@"displayName"]];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self refreshRemoteVideoViews];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.isFrontCamera = NO;
|
||||
self.trtcCloud.delegate = self;
|
||||
|
||||
[self setupDefaultUIConfig];
|
||||
[self setupTRTCCloud];
|
||||
|
||||
[self.view sendSubviewToBack:self.view];
|
||||
}
|
||||
|
||||
- (void)setupDefaultUIConfig {
|
||||
NSLog(@"TRTC - setupDefaultUIConfig:::::");
|
||||
_displayLabel.text = @"我";
|
||||
_displayLabel.hidden = NO;
|
||||
_rotation = 0;
|
||||
[Events addListener:@"userinfo.update" listener: [[UserUpdateListener alloc] init] ];
|
||||
}
|
||||
|
||||
|
||||
- (void)setupTRTCCloud {
|
||||
[[self remoteUidSet] removeAllObjects];
|
||||
|
||||
[self.trtcCloud startLocalPreview:_isFrontCamera view:self.view];
|
||||
TRTCParams *params = [TRTCParams new];
|
||||
params.sdkAppId = _sdkAppId;
|
||||
params.roomId = _roomId;
|
||||
params.userId = _userId;
|
||||
params.role = TRTCRoleAnchor;
|
||||
params.userSig = _userSig;
|
||||
|
||||
[self.trtcCloud enterRoom:params appScene:TRTCAppSceneVideoCall];
|
||||
|
||||
TRTCVideoEncParam *encParams = [TRTCVideoEncParam new];
|
||||
encParams.videoResolution = TRTCVideoResolution_640_360;
|
||||
encParams.videoBitrate = 550;
|
||||
encParams.videoFps = 15;
|
||||
|
||||
[self.trtcCloud setVideoEncoderParam:encParams];
|
||||
[self.trtcCloud startLocalAudio:TRTCAudioQualityMusic];
|
||||
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self.trtcCloud exitRoom];
|
||||
[TRTCCloud destroySharedIntance];
|
||||
}
|
||||
|
||||
- (void)changeUser:(NSInteger) a and:(NSInteger) b{
|
||||
if(a == b || a < 0 || b < 0){return;}
|
||||
NSInteger x = a > b ? b : a;
|
||||
NSInteger y = a > b ? a : b;
|
||||
if([self remoteUidSet].count > y && x>-1){
|
||||
// 交换
|
||||
NSLog(@"TRTC - onSubViewClcik:::::Before change:%@,%@",[[self remoteUidSet][x] personid],[[self remoteUidSet][y] personid]);
|
||||
TrtcUserInfo* info = [self remoteUidSet][x];
|
||||
TrtcUserInfo* info2 = [self remoteUidSet][y];
|
||||
[_remoteUidSet removeObjectAtIndex:x];
|
||||
[_remoteUidSet setObject:info atIndex: y-1];
|
||||
[_remoteUidSet insertObject:info2 atIndex:x];
|
||||
NSLog(@"TRTC - onSubViewClcik:::::after change:%@,%@",[[self remoteUidSet][x] personid],[[self remoteUidSet][y] personid]);
|
||||
[self refreshRemoteVideoViews];
|
||||
NSDictionary *event = [[NSDictionary alloc]initWithObjectsAndKeys:
|
||||
[[NSString alloc] initWithFormat:@"%d",_roomId],@"room",info2.personid,@"userId", nil];
|
||||
[CordovaEventKit fireEvent:@"onLayoutChangeMessage" obj:event];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - IBActions
|
||||
|
||||
- (IBAction)onSubVisibleChange:(UIButton*)sender {
|
||||
NSLog(@"TRTC - onSubVisibleChange:::::,before:%@",[NSNumber numberWithBool:sender.selected]);
|
||||
sender.selected = !sender.selected;
|
||||
NSLog(@"TRTC - onSubVisibleChange:::::,after:%@",[NSNumber numberWithBool:sender.selected]);
|
||||
[self refreshRemoteVideoViews];
|
||||
}
|
||||
|
||||
- (IBAction)onVideoRotate:(UIButton*)sender {
|
||||
NSLog(@"TRTC - onRouteChange:::::,%@,local index: %ld",[[self remoteUidSet][0] personid],[_viewUsers indexOfObject:_userId]);
|
||||
TRTCRenderParams *params = [[TRTCRenderParams alloc] init];
|
||||
|
||||
params.fillMode = TRTCVideoFillMode_Fit;
|
||||
_rotation += 1;
|
||||
_rotation = _rotation > 3 ? 0 : _rotation;
|
||||
params.rotation = _rotation;
|
||||
// [_trtcCloud stopRemoteView:[[self viewUsers][0] personid] streamType:TRTCVideoStreamTypeBig];
|
||||
[_trtcCloud setRemoteRenderParams:[[self remoteUidSet][0] personid] streamType:TRTCVideoStreamTypeBig params:params];
|
||||
// [_trtcCloud updateRemoteView:self.view streamType:TRTCVideoStreamTypeBig forUser:[[self remoteUidSet][0] personid]];
|
||||
// [_trtcCloud startRemoteView:[[self viewUsers][0] personid] streamType: TRTCVideoStreamTypeBig view:self.view];
|
||||
|
||||
}
|
||||
|
||||
- (IBAction)onBackClick:(UIButton*)sender {
|
||||
NSLog(@"TRTC - onBackClick:::::");
|
||||
[self.trtcCloud exitRoom];
|
||||
[TRTCCloud destroySharedIntance];
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (IBAction)onSwitchCameraClick:(UIButton*)sender {
|
||||
NSLog(@"TRTC - onSwitchCameraClick:::::");
|
||||
_isFrontCamera = !_isFrontCamera;
|
||||
[[_trtcCloud getDeviceManager] switchCamera:_isFrontCamera];
|
||||
}
|
||||
|
||||
|
||||
- (IBAction)onMicCaptureClick:(UIButton*)sender {
|
||||
NSLog(@"TRTC - onMicCaptureClick:::::");
|
||||
sender.selected = !sender.selected;
|
||||
if ([sender isSelected]) {
|
||||
[_trtcCloud muteLocalAudio:YES];
|
||||
} else {
|
||||
[_trtcCloud muteLocalAudio:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)onSwitchSpeakerClick:(UIButton*)sender {
|
||||
NSLog(@"TRTC - onSwitchSpeakerClick:::::");
|
||||
sender.selected = !sender.selected;
|
||||
if ([sender isSelected]) {
|
||||
[[_trtcCloud getDeviceManager] setAudioRoute:TXAudioRouteEarpiece];
|
||||
} else {
|
||||
[[_trtcCloud getDeviceManager] setAudioRoute:TXAudioRouteSpeakerphone];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)onSubViewClcik:(UIButton*)sender {
|
||||
NSLog(@"TRTC - onSubViewClcik:::::button");
|
||||
NSInteger index = [_subViewBtnArr indexOfObject:sender];
|
||||
if (index == NSNotFound) {return;}
|
||||
index = index + 1;
|
||||
[self changeUser:0 and:index];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - TRTCCloud Delegate
|
||||
|
||||
- (void)onEnterRoom:(NSInteger) result{
|
||||
NSLog(@"TRTC - onEnterRoom, %ld",result);
|
||||
[_trtcCloud startLocalPreview:_isFrontCamera view:self.view];
|
||||
// 添加本地用户到用户列表
|
||||
TrtcUserInfo *info = [[TrtcUserInfo alloc] initWithPersonid:_userId];
|
||||
[info setLocal:YES];
|
||||
NSInteger index = [[self remoteUidSet] indexOfObject:info];
|
||||
if (index != NSNotFound) {
|
||||
return;
|
||||
} else {
|
||||
[[self remoteUidSet] addObject:info];
|
||||
}
|
||||
|
||||
NSDictionary *event = [[NSDictionary alloc]initWithObjectsAndKeys:
|
||||
[[NSString alloc] initWithFormat:@"%d",_roomId],@"room",_userId,@"userId", nil];
|
||||
|
||||
[CordovaEventKit fireEvent:@"onLayoutChangeMessage" obj:event];
|
||||
}
|
||||
|
||||
- (void)onUserVideoAvailable:(NSString *)userId available:(BOOL)available {
|
||||
NSLog(@"TRTC - onUserVideoAvailable, user:%@,available%@",userId, available ? @"YES":@"NO");
|
||||
// 添加远端用户到用户列表
|
||||
TrtcUserInfo *info = [[TrtcUserInfo alloc] initWithPersonid:userId];
|
||||
NSInteger index = [[self remoteUidSet] indexOfObject:info];
|
||||
if (available) {
|
||||
if (index != NSNotFound) { return; }
|
||||
if([info isShareUser]){
|
||||
_hasShareUser = YES;
|
||||
}
|
||||
[[self remoteUidSet] addObject:info];
|
||||
} else {
|
||||
if (index == NSNotFound) { return; }
|
||||
if([info isShareUser]){
|
||||
_hasShareUser = NO;
|
||||
_hiddenAllSubView = NO;
|
||||
}
|
||||
[_trtcCloud stopRemoteView:userId streamType:TRTCVideoStreamTypeSmall];
|
||||
[[self remoteUidSet] removeObject:userId];
|
||||
}
|
||||
[self refreshRemoteVideoViews];
|
||||
if(_hasShareUser){
|
||||
// 切换分享屏幕到主窗口
|
||||
[self changeUser:0 and:[[self remoteUidSet] indexOfObject:info]];
|
||||
// 隐藏所有子窗口
|
||||
_hiddenAllSubView = YES;
|
||||
[self refreshRemoteVideoViews];
|
||||
// [_trtcCloud stopLocalPreview];
|
||||
}
|
||||
NSDictionary *event = @{@"room": [[NSString alloc] initWithFormat:@"%d", _roomId], @"userId": userId, @"available": [NSNumber numberWithBool:available]};
|
||||
[CordovaEventKit fireEvent:@"onUserVideoAvailable" obj:event];
|
||||
}
|
||||
|
||||
- (void)refreshRemoteVideoViews {
|
||||
NSLog(@"TRTC - refreshRemoteVideoViews:%ld",_remoteViewArr.count);
|
||||
|
||||
_subVisibleButton.hidden = _hasShareUser != nil;
|
||||
_viewRotateButton.hidden = _hasShareUser == nil;
|
||||
_viewRotateButton.enabled = !_viewRotateButton.hidden;
|
||||
for (int i = 0; i <= _remoteViewArr.count; i++) {
|
||||
TrtcUserInfo* nUser = [self remoteUidSet].count > i ? [self remoteUidSet][i] : nil;
|
||||
TrtcUserInfo* oUser = [self viewUsers].count > i ? [self viewUsers][i] : nil;
|
||||
UIView* view = i == 0 ? self.view : _remoteViewArr[i-1];
|
||||
UILabel* label = i ==0 ? _displayLabel : _displayLabelArr[i-1];
|
||||
UIButton* btn = i ==0 ? nil : _subViewBtnArr[i-1];
|
||||
NSLog(@"TRTC - refreshRemoteVideoViews - new : %@, o: %@",nUser == nil ? nil : [nUser personid],oUser == nil ? nil : [oUser personid]);
|
||||
if((_hiddenAllSubView || ![_subVisibleButton isSelected]) && i!=0){
|
||||
[view setHidden:YES];
|
||||
[label setHidden:YES];
|
||||
}else{
|
||||
if(nUser != nil){
|
||||
if([nUser isEqual:oUser] && ![view isHidden]){
|
||||
if([nUser isLocalUser]){
|
||||
[label setText:@"我"];
|
||||
} else if([nUser displayName] == nil || [nUser displayName].length == 0) {
|
||||
[label setText:[NSString stringWithFormat:@"%@%@",[nUser personid], [nUser isShareUser] ? @"的屏幕分享" : @""]];
|
||||
} else {
|
||||
[label setText:[NSString stringWithFormat:@"%@%@",[nUser displayName], [nUser isShareUser] ? @"的屏幕分享" : @""]];
|
||||
}
|
||||
[label sizeToFit];
|
||||
continue;
|
||||
}
|
||||
TRTCRenderParams *params = [[TRTCRenderParams alloc] init];
|
||||
params.rotation = 0;
|
||||
params.fillMode = [nUser isShareUser] ? TRTCVideoFillMode_Fit : TRTCVideoFillMode_Fill;
|
||||
if([nUser isLocalUser]){
|
||||
[_trtcCloud startLocalPreview:_isFrontCamera view:view];
|
||||
[_trtcCloud setLocalRenderParams:params];
|
||||
[label setText:@"我"];
|
||||
[label sizeToFit];
|
||||
} else {
|
||||
[_trtcCloud setRemoteRenderParams:[nUser personid] streamType:i == 0 ? TRTCVideoStreamTypeBig : TRTCVideoStreamTypeSmall params:params];
|
||||
[_trtcCloud startRemoteView:[nUser personid] streamType:i == 0 ? TRTCVideoStreamTypeBig : TRTCVideoStreamTypeSmall view:view];
|
||||
|
||||
if([nUser displayName] == nil || [nUser displayName].length == 0) {
|
||||
[label setText:[NSString stringWithFormat:@"%@%@",[nUser personid], [nUser isShareUser] ? @"的屏幕分享" : @""]];
|
||||
} else {
|
||||
[label setText:[NSString stringWithFormat:@"%@%@",[nUser displayName], [nUser isShareUser] ? @"的屏幕分享" : @""]];
|
||||
}
|
||||
[label sizeToFit];
|
||||
}
|
||||
[label setHidden:NO];
|
||||
[view setHidden:NO];
|
||||
if(btn != nil){
|
||||
[btn setHidden:NO];
|
||||
}
|
||||
} else {
|
||||
[view setHidden:YES];
|
||||
[label setHidden:YES];
|
||||
if(btn != nil){
|
||||
[btn setHidden:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(nUser != nil){
|
||||
[self viewUsers].count > i ? [self viewUsers][i] = nUser : [[self viewUsers] addObject:nUser];
|
||||
}else {
|
||||
while([self viewUsers].count > i){
|
||||
[[self viewUsers] removeObjectAtIndex:i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) onError:(TXLiteAVError)errCode errMsg:(NSString *)errMsg extInfo:(NSDictionary *)extInfo{
|
||||
NSLog(@"TRTC - onError::sdk callback onError code: %d,msg: %@,extInfo: %@",errCode,errMsg,extInfo);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.view makeToast: [[NSString alloc] initWithFormat:@"%@[%d]",errMsg,errCode]];
|
||||
});
|
||||
// [self.trtcCloud exitRoom];
|
||||
// [TRTCCloud destroySharedIntance];
|
||||
// [self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void) onExitRoom:(NSInteger)reason{
|
||||
if (reason == 2) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.view makeToast: @"远程协助已结束"];
|
||||
});
|
||||
}
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
@end
|
342
src/ios/VideoCallingViewController.xib
Executable file
@ -0,0 +1,342 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina5_5" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="VideoCallingViewController">
|
||||
<connections>
|
||||
<outlet property="displayLabel" destination="YvU-M3-dYi" id="51r-fe-mFR"/>
|
||||
<outlet property="subVisibleButton" destination="qp6-PK-LBy" id="eyH-u5-0dn"/>
|
||||
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
||||
<outlet property="viewRotateButton" destination="rF6-xK-afO" id="cn9-hg-AYV"/>
|
||||
<outletCollection property="remoteViewArr" destination="Kbh-1W-XwY" id="nUp-5a-Xzv"/>
|
||||
<outletCollection property="remoteViewArr" destination="MBM-E8-DcQ" id="Kml-lI-eY2"/>
|
||||
<outletCollection property="remoteViewArr" destination="ZEJ-k9-h9o" id="Dpa-XO-wvu"/>
|
||||
<outletCollection property="remoteViewArr" destination="X9k-lT-qBv" id="v8m-xW-j33"/>
|
||||
<outletCollection property="remoteViewArr" destination="onQ-DT-Yct" id="AXN-8c-H6e"/>
|
||||
<outletCollection property="remoteViewArr" destination="cDr-2L-E86" id="XHg-z0-cal"/>
|
||||
<outletCollection property="displayLabelArr" destination="bTi-oL-YQO" id="FbS-pO-ofo"/>
|
||||
<outletCollection property="displayLabelArr" destination="XZp-OY-07f" id="UA7-Ky-ODc"/>
|
||||
<outletCollection property="displayLabelArr" destination="x2c-r3-mbd" id="8tE-hB-ImW"/>
|
||||
<outletCollection property="displayLabelArr" destination="IbE-C4-eSC" id="R4x-VL-wHM"/>
|
||||
<outletCollection property="displayLabelArr" destination="Sb1-bA-J6q" id="Yal-GN-3Sv"/>
|
||||
<outletCollection property="displayLabelArr" destination="twT-zq-iCy" id="pHx-JS-dcf"/>
|
||||
<outletCollection property="subViewBtnArr" destination="g4i-xh-dif" id="SHV-gU-uJN"/>
|
||||
<outletCollection property="subViewBtnArr" destination="xNk-te-p3c" id="X4z-sY-3lH"/>
|
||||
<outletCollection property="subViewBtnArr" destination="bzt-Ez-WZQ" id="ZjO-Jq-BC5"/>
|
||||
<outletCollection property="subViewBtnArr" destination="6Uv-u2-Pqq" id="Zj1-QX-hb4"/>
|
||||
<outletCollection property="subViewBtnArr" destination="Qab-02-i9Q" id="ToR-SP-1Zn"/>
|
||||
<outletCollection property="subViewBtnArr" destination="o25-iM-e3X" id="rUU-2F-rLf"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kbh-1W-XwY" userLabel="remoteVideoView1">
|
||||
<rect key="frame" x="314" y="50" width="90" height="160"/>
|
||||
<subviews>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bTi-oL-YQO">
|
||||
<rect key="frame" x="0.0" y="139" width="48" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.34100000000000003" blue="0.29399999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button hidden="YES" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="g4i-xh-dif">
|
||||
<rect key="frame" x="0.0" y="0.0" width="82" height="160"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" title=" "/>
|
||||
<connections>
|
||||
<action selector="onSubViewClcik:" destination="-1" eventType="touchUpInside" id="PZc-UN-Jc3"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<accessibility key="accessibilityConfiguration">
|
||||
<accessibilityTraits key="traits" button="YES"/>
|
||||
</accessibility>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="160" id="auE-6N-CzV"/>
|
||||
<constraint firstAttribute="width" constant="90" id="jbq-fQ-8DY"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view tag="1" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="MBM-E8-DcQ" userLabel="remoteVideoView2">
|
||||
<rect key="frame" x="314" y="226" width="90" height="160"/>
|
||||
<subviews>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XZp-OY-07f">
|
||||
<rect key="frame" x="0.0" y="139" width="52" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.34100000000000003" blue="0.29399999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button hidden="YES" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xNk-te-p3c">
|
||||
<rect key="frame" x="4" y="0.0" width="82" height="160"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" title=" "/>
|
||||
<connections>
|
||||
<action selector="onSubViewClcik:" destination="-1" eventType="touchUpInside" id="1iJ-2y-Kiy"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="90" id="1kk-EX-8rm"/>
|
||||
<constraint firstAttribute="height" constant="160" id="9Da-yK-Ln8"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view tag="2" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ZEJ-k9-h9o" userLabel="remoteVideoView3">
|
||||
<rect key="frame" x="314" y="402" width="90" height="160"/>
|
||||
<subviews>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label3" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="x2c-r3-mbd">
|
||||
<rect key="frame" x="0.0" y="139" width="52" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.34100000000000003" blue="0.29399999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button hidden="YES" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bzt-Ez-WZQ">
|
||||
<rect key="frame" x="4" y="0.0" width="82" height="160"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" title=" "/>
|
||||
<connections>
|
||||
<action selector="onSubViewClcik:" destination="-1" eventType="touchUpInside" id="49R-TA-l5o"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="160" id="N5Y-DF-8xu"/>
|
||||
<constraint firstAttribute="width" constant="90" id="zic-Rx-SXc"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view tag="3" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="X9k-lT-qBv" userLabel="remoteVideoView4">
|
||||
<rect key="frame" x="10" y="50" width="90" height="160"/>
|
||||
<subviews>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label4" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IbE-C4-eSC">
|
||||
<rect key="frame" x="0.0" y="139" width="52" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.34100000000000003" blue="0.29399999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button hidden="YES" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6Uv-u2-Pqq">
|
||||
<rect key="frame" x="4" y="0.0" width="82" height="160"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" title=" "/>
|
||||
<connections>
|
||||
<action selector="onSubViewClcik:" destination="-1" eventType="touchUpInside" id="wEo-2D-Uwu"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="90" id="ZMf-aQ-nNf"/>
|
||||
<constraint firstAttribute="height" constant="160" id="oxO-bN-BOD"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view tag="4" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="onQ-DT-Yct" userLabel="remoteVideoView5">
|
||||
<rect key="frame" x="10" y="226" width="90" height="160"/>
|
||||
<subviews>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label5" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Sb1-bA-J6q">
|
||||
<rect key="frame" x="0.0" y="139" width="52" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.34100000000000003" blue="0.29399999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button hidden="YES" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Qab-02-i9Q">
|
||||
<rect key="frame" x="4" y="0.0" width="82" height="160"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" title=" "/>
|
||||
<connections>
|
||||
<action selector="onSubViewClcik:" destination="-1" eventType="touchUpInside" id="WnJ-3L-lmf"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="160" id="BFk-Gh-7v4"/>
|
||||
<constraint firstAttribute="width" constant="90" id="XUy-s6-P0X"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view tag="5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cDr-2L-E86" userLabel="remoteVideoView6">
|
||||
<rect key="frame" x="10" y="402" width="90" height="160"/>
|
||||
<subviews>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label6" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="twT-zq-iCy">
|
||||
<rect key="frame" x="0.0" y="139" width="52" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.34100000000000003" blue="0.29399999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button hidden="YES" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="o25-iM-e3X">
|
||||
<rect key="frame" x="4" y="0.0" width="82" height="160"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" title=" "/>
|
||||
<connections>
|
||||
<action selector="onSubViewClcik:" destination="-1" eventType="touchUpInside" id="EUB-Uu-Yt0"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="90" id="CVu-hM-f8k"/>
|
||||
<constraint firstAttribute="height" constant="160" id="Ydt-pt-cM4"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rzV-fr-mUd">
|
||||
<rect key="frame" x="0.0" y="0.0" width="41" height="31"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" image="chevron.backward" catalog="system" title=" "/>
|
||||
<connections>
|
||||
<action selector="onBackClick:" destination="-1" eventType="touchUpInside" id="aV2-bn-bDv"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="XCN-Qr-goF">
|
||||
<rect key="frame" x="82" y="666" width="250" height="50"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" selected="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="cqv-YF-v7M">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="dwe-bq-Wq8"/>
|
||||
<constraint firstAttribute="height" constant="50" id="iQf-fV-QIk"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="36"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||
<state key="normal" image="mic.slash" catalog="system">
|
||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<state key="selected" image="mic" catalog="system">
|
||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onMicCaptureClick:" destination="-1" eventType="touchUpInside" id="74S-01-YUi"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iNs-1U-f8V">
|
||||
<rect key="frame" x="50" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="N3O-bv-Nk0"/>
|
||||
</constraints>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||
<state key="normal" image="speaker.wave.2" catalog="system">
|
||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<state key="selected" image="ear" catalog="system">
|
||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onSwitchSpeakerClick:" destination="-1" eventType="touchUpInside" id="QhV-mp-sQc"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" selected="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="S9T-PM-Hhz">
|
||||
<rect key="frame" x="100" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="PuT-SC-nBb"/>
|
||||
</constraints>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||
<state key="normal" image="camera.rotate" catalog="system">
|
||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<state key="selected" image="camera.rotate" catalog="system">
|
||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onSwitchCameraClick:" destination="-1" eventType="touchUpInside" id="oQG-9m-ga4"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" selected="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qp6-PK-LBy">
|
||||
<rect key="frame" x="150" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="U8G-VC-E4h"/>
|
||||
</constraints>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||
<state key="normal" image="eye.slash" catalog="system">
|
||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<state key="selected" image="eye" catalog="system">
|
||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onSubVisibleChange:" destination="-1" eventType="touchUpInside" id="ZHg-NL-PVq"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rF6-xK-afO">
|
||||
<rect key="frame" x="200" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="xJB-cA-Wjz"/>
|
||||
</constraints>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||
<state key="normal" title=" " image="rotate.right" catalog="system">
|
||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onVideoRotate:" destination="-1" eventType="touchUpInside" id="isy-8q-IGB"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YvU-M3-dYi">
|
||||
<rect key="frame" x="10" y="704" width="40" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.34100000000000003" blue="0.29399999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Q5M-cg-NOt"/>
|
||||
<color key="backgroundColor" red="0.1176470588" green="0.1176470588" blue="0.12941176469999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="MBM-E8-DcQ" firstAttribute="top" secondItem="Kbh-1W-XwY" secondAttribute="bottom" constant="16" id="1yJ-zP-b2C"/>
|
||||
<constraint firstItem="cDr-2L-E86" firstAttribute="centerX" secondItem="onQ-DT-Yct" secondAttribute="centerX" id="3fJ-Ut-5UD"/>
|
||||
<constraint firstItem="X9k-lT-qBv" firstAttribute="leading" secondItem="Q5M-cg-NOt" secondAttribute="leading" constant="10" id="8GX-Xg-ir5"/>
|
||||
<constraint firstItem="rzV-fr-mUd" firstAttribute="leading" secondItem="Q5M-cg-NOt" secondAttribute="leading" id="98P-U1-kNm"/>
|
||||
<constraint firstItem="onQ-DT-Yct" firstAttribute="top" secondItem="X9k-lT-qBv" secondAttribute="bottom" constant="16" id="BW3-oz-96z"/>
|
||||
<constraint firstItem="Q5M-cg-NOt" firstAttribute="trailing" secondItem="Kbh-1W-XwY" secondAttribute="trailing" constant="10" id="EaZ-v3-NGd"/>
|
||||
<constraint firstItem="onQ-DT-Yct" firstAttribute="leading" secondItem="X9k-lT-qBv" secondAttribute="leading" id="KM5-ip-GY3"/>
|
||||
<constraint firstItem="XCN-Qr-goF" firstAttribute="centerX" secondItem="Q5M-cg-NOt" secondAttribute="centerX" id="Rl1-fY-2iF"/>
|
||||
<constraint firstItem="X9k-lT-qBv" firstAttribute="top" secondItem="Kbh-1W-XwY" secondAttribute="top" id="SuC-17-FjX"/>
|
||||
<constraint firstItem="ZEJ-k9-h9o" firstAttribute="top" secondItem="MBM-E8-DcQ" secondAttribute="bottom" constant="16" id="Tmr-ve-Msw"/>
|
||||
<constraint firstItem="rzV-fr-mUd" firstAttribute="top" secondItem="Q5M-cg-NOt" secondAttribute="top" id="Ucj-DC-JKE"/>
|
||||
<constraint firstItem="Q5M-cg-NOt" firstAttribute="bottom" secondItem="XCN-Qr-goF" secondAttribute="bottom" constant="20" id="YiA-vD-j7g"/>
|
||||
<constraint firstItem="cDr-2L-E86" firstAttribute="top" secondItem="onQ-DT-Yct" secondAttribute="bottom" constant="16" id="jZv-Bd-JgO"/>
|
||||
<constraint firstItem="Q5M-cg-NOt" firstAttribute="trailing" secondItem="MBM-E8-DcQ" secondAttribute="trailing" constant="10" id="t3x-XY-dgu"/>
|
||||
<constraint firstItem="Kbh-1W-XwY" firstAttribute="top" secondItem="Q5M-cg-NOt" secondAttribute="top" constant="50" id="u7E-0d-lDq"/>
|
||||
<constraint firstItem="Q5M-cg-NOt" firstAttribute="trailing" secondItem="ZEJ-k9-h9o" secondAttribute="trailing" constant="10" id="x2T-zZ-Prk"/>
|
||||
</constraints>
|
||||
<point key="canvasLocation" x="137.68115942028987" y="123.91304347826087"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="camera.rotate" catalog="system" width="128" height="94"/>
|
||||
<image name="chevron.backward" catalog="system" width="96" height="128"/>
|
||||
<image name="ear" catalog="system" width="105" height="128"/>
|
||||
<image name="eye" catalog="system" width="128" height="81"/>
|
||||
<image name="eye.slash" catalog="system" width="128" height="86"/>
|
||||
<image name="mic" catalog="system" width="108" height="128"/>
|
||||
<image name="mic.slash" catalog="system" width="108" height="128"/>
|
||||
<image name="rotate.right" catalog="system" width="118" height="128"/>
|
||||
<image name="speaker.wave.2" catalog="system" width="128" height="90"/>
|
||||
</resources>
|
||||
</document>
|
@ -1,4 +0,0 @@
|
||||
# AVSDK
|
||||
# BeautySDK
|
||||
# ILiveSDK
|
||||
# IMSDK
|
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 224 KiB |
Before Width: | Height: | Size: 681 B |
Before Width: | Height: | Size: 894 B |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 598 B |
Before Width: | Height: | Size: 832 B |
Before Width: | Height: | Size: 654 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 2.9 KiB |