This commit is contained in:
36973899@qq.com 2021-12-21 16:35:16 +08:00
parent 3fec301539
commit 234c9d8220
81 changed files with 3878 additions and 0 deletions

View File

@ -1,2 +1,12 @@
# cordova-plugin-trtc # cordova-plugin-trtc
腾讯实时音视频(TRTC)封装cordova插件 (示例)
具体原生项目参考tencent的demo, 源码大部分从这里搬运:
[https://github.com/zhaoyang21cn/Android_TRTC](https://github.com/zhaoyang21cn/Android_TRTC)
[https://github.com/zhaoyang21cn/iOS_TRTC](https://github.com/zhaoyang21cn/iOS_TRTC)
iOS的sdk需要自己添加到src/ios/libs下, 可以从官网获取

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "cordova-plugin-trtc",
"version": "0.0.1",
"description": "",
"cordova": {
"id": "cordova-plugin-trtc",
"platforms": [
"android",
"ios"
]
},
"keywords": [
"ecosystem:cordova",
"cordova-android"
],
"author": "",
"license": "ISC"
}

291
plugin.xml Normal file
View File

@ -0,0 +1,291 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
id="cordova-plugin-trtc"
version="1.0.0">
<name>Trtc</name>
<description>TRTC Plugin</description>
<keywords>cordova,TRTC,live</keywords>
<license>MIT License</license>
<dependency id="cordova-plugin-androidx"/>
<dependency id="cordova-plugin-androidx-adapter"/>
<preference name="APP_ID" default="sdk_app_id"/>
<preference name="PERMISSION_MIC_DESC" default="应用需要访问您的麦克风"/>
<preference name="PERMISSION_CAMERA_DESC" default="应用需要访问您的摄像头"/>
<engines>
<engine name="cordova-android" version=">=4.0.0"/>
</engines>
<js-module src="www/trtc.js" name="trtc">
<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"/>
<!-- <param name="onload" value="true" /> -->
</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>
<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"/>
<resource-file src="src/ios/TrtcConfig.plist"/>
<!-- 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"/>
<!-- 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"/>
<!-- sdk -->
<framework src="src/ios/libs/AVSDK/QAVSDK.framework" custom="true"/>
<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"/>
<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"/>
<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"/>
<config-file target="*TrtcConfig.plist" parent="AppId">
<string>$APP_ID</string>
</config-file>
</platform>
<platform name="android">
<config-file target="config.xml" parent="/*">
<feature name="Trtc">
<param name="android-package" value="com.chuwa.cordova.trtc.Trtc"/>
<!-- <param name="onload" value="true" /> -->
</feature>
</config-file>
<config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- <uses-feature android:name="android.hardware.camera" />-->
<!-- <uses-feature android:name="android.hardware.camera.autofocus" />-->
</config-file>
<config-file target="res/values/strings.xml" parent="/resources">
<string name="trtc_app_id">$APP_ID</string>
<string name="str_enter_tips">腾讯视频通话</string>
<string name="str_room_title">房间名</string>
<string name="str_room_id">房间ID</string>
<string name="str_create_btn">创建房间</string>
<string name="str_switch_camera">翻转</string>
<string name="str_chat_tab">聊天</string>
<string name="str_wb_tab">白板</string>
<string name="msg_title">消息</string>
<string name="str_input_tips">输入文字内容</string>
<string name="str_beauty">美颜</string>
<string name="str_log">日志</string>
<string name="str_voice">声音</string>
<string name="str_room_id_tips">输入房间号</string>
<string name="str_enter_room_btn">进入房间</string>
<string name="str_role">配置</string>
<string name="str_feedback">反馈</string>
<string name="str_set_role">请选择要配置的分辨率</string>
<string name="str_chat">聊天</string>
<string name="str_set_problem">请选择反馈类型</string>
<string name="str_problem_other">请输入具体问题描述</string>
<string name="str_feedback_ret">非常感谢您的反馈</string>
<string name="str_login_success">登录成功</string>
</config-file>
<config-file target="AndroidManifest.xml" parent="/manifest/application">
<activity android:name="com.chuwa.cordova.trtc.CreateActivity" android:launchMode="singleTask"
android:screenOrientation="portrait">
</activity>
<activity android:name="com.chuwa.cordova.trtc.RoomActivity" android:windowSoftInputMode="adjustPan"
android:launchMode="singleInstance" android:screenOrientation="portrait">
</activity>
<activity android:name="android.java.com.example.basic.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>
<activity android:name="com.tencent.trtc.videocall.VideoCallingEnterActivity" android:windowSoftInputMode="adjustPan"
android:launchMode="singleInstance" android:screenOrientation="portrait" android:theme="@style/AppTheme">
</activity>
</config-file>
<source-file src="src/android/Trtc.java" target-dir="src/com/chuwa/cordova/trtc"/>
<source-file src="src/android/java/com/example/basic/TRTCBaseActivity.java"
target-dir="src/android/java/com/example/basic"/>
<source-file src="src/android/java/com/tencent/trtc/videocall/BuildConfig.java"
target-dir="src/com/tencent/trtc/videocall"/>
<source-file src="src/android/java/com/tencent/trtc/videocall/FloatingView.java"
target-dir="src/com/tencent/trtc/videocall"/>
<source-file src="src/android/java/com/tencent/trtc/videocall/VideoCallingActivity.java"
target-dir="src/com/tencent/trtc/videocall"/>
<source-file src="src/android/java/com/tencent/trtc/videocall/VideoCallingEnterActivity.java"
target-dir="src/com/tencent/trtc/videocall"/>
<source-file src="src/android/java/com/tencent/trtc/debug/Constant.java"
target-dir="src/com/tencent/trtc/debug"/>
<resource-file src="src/android/res/drawable/videocall_background.xml"
target="res/drawable/videocall_background.xml"/>
<resource-file src="src/android/res/drawable/common_button_bg.xml"
target="res/drawable/common_button_bg.xml"/>
<resource-file src="src/android/res/drawable/common_edit_bg.xml"
target="res/drawable/common_edit_bg.xml"/>
<resource-file src="src/android/res/drawable/common_seekbar_style.xml"
target="res/drawable/common_seekbar_style.xml"/>
<resource-file src="src/android/res/drawable/common_seekbar_thumb.xml"
target="res/drawable/common_seekbar_thumb.xml"/>
<resource-file src="src/android/res/drawable/common_selector_radio_bg.xml"
target="res/drawable/common_selector_radio_bg.xml"/>
<resource-file src="src/android/res/layout/videocall_activit_enter.xml"
target="res/layout/videocall_activit_enter.xml"/>
<resource-file src="src/android/res/layout/videocall_activity_calling.xml"
target="res/layout/videocall_activity_calling.xml"/>
<resource-file src="src/android/res/layout/videocall_popup_layout.xml"
target="res/layout/videocall_popup_layout.xml"/>
<resource-file src="src/android/res/layout/videocall_view_floating_default.xml"
target="res/layout/videocall_view_floating_default.xml"/>
<resource-file src="src/android/res/mipmap-xxhdpi/videocall_float_logo.png"
target="res/mipmap-xxhdpi/videocall_float_logo.png"/>
<resource-file src="src/android/res/mipmap-xxhdpi/videocall_home.png"
target="res/mipmap-xxhdpi/videocall_home.png"/>
<resource-file src="src/android/res/mipmap-xxhdpi/common_ic_back.png"
target="res/mipmap-xxhdpi/common_ic_back.png"/>
<resource-file src="src/android/res/mipmap-xxhdpi/common_user_portrait.png"
target="res/mipmap-xxhdpi/common_user_portrait.png"/>
<resource-file src="src/android/res/mipmap-xxhdpi/earpiece.png"
target="res/mipmap-xxhdpi/earpiece.png"/>
<resource-file src="src/android/res/mipmap-xxhdpi/mac_off.png"
target="res/mipmap-xxhdpi/mac_off.png"/>
<resource-file src="src/android/res/mipmap-xxhdpi/mac_on.png"
target="res/mipmap-xxhdpi/mac_on.png"/>
<resource-file src="src/android/res/mipmap-xxhdpi/speaker.png"
target="res/mipmap-xxhdpi/speaker.png"/>
<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/values/colors.xml"
target="res/values/colors.xml"/>
<resource-file src="src/android/res/values/strings.xml"
target="res/values/strings.xml"/>
<resource-file src="src/android/res/values/styles.xml"
target="res/values/styles.xml"/>
<resource-file src="src/android/res/values-en/strings.xml"
target="res/values-en/strings.xml"/>
<resource-file src="src/android/res/values-en/styles.xml"
target="res/values-en/styles.xml"/>
<!-- <lib-file src="src/android/libs/lib.jar" /> -->
<framework src="com.tencent.liteav:LiteAVSDK_TRTC:latest.release"/>
<framework src="androidx.appcompat:appcompat:1.1.0"/>
<framework src="androidx.constraintlayout:constraintlayout:1.1.3"/>
<framework src="com.android.support:multidex:1.0.1"/>
<framework src="com.tencent.ilivesdk:ilivesdk:1.9.3"/>
<framework src="com.tencent.ilivefilter:liteav_normal:1.1.21"/>
<framework src="com.google.android.material:material:1.3.0"/>
</platform>
</plugin>

55
src/android/Trtc.java Normal file
View File

@ -0,0 +1,55 @@
package com.chuwa.cordova.trtc;
import android.app.Activity;
import android.content.Intent;
import com.tencent.trtc.videocall.VideoCallingEnterActivity;
import com.tencent.trtc.videocall.VideoCallingActivity;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class Trtc extends CordovaPlugin {
static final String ACTION_SHOW_CREATE_PAGE = "showCreatePage"; // for test
static final String ACTION_JOIN_CHANNEL = "joinChannel";
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
Activity activity = this.cordova.getActivity();
// if (ACTION_SHOW_CREATE_PAGE.equals(action)) {
// Intent intent = new Intent(activity, VideoCallingEnterActivity.class);
// cordova.startActivityForResult(this, intent, 666);
// } else
if (ACTION_JOIN_CHANNEL.equals(action)) {
Intent intent = new Intent(activity, VideoCallingActivity.class);
JSONObject arg = args.getJSONObject(0);
String ROOM_ID = arg.getString("ROOM_ID");
String USER_ID = arg.getString("USER_ID");
int SDK_APP_ID = arg.getInt("SDK_APP_ID");
String USER_SIG = arg.getString("USER_SIG");
intent.putExtra("room_id", ROOM_ID);
intent.putExtra("user_id", USER_ID);
intent.putExtra("sdk_app_id", SDK_APP_ID);
intent.putExtra("user_sig", USER_SIG);
cordova.startActivityForResult(this, intent, 666);
}
return false;
}
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
}
}

