Skip to content

Commit 518da24

Browse files
committed
feat: Implement media sources stopping in example app
1 parent bcb05eb commit 518da24

File tree

4 files changed

+122
-38
lines changed

4 files changed

+122
-38
lines changed

Example/CP_Example.xcodeproj/project.pbxproj

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88

99
/* Begin PBXBuildFile section */
1010
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
11-
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; };
1211
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
1312
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
1413
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
1514
E4CD703675EF048165295137 /* Pods_LiveDigitalSDKExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D970F2F152FCB07F1758161 /* Pods_LiveDigitalSDKExample.framework */; };
15+
FF4A03A72DB8B2A6005AE6D0 /* StartVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4A03A62DB8B2A6005AE6D0 /* StartVC.swift */; };
16+
FF4A03A82DB8B2A6005AE6D0 /* SessionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4A03A52DB8B2A6005AE6D0 /* SessionVC.swift */; };
1617
FFE7933726A95E5500FF5148 /* PeerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE7933626A95E5500FF5148 /* PeerView.swift */; };
1718
FFE7933926A95FB400FF5148 /* PeerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = FFE7933826A95FB400FF5148 /* PeerView.xib */; };
1819
FFFA321B2D30DD640043C2E7 /* StockMoodhoodAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFA321A2D30DD620043C2E7 /* StockMoodhoodAPIClient.swift */; };
@@ -36,12 +37,13 @@
3637
607FACD01AFB9204008FA782 /* LiveDigitalSDKExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LiveDigitalSDKExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
3738
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3839
607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
39-
607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
4040
607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
4141
607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
4242
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
4343
78EA401149B94AA7EBFB46CF /* Pods_LiveDigitalSDKExample_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LiveDigitalSDKExample_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4444
7A00D70263FC4BD91D4F586D /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
45+
FF4A03A52DB8B2A6005AE6D0 /* SessionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVC.swift; sourceTree = "<group>"; };
46+
FF4A03A62DB8B2A6005AE6D0 /* StartVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartVC.swift; sourceTree = "<group>"; };
4547
FFE7933626A95E5500FF5148 /* PeerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerView.swift; sourceTree = "<group>"; };
4648
FFE7933826A95FB400FF5148 /* PeerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PeerView.xib; sourceTree = "<group>"; };
4749
FFFA321A2D30DD620043C2E7 /* StockMoodhoodAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StockMoodhoodAPIClient.swift; sourceTree = "<group>"; };
@@ -91,7 +93,8 @@
9193
children = (
9294
FFFA321C2D30DFF50043C2E7 /* MoodhoodAPI */,
9395
607FACD51AFB9204008FA782 /* AppDelegate.swift */,
94-
607FACD71AFB9204008FA782 /* ViewController.swift */,
96+
FF4A03A52DB8B2A6005AE6D0 /* SessionVC.swift */,
97+
FF4A03A62DB8B2A6005AE6D0 /* StartVC.swift */,
9598
607FACD91AFB9204008FA782 /* Main.storyboard */,
9699
607FACDC1AFB9204008FA782 /* Images.xcassets */,
97100
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */,
@@ -212,8 +215,6 @@
212215
Base,
213216
);
214217
mainGroup = 607FACC71AFB9204008FA782;
215-
packageReferences = (
216-
);
217218
productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
218219
projectDirPath = "";
219220
projectRoot = "";
@@ -287,10 +288,11 @@
287288
FFFA32292D311A560043C2E7 /* MoodhoodParticipant.swift in Sources */,
288289
FFE7933726A95E5500FF5148 /* PeerView.swift in Sources */,
289290
FFFA322F2D3131ED0043C2E7 /* EmptyResult.swift in Sources */,
290-
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */,
291291
FFFA321E2D30E0010043C2E7 /* MoodhoodAPIClient.swift in Sources */,
292292
FFFA32242D30EA170043C2E7 /* MoodhoodAPIClientError.swift in Sources */,
293293
FFFA321B2D30DD640043C2E7 /* StockMoodhoodAPIClient.swift in Sources */,
294+
FF4A03A72DB8B2A6005AE6D0 /* StartVC.swift in Sources */,
295+
FF4A03A82DB8B2A6005AE6D0 /* SessionVC.swift in Sources */,
294296
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
295297
FFFA32272D30EB020043C2E7 /* MoodhoodUserToken.swift in Sources */,
296298
FFFA322B2D3120170043C2E7 /* SignalingToken.swift in Sources */,

