import SMeeting
import SRTC
import SwiftUI
@MainActor
final class MeetingViewModel: ObservableObject {
/// Main SDK entry point; create only one instance for the whole app lifecycle
let meeting = SMeetingEngine(logLevel: .info)
@Published var users: [MeetingUserInfo] = []
@Published var isInMeeting = false
@Published var isCameraOn = false
/// 1. Log in: the token comes from your backend
func login(token: String) async throws {
try await meeting.login(token: token)
meeting.delegates.add(delegate: self)
}
/// 2. Create an instant meeting and enter it
func createAndEnter(nickname: String) async throws {
var createReq = MeetingCreateReq(title: "\(nickname)'s meeting", meetingMode: .normal)
createReq.meetingType = .instant
let (_, meetingId) = try await meeting.createRoom(createReq)
try await meeting.enterRoom(MeetingEnterReq(nickname: nickname, meetingId: meetingId))
isInMeeting = true
users = meeting.getUsersInfoList()
}
/// 3. Enter an existing meeting by room number
func enter(roomNo: String, nickname: String, password: String? = nil) async throws {
var enterReq = MeetingEnterReq(nickname: nickname, roomNo: roomNo)
enterReq.password = password
try await meeting.enterRoom(enterReq)
isInMeeting = true
users = meeting.getUsersInfoList()
}
/// 4. Turn on the local microphone and camera (SwiftUI doesn't need a preview view)
func openLocalMedia() async throws {
try await meeting.requestOpenMic()
try await meeting.requestOpenCamera()
isCameraOn = true // Triggers a UI refresh so the local video shows up
}
/// 5. Exit the meeting
func exit() async {
await meeting.closeCamera()
await meeting.closeMic()
await meeting.exitRoom()
isCameraOn = false
isInMeeting = false
users = []
}
}
// Event callbacks are dispatched on the main thread; @MainActor types must declare protocol methods as nonisolated
extension MeetingViewModel: SMeetingDelegate {
nonisolated func meeting(_ meeting: SMeetingEngine, userDidEnter user: MeetingUserInfo) {
DispatchQueue.main.async { self.users = meeting.getUsersInfoList() }
}
nonisolated func meeting(_ meeting: SMeetingEngine, userDidExit data: UserExitEventData) {
DispatchQueue.main.async { self.users = meeting.getUsersInfoList() }
}
}
struct MeetingView: View {
@StateObject private var vm = MeetingViewModel()
var body: some View {
VStack(spacing: 8) {
// Local video: pass the local camera track to SRTCVideoView
if vm.isCameraOn, let cameraTrack = vm.meeting.cameraTrack {
SRTCVideoView(track: cameraTrack).frame(height: 180)
}
// Remote video: the component handles subscribing and unsubscribing internally
ForEach(vm.users.filter { $0.uid != vm.meeting.currentUserId }, id: \.uid) { user in
SMeetingRemoteVideoView(meeting: vm.meeting, uid: user.uid, trackDesc: .cameraBig)
.frame(height: 180)
}
}
}
}