View File

@ -0,0 +1,71 @@
package android.java.com.example.basic;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import java.util.ArrayList;
import java.util.List;
public abstract class TRTCBaseActivity extends AppCompatActivity {
protected static final int REQ_PERMISSION_CODE = 0x1000;
protected int mGrantedCount = 0;
protected abstract void onPermissionGranted();
private int getId(String idName, String type) {
return getResources().getIdentifier(idName, type, getPackageName());
}
protected boolean checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
List<String> permissions = new ArrayList<>();
if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)) {
permissions.add(Manifest.permission.CAMERA);
}
if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)) {
permissions.add(Manifest.permission.RECORD_AUDIO);
}
if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
if (permissions.size() != 0) {
ActivityCompat.requestPermissions(TRTCBaseActivity.this,
permissions.toArray(new String[0]),
REQ_PERMISSION_CODE);
return false;
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQ_PERMISSION_CODE:
for (int ret : grantResults) {
if (PackageManager.PERMISSION_GRANTED == ret) {
mGrantedCount ++;
}
}
if (mGrantedCount == permissions.length) {
onPermissionGranted();
} else {
Toast.makeText(this, getString(getId("common_please_input_roomid_and_userid", "string")), Toast.LENGTH_SHORT).show();
}
mGrantedCount = 0;
break;
default:
break;
}
}
}

View File

@ -0,0 +1,27 @@
package com.tencent.trtc.debug;
public class Constant {
public static final String ROOM_ID = "room_id";
public static final String USER_ID = "user_id";
public static final String SDK_APP_ID = "sdk_app_id";
public static final String USER_SIG = "user_sig";
public static final String ROLE_TYPE = "role_type";
public static final String CUSTOM_VIDEO = "custom_video";
// 美颜风格.三种美颜风格0 光滑 1自然 2朦胧
public static final int BEAUTY_STYLE_SMOOTH = 0;
public static final int BEAUTY_STYLE_NATURE = 1;
public static final int VIDEO_FPS = 15;
// RTC 通话场景640*36015fps550kbps
public static final int RTC_VIDEO_BITRATE = 550;
// 直播场景连麦小主播270*480, 15pfs, 400kbps
public static final int LIVE_270_480_VIDEO_BITRATE = 400;
public static final int LIVE_360_640_VIDEO_BITRATE = 800;
// 直播场景大主播默认 540*960, 15fps1200kbps
public static final int LIVE_540_960_VIDEO_BITRATE = 900;
public static final int LIVE_720_1280_VIDEO_BITRATE = 1500;
}

View File

