Skip to content

Commit

Permalink
Merge pull request #103 from ConnectyCube/development
Browse files Browse the repository at this point in the history
release 2.3.0
  • Loading branch information
TatankaConCube committed Jun 2, 2023
2 parents 4f3273c + 251970f commit 4e62f10
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 44 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.3.0
- (iOS) Add a method for notifying the CallKit about muting/unmuting the call;
- (iOS) Improvements for audio after accepting the call from the background or killed state;
- (Dart) Add ignoring of not supported platforms;

## 2.2.4
- (iOS) Improve the audio after accepting from the background or killed state;

Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,21 @@ ConnectycubeFlutterCallKit.onCallAcceptedWhenTerminated = onCallAcceptedWhenTerm
```

!> Attention: the functions `onCallRejectedWhenTerminated` and `onCallAcceptedWhenTerminated` must
be a top-level function and cannot be anonymous
be a top-level functions and cannot be anonymous. Mark these callbacks with the `@pragma('vm:entry-point')`
annotation to allow using them from the native code.

#### Listen for the actions performed on the CallKit screen (iOS only):

##### Listening for the muting/unmuting the call

```dart
ConnectycubeFlutterCallKit.onCallMuted = onCallMuted;
Future<void> onCallMuted(bool mute, String uuid) async {
// [mute] - `true` - the call was muted on the CallKit screen, `false` - the call was unmuted
// [uuid] - the id of the muted/unmuted call
}
```

### Get the call state

Expand All @@ -155,7 +169,7 @@ var sessionId = await ConnectycubeFlutterCallKit.getLastCallId();
```
Then you can get the state of this call using `getCallState`.

### Notify the plugin about processing the call on the Flutter app side
### Notify the plugin about user actions concerning the call on the Flutter app side

