Skip to content

Commit

Permalink
custom audio render
Browse files Browse the repository at this point in the history
  • Loading branch information
qinhui committed Oct 9, 2024
1 parent 61a95a4 commit 97f7c01
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@
F728BA222CA93EF9007813BB /* VoiceChangerRTC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F728BA212CA93EF9007813BB /* VoiceChangerRTC.swift */; };
F728BA2E2CB53E04007813BB /* RTMPStreamRTC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F728BA2D2CB53E04007813BB /* RTMPStreamRTC.swift */; };
F728BA302CB53E13007813BB /* RTMPStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = F728BA2F2CB53E13007813BB /* RTMPStream.swift */; };
F728BA352CB6576F007813BB /* CustomAudioRenderRTC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F728BA342CB6576F007813BB /* CustomAudioRenderRTC.swift */; };
F728BA372CB6577E007813BB /* CustomAudioRender.swift in Sources */ = {isa = PBXBuildFile; fileRef = F728BA362CB6577E007813BB /* CustomAudioRender.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -316,6 +318,8 @@
F728BA212CA93EF9007813BB /* VoiceChangerRTC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceChangerRTC.swift; sourceTree = "<group>"; };
F728BA2D2CB53E04007813BB /* RTMPStreamRTC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTMPStreamRTC.swift; sourceTree = "<group>"; };
F728BA2F2CB53E13007813BB /* RTMPStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTMPStream.swift; sourceTree = "<group>"; };
F728BA342CB6576F007813BB /* CustomAudioRenderRTC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAudioRenderRTC.swift; sourceTree = "<group>"; };
F728BA362CB6577E007813BB /* CustomAudioRender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAudioRender.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -476,6 +480,7 @@
E73F240E2BA82D4B0000B523 /* Advanced */ = {
isa = PBXGroup;
children = (
F728BA332CB65703007813BB /* CustomAudioRender */,
F728BA1E2CA93EDA007813BB /* VoiceChanger */,
F728BA162CA9398E007813BB /* RTMPStream */,
F728BA112CA90732007813BB /* LocalVideoTranscoding */,
Expand Down Expand Up @@ -918,6 +923,15 @@
path = VoiceChanger;
sourceTree = "<group>";
};
F728BA332CB65703007813BB /* CustomAudioRender */ = {
isa = PBXGroup;
children = (
F728BA342CB6576F007813BB /* CustomAudioRenderRTC.swift */,
F728BA362CB6577E007813BB /* CustomAudioRender.swift */,
);
path = CustomAudioRender;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -1132,6 +1146,7 @@
F728B9F32CA3FB12007813BB /* LiveStreamingRTC.swift in Sources */,
E71D54722BCD270800656537 /* StreamEncryptionRTC.swift in Sources */,
E73F248D2BA851870000B523 /* KFMP4Demuxer.m in Sources */,
F728BA352CB6576F007813BB /* CustomAudioRenderRTC.swift in Sources */,
E73F24F02BB6645C0000B523 /* RhythmPlayer.swift in Sources */,
E73F24882BA851870000B523 /* NetworkManager.swift in Sources */,
E73F24AD2BAAC6E70000B523 /* JoinChannelVideoRecorder.swift in Sources */,
Expand Down Expand Up @@ -1160,6 +1175,7 @@
E73F248A2BA851870000B523 /* ARVideoRenderer.swift in Sources */,
E71D547C2BCE1EB900656537 /* QuickSwitchChannelRTC.swift in Sources */,
E71D546E2BCD176B00656537 /* AudioMixing.swift in Sources */,
F728BA372CB6577E007813BB /* CustomAudioRender.swift in Sources */,
E73F24AE2BAAC6E70000B523 /* JoinChannelVideoRecorderRTC.swift in Sources */,
E79DFB622BBA545300904B08 /* PickerView.swift in Sources */,
F728B9F12CA3FAFE007813BB /* LiveStreaming.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions iOS/APIExample-SwiftUI/APIExample-SwiftUI/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ struct ContentView: View {
view: AnyView(VoiceChangerEntry())),
MenuItem(name: "RTMP Streaming".localized,
view: AnyView(RTMPStreamEntry())),
MenuItem(name: "Custom Audio Render".localized,
view: AnyView(CustomAudioRenderEntry())),
MenuItem(name: "Picture In Picture".localized,
view: AnyView(PictureInPictureEntry())),
MenuItem(name: "Quick Switch Channel".localized,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// CustomAudioRender.swift
// APIExample-SwiftUI
//
// Created by qinhui on 2024/10/9.
//

import SwiftUI

struct CustomAudioRenderEntry: View {
@State private var channelName: String = ""
@State private var isActive = false
@State private var configs: [String: Any] = [:]

var body: some View {
VStack {
Spacer()
TextField("Enter channel name".localized, text: $channelName).textFieldStyle(.roundedBorder).padding()
Button {
configs = ["channelName": channelName]
self.isActive = true
} label: {
Text("Join".localized)
}.disabled(channelName.isEmpty)
Spacer()
NavigationLink(destination: CustomAudioRender(configs: configs).navigationTitle(channelName).navigationBarTitleDisplayMode(.inline), isActive: $isActive) {
EmptyView()
}
Spacer()
}
.navigationBarTitleDisplayMode(.inline)
}
}

struct CustomAudioRender: View {
@State var configs: [String: Any] = [:]
@ObservedObject private var agoraKit = CustomAudioRenderRTC()

var body: some View {
VStack {
GeometryReader { geometry in
ScrollView {
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]) {
ForEach(agoraKit.audioInfos, id: \.self) { info in
VStack {
Text(info.content)
.font(.system(size: 12))
.foregroundColor(.gray)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray.opacity(0.5))
}
.frame(width: geometry.size.width / 3, height: 200)
}
}
}
}
}
.onAppear(perform: {
agoraKit.setupRTC(configs: configs)
}).onDisappear(perform: {
agoraKit.onDestory()
})
}
}

#Preview {
CustomAudioRender()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//
// CustomAudioRenderRTC.swift
// APIExample-SwiftUI
//
// Created by qinhui on 2024/10/9.
//

import Foundation
import AgoraRtcKit

struct CustomAudioRenderViewInfo: Hashable {
var uid: UInt
var content: String
}

class CustomAudioRenderRTC: NSObject, ObservableObject {
@Published var isJoined: Bool = false
@Published var audioInfos: [CustomAudioRenderViewInfo] = []
private var agoraKit: AgoraRtcEngineKit!
private var exAudio: ExternalAudio = ExternalAudio.shared()

func setupRTC(configs: [String: Any]) {
let sampleRate: UInt = 44100, channel: UInt = 1

// set up agora instance when view loaded
let config = AgoraRtcEngineConfig()
config.appId = KeyCenter.AppId
config.areaCode = GlobalSettings.shared.area
config.channelProfile = .liveBroadcasting
agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
// Configuring Privatization Parameters
Util.configPrivatization(agoraKit: agoraKit)
agoraKit.setLogFile(LogUtils.sdkLogPath())

guard let channelName = configs["channelName"] as? String else {return}

// make myself a broadcaster
agoraKit.setClientRole(GlobalSettings.shared.getUserRole())

// disable video module
agoraKit.disableVideo()
agoraKit.enableAudio()
// Set audio route to speaker
agoraKit.setDefaultAudioRouteToSpeakerphone(true)

exAudio.setupExternalAudio(withAgoraKit: agoraKit,
sampleRate: UInt32(sampleRate),
channels: UInt32(channel),
trackId: 1,
audioCRMode: .sdkCaptureExterRender,
ioType: .remoteIO)
agoraKit.setParameters("{\"che.audio.external_render\": true}")
agoraKit.setParameters("{\"che.audio.keep.audiosession\": true}")

// start joining channel
// 1. Users can only see each other after they join the
// same channel successfully using the same app id.
// 2. If app certificate is turned on at dashboard, token is needed
// when joining channel. The channel name and uid used to calculate
// the token has to match the ones used for channel join
let option = AgoraRtcChannelMediaOptions()
option.publishCameraTrack = false
option.publishMicrophoneTrack = true
option.clientRoleType = GlobalSettings.shared.getUserRole()

NetworkManager.shared.generateToken(channelName: channelName, success: { token in
let result = self.agoraKit.joinChannel(byToken: token, channelId: channelName, uid: 0, mediaOptions: option)
if result != 0 {
// Usually happens with invalid parameters
// Error code description can be found at:
// en: https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agoraerrorcode
// cn: https://doc.shengwang.cn/api-ref/rtc/ios/error-code
ToastView.show(text: "joinChannel call failed: \(result), please check your params")
}
})
}

func onDestory() {
agoraKit.disableAudio()
agoraKit.disableVideo()
if isJoined {
agoraKit.stopPreview()
agoraKit.leaveChannel { (stats) -> Void in
LogUtils.log(message: "left channel, duration: \(stats.duration)", level: .info)
}
}
AgoraRtcEngineKit.destroy()
}

private func getAudioLabel(uid: UInt, isLocal: Bool) -> String {
return "AUDIO ONLY\n\(isLocal ? "Local" : "Remote")\n\(uid)"
}
}

extension CustomAudioRenderRTC: AgoraRtcEngineDelegate {
/// callback when warning occured for agora sdk, warning can usually be ignored, still it's nice to check out
/// what is happening
/// Warning code description can be found at:
/// en:https://api-ref.agora.io/en/voice-sdk/ios/3.x/Constants/AgoraWarningCode.html
/// cn: https://docs.agora.io/cn/Voice/API%20Reference/oc/Constants/AgoraWarningCode.html
/// @param warningCode warning code of the problem
func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurWarning warningCode: AgoraWarningCode) {
LogUtils.log(message: "warning: \(warningCode.description)", level: .warning)
}

/// callback when error occured for agora sdk, you are recommended to display the error descriptions on demand
/// to let user know something wrong is happening
/// Error code description can be found at:
/// en: https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agoraerrorcode
/// cn: https://doc.shengwang.cn/api-ref/rtc/ios/error-code
/// @param errorCode error code of the problem
func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) {
LogUtils.log(message: "error: \(errorCode)", level: .error)
ToastView.show(text: "Error \(errorCode.description) occur")
}

func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
self.isJoined = true
LogUtils.log(message: "Join \(channel) with uid \(uid) elapsed \(elapsed)ms", level: .info)
audioInfos.append(CustomAudioRenderViewInfo(uid: uid, content: self.getAudioLabel(uid: uid, isLocal: true)))
}

/// callback when a remote user is joinning the channel, note audience in live broadcast mode will NOT trigger this event
/// @param uid uid of remote joined user
/// @param elapsed time elapse since current sdk instance join the channel in ms
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
LogUtils.log(message: "remote user join: \(uid) \(elapsed)ms", level: .info)
audioInfos.append(CustomAudioRenderViewInfo(uid: uid, content: self.getAudioLabel(uid: uid, isLocal: false)))
}

/// callback when a remote user is leaving the channel, note audience in live broadcast mode will NOT trigger this event
/// @param uid uid of remote joined user
/// @param reason reason why this user left, note this event may be triggered when the remote user
/// become an audience in live broadcasting profile
func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
LogUtils.log(message: "remote user left: \(uid) reason \(reason)", level: .info)

// remove remote audio view
audioInfos.removeAll { info in
return info.uid == uid
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ class LiveStreamingRTC: NSObject, ObservableObject {
self.configs = configs
self.backgroundView = localView
self.foregroundView = remoteView
let config = AgoraRtcEngineConfig()
config.appId = KeyCenter.AppId
config.channelProfile = .liveBroadcasting
agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
Util.configPrivatization(agoraKit: agoraKit)
agoraKit.setLogFile(LogUtils.sdkLogPath())

if let isFirstFrame = configs["isFirstFrame"] as? Bool, isFirstFrame == true {
agoraKit.enableInstantMediaRendering()
Expand All @@ -70,7 +76,6 @@ class LiveStreamingRTC: NSObject, ObservableObject {
showUltraLowEntry = role == .audience
showLinkStreamEntry = role == .audience

self.agoraKit.addDelegate(self)
updateClientRole(role)

// enable video module and set up video encoding configs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class SDKRenderViewModel: NSObject, ObservableObject {
rtcEngine.stopPreview()
rtcEngine.leaveChannel(nil)
}
AgoraRtcEngineKit.destroy()
}

func addRenderView(renderView: VideoView) {
Expand Down

0 comments on commit 97f7c01

Please sign in to comment.