@ -0,0 +1,280 @@
package com.tencent.trtc.debug;
import android.util.Base64;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.zip.Deflater;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* Module: GenerateTestUserSig
*
* Function: 用于生成测试用的 UserSigUserSig 是腾讯云为其云服务设计的一种安全保护签名
* 其计算方法是对 SDKAppIDUserID EXPIRETIME 进行加密加密算法为 HMAC-SHA256
*
* Attention: 请不要将如下代码发布到您的线上正式版本的 App 原因如下
*
* 本文件中的代码虽然能够正确计算出 UserSig但仅适合快速调通 SDK 的基本功能不适合线上产品
* 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解尤其是 Web 端的代码被破解的难度几乎为零
* 一旦您的密钥泄露攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量
*
* 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上然后由 App 按需向您的服务器获取实时算出的 UserSig
* 由于破解服务器的成本要高于破解客户端 App所以服务器计算的方案能够更好地保护您的加密密钥
*
* Referencehttps://cloud.tencent.com/document/product/647/17275#Server
*/
/**
* Module: GenerateTestUserSig
*
* Description: generates UserSig for testing. UserSig is a security signature designed by Tencent Cloud for its cloud services.
* It is calculated based on `SDKAppID`, `UserID`, and `EXPIRETIME` using the HMAC-SHA256 encryption algorithm.
*
* Attention: do not use the code below in your commercial app. This is because:
*
* The code may be able to calculate UserSig correctly, but it is only for quick testing of the SDKs basic features, not for commercial apps.
* `SECRETKEY` in client code can be easily decompiled and reversed, especially on web.
* Once your key is disclosed, attackers will be able to steal your Tencent Cloud traffic.
*
* The correct method is to deploy the `UserSig` calculation code and encryption key on your project server so that your app can request from your server a `UserSig` that is calculated whenever one is needed.
* Given that it is more difficult to hack a server than a client app, server-end calculation can better protect your key.
*
* Reference: https://cloud.tencent.com/document/product/647/17275#Server
*/
public class GenerateTestUserSig {
/**
* 配置为CDN发布混流的域名
*
*/
/**
* Domain name for CDN publishing and stream mixing
*/
public static final String CDN_DOMAIN_NAME = "testuser";
/**
* CDN发布功能 混流bizId
*/
/**
* `bizId` for CDN publishing and stream mixing
*/
public static final int BIZID = 111;
/**
* CDN发布功能 混流appId
*/
/**
* `appId` for CDN publishing and stream mixing
*/
public static final int APPID = 111;
/**
* 腾讯云 SDKAppId需要替换为您自己账号下的 SDKAppId
*
* 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ) 创建应用即可看到 SDKAppId
* 它是腾讯云用于区分客户的唯一标识
*/
/**
* Tencent Cloud `SDKAppID`. Set it to the `SDKAppID` of your account.
*
* You can view your `SDKAppID` after creating an application in the [TRTC console](https://console.cloud.tencent.com/rav).
* `SDKAppID` uniquely identifies a Tencent Cloud account.
*/
public static final int SDKAPPID = 1400593044;
/**
* 签名过期时间建议不要设置的过短
* <p>
* 时间单位
* 默认时间7 x 24 x 60 x 60 = 604800 = 7
*/
/**
* Signature validity period, which should not be set too short
* <p>
* Unit: second
* Default value: 604800 (7 days)
*/
private static final int EXPIRETIME = 604800;
/**
* 计算签名用的加密密钥获取步骤如下
*
* step1. 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav )如果还没有应用就创建一个
* step2. 单击应用信息并进一步找到快速上手部分
* step3. 点击复制密钥按钮复制密钥请将其拷贝并复制到如下的变量中
*
* 注意该方案仅适用于调试Demo正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上以避免加密密钥泄露导致的流量盗用
* 文档https://cloud.tencent.com/document/product/647/17275#Server
*/
/**
* Follow the steps below to obtain the key required for UserSig calculation.
*
* Step 1. Log in to the [TRTC console](https://console.cloud.tencent.com/rav), and create an application if you dont have one.
* Step 2. Find your application, click Application Info, and click the Quick Start tab.
* Step 3. Copy and paste the key to the code, as shown below.
*
* Note: this method is for testing only. Before commercial launch, please migrate the UserSig calculation code and key to your backend server to prevent key disclosure and traffic stealing.
* Reference: https://cloud.tencent.com/document/product/647/17275#Server
*/
public static final String SECRETKEY = "f814a0480cfa95a3ffffd8891844f3cd36888166dbef5347303043744f247c06";
/**
* 计算 UserSig 签名
*
* 函数内部使用 HMAC-SHA256 非对称加密算法 SDKAPPIDuserId EXPIRETIME 进行加密
*
* @note: 请不要将如下代码发布到您的线上正式版本的 App 原因如下
*
* 本文件中的代码虽然能够正确计算出 UserSig但仅适合快速调通 SDK 的基本功能不适合线上产品
* 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解尤其是 Web 端的代码被破解的难度几乎为零
* 一旦您的密钥泄露攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量
*
* 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上然后由 App 按需向您的服务器获取实时算出的 UserSig
* 由于破解服务器的成本要高于破解客户端 App所以服务器计算的方案能够更好地保护您的加密密钥
*
* 文档https://cloud.tencent.com/document/product/647/17275#Server
*/
/**
* Calculating UserSig
*
* The asymmetric encryption algorithm HMAC-SHA256 is used in the function to calculate UserSig based on `SDKAppID`, `UserID`, and `EXPIRETIME`.
*
* @note: do not use the code below in your commercial app. This is because:
*
* The code may be able to calculate UserSig correctly, but it is only for quick testing of the SDKs basic features, not for commercial apps.
* `SECRETKEY` in client code can be easily decompiled and reversed, especially on web.
* Once your key is disclosed, attackers will be able to steal your Tencent Cloud traffic.
*
* The correct method is to deploy the `UserSig` calculation code on your project server so that your app can request from your server a `UserSig` that is calculated whenever one is needed.
* Given that it is more difficult to hack a server than a client app, server-end calculation can better protect your key.
*
* Reference: https://cloud.tencent.com/document/product/647/17275#Server
*/
public static String genTestUserSig(String userId) {
return GenTLSSignature(SDKAPPID, userId, EXPIRETIME, null, SECRETKEY);
}
/**
* 生成 tls 票据
*
* @param sdkappid 应用的 appid
* @param userId 用户 id
* @param expire 有效期单位是秒
* @param userbuf 默认填写null
* @param priKeyContent 生成 tls 票据使用的私钥内容
* @return 如果出错会返回为空或者有异常打印成功返回有效的票据
*/
/**
* Generating a TLS Ticket
*
* @param sdkappid `appid` of your application
* @param userId User ID
* @param expire Validity period, in seconds
* @param userbuf `null` by default
* @param priKeyContent Private key required for generating a TLS ticket
* @return If an error occurs, an empty string will be returned or exceptions printed. If the operation succeeds, a valid ticket will be returned.
*/
private static String GenTLSSignature(long sdkappid, String userId, long expire, byte[] userbuf, String priKeyContent) {
long currTime = System.currentTimeMillis() / 1000;
JSONObject sigDoc = new JSONObject();
try {
sigDoc.put("TLS.ver", "2.0");
sigDoc.put("TLS.identifier", userId);
sigDoc.put("TLS.sdkappid", sdkappid);
sigDoc.put("TLS.expire", expire);
sigDoc.put("TLS.time", currTime);
} catch (JSONException e) {
e.printStackTrace();
}
String base64UserBuf = null;
if (null != userbuf) {
base64UserBuf = Base64.encodeToString(userbuf, Base64.NO_WRAP);
try {
sigDoc.put("TLS.userbuf", base64UserBuf);
} catch (JSONException e) {
e.printStackTrace();
}
}
String sig = hmacsha256(sdkappid, userId, currTime, expire, priKeyContent, base64UserBuf);
if (sig.length() == 0) {
return "";
}
try {
sigDoc.put("TLS.sig", sig);
} catch (JSONException e) {
e.printStackTrace();
}
Deflater compressor = new Deflater();
compressor.setInput(sigDoc.toString().getBytes(Charset.forName("UTF-8")));
compressor.finish();
byte[] compressedBytes = new byte[2048];
int compressedBytesLength = compressor.deflate(compressedBytes);
compressor.end();
return new String(base64EncodeUrl(Arrays.copyOfRange(compressedBytes, 0, compressedBytesLength)));
}
private static String hmacsha256(long sdkappid, String userId, long currTime, long expire, String priKeyContent, String base64Userbuf) {
String contentToBeSigned = "TLS.identifier:" + userId + "\n"
+ "TLS.sdkappid:" + sdkappid + "\n"
+ "TLS.time:" + currTime + "\n"
+ "TLS.expire:" + expire + "\n";
if (null != base64Userbuf) {
contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";
}
try {
byte[] byteKey = priKeyContent.getBytes("UTF-8");
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");
hmac.init(keySpec);
byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes("UTF-8"));
return new String(Base64.encode(byteSig, Base64.NO_WRAP));
} catch (UnsupportedEncodingException e) {
return "";
} catch (NoSuchAlgorithmException e) {
return "";
} catch (InvalidKeyException e) {
return "";
}
}
private static byte[] base64EncodeUrl(byte[] input) {
byte[] base64 = new String(Base64.encode(input, Base64.NO_WRAP)).getBytes();
for (int i = 0; i < base64.length; ++i)
switch (base64[i]) {
case '+':
base64[i] = '*';
break;
case '/':
base64[i] = '-';
break;
case '=':
base64[i] = '_';
break;
default:
break;
}
return base64;
}
}

