mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
EC: Native switch for audio outputs/inputs and earpiece (#4136)
* EC: native switch for audio outputs/inputs and earpiece also includes proximity sensor enabling when the earpiece is used. * test disabling and re-enabling audio on audio change * Revert "test disabling and re-enabling audio on audio change" This reverts commit 907c791b8df7009214f5c164ecf8fe7d90a8addc. * pr suggestions * pr suggestion and small refactor of the widgetAction handler
This commit is contained in:
@@ -52,15 +52,9 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
|
||||
widgetDriver = roomProxy.elementCallWidgetDriver(deviceID: deviceID)
|
||||
}
|
||||
|
||||
super.init(initialViewState: CallScreenViewState(messageHandler: Self.eventHandlerName,
|
||||
script: Self.eventHandlerInjectionScript,
|
||||
super.init(initialViewState: CallScreenViewState(script: CallScreenJavaScriptMessageName.allCasesInjectionScript,
|
||||
certificateValidator: appHooks.certificateValidatorHook))
|
||||
|
||||
state.bindings.javaScriptMessageHandler = { [weak self] message in
|
||||
guard let self, let message = message as? String else { return }
|
||||
Task { await self.widgetDriver.handleMessage(message) }
|
||||
}
|
||||
|
||||
elementCallService.actions
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] action in
|
||||
@@ -107,6 +101,13 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
NotificationCenter.default
|
||||
.publisher(for: AVAudioSession.routeChangeNotification)
|
||||
.sink { [weak self] _ in
|
||||
Task { await self?.updateOutputsListOnWeb() }
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
setupCall()
|
||||
}
|
||||
|
||||
@@ -123,6 +124,12 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
|
||||
actionsSubject.send(.pictureInPictureStopped)
|
||||
case .endCall:
|
||||
actionsSubject.send(.dismiss)
|
||||
case .mediaCapturePermissionGranted:
|
||||
Task { await updateOutputsListOnWeb() }
|
||||
case .outputDeviceSelected(deviceID: let deviceID):
|
||||
handleOutputDeviceSelected(deviceID: deviceID)
|
||||
case .widgetAction(let message):
|
||||
Task { await widgetDriver.handleMessage(message) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +139,7 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
|
||||
}
|
||||
|
||||
elementCallService.tearDownCallSession()
|
||||
UIDevice.current.isProximityMonitoringEnabled = false
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -183,6 +191,15 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
|
||||
}
|
||||
}
|
||||
|
||||
// This should always match the web app value
|
||||
private static let earpieceID = "earpiece-id"
|
||||
|
||||
private func handleOutputDeviceSelected(deviceID: String) {
|
||||
let isEarpiece = deviceID == Self.earpieceID
|
||||
MXLog.info("[handleOutputDeviceSelected] is earpiece: \(isEarpiece)")
|
||||
UIDevice.current.isProximityMonitoringEnabled = isEarpiece
|
||||
}
|
||||
|
||||
private func handleBackwardsNavigation() async {
|
||||
guard state.url != nil,
|
||||
isPictureInPictureAllowed,
|
||||
@@ -242,23 +259,29 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
|
||||
}
|
||||
}
|
||||
|
||||
private static let eventHandlerName = "elementx"
|
||||
|
||||
private static var eventHandlerInjectionScript: String {
|
||||
"""
|
||||
window.addEventListener(
|
||||
"message",
|
||||
(event) => {
|
||||
let message = {data: event.data, origin: event.origin}
|
||||
if (message.data.response && message.data.api == "toWidget"
|
||||
|| !message.data.response && message.data.api == "fromWidget") {
|
||||
window.webkit.messageHandlers.\(eventHandlerName).postMessage(JSON.stringify(message.data));
|
||||
}else{
|
||||
console.log("-- skipped event handling by the client because it is send from the client itself.");
|
||||
}
|
||||
},
|
||||
false,
|
||||
);
|
||||
"""
|
||||
/// This function updates the list of available audio outputs on the web side
|
||||
/// however since we actually handle switching the audio output through the OS,
|
||||
/// this is only used to inform the webview when the speaker is selected,
|
||||
/// so that the option to use the earpiece can be displayed.
|
||||
private func updateOutputsListOnWeb() async {
|
||||
guard let currentOutput = AVAudioSession.sharedInstance().currentRoute.outputs.first else {
|
||||
return
|
||||
}
|
||||
|
||||
let deviceList = if currentOutput.portType == .builtInSpeaker {
|
||||
// This allows the webview to display the earpiece option
|
||||
"{id: '\(currentOutput.uid)', name: '\(currentOutput.portName)', forEarpiece: true}"
|
||||
} else {
|
||||
// Doesn't matter because the switch is handled through the OS
|
||||
"{id: 'dummy', name: 'dummy'}"
|
||||
}
|
||||
|
||||
let javaScript = "window.controls.setAvailableOutputDevices([\(deviceList)])"
|
||||
do {
|
||||
let result = try await state.bindings.javaScriptEvaluator?(javaScript)
|
||||
MXLog.debug("Evaluated with result: \(String(describing: result))")
|
||||
} catch {
|
||||
MXLog.error("Received javascript evaluation error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user