Example/LiveDigitalSDK/MoodhoodAPI/StockMoodhoodAPIClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ extension StockMoodhoodAPIClient: MoodhoodAPIClient {
9696
room: String,
9797
participant: String
9898
) async throws(MoodhoodAPIClientError) {
99-
let result: EmptyResult = try await post(
99+
let _: EmptyResult = try await post(
100100
endpoint: Endpoints.joinRoom(space: space, room: room),
101101
headers: [
102102
"Authorization": "\(userToken.tokenType) \(userToken.accessToken)",

Example/LiveDigitalSDK/ViewController.swift renamed to Example/LiveDigitalSDK/SessionVC.swift

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,25 @@ import LiveDigitalSDK
33
import AVFoundation
44

55

6-
final class ViewController: UIViewController {
6+
final class SessionVC: UIViewController {
77
private struct Constants {
88
static let moodhoodAPIHost = URL(string: "https://moodhood-api.livedigital.space")!
99
static let moodhoodClientId = "moodhood-demo"
1010
static let moodhoodClientSecret = "demo12345abcde6789zxcvDemo"
1111
static let loadBalancerHost = URL(string: "https://lb.livedigital.space")!
12-
static let testSpaceId = "6144806aac512ee6cd29be10"
13-
static let testRoomId = "66cbf30dd3522636cfeb77cb"
1412
}
1513

14+
var spaceId: String?
15+
var roomId: String?
16+
1617
@IBOutlet var localPreviewShadowView: UIView!
1718
@IBOutlet var localPreviewContainer: UIView!
1819
@IBOutlet var buttonsContainer: UIView!
1920
@IBOutlet var localAudioButton: UIButton!
2021
@IBOutlet var localVideoButton: UIButton!
2122
@IBOutlet var peersViewsScroller: UIScrollView!
2223
@IBOutlet var peersViewsContainer: UIStackView!
24+
@IBOutlet var finishButton: UIButton!
2325
private var peerViews = [PeerId: PeerView]()
2426
private var peers = [PeerId: Peer]()
2527

@@ -80,6 +82,10 @@ final class ViewController: UIViewController {
8082
super.viewDidLoad()
8183
print("LiveDigitalSDK version: \(LiveDigital.version())")
8284

85+
finishButton.layer.masksToBounds = true
86+
finishButton.layer.cornerRadius = 20
87+
finishButton.backgroundColor = .systemRed
88+
8389
buttonsContainer.layer.masksToBounds = true
8490
buttonsContainer.layer.cornerRadius = 20
8591

@@ -99,20 +105,22 @@ final class ViewController: UIViewController {
99105

100106
AVCaptureDevice.requestAccess(for: .audio) { granted in
101107
AVCaptureDevice.requestAccess(for: .video) { granted in
102-
self.startConferenceSession()
108+
DispatchQueue.main.async {
109+
self.startConferenceSession()
110+
}
103111
}
104112
}
105113
}
106114
}
107115

108116
// MARK: - LiveDigitalSessionManagerDelegate implementation
109117

110-
extension ViewController: LiveDigitalSessionManagerDelegate {
118+
extension SessionVC: LiveDigitalSessionManagerDelegate {
111119
}
112120

113121
// MARK: - CameraManagerDelegate implementation
114122

115-
extension ViewController: CameraManagerDelegate {
123+
extension SessionVC: CameraManagerDelegate {
116124
func cameraManagerFailed(cameraManager: CameraManager, error: MediaCapturerError) {
117125
print("\(cameraManager) failed with error \(error)")
118126
}
@@ -123,7 +131,7 @@ extension ViewController: CameraManagerDelegate {
123131

124132
// MARK: - AudioRouterDelegate implementation
125133

126-
extension ViewController: AudioRouterDelegate {
134+
extension SessionVC: AudioRouterDelegate {
127135
func needRestartAudio() {
128136
}
129137

@@ -133,9 +141,9 @@ extension ViewController: AudioRouterDelegate {
133141

134142
// MARK: - ChannelSessionObserver implementation
135143

136-
extension ViewController: ChannelSessionObserver {
144+
extension SessionVC: ChannelSessionObserver {
137145
func channelSessionJoinedChannel(_ channelSession: any LiveDigitalSDK.ChannelSession) {
138-
guard let participantId, let userToken else {
146+
guard let participantId, let userToken, let spaceId, let roomId else {
139147
print("Failed to join room: missing participantId or userToken")
140148
return
141149
}
@@ -144,8 +152,8 @@ extension ViewController: ChannelSessionObserver {
144152
do {
145153
try await apiClient.joinRoom(
146154
userToken: userToken,
147-
space: Constants.testSpaceId,
148-
room: Constants.testRoomId,
155+
space: spaceId,
156+
room: roomId,
149157
participant: participantId
150158
)
151159
print("Joined room")
@@ -240,7 +248,7 @@ extension ViewController: ChannelSessionObserver {
240248

241249
// MARK: - ChannelSessionDelegate implementation
242250

243-
extension ViewController: ChannelSessionDelegate {
251+
extension SessionVC: ChannelSessionDelegate {
244252
func sessionNeedsRestart(_ channelSession: ChannelSession) {
245253
self.channelSession?.start(
246254
mediaRole: .host,
@@ -271,23 +279,27 @@ extension ViewController: ChannelSessionDelegate {
271279
}
272280

273281
// MARK: - Private methods
274-
private extension ViewController {
282+
private extension SessionVC {
275283
func startConferenceSession() {
284+
updateLocalVideoEnabled(false)
285+
updateLocalAudioEnabled(false)
286+
287+
guard let spaceId, let roomId else {
288+
print("Skip session start: spaceId or roomId is not specified")
289+
return
290+
}
291+
276292
Task {
277293
let userToken = try await apiClient.authorizeAsGuest()
278294
print("Created user token: \(userToken)")
279295

280-
let room = try await apiClient.fetchRoom(
281-
userToken: userToken,
282-
space: Constants.testSpaceId,
283-
room: Constants.testRoomId
284-
)
296+
let room = try await apiClient.fetchRoom(userToken: userToken, space: spaceId, room: roomId)
285297
print("Fetched room details: \(room)")
286298

287299
let participant = try await apiClient.createParticipant(
288300
userToken: userToken,
289-
space: Constants.testSpaceId,
290-
room: Constants.testRoomId,
301+
space: spaceId,
302+
room: roomId,
291303
clientUniqueId: clientUniqueId,
292304
role: "host",
293305
name: UIDevice.current.name
@@ -296,7 +308,7 @@ private extension ViewController {
296308

297309
let signalingToken = try await apiClient.createSignalingToken(
298310
userToken: userToken,
299-
space: Constants.testSpaceId,
311+
space: spaceId,
300312
participant: participant.id
301313
)
302314
print("Created signaling token: \(signalingToken)")
@@ -349,19 +361,14 @@ private extension ViewController {
349361
print("Failed to start session with error: \(error)")
350362
}
351363

352-
self.updateLocalVideoEnabled(false)
364+
self.updateLocalVideoEnabled(self.videoSource != nil)
353365
self.updateLocalAudioEnabled(self.audioSource != nil)
354366
})
355-
356-
startVideoSource()
357-
startAudioSource()
358367
}
359368

360-
func startVideoSource() {
369+
func startVideoSource(_ completion: ((VideoSource?)-> Void)) {
361370
switch engine.startVideoSource(position: .front) {
362371
case let .success(videoSource):
363-
self.videoSource = videoSource
364-
365372
videoSource.localVideoView.translatesAutoresizingMaskIntoConstraints = false
366373
localPreviewContainer.addSubview(videoSource.localVideoView)
367374
localPreviewContainer.leftAnchor
@@ -376,8 +383,11 @@ private extension ViewController {
376383
localPreviewContainer.bottomAnchor
377384
.constraint(equalTo: videoSource.localVideoView.bottomAnchor)
378385
.isActive = true
386+
completion(videoSource)
387+
379388
case let .failure(error):
380389
print("Failed to start video source: \(error)")
390+
completion(nil)
381391
}
382392
}
383393

@@ -401,6 +411,14 @@ private extension ViewController {
401411
}
402412

403413
func updateLocalVideoEnabled(_ enabled: Bool) {
414+
if enabled, videoSource == nil {
415+
startVideoSource { [weak self] source in
416+
self?.videoSource = source
417+
self?.updateLocalVideoEnabled(enabled)
418+
}
419+
return
420+
}
421+
404422
guard let session = channelSession, let source = videoSource else {
405423
print("Failed to update local video state: channel or video source is undefined.")
406424
updateVideoButtonState(isOn: false)
@@ -411,11 +429,19 @@ private extension ViewController {
411429
session.addVideoSource(source)
412430
} else {
413431
session.removeVideoSource(source)
432+
engine.stopVideoSource(source)
433+
if let camSource = source as? VideoSourceWithPreview {
434+
camSource.localVideoView.removeFromSuperview()
435+
}
436+
videoSource = nil
414437
}
415438
updateVideoButtonState(isOn: enabled)
416439
}
417440

418441
func updateLocalAudioEnabled(_ enabled: Bool) {
442+
if enabled, audioSource == nil {
443+
startAudioSource()
444+
}
419445
guard let session = channelSession, let source = audioSource else {
420446
print("Failed to update local audio state: channel or audio source is undefined.")
421447
updateAudioButtonState(isOn: false)
@@ -426,6 +452,8 @@ private extension ViewController {
426452
session.addAudioSource(source)
427453
} else {
428454
session.removeAudioSource(source)
455+
engine.stopAudioSource(source)
456+
audioSource = nil
429457
}
430458
updateAudioButtonState(isOn: enabled)
431459
}
@@ -506,7 +534,7 @@ private extension ViewController {
506534

507535
// MARK: - UI Actions
508536

509-
private extension ViewController {
537+
private extension SessionVC {
510538
@IBAction
511539
func toggleMicrophoneEnabled(_ sender: UIButton) {
512540
updateLocalAudioEnabled(!sender.isSelected)
@@ -523,11 +551,30 @@ private extension ViewController {
523551
print("Failed to flip camera: \(error)")
524552
}
525553
}
554+
555+
@IBAction
556+
func finishSession(_ sender: UIButton) {
557+
if let videoSource {
558+
engine.stopVideoSource(videoSource)
559+
}
560+
if let audioSource {
561+
engine.stopAudioSource(audioSource)
562+
}
563+
564+
guard let channelSession else {
565+
dismiss(animated: true)
566+
return
567+
}
568+
finishButton.isEnabled = false
569+
channelSession.stop(completion: { [weak self] in
570+
self?.dismiss(animated: true)
571+
})
572+
}
526573
}
527574

528575
// MARK: - UIScrollViewDelegate implementation
529576

530-
extension ViewController: UIScrollViewDelegate {
577+
extension SessionVC: UIScrollViewDelegate {
531578
func scrollViewDidScroll(_ scrollView: UIScrollView) {
532579
updateVisiblePeersVideos()
533580
}
@@ -539,7 +586,7 @@ extension ViewController: UIScrollViewDelegate {
539586

540587
// MARK: - UIContextMenuInteractionDelegate implementation
541588

542-
extension ViewController: UIContextMenuInteractionDelegate {
589+
extension SessionVC: UIContextMenuInteractionDelegate {
543590
func contextMenuInteraction(_ interaction: UIContextMenuInteraction,
544591
configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
545592

Example/LiveDigitalSDK/StartVC.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Foundation
2+
import UIKit
3+
4+
5+
final class StartVC: UIViewController {
6+
private struct Constants {
7+
static let testSpaceId = "612dbb98b2f9d4a99f18f553"
8+
static let testRoomId = "61554214ae218a31f78e8bc8"
9+
}
10+
11+
@IBOutlet var spaceIdInput: UITextField!
12+
@IBOutlet var roomIdInput: UITextField!
13+
@IBOutlet var startButton: UIButton!
14+
15+
override func viewDidLoad() {
16+
super.viewDidLoad()
17+
spaceIdInput.text = Constants.testSpaceId
18+
roomIdInput.text = Constants.testRoomId
19+
}
20+
21+
@IBAction func startTapped() {
22+
performSegue(withIdentifier: "StartSession", sender: self)
23+
}
24+
25+
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
26+
guard segue.identifier == "StartSession" else {
27+
return
28+
}
29+
30+
if let destination = segue.destination as? SessionVC {
31+
destination.spaceId = spaceIdInput.text
32+
destination.roomId = roomIdInput.text
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)