View File

@ -0,0 +1,18 @@
/**
* Automatically generated file. DO NOT MODIFY
*/
package com.tencent.trtc.videocall;
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String LIBRARY_PACKAGE_NAME = "com.tencent.trtc.videocall";
/**
* @deprecated APPLICATION_ID is misleading in libraries. For the library package name use LIBRARY_PACKAGE_NAME
*/
@Deprecated
public static final String APPLICATION_ID = "com.tencent.trtc.videocall";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
}

View File

@ -0,0 +1,193 @@
package com.tencent.trtc.videocall;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.PopupWindow;
/**
* 悬浮球点击可以弹出菜单
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class FloatingView extends FrameLayout implements GestureDetector.OnGestureListener {
private Context mContext;
private WindowManager mWindowManager;
private GestureDetector mGestureDetector;
private WindowManager.LayoutParams mLayoutParams;
private float mLastX;
private float mLastY;
private PopupWindow mPopupWindow;
private long mTapOutsideTime;
private boolean mIsShowing = false;
public FloatingView(Context context) {
super(context);
this.mContext = context;
this.mGestureDetector = new GestureDetector(context, this);
}
public FloatingView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
this.mGestureDetector = new GestureDetector(context, this);
}
public FloatingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
this.mGestureDetector = new GestureDetector(context, this);
}
public FloatingView(Context context, int viewResId) {
super(context);
this.mContext = context;
View.inflate(context, viewResId, this);
this.mGestureDetector = new GestureDetector(context, this);
}
public void showView(View view) {
showView(view, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
}
public void showView(View view, int width, int height) {
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
int type = WindowManager.LayoutParams.TYPE_TOAST;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
type = WindowManager.LayoutParams.TYPE_PHONE;
}
mLayoutParams = new WindowManager.LayoutParams(type);
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLayoutParams.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
mLayoutParams.width = width;
mLayoutParams.height = height;
mLayoutParams.format = PixelFormat.TRANSLUCENT;
mWindowManager.addView(view, mLayoutParams);
}
public void hideView() {
if (null != mWindowManager) {
mWindowManager.removeViewImmediate(this);
}
mWindowManager = null;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent e) {
mLastX = e.getRawX();
mLastY = e.getRawY();
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
float nowX, nowY, tranX, tranY;
nowX = e2.getRawX();
nowY = e2.getRawY();
tranX = nowX - mLastX;
tranY = nowY - mLastY;
mLayoutParams.x += tranX;
mLayoutParams.y += tranY;
mWindowManager.updateViewLayout(this, mLayoutParams);
mLastX = nowX;
mLastY = nowY;
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
public void setPopupWindow(int id) {
mPopupWindow = new PopupWindow(this);
mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow.setTouchable(true);
mPopupWindow.setOutsideTouchable(true);
mPopupWindow.setFocusable(false);
mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
mPopupWindow.setContentView(LayoutInflater.from(getContext()).inflate(id, null));
mPopupWindow.setTouchInterceptor(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
mPopupWindow.dismiss();
mTapOutsideTime = System.currentTimeMillis();
return true;
}
return false;
}
});
}
public View getPopupView() {
return mPopupWindow.getContentView();
}
public void setOnPopupItemClickListener(OnClickListener listener) {
if (mPopupWindow == null)
return;
ViewGroup layout = (ViewGroup) mPopupWindow.getContentView();
for (int i = 0; i < layout.getChildCount(); i++) {
layout.getChildAt(i).setOnClickListener(listener);
}
}
public void show() {
if (!mIsShowing) {
showView(this);
}
mIsShowing = true;
}
public void dismiss() {
if (mIsShowing) {
hideView();
}
mIsShowing = false;
ViewGroup layout = (ViewGroup) mPopupWindow.getContentView();
for (int i = 0; i < layout.getChildCount(); i++) {
layout.getChildAt(i).setOnClickListener(null);
}
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (null != mPopupWindow)
mPopupWindow.dismiss();
if (!(System.currentTimeMillis() - mTapOutsideTime < 80)) {
mPopupWindow.showAtLocation(this, Gravity.NO_GRAVITY, 100, 0);
}
return false;
}
}

View File

@ -0,0 +1,397 @@
package com.tencent.trtc.videocall;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.java.com.example.basic.TRTCBaseActivity;
import com.tencent.liteav.TXLiteAVCode;
import com.tencent.liteav.device.TXDeviceManager;
import com.tencent.rtmp.ui.TXCloudVideoView;
import com.tencent.trtc.TRTCCloud;
import com.tencent.trtc.TRTCCloudDef;
import com.tencent.trtc.TRTCCloudListener;
import com.tencent.trtc.debug.Constant;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* TRTC视频通话的主页面
* <p>
* 包含如下简单功能
* - 进入视频通话房间{@link VideoCallingActivity#enterRoom()}
* - 退出视频通话房间{@link VideoCallingActivity#exitRoom()}
* - 切换前置/后置摄像头{@link VideoCallingActivity#switchCamera()}
* - 打开/关闭摄像头{@link VideoCallingActivity#muteVideo()}
* - 打开/关闭麦克风{@link VideoCallingActivity#muteAudio()}
* - 显示房间内其他用户的视频画面当前示例最多可显示6个其他用户的视频画面{@link TRTCCloudImplListener#refreshRemoteVideoViews()}
* <p>
* - 详见接入文档{https://cloud.tencent.com/document/product/647/42045}
*/
/**
* Video Call
*
* Features:
* - Enter a video call room: {@link VideoCallingActivity#enterRoom()}
* - Exit a video call room: {@link VideoCallingActivity#exitRoom()}
* - Switch between the front and rear cameras: {@link VideoCallingActivity#switchCamera()}
* - Turn on/off the camera: {@link VideoCallingActivity#muteVideo()}
* - Turn on/off the mic: {@link VideoCallingActivity#muteAudio()}
* - Display the video of other users (max. 6) in the room: {@link TRTCCloudImplListener#refreshRemoteVideoViews()}
*
* - For more information, please see the integration document {https://cloud.tencent.com/document/product/647/42045}.
*/
public class VideoCallingActivity extends TRTCBaseActivity implements View.OnClickListener {
private static final String TAG = "VideoCallingActivity";
private static final int OVERLAY_PERMISSION_REQ_CODE = 1234;
private TextView mTextTitle;
private TXCloudVideoView mTXCVVLocalPreviewView;
private ImageView mImageBack;
private ImageView mButtonMuteVideo;
private ImageView mButtonMuteAudio;
private ImageView mButtonSwitchCamera;
private ImageView mButtonAudioRoute;
private TRTCCloud mTRTCCloud;
private TXDeviceManager mTXDeviceManager;
private boolean mIsFrontCamera = true;
private List<String> mRemoteUidList;
private List<TXCloudVideoView> mRemoteViewList;
private int mUserCount = 0;
private String mRoomId;
private String mUserId;
private int sdkAppId;
private String userSig;
private boolean mAudioRouteFlag = true;
private FloatingView mFloatingView;
private int getId(String idName, String type) {
return getResources().getIdentifier(idName, type, getPackageName());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// R.layout.videocall_activity_calling
setContentView(getId("videocall_activity_calling", "layout"));
getSupportActionBar().hide();
handleIntent();
if (checkPermission()) {
initView();
enterRoom();
}
}
private void handleIntent() {
Intent intent = getIntent();
if (null != intent) {
if (intent.getStringExtra(Constant.USER_ID) != null) {
mUserId = intent.getStringExtra(Constant.USER_ID);
}
if (intent.getStringExtra(Constant.ROOM_ID) != null) {
mRoomId = intent.getStringExtra(Constant.ROOM_ID);
}
if (intent.getIntExtra(Constant.SDK_APP_ID, 0) != 0) {
sdkAppId = intent.getIntExtra(Constant.SDK_APP_ID, 0);
}
if (intent.getStringExtra(Constant.USER_SIG) != null) {
userSig = intent.getStringExtra(Constant.USER_SIG);
}
}
System.out.println("sdkAppId = " + sdkAppId);
System.out.println("userSig = " + userSig);
}
private void initView() {
// R.id.tv_room_number
mTextTitle = findViewById(getId("tv_room_number", "id"));
// R.id.iv_back
mImageBack = findViewById(getId("iv_back", "id"));
// R.id.txcvv_main
mTXCVVLocalPreviewView = findViewById(getId("txcvv_main", "id"));
// R.id.btn_mute_video
mButtonMuteVideo = findViewById(getId("btn_mute_video", "id"));
// R.id.btn_mute_audio
mButtonMuteAudio = findViewById(getId("btn_mute_audio", "id"));
// R.id.btn_switch_camera
mButtonSwitchCamera = findViewById(getId("btn_switch_camera", "id"));
// R.id.btn_audio_route
mButtonAudioRoute = findViewById(getId("btn_audio_route", "id"));
if (!TextUtils.isEmpty(mRoomId)) {
mTextTitle.setText(getString(getId("videocall_roomid", "string")) + mRoomId);
}
mImageBack.setOnClickListener(this);
mButtonMuteVideo.setOnClickListener(this);
mButtonMuteAudio.setOnClickListener(this);
mButtonSwitchCamera.setOnClickListener(this);
mButtonAudioRoute.setOnClickListener(this);
mRemoteUidList = new ArrayList<>();
mRemoteViewList = new ArrayList<>();
// R.id.trtc_view_1
mRemoteViewList.add((TXCloudVideoView) findViewById(getId("trtc_view_1", "id")));
mRemoteViewList.add((TXCloudVideoView) findViewById(getId("trtc_view_2", "id")));
mRemoteViewList.add((TXCloudVideoView) findViewById(getId("trtc_view_3", "id")));
mRemoteViewList.add((TXCloudVideoView) findViewById(getId("trtc_view_4", "id")));
mRemoteViewList.add((TXCloudVideoView) findViewById(getId("trtc_view_5", "id")));
mRemoteViewList.add((TXCloudVideoView) findViewById(getId("trtc_view_6", "id")));
// R.layout.videocall_view_floating_default
mFloatingView = new FloatingView(getApplicationContext(), getId("videocall_view_floating_default", "layout"));
// R.layout.videocall_popup_layout
mFloatingView.setPopupWindow(getId("videocall_popup_layout", "layout"));
mFloatingView.setOnPopupItemClickListener(this);
}
private void enterRoom() {
mTRTCCloud = TRTCCloud.sharedInstance(getApplicationContext());
mTRTCCloud.setListener(new TRTCCloudImplListener(VideoCallingActivity.this));
mTXDeviceManager = mTRTCCloud.getDeviceManager();
TRTCCloudDef.TRTCParams trtcParams = new TRTCCloudDef.TRTCParams();
trtcParams.sdkAppId = this.sdkAppId;
trtcParams.userId = mUserId;
trtcParams.roomId = Integer.parseInt(mRoomId);
trtcParams.userSig = this.userSig;
mTRTCCloud.startLocalPreview(mIsFrontCamera, mTXCVVLocalPreviewView);
mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_SPEECH);
mTRTCCloud.enterRoom(trtcParams, TRTCCloudDef.TRTC_APP_SCENE_VIDEOCALL);
}
@Override
protected void onStop() {
super.onStop();
requestDrawOverLays();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mFloatingView != null && mFloatingView.isShown()) {
mFloatingView.dismiss();
}
exitRoom();
}
private void exitRoom() {
if (mTRTCCloud != null) {
mTRTCCloud.stopLocalAudio();
mTRTCCloud.stopLocalPreview();
mTRTCCloud.exitRoom();
mTRTCCloud.setListener(null);
}
mTRTCCloud = null;
TRTCCloud.destroySharedInstance();
}
@Override
protected void onResume() {
super.onResume();
if (mFloatingView != null && mFloatingView.isShown()) {
mFloatingView.dismiss();
}
}
@Override
protected void onPermissionGranted() {
initView();
enterRoom();
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == getId("iv_back", "id")) {
finish();
} else if (id == getId("btn_mute_video", "id")) {
muteVideo();
} else if (id == getId("btn_mute_audio", "id")) {
muteAudio();
} else if (id == getId("btn_switch_camera", "id")) {
switchCamera();
} else if (id == getId("btn_audio_route", "id")) {
audioRoute();
} else if (id == getId("iv_return", "id")) {
floatViewClick();
}
}
private void floatViewClick() {
Intent intent = new Intent(this, VideoCallingActivity.class);
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
try {
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
private void muteVideo() {
boolean isSelected = mButtonMuteVideo.isSelected();
if (!isSelected) {
mButtonMuteVideo.setImageDrawable(getDrawable(getId("view_open", "mipmap")));
closeRemoteVideoViews();
} else {
mButtonMuteVideo.setImageDrawable(getDrawable(getId("view_close", "mipmap")));
refreshRemoteVideoViews();
}
mButtonMuteVideo.setSelected(!isSelected);
}
private void closeRemoteVideoViews() {
for (int i = 0; i < mRemoteViewList.size(); i++) {
if (i < mRemoteUidList.size()) {
mRemoteViewList.get(i).setVisibility(View.INVISIBLE);
} else {
mRemoteViewList.get(i).setVisibility(View.GONE);
}
}
}
private void refreshRemoteVideoViews() {
for (int i = 0; i < mRemoteViewList.size(); i++) {
if (i < mRemoteUidList.size()) {
String remoteUid = mRemoteUidList.get(i);
mRemoteViewList.get(i).setVisibility(View.VISIBLE);
} else {
mRemoteViewList.get(i).setVisibility(View.GONE);
}
}
}
private void muteAudio() {
boolean isSelected = mButtonMuteAudio.isSelected();
if (!isSelected) {
mTRTCCloud.muteLocalAudio(true);
mButtonMuteAudio.setImageDrawable(getDrawable(getId("mac_on", "mipmap")));
} else {
mTRTCCloud.muteLocalAudio(false);
mButtonMuteAudio.setImageDrawable(getDrawable(getId("mac_off", "mipmap")));
}
mButtonMuteAudio.setSelected(!isSelected);
}
private void switchCamera() {
mIsFrontCamera = !mIsFrontCamera;
mTXDeviceManager.switchCamera(mIsFrontCamera);
}
private void audioRoute() {
if (mAudioRouteFlag) {
mAudioRouteFlag = false;
mTXDeviceManager.setAudioRoute(TXDeviceManager.TXAudioRoute.TXAudioRouteEarpiece);
mButtonAudioRoute.setImageDrawable(getDrawable(getId("earpiece", "mipmap")));
} else {
mAudioRouteFlag = true;
mTXDeviceManager.setAudioRoute(TXDeviceManager.TXAudioRoute.TXAudioRouteSpeakerphone);
mButtonAudioRoute.setImageDrawable(getDrawable(getId("speaker", "mipmap")));
}
}
private class TRTCCloudImplListener extends TRTCCloudListener {
private WeakReference<VideoCallingActivity> mContext;
public TRTCCloudImplListener(VideoCallingActivity activity) {
super();
mContext = new WeakReference<>(activity);
}
@Override
public void onUserVideoAvailable(String userId, boolean available) {
Log.d(TAG, "onUserVideoAvailable userId " + userId + ", mUserCount " + mUserCount + ",available " + available);
int index = mRemoteUidList.indexOf(userId);
if (available) {
if (index != -1) {
return;
}
mRemoteUidList.add(userId);
refreshRemoteVideoViews();
} else {
if (index == -1) {
return;
}
mTRTCCloud.stopRemoteView(userId);
mRemoteUidList.remove(index);
refreshRemoteVideoViews();
}
}
private void refreshRemoteVideoViews() {
for (int i = 0; i < mRemoteViewList.size(); i++) {
if (i < mRemoteUidList.size()) {
String remoteUid = mRemoteUidList.get(i);
mRemoteViewList.get(i).setVisibility(View.VISIBLE);
mTRTCCloud.startRemoteView(remoteUid, TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SMALL, mRemoteViewList.get(i));
} else {
mRemoteViewList.get(i).setVisibility(View.GONE);
}
}
}
@Override
public void onError(int errCode, String errMsg, Bundle extraInfo) {
Log.d(TAG, "sdk callback onError");
VideoCallingActivity activity = mContext.get();
if (activity != null) {
Toast.makeText(activity, "onError: " + errMsg + "[" + errCode + "]", Toast.LENGTH_SHORT).show();
finish();
}
}
@Override
public void onExitRoom(int reason) {
super.onExitRoom(reason);
if (reason == 2) {
VideoCallingActivity activity = mContext.get();
Toast.makeText(activity, "远程协助已结束", Toast.LENGTH_LONG).show();
}
finish();
}
}
public void requestDrawOverLays() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N && !Settings.canDrawOverlays(VideoCallingActivity.this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + VideoCallingActivity.this.getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
} else {
showFloatingView();
}
}
private void showFloatingView() {
if (mFloatingView != null && !mFloatingView.isShown()) {
if ((null != mTRTCCloud)) {
mFloatingView.show();
mFloatingView.setOnPopupItemClickListener(this);
}
}
}
}

