/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import Foundation
import linphonesw
import AVFoundation
class ConferenceViewModel {
let core = Core.get()
static let shared = ConferenceViewModel()
let isConferencePaused = MutableLiveData()
let canResumeConference = MutableLiveData()
let isMeConferenceFocus = MutableLiveData()
let isMeAdmin = MutableLiveData()
let conferenceAddress = MutableLiveData()
let conferenceParticipants = MutableLiveData<[ConferenceParticipantData]>()
let conferenceParticipantDevices = MutableLiveData<[ConferenceParticipantDeviceData]>()
let conferenceDisplayMode = MutableLiveData()
let isInConference = MutableLiveData()
let isVideoConference = MutableLiveData()
let isRecording = MutableLiveData()
let isRemotelyRecorded = MutableLiveData()
let subject = MutableLiveData()
let conference = MutableLiveData()
let maxParticipantsForMosaicLayout = ConfigManager.instance().lpConfigIntForKey(key: "max_conf_part_mosaic_layout",defaultValue: 6)
private var conferenceDelegate : ConferenceDelegateStub?
private var coreDelegate : CoreDelegateStub?
init () {
conferenceDelegate = ConferenceDelegateStub(onParticipantAdded: { (conference: Conference, participant: Participant)in
if (conference.isMe(uri: participant.address!)) {
Log.i("[Conference] \(conference) Entered conference")
self.isConferencePaused.value = false
} else {
Log.i("[Conference] \(conference) Participant \(participant) added")
}
self.updateParticipantsList(conference)
self.updateParticipantsDevicesList(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
if (conference.isMe(uri: participant.address!)) {
Log.i("[Conference] \(conference) \(participant) Left conference")
self.isConferencePaused.value = true
} else {
Log.i("[Conference] \(conference) \(participant) Participant removed")
}
self.updateParticipantsList(conference)
self.updateParticipantsDevicesList(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)
}
)
coreDelegate = CoreDelegateStub(
onConferenceStateChanged: { (core, conference, state) in
Log.i("[Conference] \(conference) Conference state changed: \(state)")
self.isConferencePaused.value = !conference.isIn
self.canResumeConference.value = true // TODO: How can this value be false?
self.isVideoConference.value = conference.currentParams?.isVideoEnabled == true
if (state == Conference.State.Instantiated) {
self.conference.value = conference
self.isInConference.value = true
conference.addDelegate(delegate: self.conferenceDelegate!)
} else if (state == Conference.State.Created) {
self.updateParticipantsList(conference)
self.updateParticipantsDevicesList(conference)
self.isMeConferenceFocus.value = conference.me?.isFocus == true
self.isMeAdmin.value = conference.me?.isAdmin == true
self.conferenceAddress.value = conference.conferenceAddress
self.subject.value = conference.subject.isEmpty ? (
conference.me?.isFocus == true ? (
VoipTexts.conference_local_title
) : (
VoipTexts.conference_default_title
)
) : (
conference.subject
)
} else if (state == Conference.State.Terminated || state == Conference.State.TerminationFailed) {
self.isInConference.value = false
self.isVideoConference.value = false
conference.removeDelegate(delegate: self.conferenceDelegate!)
self.conferenceParticipants.value?.forEach{ $0.destroy()}
self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
self.conferenceParticipants.value = []
self.conferenceParticipantDevices.value = []
}
let layout = conference.layout == .None ? .Grid : conference.layout
self.conferenceDisplayMode.value = layout
Log.i("[Conference] \(conference) Conference current layout is: \(layout)")
}
)
Core.get().addDelegate(delegate: coreDelegate!)
conferenceParticipants.value = []
conferenceParticipantDevices.value = []
conferenceDisplayMode.value = .Grid
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)")
self.conference.value = conference
conference.addDelegate(delegate: self.conferenceDelegate!)
isInConference.value = true
isConferencePaused.value = !conference.isIn
isMeConferenceFocus.value = conference.me?.isFocus == true
isMeAdmin.value = conference.me?.isAdmin == true
isVideoConference.value = conference.currentParams?.isVideoEnabled == true
conferenceAddress.value = conference.conferenceAddress
if (!conference.subject.isEmpty) {
subject.value = conference.subject
}
let layout = conference.layout == .None ? .Grid : conference.layout
conferenceDisplayMode.value = layout
Log.i("[Conference] \(conference) Conference current layout is: \(layout)")
updateParticipantsList(conference)
updateParticipantsDevicesList(conference)
}
}
func destroy() {
core.removeDelegate(delegate: self.coreDelegate!)
self.conferenceParticipants.value?.forEach{ $0.destroy()}
self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
}
func pauseConference() {
let defaultProxyConfig = core.defaultProxyConfig
let localAddress = defaultProxyConfig?.identityAddress
let participants : [Address] = []
let remoteConference = core.searchConference(params: nil, localAddr: localAddress, remoteAddr: conferenceAddress.value, participants: participants)
let localConference = core.searchConference(params: nil, localAddr: conferenceAddress.value, remoteAddr: conferenceAddress.value, participants: participants)
let conference = remoteConference != nil ? remoteConference : localConference
if (conference != nil) {
Log.i("[Conference] Leaving conference with address \(conference) temporarily")
conference!.leave()
} else {
Log.w("[Conference] Unable to find conference with address \(conference)")
}
}
func resumeConference() {
let defaultProxyConfig = core.defaultProxyConfig
let localAddress = defaultProxyConfig?.identityAddress
let participants : [Address] = []
let remoteConference = core.searchConference(params: nil, localAddr: localAddress, remoteAddr: conferenceAddress.value, participants: participants)
let localConference = core.searchConference(params: nil, localAddr: conferenceAddress.value, remoteAddr: conferenceAddress.value, participants: participants)
if let conference = remoteConference != nil ? remoteConference : localConference {
Log.i("[Conference] Entering again conference with address \(conference)")
conference.enter()
} else {
Log.w("[Conference] Unable to find conference with address \(conference)")
}
}
func togglePlayPause () {
if (isConferencePaused.value == true) {
resumeConference()
isConferencePaused.value = false
} else {
pauseConference()
isConferencePaused.value = true
}
}
func toggleRecording() {
guard let conference = core.conference else {
Log.e("[Conference] Failed to find conference!")
return
}
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
}
private func updateParticipantsList(_ conference: Conference) {
self.conferenceParticipants.value?.forEach{ $0.destroy()}
var participants :[ConferenceParticipantData] = []
let participantsList = conference.participantList
Log.i("[Conference] \(conference) Conference has \(participantsList.count) participants")
participantsList.forEach { (participant) in
let participantDevices = participant.devices
Log.i("[Conference] \(conference) Participant found: \(participant) with \(participantDevices.count) device(s)")
let participantData = ConferenceParticipantData(conference: conference, participant: participant)
participants.append(participantData)
}
conferenceParticipants.value = participants
}
private func updateParticipantsDevicesList(_ conference: Conference) {
self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
var devices :[ConferenceParticipantDeviceData] = []
let participantsList = conference.participantList
Log.i("[Conference] \(conference) Conference has \(participantsList.count) participants")
participantsList.forEach { (participant) in
let participantDevices = participant.devices
Log.i("[Conference] \(conference) Participant found: \(participant) with \(participantDevices.count) device(s)")
participantDevices.forEach { (device) in
Log.i("[Conference] \(conference) Participant device found: \(device.name) (\(device.address!.asStringUriOnly()))")
let deviceData = ConferenceParticipantDeviceData(participantDevice: device, isMe: false)
devices.append(deviceData)
}
}
conference.me?.devices.forEach { (device) in
Log.i("[Conference] \(conference) Participant device for myself found: \(device.name) (\(device.address!.asStringUriOnly()))")
let deviceData = ConferenceParticipantDeviceData(participantDevice: device, isMe: true)
devices.append(deviceData)
}
conferenceParticipantDevices.value = devices
}
}
enum FlexDirection {
case ROW
case ROW_REVERSE
case COLUMN
case COLUMN_REVERSE
}