For dismissing the Incoming call screen (or the Call Kit for iOS) you should notify the plugin about
these events.
Expand All @@ -166,6 +180,13 @@ ConnectycubeFlutterCallKit.reportCallAccepted(sessionId: uuid);
ConnectycubeFlutterCallKit.reportCallEnded(sessionId: uuid);
```

Notifying the plugin about muting/unmuting the call (iOS only):
```dart
var muted = true; // set `true` if the call was muted or `false` if the call was unmuted
ConnectycubeFlutterCallKit.reportCallMuted(sessionId: uuid, muted: muted);
```

### Clear call data
After finishing the call you can clear all data on the plugin side related to this call, call the
next code for it
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group 'com.connectycube.flutter.connectycube_flutter_call_kit'
version '2.2.4'
version '2.3.0'

buildscript {
ext.kotlin_version = '1.6.21'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@ class ConnectycubeFlutterCallKitPlugin : FlutterPlugin, MethodCallHandler,
}
}

"muteCall" -> {
result.success(null)
}

else ->
result.notImplemented()

Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: Demonstrates how to use the connectycube_flutter_call_kit plugin.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

environment:
sdk: ">=2.7.0 <3.0.0"
sdk: ">=2.12.0 <3.0.0"

dependencies:
flutter:
Expand Down
82 changes: 50 additions & 32 deletions ios/Classes/CallKitController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,12 @@ class CallKitController : NSObject {
if(icon != nil){
let iconImage = UIImage(named: icon!)
let iconData = iconImage?.pngData()

providerConfiguration.iconTemplateImageData = iconData
}
}

func reportIncomingCall(
@objc func reportIncomingCall(
uuid: String,
callType: Int,
callInitiatorId: Int,
Expand All @@ -95,7 +96,8 @@ class CallKitController : NSObject {
userInfo: String?,
completion: ((Error?) -> Void)?
) {
print("[CallKitController][reportIncomingCall] call data: \(uuid), \(callType), \(callInitiatorId), \(callInitiatorName), \(opponents), \(userInfo ?? ""), ")
print("[CallKitController][reportIncomingCall] call data: \(uuid), \(callType), \(callInitiatorId), \(callInitiatorName), \(opponents), \(userInfo ?? "nil")")

let update = CXCallUpdate()
update.localizedCallerName = callInitiatorName
update.remoteHandle = CXHandle(type: .generic, value: uuid)
Expand All @@ -105,11 +107,12 @@ class CallKitController : NSObject {
update.supportsHolding = false
update.supportsDTMF = false

configureAudioSession(active: true)
if (self.currentCallData["session_id"] == nil || self.currentCallData["session_id"] as! String != uuid) {
print("[CallKitController][reportIncomingCall] report new call: \(uuid)")

provider.reportNewIncomingCall(with: UUID(uuidString: uuid)!, update: update) { error in
completion?(error)

if(error == nil){
self.configureAudioSession(active: true)

Expand All @@ -126,12 +129,16 @@ class CallKitController : NSObject {
}
} else if (self.currentCallData["session_id"] as! String == uuid) {
print("[CallKitController][reportIncomingCall] update existing call: \(uuid)")

provider.reportCall(with: UUID(uuidString: uuid)!, updated: update)

completion?(nil)
}
}

func reportOutgoingCall(uuid : UUID, finishedConnecting: Bool){
print("CallKitController: report outgoing call: \(uuid) connected:\(finishedConnecting)")
print("[CallKitController][reportOutgoingCall] uuid: \(uuid.uuidString.lowercased()) connected: \(finishedConnecting)")

if !finishedConnecting {
self.provider.reportOutgoingCall(with: uuid, startedConnectingAt: nil)
} else {
Expand All @@ -140,7 +147,8 @@ class CallKitController : NSObject {
}

func reportCallEnded(uuid : UUID, reason: CallEndedReason){
print("CallKitController: report call ended: \(uuid)")
print("[CallKitController][reportCallEnded] uuid: \(uuid.uuidString.lowercased())")

var cxReason : CXCallEndedReason
switch reason {
case .unanswered:
Expand All @@ -150,12 +158,14 @@ class CallKitController : NSObject {
default:
cxReason = CXCallEndedReason.failed
}

self.callStates[uuid.uuidString.lowercased()] = .rejected
self.provider.reportCall(with: uuid, endedAt: Date.init(), reason: cxReason)
}

func getCallState(uuid: String) -> CallState {
print("CallKitController: getCallState: \(self.callStates[uuid.lowercased()] ?? .unknown)")
print("[CallKitController][getCallState] uuid: \(uuid), state: \(self.callStates[uuid.lowercased()] ?? .unknown)")

return self.callStates[uuid.lowercased()] ?? .unknown
}

Expand All @@ -173,15 +183,18 @@ class CallKitController : NSObject {
}

func sendAudioInterruptionNotification(){
print("[CallKitController][sendAudioInterruptionNotification]")
var userInfo : [AnyHashable : Any] = [:]
let intrepEndeRaw = AVAudioSession.InterruptionType.ended.rawValue
userInfo[AVAudioSessionInterruptionTypeKey] = intrepEndeRaw
userInfo[AVAudioSessionInterruptionOptionKey] = AVAudioSession.InterruptionOptions.shouldResume.rawValue

NotificationCenter.default.post(name: AVAudioSession.interruptionNotification, object: self, userInfo: userInfo)
}

func configureAudioSession(active: Bool){
print("CallKitController: [configureAudioSession]")
print("[CallKitController][configureAudioSession] active: \(active)")

let audioSession = AVAudioSession.sharedInstance()

do {
Expand All @@ -190,7 +203,6 @@ class CallKitController : NSObject {
options: [
.allowBluetooth,
.allowBluetoothA2DP,
.duckOthers,
])
try audioSession.setMode(AVAudioSession.Mode.videoChat)
try audioSession.setPreferredSampleRate(44100.0)
Expand All @@ -206,26 +218,29 @@ class CallKitController : NSObject {
extension CallKitController {

func end(uuid: UUID) {
print("CallKitController: user requested end call")
print("[CallKitController][end] uuid: \(uuid.uuidString.lowercased())")

let endCallAction = CXEndCallAction(call: uuid)
let transaction = CXTransaction(action: endCallAction)

self.callStates[uuid.uuidString.lowercased()] = .rejected

requestTransaction(transaction)
}

private func requestTransaction(_ transaction: CXTransaction) {
callController.request(transaction) { error in
if let error = error {
print("CallKitController: Error requesting transaction: \(error.localizedDescription)")
print("[CallKitController][requestTransaction] Error: \(error.localizedDescription)")
} else {
print("CallKitController: Requested transaction successfully")
print("[CallKitController][requestTransaction] successfully")
}
}
}

func setHeld(uuid: UUID, onHold: Bool) {
print("CallKitController: user requested hold call")
print("[CallKitController][setHeld] uuid: \(uuid.uuidString.lowercased()), onHold: \(onHold)")

let setHeldCallAction = CXSetHeldCallAction(call: uuid, onHold: onHold)

let transaction = CXTransaction()
Expand All @@ -235,15 +250,18 @@ extension CallKitController {
}

func setMute(uuid: UUID, muted: Bool){
print("CallKitController: user requested mute call: muted - \(muted)")
print("[CallKitController][setMute] uuid: \(uuid.uuidString.lowercased()), muted: \(muted)")

let muteCallAction = CXSetMutedCallAction(call: uuid, muted: muted);
let transaction = CXTransaction()
transaction.addAction(muteCallAction)

requestTransaction(transaction)
}

func startCall(handle: String, videoEnabled: Bool, uuid: String? = nil) {
print("CallKitController: user requested start call handle:\(handle), videoEnabled: \(videoEnabled) uuid: \(uuid ?? "")")
print("[CallKitController][startCall] handle:\(handle), videoEnabled: \(videoEnabled) uuid: \(uuid ?? "nil")")

let handle = CXHandle(type: .generic, value: handle)
let callUUID = uuid == nil ? UUID() : UUID(uuidString: uuid!)
let startCallAction = CXStartCallAction(call: callUUID!, handle: handle)
Expand All @@ -257,7 +275,8 @@ extension CallKitController {
}

func answerCall(uuid: String) {
print("CallKitController: user requested answer call, uuid: \(uuid)")
print("[CallKitController][answerCall] uuid: \(uuid)")

let callUUID = UUID(uuidString: uuid)
let answerCallAction = CXAnswerCallAction(call: callUUID!)
let transaction = CXTransaction(action: answerCallAction)
Expand All @@ -275,50 +294,46 @@ extension CallKitController: CXProviderDelegate {
}

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
print("CallKitController: Answer Call \(action.callUUID.uuidString)")

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1200)) {
self.configureAudioSession(active: true)
}
print("[CallKitController][CXAnswerCallAction] callUUID: \(action.callUUID.uuidString.lowercased())")

configureAudioSession(active: true)
callStates[action.callUUID.uuidString.lowercased()] = .accepted
actionListener?(.answerCall, action.callUUID, self.currentCallData)

action.fulfill()
}

func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("CallKitController: Audio session activated")

if(currentCallData["session_id"] != nil
&& callStates[currentCallData["session_id"] as! String] == .accepted){
sendAudioInterruptionNotification()
return
}
print("[CallKitController] Audio session activated")

sendAudioInterruptionNotification()
configureAudioSession(active: true)
}

func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
print("CallKitController: Audio session deactivated")
print("[CallKitController] Audio session deactivated")
}

func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
print("CallKitController: End Call")
print("[CallKitController][CXEndCallAction]")

actionListener?(.endCall, action.callUUID, currentCallData)
callStates[action.callUUID.uuidString.lowercased()] = .rejected

action.fulfill()
}

func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
print("CallKitController: Set Held")
print("[CallKitController][CXSetHeldCallAction] callUUID: \(action.callUUID.uuidString.lowercased())")

actionListener?(.setHeld, action.callUUID, ["isOnHold": action.isOnHold])

action.fulfill()
}

func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
print("CallKitController: Mute call")
print("[CallKitController][CXSetMutedCallAction] callUUID: \(action.callUUID.uuidString.lowercased())")

if (action.isMuted){
actionListener?(.setMuted, action.callUUID, currentCallData)
} else {
Expand All @@ -329,9 +344,12 @@ extension CallKitController: CXProviderDelegate {
}

func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
print("CallKitController: Start Call")
print("[CallKitController][CXStartCallAction]: callUUID: \(action.callUUID.uuidString.lowercased())")

actionListener?(.startCall, action.callUUID, currentCallData)
callStates[action.callUUID.uuidString.lowercased()] = .accepted
configureAudioSession(active: true)

action.fulfill()
}
}
15 changes: 13 additions & 2 deletions ios/Classes/SwiftConnectycubeFlutterCallKitPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class SwiftConnectycubeFlutterCallKitPlugin: NSObject, FlutterPlugin {
static let callController = CallKitController()
static let voipController = VoIPController(withCallKitController: callController)

public static func register(with registrar: FlutterPluginRegistrar) {
@objc public static func register(with registrar: FlutterPluginRegistrar) {
print("[SwiftConnectycubeFlutterCallKitPlugin][register]")
//setup method channels
let methodChannel = FlutterMethodChannel(name: _methodChannelName, binaryMessenger: registrar.messenger())
Expand All @@ -52,7 +52,7 @@ public class SwiftConnectycubeFlutterCallKitPlugin: NSObject, FlutterPlugin {
}

///useful for integrating with VIOP notifications
static public func reportIncomingCall(uuid: String,
@objc static public func reportIncomingCall(uuid: String,
callType: Int,
callInitiatorId: Int,
callInitiatorName: String,
Expand Down Expand Up @@ -182,6 +182,17 @@ public class SwiftConnectycubeFlutterCallKitPlugin: NSObject, FlutterPlugin {
else if call.method == "getLastCallId" {
result(SwiftConnectycubeFlutterCallKitPlugin.callController.currentCallData["session_id"])
}
else if call.method == "muteCall" {
guard let arguments = arguments else {
result(FlutterError(code: "invalid_argument", message: "No data was provided.", details: nil))
return
}
let callId = arguments["session_id"] as! String
let muted = arguments["muted"] as! Bool

SwiftConnectycubeFlutterCallKitPlugin.callController.setMute(uuid: UUID(uuidString: callId)!, muted: muted)
result(true)
}
else {
result(FlutterMethodNotImplemented)
}
Expand Down
Loading

0 comments on commit 4e62f10

Please sign in to comment.