Add audio only mode to conference call view

Enable FEC
Fix active speaker video
This commit is contained in:
Benoit Martins 2024-04-30 11:28:36 +02:00 committed by QuentinArguillere
parent 5c5fd2ad8d
commit f6e935c65f
5 changed files with 151 additions and 5 deletions

View file

@ -111,6 +111,8 @@ final class CoreContext: ObservableObject {
self.mCore.videoDisplayEnabled = true
self.mCore.videoPreviewEnabled = false
self.mCore.fecEnabled = true
self.mCoreSuscriptions.insert(self.mCore.publisher?.onGlobalStateChanged?.postOnMainQueue { (cbVal: (core: Core, state: GlobalState, message: String)) in
if cbVal.state == GlobalState.On {
self.hasDefaultAccount = true

View file

@ -40,4 +40,7 @@ version_check_url_root=https://www.linphone.org/releases
max_calls=10
conference_layout=1
[fec]
fec_enabled=1
## End of default rc

View file

@ -385,15 +385,25 @@ class TelecomManager: ObservableObject {
if call.conference != nil {
if call.conference!.activeSpeakerParticipantDevice != nil {
let direction = call.conference?.activeSpeakerParticipantDevice!.getStreamCapability(streamType: StreamType.Video)
self.remoteConfVideo = direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly
self.remoteConfVideo = direction == .SendRecv || direction == .SendOnly
} else if call.conference!.participantList.first != nil
&& call.conference!.participantList.first?.address != nil
&& call.conference!.participantList.first!.address!.clone()!.equal(address2: (call.conference!.me?.address)!) {
let direction = call.conference!.participantDeviceList.first!.getStreamCapability(streamType: StreamType.Video)
self.remoteConfVideo = direction == .SendRecv || direction == .SendOnly
} else if call.conference!.participantList.last != nil
&& call.conference!.participantList.last?.address != nil {
let direction = call.conference!.participantDeviceList.last!.getStreamCapability(streamType: StreamType.Video)
self.remoteConfVideo = direction == .SendRecv || direction == .SendOnly
} else {
self.remoteConfVideo = true
self.remoteConfVideo = false
}
} else {
self.remoteConfVideo = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.remoteConfVideo = call.currentParams!.videoEnabled && call.currentParams!.videoDirection == MediaDirection.SendRecv || call.currentParams!.videoDirection == MediaDirection.SendOnly
self.remoteConfVideo = call.currentParams!.videoEnabled && call.currentParams!.videoDirection == .SendRecv || call.currentParams!.videoDirection == .SendOnly
}
}

View file

@ -283,6 +283,7 @@ struct CallView: View {
VStack(spacing: 0) {
Button(action: {
optionsChangeLayout = 1
callViewModel.toggleVideoMode(isAudioOnlyMode: false)
changeLayoutSheet = false
}, label: {
HStack {
@ -312,6 +313,7 @@ struct CallView: View {
Button(action: {
optionsChangeLayout = 2
callViewModel.toggleVideoMode(isAudioOnlyMode: false)
changeLayoutSheet = false
}, label: {
HStack {
@ -339,6 +341,10 @@ struct CallView: View {
Button(action: {
optionsChangeLayout = 3
if callViewModel.videoDisplayed {
callViewModel.displayMyVideo()
}
callViewModel.toggleVideoMode(isAudioOnlyMode: true)
changeLayoutSheet = false
}, label: {
HStack {
@ -509,6 +515,10 @@ struct CallView: View {
currentOffset = (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78)
pointingUp = -(((currentOffset - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78)) / ((maxBottomSheetHeight * geometry.size.height) - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78))) - 0.5) * 2
}
.onChange(of: optionsChangeLayout) { optionsChangeLayoutValue in
currentOffset = (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78)
pointingUp = -(((currentOffset - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78)) / ((maxBottomSheetHeight * geometry.size.height) - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78))) - 0.5) * 2
}
.edgesIgnoringSafeArea(.bottom)
}
}
@ -674,8 +684,11 @@ struct CallView: View {
)
}
} else if callViewModel.isConference && !telecomManager.outgoingCallStarted && callViewModel.activeSpeakerParticipant != nil {
let heightValue = (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom)
if optionsChangeLayout == 1 && callViewModel.participantList.count <= 5 {
mosaicMode(geometry: geometry, height: (fullscreenVideo && !telecomManager.isPausedByRemote ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - (minBottomSheetHeight * geometry.size.height > 80 ? minBottomSheetHeight * geometry.size.height : 78) - 40 - 20 + geometry.safeAreaInsets.bottom))
mosaicMode(geometry: geometry, height: heightValue)
} else if optionsChangeLayout == 3 {
audioOnlyMode(geometry: geometry, height: heightValue)
} else {
activeSpeakerMode(geometry: geometry)
}
@ -1645,6 +1658,103 @@ struct CallView: View {
}
}
func audioOnlyMode(geometry: GeometryProxy, height: Double) -> some View {
VStack {
let layout = [
GridItem(.fixed((geometry.size.width/2)-10)),
GridItem(.fixed((geometry.size.width/2)-10))
]
ScrollView {
LazyVGrid(columns: layout) {
if callViewModel.myParticipantModel != nil {
HStack {
Avatar(contactAvatarModel: callViewModel.myParticipantModel!.avatarModel, avatarSize: 50, hidePresence: true)
Text(callViewModel.myParticipantModel!.name)
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundStyle(Color.white)
.default_text_style_500(styleSize: 14)
.lineLimit(1)
.padding(.horizontal, 10)
if callViewModel.myParticipantModel!.isMuted {
HStack(alignment: .center) {
Image("microphone-slash")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c800)
.frame(width: 20, height: 20)
}
.padding(2)
.background(.white)
.cornerRadius(40)
}
if callViewModel.myParticipantModel!.onPause {
Image("pause")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 25, height: 25)
}
}
.frame(height: 80)
.padding(.all, 10)
.background(Color.gray600)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(callViewModel.myParticipantModel!.isSpeaking ? .white : Color.gray600, lineWidth: 4)
)
.cornerRadius(20)
}
ForEach(0..<callViewModel.participantList.count, id: \.self) { index in
HStack {
Avatar(contactAvatarModel: callViewModel.participantList[index].avatarModel, avatarSize: 50, hidePresence: true)
Text(callViewModel.participantList[index].name)
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundStyle(Color.white)
.default_text_style_500(styleSize: 14)
.lineLimit(1)
.padding(.horizontal, 10)
if callViewModel.participantList[index].isMuted {
HStack(alignment: .center) {
Image("microphone-slash")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c800)
.frame(width: 20, height: 20)
}
.padding(2)
.background(.white)
.cornerRadius(40)
}
if callViewModel.participantList[index].onPause {
Image("pause")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 25, height: 25)
}
}
.frame(height: 80)
.padding(.all, 10)
.background(Color.gray600)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(callViewModel.participantList[index].isSpeaking ? .white : Color.gray600, lineWidth: 4)
)
.cornerRadius(20)
}
}
}
.frame(width: geometry.size.width, height: height)
}
}
// swiftlint:disable function_body_length
func bottomSheetContent(geo: GeometryProxy) -> some View {
GeometryReader { _ in
@ -1686,7 +1796,12 @@ struct CallView: View {
Spacer()
Button {
callViewModel.displayMyVideo()
if optionsChangeLayout == 3 {
optionsChangeLayout = 2
callViewModel.toggleVideoMode(isAudioOnlyMode: false)
} else {
callViewModel.displayMyVideo()
}
} label: {
HStack {
Image(callViewModel.videoDisplayed ? "video-camera" : "video-camera-slash")

View file

@ -647,6 +647,22 @@ class CallViewModel: ObservableObject {
}
}
func toggleVideoMode(isAudioOnlyMode: Bool) {
coreContext.doOnCoreQueue { core in
if self.currentCall != nil {
do {
let params = try core.createCallParams(call: self.currentCall)
params.videoEnabled = !isAudioOnlyMode
try self.currentCall!.update(params: params)
} catch {
}
}
}
}
func switchCamera() {
coreContext.doOnCoreQueue { core in
let currentDevice = core.videoDevice