Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 7bc9a62

Browse files
graduadkbetl-dlb
andauthored
Create audio preview iOS wrapper (#399)
Includes: * Create audio preview iOS wrapper * Implement audio preview on status change * Fix conference service on participants change test * Speed up audio preview tests * Fix script to run android mocked test locally * Update android tests for audio preview * Move 2 misplaced files * Fix a formatting issue in AudioPreviewAsserts.kt * Improve a closure in AudioPreviewAssets.kt --------- Co-authored-by: kbetl-dlb <krzysztof.betlej@dolby.com>
1 parent e5b33b5 commit 7bc9a62

File tree

25 files changed

+859
-236
lines changed

25 files changed

+859
-236
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import Foundation
2+
import VoxeetSDK
3+
4+
class AudioPreviewServiceBinding: Binding {
5+
6+
override init(name: String, registrar: FlutterPluginRegistrar) {
7+
super.init(name: name, registrar: registrar)
8+
VoxeetSDK.shared.audio.local.preview.onStatusChanged = { [weak self] (status) -> Void in
9+
do {
10+
try self?.nativeEventEmitter.sendEvent(
11+
event: EventKeys.statusChanged,
12+
body: DTO.RecorderStatus(recorderStatus: status)
13+
)
14+
} catch {
15+
fatalError(error.localizedDescription)
16+
}
17+
}
18+
}
19+
20+
/// Retrieves the status.
21+
/// - Parameters:
22+
/// - flutterArguments: Method arguments passed from Flutter.
23+
/// - completionHandler: Call methods on this instance when execution has finished.
24+
public func status(
25+
flutterArguments: FlutterMethodCallArguments,
26+
completionHandler: FlutterMethodCallCompletionHandler
27+
) {
28+
let status = audioPreview().status
29+
completionHandler.success(encodable: DTO.RecorderStatus(recorderStatus: status))
30+
}
31+
32+
/// Retrieves the comfort noise level setting for output devices in Dolby Voice conferences.
33+
/// - Parameters:
34+
/// - flutterArguments: Method arguments passed from Flutter.
35+
/// - completionHandler: Call methods on this instance when execution has finished.
36+
public func getCaptureMode(
37+
flutterArguments: FlutterMethodCallArguments,
38+
completionHandler: FlutterMethodCallCompletionHandler
39+
) {
40+
do {
41+
let audioCaptureMode = audioPreview().captureMode
42+
completionHandler.success(encodable: try DTO.AudioCaptureOptions(audioCaptureMode: audioCaptureMode))
43+
} catch {
44+
completionHandler.failure(error)
45+
}
46+
}
47+
48+
/// Sets the comfort noise level for output devices in Dolby Voice conferences.
49+
/// - Parameters:
50+
/// - flutterArguments: Method arguments passed from Flutter.
51+
/// - completionHandler: Call methods on this instance when execution has finished.
52+
public func setCaptureMode(
53+
flutterArguments: FlutterMethodCallArguments,
54+
completionHandler: FlutterMethodCallCompletionHandler
55+
) {
56+
do {
57+
let captureOptions = try flutterArguments
58+
.asSingle()
59+
.decode(type: DTO.AudioCaptureOptions.self)
60+
VoxeetSDK.shared.audio.local.preview.captureMode = try captureOptions.toSdk()
61+
completionHandler.success()
62+
} catch {
63+
completionHandler.failure(error)
64+
}
65+
}
66+
67+
68+
/// Plays the recorded audio preview.
69+
/// - Parameters:
70+
/// - flutterArguments: Method arguments passed from Flutter.
71+
/// - completionHandler: Call methods on this instance when execution has finished.
72+
public func play(
73+
flutterArguments: FlutterMethodCallArguments,
74+
completionHandler: FlutterMethodCallCompletionHandler
75+
) {
76+
do {
77+
let loop: Bool = try flutterArguments.asDictionary(argKey: "loop").decode()
78+
audioPreview().play(loop: loop) { error in
79+
completionHandler.handleError(error)?.orSuccess()
80+
}
81+
} catch {
82+
completionHandler.failure(error)
83+
}
84+
}
85+
86+
/// Records an audio preview.
87+
/// - Parameters:
88+
/// - flutterArguments: Method arguments passed from Flutter.
89+
/// - completionHandler: Call methods on this instance when execution has finished.
90+
public func record(
91+
flutterArguments: FlutterMethodCallArguments,
92+
completionHandler: FlutterMethodCallCompletionHandler
93+
) {
94+
do {
95+
let duration: Int = try flutterArguments.asDictionary(argKey: "duration").decode()
96+
audioPreview().record(duration: duration) { error in
97+
completionHandler.handleError(error)?.orSuccess()
98+
}
99+
} catch {
100+
completionHandler.failure(error)
101+
}
102+
}
103+
104+
/// If playing or recording is underway calling this method will cancel the ongoing activity.
105+
/// - Parameters:
106+
/// - flutterArguments: Method arguments passed from Flutter.
107+
/// - completionHandler: Call methods on this instance when execution has finished.
108+
public func cancel(
109+
flutterArguments: FlutterMethodCallArguments,
110+
completionHandler: FlutterMethodCallCompletionHandler
111+
) {
112+
audioPreview().cancel()
113+
completionHandler.success(flutterConvertible: true)
114+
}
115+
116+
/// Release the internal memory and restart the audio session configuration.
117+
/// - Parameters:
118+
/// - flutterArguments: Method arguments passed from Flutter.
119+
/// - completionHandler: Call methods on this instance when execution has finished.
120+
public func release(
121+
flutterArguments: FlutterMethodCallArguments,
122+
completionHandler: FlutterMethodCallCompletionHandler
123+
) {
124+
audioPreview().release()
125+
completionHandler.success()
126+
}
127+
}
128+
129+
extension AudioPreviewServiceBinding: FlutterBinding {
130+
func handle(
131+
methodName: String,
132+
flutterArguments: FlutterMethodCallArguments,
133+
completionHandler: FlutterMethodCallCompletionHandler
134+
) {
135+
switch methodName {
136+
case "status":
137+
status(flutterArguments: flutterArguments, completionHandler: completionHandler)
138+
case "getCaptureMode":
139+
getCaptureMode(flutterArguments: flutterArguments, completionHandler: completionHandler)
140+
141+
case "setCaptureMode":
142+
setCaptureMode(flutterArguments: flutterArguments, completionHandler: completionHandler)
143+
case "play":
144+
play(flutterArguments: flutterArguments, completionHandler: completionHandler)
145+
case "record":
146+
record(flutterArguments: flutterArguments, completionHandler: completionHandler)
147+
case "cancel":
148+
cancel(flutterArguments: flutterArguments, completionHandler: completionHandler)
149+
case "release":
150+
release(flutterArguments: flutterArguments, completionHandler: completionHandler)
151+
default:
152+
completionHandler.methodNotImplemented()
153+
}
154+
}
155+
}
156+
157+
private enum EventKeys: String, CaseIterable {
158+
/// Emitted when the status changes.
159+
case statusChanged = "EVENT_AUDIO_PREVIEW_STATUS_CHANGED"
160+
}
161+
162+
private func audioPreview() -> AudioPreview {
163+
return VoxeetSDK.shared.audio.local.preview
164+
}
165+

ios/Classes/Bindings/Models/AudioDTO.swift

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import WebRTC
44

55
typealias VTAudioCaptureMode = AudioCaptureMode
66
typealias VTVoiceFont = VoiceFont
7+
typealias VTRecorderStatus = RecorderStatus
78

89
extension DTO {
910

@@ -23,7 +24,7 @@ extension DTO {
2324
}
2425
}
2526

26-
func toSdk() throws -> VTAudioCaptureMode? {
27+
func toSdk() throws -> VTAudioCaptureMode {
2728
switch mode.mode {
2829
case .standard:
2930
guard let noiseReduction = noiseReduction?.noiseReduction,
@@ -155,4 +156,37 @@ extension DTO {
155156
}
156157
}
157158
}
159+
160+
struct RecorderStatus: Codable {
161+
162+
let recorderStatus: VTRecorderStatus
163+
164+
init(recorderStatus: VTRecorderStatus) {
165+
self.recorderStatus = recorderStatus
166+
}
167+
168+
init(from decoder: Decoder) throws {
169+
let container = try decoder.singleValueContainer()
170+
switch try container.decode(String.self) {
171+
case "NoRecordingAvailable": recorderStatus = .noRecordingAvailable
172+
case "RecordingAvailable": recorderStatus = .recordingAvailable
173+
case "Recording": recorderStatus = .recording
174+
case "Playing": recorderStatus = .playing
175+
case "Released": recorderStatus = .released
176+
default: throw EncoderError.decoderFailed()
177+
}
178+
}
179+
180+
func encode(to encoder: Encoder) throws {
181+
var container = encoder.singleValueContainer()
182+
switch recorderStatus {
183+
case .noRecordingAvailable: try container.encode("NoRecordingAvailable")
184+
case .recordingAvailable: try container.encode("RecordingAvailable")
185+
case .recording: try container.encode("Recording")
186+
case .playing: try container.encode("Playing")
187+
case .released: try container.encode("Released")
188+
@unknown default: throw EncoderError.encoderFailed()
189+
}
190+
}
191+
}
158192
}

ios/Classes/SwiftDolbyioCommsSdkFlutterPlugin.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public class SwiftDolbyioCommsSdkFlutterPlugin: NSObject, FlutterPlugin {
2929
LocalAudioServiceBinding(name: "local_audio", registrar: registrar),
3030
RemoteAudioServiceBinding(name: "remote_audio", registrar: registrar),
3131
LocalVideoServiceBinding(name: "local_video", registrar: registrar),
32-
RemoteVideoServiceBinding(name: "remote_video", registrar: registrar)
32+
RemoteVideoServiceBinding(name: "remote_video", registrar: registrar),
33+
AudioPreviewServiceBinding(name: "audio_preview", registrar: registrar)
3334
]
3435

3536
let factory = FLVideoViewFactory(messenger: registrar.messenger())

ios/dolbyio_comms_sdk_flutter.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ DESC
1919
s.source = { :path => '.' }
2020
s.source_files = 'Classes/**/*'
2121
s.dependency 'Flutter'
22-
s.dependency 'VoxeetSDK', '~> 3.9.0'
22+
s.dependency 'VoxeetSDK', '~> 3.10.0'
2323
s.platforms = {
2424
:ios => "12.0"
2525
}

lib/src/sdk_api/models/enums.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,11 @@ enum RecordingServiceEventNames implements EnumWithStringValue {
219219
}
220220
}
221221

