in amazon-chime-sdk/src/main/java/com/amazonaws/services/chime/sdk/meetings/audiovideo/DefaultAudioVideoController.kt [140:220]
override fun promoteToPrimaryMeeting(
credentials: MeetingSessionCredentials,
observer: PrimaryMeetingPromotionObserver
) {
// We create a pseudo-'Wait Group' to wait for both audio and video to complete,
// and then merge their statuses
val audioVideoPromotionsToComplete = AtomicInteger()
audioVideoPromotionsToComplete.addAndGet(2)
var audioClientStatus = MeetingSessionStatus(MeetingSessionStatusCode.OK)
var videoClientStatus = MeetingSessionStatus(MeetingSessionStatusCode.OK)
val mergeAndReturnPromotionStatuses = fun (
firstStatus: MeetingSessionStatus,
secondStatus: MeetingSessionStatus
) {
// We intentionally don't differentiate status sources here
// as the preferences towards audio vs. video really doesn't matter
val mergedStatus = when {
firstStatus.statusCode != MeetingSessionStatusCode.OK -> firstStatus
secondStatus.statusCode != MeetingSessionStatusCode.OK -> secondStatus
else -> firstStatus
}
CoroutineScope(Dispatchers.Main).launch {
primaryMeetingPromotionObserver?.onPrimaryMeetingPromotion(mergedStatus)
}
}
primaryMeetingPromotionObserver = observer
// In these observers we try demoting the other client. Note that the individual controllers
// do not follow the exact same pattern of calling back on observer (with `MeetingSessionStatusCode.OK` in
// the case of explicit demotion request so we don't need to worry about any infinite loops
val audioPrimaryMeetingPromotionObserverAdapter = object : PrimaryMeetingPromotionObserver {
override fun onPrimaryMeetingPromotion(status: MeetingSessionStatus) {
audioClientStatus = status
if (audioVideoPromotionsToComplete.decrementAndGet() == 0) {
mergeAndReturnPromotionStatuses(audioClientStatus, videoClientStatus)
}
}
override fun onPrimaryMeetingDemotion(status: MeetingSessionStatus) {
videoClientController.demoteFromPrimaryMeeting()
CoroutineScope(Dispatchers.Main).launch {
primaryMeetingPromotionObserver?.onPrimaryMeetingDemotion(status)
}
}
}
val videoPrimaryMeetingPromotionObserverAdapter = object : PrimaryMeetingPromotionObserver {
override fun onPrimaryMeetingPromotion(status: MeetingSessionStatus) {
videoClientStatus = status
if (audioVideoPromotionsToComplete.decrementAndGet() == 0) {
mergeAndReturnPromotionStatuses(audioClientStatus, videoClientStatus)
}
}
override fun onPrimaryMeetingDemotion(status: MeetingSessionStatus) {
audioClientController.demoteFromPrimaryMeeting()
CoroutineScope(Dispatchers.Main).launch {
primaryMeetingPromotionObserver?.onPrimaryMeetingDemotion(status)
}
}
}
audioClientController.promoteToPrimaryMeeting(credentials, audioPrimaryMeetingPromotionObserverAdapter)
videoClientController.promoteToPrimaryMeeting(credentials, videoPrimaryMeetingPromotionObserverAdapter)
// Setup timeout in case those callbacks are never hit for extremely spurious network or server issues
Timer().schedule(timerTask {
// Setting to -1 will invalidate any late callbacks
if (audioVideoPromotionsToComplete.getAndSet(-1) != 0) {
// Try demoting to clean up separate state but remove existing cached observer to avoid repeated callbacks
primaryMeetingPromotionObserver = null
videoClientController.demoteFromPrimaryMeeting()
audioClientController.demoteFromPrimaryMeeting()
// Using observer from scope
CoroutineScope(Dispatchers.Main).launch {
observer.onPrimaryMeetingPromotion(MeetingSessionStatus(MeetingSessionStatusCode.AudioInternalServerError))
}
}
}, PRIMARY_MEETING_PROMOTION_TIMEOUT)
}