View File

@ -0,0 +1,93 @@
package com.tencent.trtc.videocall;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.tencent.trtc.debug.Constant;
/**
* TRTC视频通话的入口页面可以设置房间id和用户id
*
* - 可跳转TRTC视频通话页面{@link VideoCallingActivity}
*/
/**
* Video Call Entrance View (set room ID and user ID)
*
* - Direct to the video call view: {@link VideoCallingActivity}
*/
public class VideoCallingEnterActivity extends AppCompatActivity {
private EditText mEditInputUserId;
private EditText mEditInputRoomId;
private int getId(String idName, String type) {
return getResources().getIdentifier(idName, type, getPackageName());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.videocall_activit_enter);
setContentView(getId("videocall_activit_enter", "layout"));
getSupportActionBar().hide();
// mEditInputUserId = findViewById(R.id.et_input_username);
// mEditInputRoomId = findViewById(R.id.et_input_room_id);
mEditInputUserId = findViewById(getId("et_input_username", "id"));
mEditInputRoomId = findViewById(getId("et_input_room_id", "id"));
// R.id.btn_enter_room
findViewById(getId("btn_enter_room", "id")).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startEnterRoom();
}
});
// R.id.rl_entrance_main
findViewById(getId("rl_entrance_main", "id")).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
hideInput();
}
});
// R.id.iv_back
findViewById(getId("iv_back", "id")).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
mEditInputRoomId.setText("1256732");
String time = String.valueOf(System.currentTimeMillis());
String userId = time.substring(time.length() - 8);
mEditInputUserId.setText(userId);
}
private void startEnterRoom() {
if (TextUtils.isEmpty(mEditInputUserId.getText().toString().trim())
|| TextUtils.isEmpty(mEditInputRoomId.getText().toString().trim())) {
// R.string.videocall_room_input_error_tip
Toast.makeText(VideoCallingEnterActivity.this, getString(getId("videocall_room_input_error_tip", "string")), Toast.LENGTH_LONG).show();
return;
}
Intent intent = new Intent(VideoCallingEnterActivity.this, VideoCallingActivity.class);
intent.putExtra(Constant.ROOM_ID, mEditInputRoomId.getText().toString().trim());
intent.putExtra(Constant.USER_ID, mEditInputUserId.getText().toString().trim());
startActivity(intent);
}
protected void hideInput() {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
View v = getWindow().peekDecorView();
if (null != v) {
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
}
}

