// // ActiveSpeakerDetectorTests.swift // AmazonChimeSDK // // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // import Foundation @testable import AmazonChimeSDK import XCTest class ActiveSpeakerDetectorTests: XCTestCase, ActiveSpeakerPolicy, ActiveSpeakerObserver { let scores = [0.1, 0.2, 0.3, 0.4, 0.5] let volumes: [VolumeLevel] = [.muted, .notSpeaking, .low, .medium, .high] var attendees = [AttendeeInfo]() var volumeUpdates = [VolumeUpdate]() var attendeesReceived: [AttendeeInfo] = [AttendeeInfo(attendeeId: "", externalUserId: "")] var scoreChangeAttendees: [AttendeeInfo: Double] = [AttendeeInfo(attendeeId: "", externalUserId: ""): 0.0] var scoreIndex = 0 var activeSpeakerDetector = DefaultActiveSpeakerDetector(selfAttendeeId: "") private let emptyAttendeesReceivedExpectation = XCTestExpectation( description: "Is fulfilled when received no attendees in callback") private let calculateScoreExpectation = XCTestExpectation( description: "Is fulfilled when calculateScore is called") private let prioritizeBandwidthExpectation = XCTestExpectation( description: "Is fulfilled when prioritizeVideoSendBandwidthForActiveSpeaker is called") private let activeSpeakerDidDetectExpectation = XCTestExpectation( description: "Is fulfilled when activeSpeakerDidDetect is called") private let activeSpeakerScoreDidChangeExpectation = XCTestExpectation( description: "Is fulfilled when activeSpeakerScoreDidChange is called") private let oneMilliSecondInSeconds = 0.001 private let twoHundredMilliSecondsInSeconds = 0.2 private let halfSeconds = 0.5 override func setUp() { for index in 0 ... 4 { attendees.append(AttendeeInfo( attendeeId: "attendee" + String(index), externalUserId: "attendee" + String(index) )) volumeUpdates.append(VolumeUpdate(attendeeInfo: attendees[index], volumeLevel: volumes[index])) } calculateScoreExpectation.expectedFulfillmentCount = 5 prioritizeBandwidthExpectation.assertForOverFulfill = true activeSpeakerDetector = DefaultActiveSpeakerDetector(selfAttendeeId: "attendee0") activeSpeakerDetector.addActiveSpeakerObserver(policy: self, observer: self) activeSpeakerDetector.attendeesDidJoin(attendeeInfo: attendees) } override func tearDown() { activeSpeakerDetector.removeActiveSpeakerObserver(observer: self) } func testActiveSpeakerDetectorShouldOnAttendeesJoinMakeExpectedCallbacks() { // received no attendees in callback because scores are not calculated until volumes are received wait(for: [emptyAttendeesReceivedExpectation], timeout: oneMilliSecondInSeconds) XCTAssertEqual(attendeesReceived.isEmpty, true) calculateScoreExpectation.isInverted = true wait(for: [calculateScoreExpectation], timeout: oneMilliSecondInSeconds) prioritizeBandwidthExpectation.isInverted = true wait(for: [prioritizeBandwidthExpectation], timeout: oneMilliSecondInSeconds) wait(for: [activeSpeakerDidDetectExpectation], timeout: oneMilliSecondInSeconds) } func testActiveSpeakerDetectorShouldOnVolumeChangeMakeExpectedCallbacks() { activeSpeakerDetector.volumeDidChange(volumeUpdates: volumeUpdates) calculateScoreExpectation.expectedFulfillmentCount = 2 wait(for: [calculateScoreExpectation], timeout: twoHundredMilliSecondsInSeconds) wait(for: [prioritizeBandwidthExpectation], timeout: oneMilliSecondInSeconds) wait(for: [activeSpeakerDidDetectExpectation], timeout: oneMilliSecondInSeconds) XCTAssertEqual(attendeesReceived, attendees.reversed()) } func testActiveSpeakerDetectorMakesScoresCallback() { activeSpeakerDetector.volumeDidChange(volumeUpdates: volumeUpdates) wait(for: [activeSpeakerScoreDidChangeExpectation], timeout: TimeInterval(scoresCallbackIntervalMs)) XCTAssertEqual(scoreChangeAttendees.map { $0.key }.sorted(), attendees.sorted()) } func testActiveSpeakerDetectorShouldOnAttendeesLeaveReceiveCorrectActiveSpeakers() { activeSpeakerDetector.volumeDidChange(volumeUpdates: volumeUpdates) wait(for: [activeSpeakerScoreDidChangeExpectation], timeout: twoHundredMilliSecondsInSeconds) XCTAssertEqual(attendeesReceived, attendees.reversed()) activeSpeakerDetector.attendeesDidLeave(attendeeInfo: [attendees[0], attendees[1]]) let newVolumeUpdates = [volumeUpdates[2], volumeUpdates[3], volumeUpdates[4]] scoreIndex = 0 activeSpeakerDetector.volumeDidChange(volumeUpdates: newVolumeUpdates) let adhocWaitingExpectation = XCTestExpectation(description: "Is fulfilled after adhoc waiting") Timer.scheduledTimer(withTimeInterval: twoHundredMilliSecondsInSeconds, repeats: false, block: { _ in adhocWaitingExpectation.fulfill() }) wait(for: [adhocWaitingExpectation], timeout: halfSeconds) XCTAssertEqual(attendeesReceived, [attendees[4], attendees[3], attendees[2]]) } func testActiveSpeakerDetectorShouldRemoveActiveSpeakerObserver() { activeSpeakerDetector.removeActiveSpeakerObserver(observer: self) activeSpeakerDetector.attendeesDidJoin(attendeeInfo: attendees) activeSpeakerDetector.volumeDidChange(volumeUpdates: volumeUpdates) wait(for: [activeSpeakerDidDetectExpectation], timeout: oneMilliSecondInSeconds) XCTAssertEqual(attendeesReceived, []) } func calculateScore(attendeeInfo: AttendeeInfo, volume: VolumeLevel) -> Double { calculateScoreExpectation.fulfill() let returnScore = scoreIndex < 5 ? scores[scoreIndex] : 0.0 scoreIndex += 1 return returnScore } func prioritizeVideoSendBandwidthForActiveSpeaker() -> Bool { prioritizeBandwidthExpectation.fulfill() return true } var observerId: String = "fakeObserverId" func activeSpeakerDidDetect(attendeeInfo: [AttendeeInfo]) { activeSpeakerDidDetectExpectation.fulfill() if attendeeInfo.isEmpty { emptyAttendeesReceivedExpectation.fulfill() } attendeesReceived = attendeeInfo } var scoresCallbackIntervalMs: Int = 1 func activeSpeakerScoreDidChange(scores: [AttendeeInfo: Double]) { activeSpeakerScoreDidChangeExpectation.fulfill() scoreChangeAttendees = scores } }