222-
/// The NotificationServiceEventNames enum gathers the NotificationService events.
222+
/// The AudioPreviewEventNames enum gathers the AudioPreview events.
223223
///
224224
/// {@category Models}
225225
enum AudioPreviewEventNames implements EnumWithStringValue {
226-
/// Emitted when an application user receives an invitation.
226+
/// Emitted when the status of the audio preview changes.
227227
onStatusChanged('EVENT_AUDIO_PREVIEW_STATUS_CHANGED');
228228

229229
@override
Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,16 @@
11
#! /usr/bin/env bash
22

3-
device_name="testAVD"
4-
system_image="system-images;android-29;default;x86"
5-
device_port="5554"
6-
serial_no="emulator-$device_port"
7-
gpu_mode="swiftshader_indirect"
8-
9-
103
export ANDROID_HOME=~/.android/
11-
export HOME=/Users/runner
4+
export HOME=~/
125

136
export PATH="$PATH:$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin"
147

15-
echo $PATH
16-
17-
echo "Check available avd"
18-
19-
avdmanager list avd
20-
21-
echo "Create system image: $system_image"
22-
23-
echo "no" | avdmanager --verbose create avd --force --name $device_name --abi "default/x86" --package "$system_image"
24-
echo "disk.dataPartition.size=8G" >> ~/.android/avd/$device_name.avd/config.ini
25-
echo "hw.ramSize = 3.072MB" >> ~/.android/avd/$device_name.avd/config.ini
26-
touch ~/.android/emu-update-last-check.ini
27-
28-
ls -la ~/.android/
29-
308
adb start-server
319

32-
sleep 5
33-
34-
echo "accel check: "
35-
emulator -accel-check
36-
37-
echo "check emulator list:"
38-
emulator -list-avds
39-
40-
echo "start: $device_name"
41-
$ANDROID_SDK_ROOT/emulator/emulator -avd $device_name -port $device_port -noaudio -no-window -no-snapshot -no-boot-anim &
42-
43-
echo "Device name: $device_name"
44-
45-
sleep 8
10+
sleep 1
4611

4712
i=1
48-
while [ "`adb -s $serial_no shell getprop sys.boot_completed | tr -d '\r' `" != "1" ] ; do
13+
while [ "`adb shell getprop sys.boot_completed | tr -d '\r' `" != "1" ] ; do
4914
sleep 2;
5015
i=$((i+1))
5116
if [[ "$i" -gt 120 ]]; then
@@ -55,28 +20,19 @@ while [ "`adb -s $serial_no shell getprop sys.boot_completed | tr -d '\r' `" !=
5520

5621
done
5722

58-
echo "Device: $device_name is booted"
59-
60-
adb devices
61-
6223
./scripts/change-to-mock.sh
6324

6425
currentFolder=`pwd`
6526

6627
cd test_app
6728

68-
USE_SDK_MOCK=true flutter test integration_tests/mocked/master_test.dart -d $serial_no
29+
USE_SDK_MOCK=true flutter test integration_tests/mocked/master_test.dart -r expanded
6930
test_exit_code=$?
7031

7132
cd $currentFolder
7233

7334
./scripts/remove-mocks.sh
7435

7536
echo "Shutting down simulator $device_id ..."
76-
adb -s $serial_no emu kill
77-
echo "Simulator $device_name shut down"
78-
wait
79-
echo "Remove avd: "
80-
avdmanager delete avd -n testAVD
8137

8238
exit $test_exit_code

test_app/android/app_utils/src/main/java/io/dolby/asserts/AssertUtils.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,28 @@ public static boolean isInteger(String obj) {
2222

2323
public static<T> void compareWithExpectedValue(T actual, T expected, String errorMsg) throws MethodDelegate.AssertionFailed {
2424
StackTraceElement caller = Thread.currentThread().getStackTrace()[3];
25+
if (actual == null && expected == null) {
26+
return;
27+
}
2528
if (actual == null && expected != actual) {
26-
throw new MethodDelegate.AssertionFailed(actual, expected, errorMsg, caller.getFileName(), caller.getMethodName(), caller.getLineNumber());
29+
throw new MethodDelegate.AssertionFailed(toString(actual), toString(expected), errorMsg, caller.getFileName(), caller.getMethodName(), caller.getLineNumber());
2730
}
2831
if (!actual.getClass().isInstance(expected)) {
2932
throw new ClassCastException("Object type doesn't match, actual: " + actual.getClass().getSimpleName() + ", expected: " + expected.getClass().getSimpleName() + ", where: " + caller.getFileName() + " method: " + caller.getMethodName() + " line: " + caller.getLineNumber()) ;
3033
}
3134
if (expected.getClass().isPrimitive() && expected != actual ) {
32-
throw new MethodDelegate.AssertionFailed(actual, expected, errorMsg, caller.getFileName(), caller.getMethodName(), caller.getLineNumber());
35+
throw new MethodDelegate.AssertionFailed(toString(actual), toString(expected), errorMsg, caller.getFileName(), caller.getMethodName(), caller.getLineNumber());
3336
}
3437
if (!expected.equals(actual)) {
35-
throw new MethodDelegate.AssertionFailed(actual, expected, errorMsg, caller.getFileName(), caller.getMethodName(), caller.getLineNumber());
38+
throw new MethodDelegate.AssertionFailed(toString(actual), toString(expected), errorMsg, caller.getFileName(), caller.getMethodName(), caller.getLineNumber());
3639
}
3740

3841
}
3942

43+
private static<T> String toString(T obj) {
44+
return obj == null ? "NULL" : obj.toString();
45+
}
46+
4047
public static double getDouble(Object x) {
4148
if (x instanceof Integer) {
4249
return ((Integer) x).doubleValue();

0 commit comments

Comments
 (0)