View File

@ -0,0 +1,10 @@
dependencies {
compile fileTree(dir: "libs", include: ["*.jar"])
compile 'androidx.appcompat:appcompat:1.1.0'
compile 'androidx.constraintlayout:constraintlayout:1.1.3'
compile 'com.android.support:multidex:1.0.1'
compile 'com.tencent.ilivesdk:ilivesdk:1.9.3'
compile 'com.tencent.ilivefilter:liteav_normal:1.1.21'
compile 'com.google.android.material:material:1.3.0'
compile 'com.tencent.liteav:LiteAVSDK_TRTC:latest.release'
}

BIN
src/android/liteavsdk.jar Normal file

Binary file not shown.

View File

@ -0,0 +1,11 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/common_green_bg" />
<corners
android:bottomLeftRadius="5dip"
android:bottomRightRadius="5dip"
android:topLeftRadius="5dip"
android:topRightRadius="5dip" />
</shape>

View File

@ -0,0 +1,16 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/common_edit_solid" />
<corners
android:bottomLeftRadius="2dip"
android:bottomRightRadius="2dip"
android:topLeftRadius="2dip"
android:topRightRadius="2dip" />
<stroke
android:width="5px"
android:color="@color/common_edit_stroke" />
</shape>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="1dp" />
<solid android:color="#999" />
<size android:height="2dp" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="1dp" />
<solid android:color="#999" />
<size android:height="5dp"/>
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="5dip" />
<solid android:color="#999" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<size android:width="20dp"
android:height="20dp"/>
<solid android:color="@android:color/white"/>
<corners android:radius="10dp"/>
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true"
android:color="@color/common_radio_select"/>
<item android:state_checked="false"
android:color="@color/common_raido_select_off"/>
</selector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#6f000000" />
<corners android:radius="40dp" />
</shape>

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/rl_entrance_main"
android:background="@color/rl_main_bg">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="15dp"
android:background="@mipmap/common_ic_back" />
<TextView
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="15dp"
android:gravity="center_horizontal"
android:text="@string/videocall_title"
android:textColor="@android:color/white"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_room_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="100dp"
android:layout_marginRight="60dp"
android:layout_marginLeft="60dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:text="@string/videocall_please_input_roomid" />
<EditText
android:id="@+id/et_input_room_id"
android:layout_width="match_parent"
android:layout_height="34dp"
android:background="@drawable/common_edit_bg"
android:layout_marginTop="5dp"
android:inputType="number" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/ll_room_info"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:layout_marginLeft="60dp"
android:layout_marginRight="60dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:text="@string/videocall_please_input_userid" />
<EditText
android:id="@+id/et_input_username"
android:layout_width="match_parent"
android:layout_height="34dp"
android:layout_marginTop="5dp"
android:background="@drawable/common_edit_bg"/>
</LinearLayout>
<Button
android:id="@+id/btn_enter_room"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/common_button_bg"
android:textColor="@android:color/white"
android:layout_margin="60dp"
android:text="@string/videocall_enter_room" />
</RelativeLayout>

