mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-17 11:08:06 +00:00
ConferenceViewModel updates (aligned with Android)
This commit is contained in:
parent
38a9e888da
commit
f452f010bb
5 changed files with 223 additions and 111 deletions
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
|
||||
extension Optional {
|
||||
var logable: Any {
|
||||
var orNil: Any {
|
||||
switch self {
|
||||
case .none:
|
||||
return "<nil>|⭕️"
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class CallData {
|
|||
let localAddress = try?Factory.Instance.createAddress(addr: localSipUri),
|
||||
let remoteSipAddress = try?Factory.Instance.createAddress(addr: remoteSipUri)
|
||||
else {
|
||||
Log.e("[Call] Failed to get either local \(localSipUri.logable) or remote \(remoteSipUri.logable) SIP address!")
|
||||
Log.e("[Call] Failed to get either local \(localSipUri.orNil) or remote \(remoteSipUri.orNil) SIP address!")
|
||||
return
|
||||
}
|
||||
do {
|
||||
|
|
|
|||
|
|
@ -32,69 +32,104 @@ class ConferenceViewModel {
|
|||
let isConferenceLocallyPaused = MutableLiveData<Bool>()
|
||||
let isVideoConference = MutableLiveData<Bool>()
|
||||
let isMeAdmin = MutableLiveData<Bool>()
|
||||
|
||||
|
||||
let conference = MutableLiveData<Conference>()
|
||||
let conferenceCreationPending = MutableLiveData<Bool>()
|
||||
let conferenceParticipants = MutableLiveData<[ConferenceParticipantData]>()
|
||||
let conferenceParticipantDevices = MutableLiveData<[ConferenceParticipantDeviceData]>()
|
||||
let conferenceDisplayMode = MutableLiveData<ConferenceLayout>()
|
||||
|
||||
let isRecording = MutableLiveData<Bool>()
|
||||
let isRemotelyRecorded = MutableLiveData<Bool>()
|
||||
|
||||
let participantAdminStatusChangedEvent = MutableLiveData<ConferenceParticipantData>()
|
||||
|
||||
let maxParticipantsForMosaicLayout = ConfigManager.instance().lpConfigIntForKey(key: "max_conf_part_mosaic_layout",defaultValue: 6)
|
||||
|
||||
let speakingParticipant = MutableLiveData<ConferenceParticipantDeviceData>()
|
||||
|
||||
private var conferenceDelegate : ConferenceDelegateStub?
|
||||
private var coreDelegate : CoreDelegateStub?
|
||||
|
||||
init () {
|
||||
conferenceDelegate = ConferenceDelegateStub(onParticipantAdded: { (conference: Conference, participant: Participant) in
|
||||
Log.i("[Conference] \(conference) Participant \(participant) added")
|
||||
self.updateParticipantsList(conference)
|
||||
let count = self.conferenceParticipantDevices.value!.count
|
||||
if (count > self.maxParticipantsForMosaicLayout) {
|
||||
Log.w("[Conference] \(conference) More than \(self.maxParticipantsForMosaicLayout) participants \(count), forcing active speaker layout")
|
||||
self.conferenceDisplayMode.value = .ActiveSpeaker
|
||||
conferenceDelegate = ConferenceDelegateStub(
|
||||
onParticipantAdded: { (conference: Conference, participant: Participant) in
|
||||
Log.i("[Conference] \(conference) Participant \(participant) added")
|
||||
self.updateParticipantsList(conference)
|
||||
let count = self.conferenceParticipantDevices.value!.count
|
||||
if (count > self.maxParticipantsForMosaicLayout) {
|
||||
Log.w("[Conference] \(conference) More than \(self.maxParticipantsForMosaicLayout) participants \(count), forcing active speaker layout")
|
||||
self.conferenceDisplayMode.value = .ActiveSpeaker
|
||||
}
|
||||
},
|
||||
onParticipantRemoved: {(conference: Conference, participant: Participant) in
|
||||
Log.i("[Conference] \(conference) \(participant) Participant removed")
|
||||
self.updateParticipantsList(conference)
|
||||
},
|
||||
onParticipantDeviceAdded: {(conference: Conference, participantDevice: ParticipantDevice) in
|
||||
Log.i("[Conference] \(conference) Participant device \(participantDevice) added")
|
||||
self.addParticipantDevice(device: participantDevice)
|
||||
|
||||
},
|
||||
onParticipantDeviceRemoved: { (conference: Conference, participantDevice: ParticipantDevice) in
|
||||
Log.i("[Conference] \(conference) Participant device \(participantDevice) removed")
|
||||
self.removeParticipantDevice(device: participantDevice)
|
||||
},
|
||||
onParticipantAdminStatusChanged: { (conference: Conference, participant: Participant) in
|
||||
Log.i("[Conference] \(conference) Participant admin status changed")
|
||||
self.isMeAdmin.value = conference.me?.isAdmin
|
||||
self.updateParticipantsList(conference)
|
||||
if let participantData = self.conferenceParticipants.value?.filter ({$0.participant.address!.weakEqual(address2: participant.address!)}).first {
|
||||
self.participantAdminStatusChangedEvent.value = participantData
|
||||
} else {
|
||||
Log.w("[Conference] Failed to find participant [\(participant.address!.asStringUriOnly())] in conferenceParticipants list")
|
||||
}
|
||||
},
|
||||
onParticipantDeviceLeft: { (conference: Conference, device: ParticipantDevice) in
|
||||
Log.i("[Conference] onParticipantDeviceJoined Entered conference")
|
||||
self.isConferenceLocallyPaused.value = true
|
||||
},
|
||||
onParticipantDeviceJoined: { (conference: Conference, device: ParticipantDevice) in
|
||||
Log.i("[Conference] onParticipantDeviceJoined Entered conference")
|
||||
self.isConferenceLocallyPaused.value = false
|
||||
},
|
||||
onStateChanged: { (conference: Conference, state: Conference.State) in
|
||||
Log.i("[Conference] State changed: \(state)")
|
||||
self.isVideoConference.value = conference.currentParams?.isVideoEnabled
|
||||
if (state == .Created) {
|
||||
self.configureConference(conference)
|
||||
self.conferenceCreationPending.value = false
|
||||
}
|
||||
if (state == .TerminationPending) {
|
||||
self.terminateConference(conference)
|
||||
}
|
||||
},
|
||||
onSubjectChanged: { (conference: Conference, subject: String) in
|
||||
self.subject.value = subject
|
||||
},
|
||||
onParticipantDeviceIsSpeakingChanged: { (conference: Conference, participantDevice: ParticipantDevice, isSpeaking:Bool) in
|
||||
Log.i("[Conference] Participant [\(participantDevice.address!.asStringUriOnly())] is speaking = \(isSpeaking)")
|
||||
if (isSpeaking) {
|
||||
if let device = self.conferenceParticipantDevices.value?.filter ({
|
||||
$0.participantDevice.address!.weakEqual(address2: participantDevice.address!)
|
||||
}).first {
|
||||
self.speakingParticipant.value = device
|
||||
} else {
|
||||
Log.w("[Conference] Participant device [\((participantDevice.address?.asStringUriOnly()).orNil)] is speaking but couldn't find it in devices list")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}, onParticipantRemoved: {(conference: Conference, participant: Participant) in
|
||||
Log.i("[Conference] \(conference) \(participant) Participant removed")
|
||||
self.updateParticipantsList(conference)
|
||||
}, onParticipantDeviceAdded: {(conference: Conference, participantDevice: ParticipantDevice) in
|
||||
Log.i("[Conference] \(conference) Participant device \(participantDevice) added")
|
||||
self.updateParticipantsDevicesList(conference)
|
||||
}, onParticipantDeviceRemoved: { (conference: Conference, participantDevice: ParticipantDevice) in
|
||||
Log.i("[Conference] \(conference) Participant device \(participantDevice) removed")
|
||||
self.updateParticipantsDevicesList(conference)
|
||||
}, onParticipantAdminStatusChanged: { (conference: Conference, participant: Participant) in
|
||||
Log.i("[Conference] \(conference) Participant admin status changed")
|
||||
self.isMeAdmin.value = conference.me?.isAdmin
|
||||
self.updateParticipantsList(conference)
|
||||
}, onParticipantDeviceLeft: { (conference: Conference, device: ParticipantDevice) in
|
||||
Log.i("[Conference] onParticipantDeviceJoined Entered conference")
|
||||
self.isConferenceLocallyPaused.value = true
|
||||
}, onParticipantDeviceJoined: { (conference: Conference, device: ParticipantDevice) in
|
||||
Log.i("[Conference] onParticipantDeviceJoined Entered conference")
|
||||
self.isConferenceLocallyPaused.value = false
|
||||
}, onSubjectChanged: { (conference: Conference, subject: String) in
|
||||
self.subject.value = subject
|
||||
}
|
||||
)
|
||||
|
||||
coreDelegate = CoreDelegateStub(
|
||||
onConferenceStateChanged: { (core, conference, state) in
|
||||
Log.i("[Conference] \(conference) Conference state changed: \(state)")
|
||||
self.isVideoConference.value = conference.currentParams?.videoEnabled == true
|
||||
|
||||
if (state == Conference.State.Instantiated) {
|
||||
self.conferenceCreationPending.value = true
|
||||
self.initConference(conference)
|
||||
} else if (state == Conference.State.Created) {
|
||||
self.initConference(conference)
|
||||
self.configureConference(conference)
|
||||
} else if (state == Conference.State.Terminated || state == Conference.State.TerminationFailed) {
|
||||
self.terminateConference(conference)
|
||||
|
||||
}
|
||||
|
||||
let layout = conference.layout == .Legacy ? .Grid : conference.layout
|
||||
self.conferenceDisplayMode.value = layout
|
||||
Log.i("[Conference] \(conference) Conference current layout is: \(layout)")
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -105,28 +140,47 @@ class ConferenceViewModel {
|
|||
subject.value = VoipTexts.conference_default_title
|
||||
|
||||
if let conference = core.conference != nil ? core.conference : core.currentCall?.conference {
|
||||
Log.i("[Conference] Found an existing conference: \(conference)")
|
||||
initConference(conference)
|
||||
configureConference(conference)
|
||||
Log.i("[Conference] Found an existing conference: \(conference) in state \(conference.state)")
|
||||
|
||||
if (conference.state != .TerminationPending && conference.state != .Terminated) {
|
||||
initConference(conference)
|
||||
if (conference.state == Conference.State.Created) {
|
||||
configureConference(conference)
|
||||
} else {
|
||||
conferenceCreationPending.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
func pauseConference() {
|
||||
Log.i("[Conference] Leaving conference with address \(conference) temporarily")
|
||||
let _ = conference.value?.leave()
|
||||
}
|
||||
|
||||
func resumeConference() {
|
||||
Log.i("[Conference] entering conference with address \(conference)")
|
||||
let _ = conference.value?.enter()
|
||||
}
|
||||
|
||||
func toggleRecording() {
|
||||
if (conference.value?.isRecording == true) {
|
||||
Log.i("[Conference] Stopping conference recording")
|
||||
let _ = conference.value?.stopRecording()
|
||||
} else {
|
||||
let writablePath = AppManager.recordingFilePathFromCall(address: (conference.value?.conferenceAddress!.asString())!)
|
||||
Log.i("[Conference] Starting recording in file $path")
|
||||
let _ = conference.value?.startRecording(path: writablePath)
|
||||
}
|
||||
isRecording.value = conference.value?.isRecording
|
||||
}
|
||||
|
||||
func initConference(_ conference: Conference) {
|
||||
conferenceExists.value = true
|
||||
conferenceExists.value = true
|
||||
self.conference.value = conference
|
||||
conference.addDelegate(delegate: self.conferenceDelegate!)
|
||||
isRecording.value = conference.isRecording
|
||||
}
|
||||
|
||||
func terminateConference(_ conference: Conference) {
|
||||
conferenceExists.value = false
|
||||
isVideoConference.value = false
|
||||
self.conferenceParticipants.value?.forEach{ $0.destroy()}
|
||||
self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
|
||||
conferenceParticipants.value = []
|
||||
conferenceParticipantDevices.value = []
|
||||
isRecording.value = conference.isRecording
|
||||
updateConferenceLayout(conference: conference)
|
||||
}
|
||||
|
||||
func configureConference(_ conference: Conference) {
|
||||
|
|
@ -136,7 +190,7 @@ class ConferenceViewModel {
|
|||
isConferenceLocallyPaused.value = !conference.isIn
|
||||
self.isMeAdmin.value = conference.me?.isAdmin == true
|
||||
isVideoConference.value = conference.currentParams?.videoEnabled == true
|
||||
|
||||
|
||||
self.subject.value = conference.subject.isEmpty ? (
|
||||
conference.me?.isFocus == true ? (
|
||||
VoipTexts.conference_local_title
|
||||
|
|
@ -149,44 +203,55 @@ class ConferenceViewModel {
|
|||
}
|
||||
|
||||
|
||||
|
||||
func pauseConference() {
|
||||
Log.i("[Conference] Leaving conference with address \(conference) temporarily")
|
||||
conference.value?.leave()
|
||||
}
|
||||
|
||||
func resumeConference() {
|
||||
Log.i("[Conference] entering conference with address \(conference)")
|
||||
conference.value?.enter()
|
||||
}
|
||||
|
||||
func togglePlayPause () {
|
||||
if (isConferenceLocallyPaused.value == true) {
|
||||
resumeConference()
|
||||
isConferenceLocallyPaused.value = false
|
||||
} else {
|
||||
pauseConference()
|
||||
isConferenceLocallyPaused.value = true
|
||||
}
|
||||
}
|
||||
|
||||
func toggleRecording() {
|
||||
guard let conference = conference.value else {
|
||||
Log.e("[Conference] Failed to find conference!")
|
||||
func addCallsToConference() {
|
||||
Log.i("[Conference] Trying to merge all calls into existing conference")
|
||||
guard let conf = conference.value else {
|
||||
return
|
||||
}
|
||||
/* frogtrust has is own recording method
|
||||
if (conference.isRecording == true) {
|
||||
conference.stopRecording()
|
||||
} else {
|
||||
let path = AppManager.recordingFilePathFromCall(address: conference.conferenceAddress?.asStringUriOnly() ?? "")
|
||||
Log.i("[Conference] Starting recording \(conference) in file \(path)")
|
||||
conference.startRecording(path: path)
|
||||
}*/
|
||||
|
||||
isRecording.value = conference.isRecording
|
||||
core.calls.forEach { call in
|
||||
if (call.conference == nil) {
|
||||
try? conf.addParticipant(call: call)
|
||||
}
|
||||
}
|
||||
if (conf.isIn) {
|
||||
Log.i("[Conference] Conference was paused, resuming it")
|
||||
let _ = conf.enter()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func changeLayout(layout: ConferenceLayout) {
|
||||
Log.i("[Conference] Trying to change conference layout to $layout")
|
||||
if let conference = conference.value {
|
||||
conference.layout = layout
|
||||
updateConferenceLayout(conference: conference)
|
||||
} else {
|
||||
Log.e("[Conference] Conference is null in ConferenceViewModel")
|
||||
}
|
||||
}
|
||||
|
||||
private func updateConferenceLayout(conference: Conference) {
|
||||
conferenceDisplayMode.value = conference.layout == .Legacy ? .Grid : conference.layout
|
||||
let list = sortDevicesDataList(devices: conferenceParticipantDevices.value!)
|
||||
conferenceParticipantDevices.value = list
|
||||
Log.i("[Conference] Conference current layout is: \(conference.layout)")
|
||||
}
|
||||
|
||||
|
||||
|
||||
func terminateConference(_ conference: Conference) {
|
||||
conferenceExists.value = false
|
||||
isVideoConference.value = false
|
||||
|
||||
conference.removeDelegate(delegate: conferenceDelegate!)
|
||||
|
||||
self.conferenceParticipants.value?.forEach{ $0.destroy()}
|
||||
self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
|
||||
conferenceParticipants.value = []
|
||||
conferenceParticipantDevices.value = []
|
||||
}
|
||||
|
||||
|
||||
private func updateParticipantsList(_ conference: Conference) {
|
||||
self.conferenceParticipants.value?.forEach{ $0.destroy()}
|
||||
var participants :[ConferenceParticipantData] = []
|
||||
|
|
@ -232,6 +297,68 @@ class ConferenceViewModel {
|
|||
conferenceParticipantDevices.value = devices
|
||||
}
|
||||
|
||||
private func addParticipantDevice(device: ParticipantDevice) {
|
||||
var devices :[ConferenceParticipantDeviceData] = []
|
||||
conferenceParticipantDevices.value?.forEach{devices.append($0)}
|
||||
|
||||
if let deviceAddress = device.address, let _ = devices.filter({ $0.participantDevice.address!.weakEqual(address2: deviceAddress)}).first {
|
||||
Log.e("[Conference] Participant is already in devices list: \(device.name) (\((device.address?.asStringUriOnly()) ?? "nil")")
|
||||
return
|
||||
}
|
||||
|
||||
Log.i("[Conference] New participant device found: \(device.name) (\((device.address?.asStringUriOnly()).orNil)")
|
||||
let deviceData = ConferenceParticipantDeviceData(participantDevice: device, isMe: false)
|
||||
devices.append(deviceData)
|
||||
|
||||
let sortedDevices = sortDevicesDataList(devices: devices)
|
||||
|
||||
if (speakingParticipant.value == nil) {
|
||||
speakingParticipant.value = deviceData
|
||||
}
|
||||
|
||||
conferenceParticipantDevices.value = sortedDevices
|
||||
}
|
||||
|
||||
private func removeParticipantDevice(device: ParticipantDevice) {
|
||||
let devices = conferenceParticipantDevices.value?.filter {
|
||||
$0.participantDevice.address?.asStringUriOnly() != device.address?.asStringUriOnly()
|
||||
}
|
||||
if (devices?.count == conferenceParticipantDevices.value?.count) {
|
||||
Log.e("[Conference] Failed to remove participant device: \(device.name) (\((device.address?.asStringUriOnly()).orNil)")
|
||||
} else {
|
||||
Log.i("[Conference] Participant device removed: \(device.name) (\((device.address?.asStringUriOnly()).orNil)")
|
||||
}
|
||||
|
||||
conferenceParticipantDevices.value = devices
|
||||
}
|
||||
|
||||
|
||||
private func sortDevicesDataList(devices: [ConferenceParticipantDeviceData]) -> [ConferenceParticipantDeviceData] {
|
||||
if let meDeviceData = devices.filter({$0.isMe}).first {
|
||||
var devicesWithoutMe = devices.filter { !$0.isMe }
|
||||
if (conferenceDisplayMode.value == .ActiveSpeaker) {
|
||||
devicesWithoutMe.insert(meDeviceData, at: 0)
|
||||
} else {
|
||||
devicesWithoutMe.append(meDeviceData)
|
||||
}
|
||||
return devicesWithoutMe
|
||||
}
|
||||
return devices
|
||||
}
|
||||
|
||||
|
||||
func togglePlayPause () {
|
||||
if (isConferenceLocallyPaused.value == true) {
|
||||
resumeConference()
|
||||
isConferenceLocallyPaused.value = false
|
||||
} else {
|
||||
pauseConference()
|
||||
isConferenceLocallyPaused.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// Review below (dynamic add/remove)
|
||||
|
||||
func updateParticipants(addresses:[Address]) {
|
||||
guard let conference = conference.value else {
|
||||
Log.w("[Conference Participants] conference not set, can't update participants")
|
||||
|
|
@ -252,7 +379,7 @@ class ConferenceViewModel {
|
|||
try conference.participantList.forEach { participant in
|
||||
let member = addresses.filter { $0.asStringUriOnly() == participant.address?.asStringUriOnly() }.first
|
||||
if (member == nil) {
|
||||
Log.w("[Conference Participants] Participant \(participant.address?.asStringUriOnly()) will be removed from conference")
|
||||
Log.w("[Conference Participants] Participant \((participant.address?.asStringUriOnly()).orNil) will be removed from conference")
|
||||
try conference.removeParticipant(participant: participant)
|
||||
}
|
||||
}
|
||||
|
|
@ -261,19 +388,6 @@ class ConferenceViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
func addCallsToConference() {
|
||||
Log.i("[Conference] Trying to merge all calls into existing conference")
|
||||
guard let conf = conference.value else {
|
||||
return
|
||||
}
|
||||
core.calls.forEach { call in
|
||||
if (call.conference == nil) {
|
||||
try? conf.addParticipant(call: call)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@objc class ConferenceViewModelBridge : NSObject {
|
||||
|
|
|
|||
|
|
@ -311,7 +311,6 @@ import linphonesw
|
|||
}
|
||||
|
||||
func goToChat() {
|
||||
let core = Core.get()
|
||||
guard
|
||||
let chatRoom = CallsViewModel.shared.currentCallData.value??.chatRoom
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -182,7 +182,6 @@ class VoipConferenceGridView: UIView, UICollectionViewDataSource, UICollectionVi
|
|||
if (width > 0) {
|
||||
self.grid.removeConstraints().width(width).height(height).center().done()
|
||||
}
|
||||
Log.e("cdes > \(width)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -244,7 +243,7 @@ class VoipConferenceGridView: UIView, UICollectionViewDataSource, UICollectionVi
|
|||
layout collectionViewLayout: UICollectionViewLayout,
|
||||
sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
|
||||
guard let participantsCount = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
|
||||
guard let _ = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
|
||||
return .zero
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue