You can download and install the demo we provide to try out TRTC’s duet features, including low-latency duet, gift sending and receiving, text chat, etc.
Room Owner | Listener |
---|---|
![]() |
![]() |
To quickly enable the duet feature, you can modify the demo app we provide and adapt it to your needs. You may also use the TUIChorus
component and customize your own UI.
TestChorus
and click Create.Note:This feature uses two basic PaaS services of Tencent Cloud, namely TRTC and IM. When you activate TRTC, IM will be activated automatically. IM is a value-added service. See Pricing for its billing details.
Click TUIChorus to clone or download the source code.
In the Modify Configuration step, select your platform.
Find and open TUIChorus/Debug/GenerateTestUserSig.swift
.
Set the following parameters in GenerateTestUserSig.swift
:
Click Next to complete the creation.
After compilation, click Return to Overview Page.
Note:
- The method for generating
UserSig
described in this document involves configuringSECRETKEY
in client code. In this method,SECRETKEY
may be easily decompiled and reversed, and if your key is disclosed, attackers can steal your Tencent Cloud traffic. Therefore, this method is suitable only for the local execution and debugging of the app.- The correct
UserSig
distribution method is to integrate the calculation code ofUserSig
into your server and provide an application-oriented API. WhenUserSig
is needed, your application can send a request to the business server for a dynamicUserSig
. For more information, see How do I calculate UserSig on the server?.
Open TUIChorus/TUIChorusApp.xcworkspace
with Xcode (11.0 or above) and click the run button.
The Source
folder in the source code contains two subfolders: ui
and model
. The ui
subfolder contains UI code and UI-related logic. The table below lists the Swift files (folders) and the UI views they represent. You can refer to it when making UI changes.
File or Folder | Description |
---|---|
TRTCChorusEnteryControl.swift | The initialization method for all view controllers. You can use the instance to quickly get a ViewController object. |
TRTCCreateChorusViewController | Logic for the room creation view |
TRTCChorusViewController | Main room views for room owner and listener |
Each TRTC'XXXX'ViewController
folder contains ViewController
, RootView
, and ViewModel
, whose use is described below.
File | Description |
---|---|
ViewController.swift | Page controller, which is responsible for routing pages and binding RootView and ViewModel |
RootView.swift | Layout of all views |
ViewModel.swift | View controller, which is responsible for responding to users’ interactions with views and returning response status |
Note:You need at least two devices to try out the application.
Note:You can find the room ID at the top of user A’s room view.
The Source
folder in the source code contains two subfolders: ui
and model
. The model
subfolder contains the reusable open-source component TRTCChorusRoom
. You can find the component's APIs in TRTCChorusRoom.h
and use them to customize your own UI.
The TRTCChorusRoom
component depends on the TRTC SDK and IM SDK. Follow the steps below to integrate them into your project.
pod 'TXIMSDK_iOS'
pod 'TXLiteAVSDK_TRTC'
SDK | Download Page | Integration Guide |
---|---|---|
TRTC SDK | Download | Integration Documentation |
IM SDK | Download | Integration Documentation |
In info.plist
, add Privacy > Microphone Usage Description
to request mic access.
TUIChorus
componentImport the component using CocoaPods. See below for detailed directions.
Source
, Resources
, and TXAppBasic
folders and the TUIChorus.podspec
file to your project directory.Podfile
and run pod install
to complete the import.pod 'TXAppBasic', :path => "TXAppBasic/"
pod 'TXLiteAVSDK_TRTC'
pod 'TUIChorus', :path => "./", :subspecs => ["TRTC"]
sharedInstance
of TRTCChorusRoom
to create an instance that complies with the protocol of TRTCChorusRoom
, or call shared
to get a TRTCChorusRoom
instance. There is no difference between the two methods with respect to API usage.setDelegate
function to register event callbacks of the component.login
API to log in to the component, and set the key parameters as described below.Parameter | Description |
---|---|
SDKAppID | You can view `SDKAppID` in the TRTC console. |
userId | ID of the current user, which is a string that can contain letters (a-z and A-Z), digits (0-9), hyphens (-), and underscores (_). We recommend you set it based on your business account system. |
userSig | Tencent Cloud's proprietary security signature. To obtain one, see UserSig. |
callback | Login callback. The code is `0` if login is successful. |
// Swift example
// The class responsible for business logic in your code
class YourController {
// Calculate attributes to get a singleton object.
var chorusRoom: TRTCChorusRoom {
return TRTCChorusRoom.shared()
}
// Other code logic
......
}
// Set a delegate
self.chorusRoom.setDelegate(delegate: chorusRoomDelegate)
// Below is the calling method. We recommend that you use `weak self` in the closure to prevent circular references. The weak self part is not included in the sample code below.
self.chorusRoom.login(sdkAppId: sdkAppID, userId: userId, userSig: userSig) { [weak self] (code, message) in
guard let `self` = self else { return }
// Your callback business logic
}
setSelfProfile
to set your nickname and profile photo.createRoom
to create a room, passing in room attributes such as room ID and whether listeners need the room owner’s consent to speak.enterSeat
to take seat No. 1.onSeatListChanget
notification about the change of the seat list, and can update the change to the UI.onAnchorEnterSeat
notification about the occupation of a seat, and mic capturing will be enabled automatically.Sample code:
// 1. Set your nickname and profile photo.
self.chorusRoom.setSelfProfile(userName: userName, avatarUrl: avatarURL) { (code, message) in
// Callback of the result
}
// 2. Create a room.
let param = RoomParam.init()
param.roomName = "Room name"
param.needRequest = true // Whether your consent is required for listeners to speak
param.coverUrl = "Cover URL"
param.seatCount = 2 // Number of seats in the room. Set it to `2`, one for the room owner and the other for the co-singer.
param.seatInfoList = []
for _ in 0..< param.seatCount {
let seatInfo = SeatInfo.init()
param.seatInfoList.append(seatInfo)
}
self.chorusRoom.createRoom(roomID: yourRoomID, roomParam: param) { (code, message) in
guard code == 0 else { return }
// Room created successfully
// 3. Take a seat
self.chorusRoom.enterSeat(seatIndex: 0) { [weak self] (code, message) in
guard let `self` = self else { return }
if code == 0 {
// Seat taken successfully
} else {
// Failed to take a seat
}
}
}
// 4. After taking a seat, you receive an `onSeatListChange` notification
func onSeatListChange(seatInfoList: [SeatInfo]) {
// Refresh your seat list
}
// 5. You receive an `onAnchorEnterSeat` notification
func onAnchorEnterSeat(index: Int, user: UserInfo) {
// Handle the seat taking event
}
setSelfProfile
to set your nickname and profile photo.Note:The room list in the demo app is for demonstration only. The business logic of room lists varies significantly. Tencent Cloud does not provide room list management services currently. Please manage the list by yourself.
getRoomInfoList
to get short descriptions of the rooms, which are provided by room owners when they call createRoom
.
Note:If your room list already contains enough room information, you can skip the step of calling
getRoomInfoList
.
enterRoom
with the room ID passed in to enter.onRoomInfoChange
notification about room change from the component. Record the room information, including room name, whether the room owner’s consent is required for listeners to speak, etc., and update it to the UI.onSeatListChange
notification about seat change from the component. Update the change to the UI.onAnchorEnterSeat
notification about the occupation of a seat.// 1. Set your nickname and profile photo.
self.chorusRoom.setSelfProfile(userName: userName, avatarUrl: avatarURL) { (code, message) in
// Callback of the result
}
// 2. Get the room list from the backend. Suppose it is `roomList`.
let roomList: [Int] = getRoomIDList() // The function you use to get the list of room IDs
// 3. Call `getRoomInfoList` to get details of the rooms.
self.chorusRoom.getRoomInfoList(roomIdList: roomIdsInt) { (code, message, roomInfos: [RoomInfo]) in
// Refresh the UI after getting the result.
}
// 4. Select a room, passing in `roomId` to enter
self.chorusRoom.enterRoom(roomID: roomInfo.roomID) { (code, message) in
// Callback of the room entry result
if code == 0 {
// Entered room
}
}
// 5. After successful room entry, you receive an `onRoomInfoChange` notification.
func onRoomInfoChange(roomInfo: RoomInfo) {
// Update the room name and other information.
}
// 6. You receive an `onSeatListChange` notification
func onSeatListChange(seatInfoList: [SeatInfo]) {
// Refresh the seat list
}
// 7. You receive an `onAnchorEnterSeat` notification
func onAnchorEnterSeat(index: Int, user: UserInfo) {
// Handle the mic-on event.
}
pickSeat
, passing in a seat number and the userId
of a listener to place the listener in the seat. All users in the room will receive an onSeatListChange
notification and an onAnchorEnterSeat
notification.kickSeat
, passing in the seat number to remove the speaker from the seat. All users in the room will receive an onSeatListChange
notification and an onAnchorLeaveSeat
notification.closeSeat
, passing in a seat number to block/unblock the seat. Listeners cannot take a blocked seat, and all users in the room will receive an onSeatListChange
notification and an onSeatClose
notification.In seat management, listeners can take and leave seats without the room owner’s consent, and the room owner can put listeners in seats without the listeners’ consent.
If you want listeners and room owners to obtain each other’s consent before performing the above actions in your application, you can use signaling for invitation sending.
sendInvitation
, passing in information including the room owner’s userId
and custom command words. The API will return an inviteId
, which should be recorded.onReceiveNewInvitation
notification, and a window pops up on the UI asking the room owner whether to accept the request.acceptInvitation
with the inviteId
passed in to accept the request.onInviteeAccepted
notification and calls enterSeat
to become a speaker.// Listener
// 1. Call sendInvitation
to request to take seat 1
let inviteId = self.chorusRoom.sendInvitation(cmd: "ENTER_SEAT", userId: ownerUserId, content: "1") { (code, message) in
// Callback of the result
}
// 2. Place the user in the seat after the invitation is accepted
func onInviteeAccepted(identifier: String, invitee: String) {
if identifier == selfID {
self.chorusRoom.enterSeat(seatIndex: ) { (code, message) in
// Callback of the result
}
}
}
// Room owner
// 1. The room owner receives the request.
func onReceiveNewInvitation(identifier: String, inviter: String, cmd: String, content: String) {
if cmd == "ENTER_SEAT" {
// 2. The room owner accepts the request.
self.chorusRoom.acceptInvitation(identifier: identifier, callback: nil)
}
}
sendRoomTextMsg
to send common text messages. All users in the room will receive an onRecvRoomTextMsg
callback.// Sender: send text messages
self.chorusRoom.sendRoomTextMsg(message: message) { (code, message) in
}
// Recipient: listen for text messages
func onRecvRoomTextMsg(message: String, userInfo: UserInfo) {
// Handling of the messages received
}
sendRoomCustomMsg
to send custom (signaling) messages. All speakers and listeners in the room will receive the onRecvRoomCustomMsg
callback.// For example, a sender can customize commands to distinguish on-screen comments and likes.
// For example, use "CMD_DANMU" to indicate on-screen comments and "CMD_LIKE" to indicate likes.
self.chorusRoom.sendRoomCustomMsg(cmd: “CMD_DANMU”, message: "hello world", callback: nil)
self.chorusRoom.sendRoomCustomMsg(cmd: "CMD_LIKE", message: "", callback: nil)
// Recipient: listen for custom messages
func onRecvRoomCustomMsg(cmd: String, message: String, userInfo: UserInfo) {
if cmd == "CMD_DANMU" {
// An on-screen comment is received.
}
if cmd == "CMD_LIKE" {
// A like is received.
}
}
Was this page helpful?