View File

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/rl_main_bg">
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/txcvv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="72dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="15dp"
android:background="@mipmap/common_ic_back" />
<TextView
android:id="@+id/tv_room_number"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="15dp"
android:gravity="center_horizontal"
android:text="1111"
android:textColor="@android:color/white"
android:textSize="20sp"
android:visibility="invisible" />
</LinearLayout>
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/trtc_view_1"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_alignParentRight="true"
android:layout_marginTop="70dp"
android:layout_marginRight="15dp"/>
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/trtc_view_2"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_below="@id/trtc_view_1"
android:layout_alignParentRight="true"
android:layout_marginRight="15dp"
android:layout_marginTop="10dp"/>
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/trtc_view_3"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_below="@id/trtc_view_2"
android:layout_alignParentRight="true"
android:layout_marginRight="15dp"
android:layout_marginTop="10dp"/>
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/trtc_view_4"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_alignParentLeft="true"
android:layout_alignTop="@+id/trtc_view_3"
android:layout_marginLeft="15dp"/>
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/trtc_view_5"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_alignParentLeft="true"
android:layout_alignTop="@+id/trtc_view_2"
android:layout_marginLeft="15dp"/>
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/trtc_view_6"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_alignParentLeft="true"
android:layout_alignTop="@+id/trtc_view_1"
android:layout_marginLeft="15dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="20dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_controller"
android:layout_width="300dp"
android:layout_height="match_parent"
android:orientation="horizontal"
android:visibility="visible">
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:visibility="visible">
<ImageView
android:id="@+id/btn_mute_audio"
android:layout_width="40dp"
android:layout_height="20dp"
android:src="@mipmap/mac_off"
android:layout_weight="1" />
<ImageView
android:id="@+id/btn_audio_route"
android:layout_width="40dp"
android:layout_height="20dp"
android:src="@mipmap/speaker"
android:layout_weight="1" />
<ImageView
android:id="@+id/btn_switch_camera"
android:layout_width="40dp"
android:layout_height="20dp"
android:src="@mipmap/swap_camera"
android:layout_weight="1" />
<ImageView
android:id="@+id/btn_mute_video"
android:layout_width="40dp"
android:layout_height="20dp"
android:src="@mipmap/view_close"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:background="@drawable/videocall_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_return"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:padding="2dp"
android:src="@mipmap/videocall_home"
android:layout_width="25dp"
android:layout_height="25dp" />
</LinearLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="200dp"
android:layout_height="wrap_content">
<ImageView
android:src="@mipmap/videocall_float_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -0,0 +1,55 @@
<resources>
<string name="app_name">TRTC-API-Example</string>
<string name="activity_name">test</string>
<string name="launcher_name">testtest</string>
<string name="main_trtc_title">TRTC API Example</string>
<string name="common_please_input_roomid_and_userid">Failed to join call as the user did not grant the required permission.</string>
<string name="main_trtc_base_funciton">Basic Features</string>
<string name="main_item_aduio_call">Audio Call</string>
<string name="main_item_aduio_call_desc">One-to-one/Group audio call, which supports muting, hands-free mode, etc.</string>
<string name="main_item_video_call">Video Call</string>
<string name="main_item_video_call_desc">One-to-one/Group video call, which supports muting, hands-free mode, etc.</string>
<string name="main_item_live">Interactive Live Video Streaming</string>
<string name="main_item_voice_chat_room">Interactive Live Audio Streaming</string>
<string name="main_item_screen_share">Screen Recording Live Streaming</string>
<string name="main_item_screen_share_desc">Share the screen during live streaming, designed for online education, game streaming, etc.</string>
<string name="main_trtc_advanced">Advanced Features</string>
<string name="main_item_string_room_id">String-type Room ID</string>
<string name="main_rtrc_set_video_quality">Video Quality Setting</string>
<string name="main_rtrc_set_audio_quality">Audio Quality Setting</string>
<string name="main_rerc_render_params">Render Control</string>
<string name="main_item_speed_test">Network Speed Testing</string>
<string name="main_item_pushcdn">Publish via CDN</string>
<string name="main_trtc_custom_camera">Custom Video Capturing &amp; Rendering</string>
<string name="main_rtrc_set_audio_effect">Audio Effect Setting</string>
<string name="main_trtc_set_bgm">Background Music Setting</string>
<string name="main_trtc_local_video_share">Local Video Sharing</string>
<string name="main_item_local_record">Local Recording</string>
<string name="main_item_join_multiple_room">Enter Multiple Rooms</string>
<string name="main_item_sei_message">Receive/Send SEI Message</string>
<string name="main_item_switch_room">Switch Room</string>
<string name="main_trtc_connect_other_room_pk">Cross-room Competition</string>
<string name="main_item_third_beauty">Third-party Beauty Filter</string>
<string name="videocall_title">Video Call</string>
<string name="videocall_room_input_error_tip">Enter a room ID and username</string>
<string name="videocall_video_item">Video Settings</string>
<string name="videocall_audio_item">Audio Settings</string>
<string name="videocall_user_back_camera">Use Rear Camera</string>
<string name="videocall_user_front_camera">Use Front Camera</string>
<string name="videocall_open_camera">Camera On</string>
<string name="videocall_close_camera">Camera Off</string>
<string name="videocall_mute_audio">Mic On</string>
<string name="videocall_close_mute_audio">Mic Off</string>
<string name="videocall_use_speaker">Use Speaker</string>
<string name="videocall_use_receiver">Use Receiver</string>
<string name="videocall_enter_room">Enter Room</string>
<string name="videocall_please_input_userid">User ID (Required)</string>
<string name="videocall_please_input_roomid">Room ID (Required)</string>
<string name="videocall_roomid">Room ID: </string>
</resources>

View File

@ -0,0 +1,13 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="rl_main_bg">#1B1B1B</color>
<color name="videocall_green_bg">#00A66B</color>
<color name="videocall_title_bg">#B7B7B7</color>
<color name="common_green_bg">#34c759</color>
<color name="common_edit_solid">#EFEFEF</color>
<color name="common_edit_stroke">#FFFFFF</color>
<color name="common_radio_select">#34c759</color>
<color name="common_raido_select_off">#999999</color>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>

View File

@ -0,0 +1,55 @@
<resources>
<string name="app_name">TRTC-API-Example</string>
<string name="activity_name">测试</string>
<string name="launcher_name">测试测试</string>
<string name="main_trtc_title">腾讯云TRTC API Example</string>
<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>
<string name="main_item_video_call">视频通话</string>
<string name="main_item_video_call_desc">双人/多人视频通话、包含静音/免提等功能</string>
<string name="main_item_live">视频互动直播</string>
<string name="main_item_voice_chat_room">语音互动直播</string>
<string name="main_item_screen_share">录屏直播</string>
<string name="main_item_screen_share_desc">直播过程中分享屏幕,适用于在线教育,游戏直播等场景</string>
<string name="main_trtc_advanced">进阶功能</string>
<string name="main_item_string_room_id">字符串房间号</string>
<string name="main_rtrc_set_video_quality">画质设定</string>
<string name="main_rtrc_set_audio_quality">音质设定</string>
<string name="main_rerc_render_params">渲染控制</string>
<string name="main_item_speed_test">网络测速</string>
<string name="main_item_pushcdn">CDN发布</string>
<string name="main_trtc_custom_camera">自定义视频采集&amp;渲染</string>
<string name="main_rtrc_set_audio_effect">设置音效</string>
<string name="main_trtc_set_bgm">设置背景音乐</string>
<string name="main_trtc_local_video_share">本地视频分享</string>
<string name="main_item_local_record">本地媒体录制</string>
<string name="main_item_join_multiple_room">加入多个房间</string>
<string name="main_item_sei_message">收发SEI消息</string>
<string name="main_item_switch_room">快速切换房间</string>
<string name="main_trtc_connect_other_room_pk">跨房PK</string>
<string name="main_item_third_beauty">第三方美颜</string>
<string name="videocall_title">TRTC 视频通话示例</string>
<string name="videocall_room_input_error_tip">"房间号和用户名不能为空"</string>
<string name="videocall_video_item">视频选项</string>
<string name="videocall_audio_item">音频选项</string>
<string name="videocall_user_back_camera">使用后置摄像头</string>
<string name="videocall_user_front_camera">使用前置摄像头</string>
<string name="videocall_open_camera">打开摄像头</string>
<string name="videocall_close_camera">关闭摄像头</string>
<string name="videocall_mute_audio">打开麦克风</string>
<string name="videocall_close_mute_audio">关闭麦克风</string>
<string name="videocall_use_speaker">使用扬声器</string>
<string name="videocall_use_receiver">使用听筒</string>
<string name="videocall_enter_room">进入房间</string>
<string name="videocall_please_input_userid">请输入用户名(必填项)</string>
<string name="videocall_please_input_roomid">请输入房间号(必填项)</string>
<string name="videocall_roomid">房间号:</string>
</resources>

View File

@ -0,0 +1,13 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>

73
src/ios/Trtc/Info.plist Normal file
View File

@ -0,0 +1,73 @@
<?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>

View File

@ -0,0 +1,15 @@
//
// 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 */

View File

@ -0,0 +1,17 @@
//
// 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

View File

@ -0,0 +1,112 @@
//
// 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

View File

@ -0,0 +1,21 @@
//
// 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

View File

@ -0,0 +1,98 @@
//
// 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

View File

@ -0,0 +1,17 @@
//
// 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

View File

@ -0,0 +1,177 @@
//
// 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

View File

@ -0,0 +1,14 @@
//
// 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

View File

@ -0,0 +1,59 @@
//
// 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

View File

@ -0,0 +1,15 @@
//
// 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

View File

@ -0,0 +1,275 @@
//
// 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

View File

@ -0,0 +1,13 @@
//
// 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

View File

@ -0,0 +1,25 @@
//
// 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

View File

@ -0,0 +1,20 @@
//
// 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

View File

@ -0,0 +1,96 @@
//
// 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

View File

@ -0,0 +1,34 @@
//
// 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

View File

@ -0,0 +1,298 @@
//
// 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

View File

@ -0,0 +1,42 @@
//
// 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

View File

@ -0,0 +1,403 @@
//
// 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;
}
//(frameframes)
- (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

10
src/ios/TrtcConfig.plist Normal file
View File

@ -0,0 +1,10 @@
<?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>AppId</key>
<string></string>
<key>IsDebug</key>
<true/>
</dict>
</plist>

13
src/ios/TrtcPlugin.h Normal file
View File

@ -0,0 +1,13 @@
//
// TrtcPlugin.h
//
// Created by 布丁丸子酱 on 2018/12/26.
//
#import <Cordova/CDVPlugin.h>
@interface TrtcPlugin : CDVPlugin
- (void) showCreatePage:(CDVInvokedUrlCommand*)command;
@end

49
src/ios/TrtcPlugin.m Normal file
View File

@ -0,0 +1,49 @@
//
// 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>
@interface TrtcPlugin()
{}
@end
@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];
}];
}
});
}
}];
TCLiveJoinRoomViewController *vc = [TCLiveJoinRoomViewController new];
// vc.defaultRoomId = self.defaultRoomId;
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
[self.viewController presentViewController:nav animated:YES completion:nil];
}
@end

4
src/ios/libs/.gitkeep Normal file
View File

@ -0,0 +1,4 @@
# AVSDK
# BeautySDK
# ILiveSDK
# IMSDK

BIN
src/ios/res/beauty-dis.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/ios/res/beauty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
src/ios/res/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
src/ios/res/camera-gray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

BIN
src/ios/res/camera.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

BIN
src/ios/res/chat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
src/ios/res/doubleroom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/ios/res/feedback.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

BIN
src/ios/res/log.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

BIN
src/ios/res/log2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

BIN
src/ios/res/mic-dis.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/ios/res/mic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
src/ios/res/role.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

13
www/trtc.js Normal file
View File

@ -0,0 +1,13 @@
var exec = require('cordova/exec');
function Trtc() {}
Trtc.prototype.joinChannel = function(arg, success, error) {
exec(success, error, 'Trtc', 'joinChannel', [arg]);
};
Trtc.prototype.showCreatePage = function(success, error) {
exec(success, error, 'Trtc', 'showCreatePage', []);
}
module.exports = new Trtc();