No Voice Communication when call is streaming using CallKit - ios

I was trying to make a call from extension 100(Linphone App) to 102(My application). The extension 102 has shown the incoming call screen and then clicked accept button and then, extension 100 starts to talk to extension 102 but extension 102 unable to hear anything from 100 and also extension 100 unable to hear from 102 as well.
I think the problem because of app. But i don't know what is wrong with my application.
These functions i declared in appdelegate file in order to handle incoming call
let callKitManager = CallKitCallInit(uuid: UUID(), handle: "")
lazy var providerDelegate: ProviderDelegate = ProviderDelegate(callKitManager: self.callKitManager)
func displayIncomingCall(uuid: UUID, handle: String, completion: ((NSError?) -> Void)?) {
providerDelegate.reportIncomingCall(uuid: uuid, handle: handle, completion: completion)
}
This is ProviderDelegate file
extension ProviderDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
print("Stop Audio ==STOP-AUDIO==")
for call in callKitManager.calls {
call.end(uuid: UUID())
}
callKitManager.removeAllCalls()
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
guard let call = callKitManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
configureAudioSession()
call.answer()
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
guard let call = callKitManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
print("Stop audio ==STOP-AUDIO==")
configureAudioSession()
call.end(uuid: action.callUUID)
action.fulfill()
callKitManager.remove(call: call)
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("Starting audio ==STARTING-AUDIO==")
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
print("Received \(#function)")
}
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do {
try? session.setCategory(AVAudioSessionCategoryPlayAndRecord)
try? session.setMode(AVAudioSessionModeVoiceChat)
try? session.setPreferredSampleRate(44100.0)
try? session.setPreferredIOBufferDuration(0.005)
try? session.setActive(true)
}
}
}
class ProviderDelegate: NSObject {
fileprivate let callKitManager: CallKitCallInit
fileprivate let provider: CXProvider
init(callKitManager: CallKitCallInit) {
self.callKitManager = callKitManager
provider = CXProvider(configuration: type(of: self).providerConfiguration)
super.init()
provider.setDelegate(self, queue: nil)
}
static var providerConfiguration: CXProviderConfiguration {
let providerConfiguration = CXProviderConfiguration(localizedName: "vKclub")
providerConfiguration.supportsVideo = false
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.phoneNumber]
return providerConfiguration
}
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
print("This is UUID === ", uuid)
configureAudioSession()
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
// 3.
self.configureAudioSession()
let call = CallKitCallInit(uuid: uuid, handle: handle)
self.callKitManager.add(call: call)
lastCallUUID = uuid
print("UUID === ", uuid)
} else {
}
// 4.
completion?(error as NSError?)
}
}
}

Related

iOS media playback controls notification

I am new to iOS, and developing a cross platform app with Flutter. I am trying to play audio from network URL, which i found it can be done using the AVPlayer. The audio plays when the app is in foreground and in background, but i can display the media playback controls like this: .
i used the let mediaController = MPMusicPlayerController.applicationMusicPlayer and then calling self.mediaController.beginGeneratingPlaybackNotifications(), also providing the playing info MPNowPlayingInfoCenter.default().nowPlayingInfo = mediaInfo and setting the targets for the remote command center in self.registerCommands() method.
i did alot of research but no luck in finding the problem, and as i said before am new to ios.
AppDelegate
import UIKit
import Flutter
import AVFoundation
import MediaPlayer
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
static let CHANNEL = "APP_CHANNEL"
let mPlayer = AudioPlayer()
let mediaController = MPMusicPlayerController.applicationMusicPlayer
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
self.requestNotificationPermission(application: application)
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let mainChannel = FlutterMethodChannel(name: AppDelegate.CHANNEL,
binaryMessenger: controller.binaryMessenger)
mainChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: #escaping FlutterResult) -> Void in
switch(call.method) {
case "getSharedContainerPath":
let path = Utils.getSharedContainerPath()
result(path)
break
case "saveSelectedCity":
let city = call.arguments as! String
Utils.saveCityToUserDefaults(city: city)
result(true)
break
case "playSurah":
let number = call.arguments as! Int
self.initAudioPlayer()
self.mPlayer.toggle(num: number)
result(true)
break
default:
result(FlutterMethodNotImplemented)
return
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func initAudioPlayer() {
self.mediaController.beginGeneratingPlaybackNotifications()
self.mPlayer.initPlayer(object: self)
self.registerCommands()
let nc = NotificationCenter.default
nc.addObserver(self,
selector: #selector(handleInterruption),
name: AVAudioSession.interruptionNotification,
object: nil)
nc.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: nil)
}
func requestNotificationPermission(application: UIApplication) {
if #available(iOS 10, *) {
// iOS 10 support
//create the notificationCenter
let center = UNUserNotificationCenter.current()
center.delegate = self as UNUserNotificationCenterDelegate
// set the type as sound or badge
center.requestAuthorization(options: [.sound,.alert,.badge]) { (granted, error) in
if granted {
print("Notification Enable Successfully")
}else{
print("Some Error Occure")
}
}
application.registerForRemoteNotifications()
} else if #available(iOS 9, *) {
// iOS 9 support
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil))
UIApplication.shared.registerForRemoteNotifications()
} else if #available(iOS 8, *) {
// iOS 8 support
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil))
UIApplication.shared.registerForRemoteNotifications()
} else { // iOS 7 support
application.registerForRemoteNotifications(matching: [.badge, .sound, .alert])
}
}
func registerCommands() {
let command = MPRemoteCommandCenter.shared()
command.playCommand.isEnabled = true;
command.playCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.play()
return .success
}
command.pauseCommand.isEnabled = true;
command.pauseCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.pause()
return .success
}
command.togglePlayPauseCommand.isEnabled = true;
command.togglePlayPauseCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.toggle(num: self.mPlayer.index)
return .success
}
command.nextTrackCommand.isEnabled = true;
command.nextTrackCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.playNext()
return .success
}
command.previousTrackCommand.isEnabled = true;
command.previousTrackCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.playPrev()
return .success
}
command.stopCommand.isEnabled = true;
command.stopCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.stop()
return .success
}
}
// [notificationCenter addObserver: self
// selector: #selector (handle_NowPlayingItemChanged:)
// name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification
// object: musicPlayer];
//
// [notificationCenter addObserver: self
// selector: #selector (handle_PlaybackStateChanged:)
// name: MPMusicPlayerControllerPlaybackStateDidChangeNotification
// object: musicPlayer];
//
// [notificationCenter addObserver: self
// selector: #selector (handle_VolumeChanged:)
// name: MPMusicPlayerControllerVolumeDidChangeNotification
// object: musicPlayer];
func destroyPlayer() {
self.mPlayer.stop()
let nc = NotificationCenter.default
nc.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil)
nc.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
self.mediaController.endGeneratingPlaybackNotifications()
let command = MPRemoteCommandCenter.shared()
command.playCommand.isEnabled = false;
command.pauseCommand.isEnabled = false;
command.togglePlayPauseCommand.isEnabled = false;
command.nextTrackCommand.isEnabled = false;
command.previousTrackCommand.isEnabled = false;
command.stopCommand.isEnabled = false;
}
// override func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
// self.destroyPlayer()
// }
override func applicationWillTerminate(_ application: UIApplication) {
self.destroyPlayer()
}
#objc func playerDidFinishPlaying(note: NSNotification) {
self.mPlayer.playNext()
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
// Only handle observations for the playerItemContext
guard context == &mPlayer.playerItemContext else {
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
return
}
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItem.Status
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItem.Status(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
// Switch over status value
switch status {
case .readyToPlay:
self.mPlayer.updateMediaInfo()
break
// Player item is ready to play.
case .failed: break
// Player item failed. See error.
case .unknown: break
// Player item is not yet ready.
#unknown default:
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
}
} else if keyPath == #keyPath(AVPlayer.timeControlStatus) {
if object is AVPlayer {
if (object as? AVPlayer) != nil {
self.mPlayer.updateMediaInfo()
}
}
} else {
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
}
}
#objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
return
}
// Switch over the interruption type.
switch type {
case .began:
// An interruption began. Update the UI as needed.
self.mPlayer.pause()
break
case .ended:
// An interruption ended. Resume playback, if appropriate.
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
// Interruption ended. Playback should resume.
self.mPlayer.play()
} else {
// Interruption ended. Playback should not resume.
}
default: ()
}
}
}
Audio Player class
//
// AudioPlayer.swift
// Runner
import Foundation
import AVFoundation
import MediaPlayer
class AudioPlayer {
private var player: AVPlayer?
var index: Int = 0
private var object: NSObject!
// Key-value observing context
var playerItemContext = 0
private var mediaInfo = [String : Any]()
func initPlayer(object: NSObject) {
self.object = object
do {
if #available(iOS 10.0, *) {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.default, options: [.mixWithOthers, .allowAirPlay])
try AVAudioSession.sharedInstance().setActive(false)
} else {
// Fallback on earlier versions
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: .mixWithOthers)
}
} catch {
print(error)
}
}
func startPlayer() {
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print(error)
}
self.mediaInfo[MPMediaItemPropertyTitle] = ""
self.mediaInfo[MPMediaItemPropertyArtist] = ""
updateMediaInfo()
let url = getUrl()
let playerItem = AVPlayerItem(url: url!)
playerItem.addObserver(self.object, forKeyPath: #keyPath(AVPlayerItem.status), options: [.old, .new], context: &playerItemContext)
if self.player == nil {
self.player = AVPlayer(playerItem: playerItem)
} else {
self.player?.replaceCurrentItem(with: playerItem)
}
self.player?.addObserver(self.object, forKeyPath: #keyPath(AVPlayer.timeControlStatus), options: [.new, .old], context: &playerItemContext)
if let p = self.player {
p.play()
}
getMetadata(for: url!, completionHandler: { (metadata) in
self.mediaInfo[MPMediaItemPropertyTitle] = metadata?["title"]
self.mediaInfo[MPMediaItemPropertyArtist] = metadata!["artist"]
self.mediaInfo[MPMediaItemPropertyPlaybackDuration] = playerItem.asset.duration.seconds
self.updateMediaInfo()
})
}
func toggle(num: Int) {
if self.index == num {
if let p = self.player {
if(p.isPlaying) {
p.pause()
}
else {
p.play()
}
self.updateMediaInfo()
}
} else {
self.index = num
startPlayer()
}
}
func pause() {
if let p = self.player {
if(p.isPlaying) {
p.pause()
self.updateMediaInfo()
}
}
}
func play() {
if let p = self.player {
if(!p.isPlaying ) {
p.play()
self.updateMediaInfo()
}
}
}
func playNext() {
if self.index + 1 <= 114 {
self.index += 1
} else {
self.index = 1
}
self.startPlayer()
}
func playPrev() {
if self.index - 1 >= 1 {
self.index -= 1
} else {
self.index = 114
}
self.startPlayer()
}
func stop() {
if let p = self.player {
p.pause()
self.player?.replaceCurrentItem(with: nil)
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
}
func getUrl() -> URL? {
return URL(string: String(format: Utils.QURAN_AUDIO, self.index))
}
func updateMediaInfo() {
mediaInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate
mediaInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentTime().seconds
if #available(iOS 10.0, *) {
mediaInfo[MPNowPlayingInfoPropertyMediaType] = NSNumber(value: MPNowPlayingInfoMediaType.audio.rawValue)
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = mediaInfo
}
func getMetadata(for url: URL, completionHandler: #escaping (_ metadata: [String : String]?) -> ()) {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil,
let res1 = response as? HTTPURLResponse,
let contentLength = res1.allHeaderFields["Content-Length"] as? String else {
completionHandler(nil)
return
}
do {
var req = URLRequest(url: url)
req.setValue("bytes=\(UInt64(contentLength)! - 128)-", forHTTPHeaderField: "Range")
let data = try NSURLConnection.sendSynchronousRequest(req, returning: nil)
let titleBytes = data.subdata(in: Range<Int>(NSRange(location: 3, length: 29))!)
.filter { (data) -> Bool in
data != 0
}
let artistBytes = data.subdata(in: Range<Int>(NSRange(location: 33, length: 29))!)
.filter { (data) -> Bool in
data != 0
}
let title = String(data: titleBytes, encoding: String.Encoding.utf8)
let artist = String(data: artistBytes, encoding: String.Encoding.utf8)
completionHandler(["title": title!, "artist": artist!])
} catch {
completionHandler(nil)
}
}
task.resume()
}
}
extension AVPlayer {
var isPlaying: Bool {
if #available(iOS 10.0, *) {
return timeControlStatus.rawValue == TimeControlStatus.playing.rawValue
}
return rate != 0 && error == nil
}
}
From a comment:
i don't have a real device, i am using the IPhone 11 pro max simulator
That’s the problem. You cannot test this feature except on a device. The simulator is not a reliable guide for many iOS features / behaviors, and this is one of them. Without a device, you have no evidence of whether your code works as desired.
If I get you right, the NowPlayingInfo doesn't show your MediaInfo (Title, etc..).
This is because currently iOS ignores NowPlayingInfo from AVAudioSessions with .mixWithOthers option enabled.
I did setup a little test project with your code. With .mixWithOthers option I could reproduce your problem. After removing this option NowPlayingInfoCenter worked as expected.
One more thing:
When trying to set AVAudioSession category I always get an error Error Domain=NSOSStatusErrorDomain Code=-50 "(null)".
This is because setting the .allowsAirPlay option isn't allowed for category .playback. (https://developer.apple.com/documentation/avfoundation/avaudiosession/categoryoptions/1771736-allowairplay)

How do I run a function in the background on watchOS and iOS?

I'm trying to run a function in the background on iOS and watchOS, and I found a code sample, but it didn't work for me.
I tried some sample code that I found on GitHub, and a dispatch thread function.
..........
private func startWorkout() {
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .other
do {
workoutSession = try HKWorkoutSession(healthStore: healthStore, configuration: workoutConfiguration)
workoutSession?.delegate = self
// HKWorkoutSession.startActivity(workoutSession!)
healthStore.start(workoutSession!)
} catch {
print(error)
}
}
#objc fileprivate func vibrate() {
WKInterfaceDevice.current().play(.success)
}
........
extension InterfaceController: HKWorkoutSessionDelegate {
func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
}
func workoutSession(_ workoutSession: HKWorkoutSession, didGenerate event: HKWorkoutEvent) {
}
func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
switch toState {
case .running:
hapticFeedbackTimer = Timer(timeInterval: 0, target: self, selector: #selector(vibrate), userInfo: nil, repeats: true)
RunLoop.main.add(hapticFeedbackTimer!, forMode: .default)
default:
hapticFeedbackTimer?.invalidate()
hapticFeedbackTimer = nil
}
}
}```
I expected the function vibrate to be run in the background, but instead, nothing happened
to use the background thread you can use this extension:
extension DispatchQueue {
static func backgroundQueue(_ backgroundOperations: ( () -> Void )? = nil, completion: (() -> Void)? = nil) {
DispatchQueue.global(qos: .background).async {
backgroundOperations?()
if let completion = completion {
DispatchQueue.main.async {
completion()
}
}
}
}
}
so you can perform your actions in background and then, after the background finish, do something on main Thread

URLSession delegates not working after app resume

I've recently been integrating Background Transfer Service into an application so that the user is able to download files in the background.
Everything works as expected. But my delegate methods stops getting called after sending the application into the background and then re-opening the application.
File is actually being downloaded in the background but I am not receiving any call to my delegate methods. So cant show any progress to the users. So it feels like download is got stuck.
I had to remove our app from the app store as it is hurting our app. I need to resubmit the app as soon as possible. But with this problem, it's not possible.
My download manager code:
import Foundation
import Zip
import UserNotifications
////------------------------------------------------------
//// MARK: - Download Progress Struct
////------------------------------------------------------
public struct DownloadProgress {
public let name: String
public let progress: Float
public let completedUnitCount: Float
public let totalUnitCount: Float
}
protocol DownloadDelegate: class {
func downloadProgressUpdate(for progress: DownloadProgress)
func unzipProgressUpdate(for progress: Double)
func onFailure()
}
class DownloadManager : NSObject, URLSessionDownloadDelegate {
//------------------------------------------------------
// MARK: - Downloader Properties
//------------------------------------------------------
static var shared = DownloadManager()
private lazy var session: URLSession = {
let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).bookDownloader")
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
var delegate: DownloadDelegate?
var previousUrl: URL?
var resumeData: Data?
var task: URLSessionDownloadTask?
// ProgressHandler --> identifier, progress, completedUnitCount, totalUnitCount
typealias ProgressHandler = (String, Float, Float, Float) -> ()
//------------------------------------------------------
// MARK: - Downloader Initializer
//------------------------------------------------------
override private init() {
super.init()
}
func activate() -> URLSession {
// Warning: If an URLSession still exists from a previous download, it doesn't create a new URLSession object but returns the existing one with the old delegate object attached!
return session
}
//------------------------------------------------------
// MARK: - Downloader start download
//------------------------------------------------------
func startDownload(url: URL) {
if let previousUrl = self.previousUrl {
if url == previousUrl {
if let data = resumeData {
let downloadTask = session.downloadTask(withResumeData: data)
downloadTask.resume()
self.task = downloadTask
} else {
let downloadTask = session.downloadTask(with: url)
downloadTask.resume()
self.task = downloadTask
}
} else {
let downloadTask = session.downloadTask(with: url)
downloadTask.resume()
self.task = downloadTask
}
} else {
let downloadTask = session.downloadTask(with: url)
downloadTask.resume()
self.task = downloadTask
}
}
//------------------------------------------------------
// MARK: - Downloader stop download
//------------------------------------------------------
func stopDownload() {
if let task = task {
task.cancel { resumeDataOrNil in
guard let resumeData = resumeDataOrNil else {
// download can't be resumed; remove from UI if necessary
return
}
self.resumeData = resumeData
}
}
}
//------------------------------------------------------
// MARK: - Downloader Progress Calculator
//------------------------------------------------------
private func calculateProgress(session : URLSession, completionHandler : #escaping ProgressHandler) {
session.getTasksWithCompletionHandler { (tasks, uploads, downloads) in
let progress = downloads.map({ (task) -> Float in
if task.countOfBytesExpectedToReceive > 0 {
return Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive)
} else {
return 0.0
}
})
let countOfBytesReceived = downloads.map({ (task) -> Float in
return Float(task.countOfBytesReceived)
})
let countOfBytesExpectedToReceive = downloads.map({ (task) -> Float in
return Float(task.countOfBytesExpectedToReceive)
})
if let name = UserDefaults.standard.string(forKey: UserDefaultKeys.OnBookDownload) {
if name.isEmpty {
return self.session.invalidateAndCancel()
}
completionHandler(name, progress.reduce(0.0, +), countOfBytesReceived.reduce(0.0, +), countOfBytesExpectedToReceive.reduce(0.0, +))
}
}
}
//------------------------------------------------------
// MARK: - Downloader Notifiers
//------------------------------------------------------
func postUnzipProgress(progress: Double) {
if let delegate = self.delegate {
delegate.unzipProgressUpdate(for: progress)
}
// NotificationCenter.default.post(name: .UnzipProgress, object: progress)
}
func postDownloadProgress(progress: DownloadProgress) {
if let delegate = self.delegate {
delegate.downloadProgressUpdate(for: progress)
}
// NotificationCenter.default.post(name: .BookDownloadProgress, object: progress)
}
func postNotification() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
}
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: "Download Completed".localized(), arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: "Quran Touch app is ready to use".localized(), arguments: nil)
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "com.qurantouch.qurantouch.BookDownloadComplete"
// Deliver the notification in 60 seconds.
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 2.0, repeats: false)
let request = UNNotificationRequest.init(identifier: "BookDownloadCompleted", content: content, trigger: trigger)
// Schedule the notification.
center.add(request)
}
//------------------------------------------------------
// MARK: - Downloader Delegate methods
//------------------------------------------------------
// On Progress Update
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if let name = UserDefaults.standard.string(forKey: UserDefaultKeys.OnBookDownload) {
if name.isEmpty {
return self.session.invalidateAndCancel()
}
} else {
return self.session.invalidateAndCancel()
}
if totalBytesExpectedToWrite > 0 {
calculateProgress(session: session, completionHandler: { (name, progress, completedUnitCount, totalUnitCount) in
let progressInfo = DownloadProgress(name: name, progress: progress, completedUnitCount: completedUnitCount, totalUnitCount: totalUnitCount)
print(progressInfo.progress)
self.postDownloadProgress(progress: progressInfo)
})
}
}
// On Successful Download
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
if let name = UserDefaults.standard.string(forKey: UserDefaultKeys.OnBookDownload) {
if name.isEmpty {
return self.session.invalidateAndCancel()
}
let folder = URL.createFolder(folderName: "\(Config.bookFolder)\(name)")
let fileURL = folder!.appendingPathComponent("\(name).zip")
if let url = URL.getFolderUrl(folderName: "\(Config.bookFolder)\(name)") {
do {
try FileManager.default.moveItem(at: location, to: fileURL)
// Download completed. Now time to unzip the file
try Zip.unzipFile((fileURL), destination: url, overwrite: true, password: nil, progress: { (progress) -> () in
if progress == 1 {
App.quranDownloaded = true
UserDefaults.standard.set("selected", forKey: name)
DispatchQueue.main.async {
Reciter().downloadCompleteReciter(success: true).done{_ in}.catch{_ in}
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let backgroundCompletionHandler =
appDelegate.backgroundCompletionHandler else {
return
}
backgroundCompletionHandler()
self.postNotification()
}
// Select the book that is downloaded
// Delete the downlaoded zip file
URL.removeFile(file: fileURL)
}
self.postUnzipProgress(progress: progress)
}, fileOutputHandler: {(outputUrl) -> () in
})
} catch {
print(error)
}
}
} else {
return self.session.invalidateAndCancel()
}
}
// On Dwonload Completed with Failure
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
debugPrint("Task completed: \(task), error: \(error)")
guard let error = error else {
// Handle success case.
return
}
let userInfo = (error as NSError).userInfo
if let resumeData = userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
self.resumeData = resumeData
}
if let delegate = self.delegate {
if !error.isCancelled {
delegate.onFailure()
}
}
}
// On Dwonload Invalidated with Error
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
guard let error = error else {
// Handle success case.
return
}
if let delegate = self.delegate {
if !error.isCancelled {
delegate.onFailure()
}
}
}
}
// MARK: - URLSessionDelegate
extension DownloadManager: URLSessionDelegate {
// Standard background session handler
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let completionHandler = appDelegate.backgroundCompletionHandler {
completionHandler()
appDelegate.backgroundCompletionHandler = nil
}
}
}
}
And in app delegate:
var backgroundCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
backgroundCompletionHandler = completionHandler
}
Finally found a workaround for the issue. Once the application did return from background mode, make sure to call resume on all running tasks. This seems to reactivate callbacks to the delegate.
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
DownloadManager.shared.session.getAllTasks(completionHandler: { tasks in
for task in tasks {
task.resume()
}
})
}
For more information on this topic, Follow this link:
https://forums.developer.apple.com/thread/77666
You need to resume all tasks once you come back to active state.
URLSession.shared.getAllTasks { (tasks) in
for task in tasks
{
task.resume()
}
}

CallKit Interferes With WebRTC Video

When I use CallKit to answer a WebRTC call, the video chat works most of the time. Once in a while the camera on my local iPhone isn't accessed correctly because of CallKit. When I remove CallKit, the video chat always works. Also if I set a delay for 1.5 second after I answer a CallKit call and then start the video chat, it seems to work well all of the time. What's the reason for this?
Callkit is intended for Audio Call only, so callkit will activate AudioSession only.
Video will be activated only after you navigate to your application.
Try answering the call on phone lock screen, then you will get understood.
Test Facebook or any other popular app.
Try implementing your (re)connection logic in providerDidReset(_ provider: CXProvider). You may also want to provide checks in this method to ensure you aren't resetting a successful connection.
I am using WebRTC + CallKit in my App.
I started a call and when I press Lock / Power button then the CallKit call is getting disconnected and the My Voip call audio route changes to Receiver and remains.
Why locking the iPhone terminating the call.
Here is my Code.
var callUUID: UUID?
extension AppDelegate {
func initiateCallKitCall() {
let config = CXProviderConfiguration(localizedName: "Huddl.ai")
config.includesCallsInRecents = false;
config.supportsVideo = true;
config.maximumCallsPerCallGroup = 1
provider = CXProvider(configuration: config)
guard let provider = provider else { return }
provider.setDelegate(self, queue: nil)
callController = CXCallController()
guard let callController = callController else { return }
callUUID = UUID()
let transaction = CXTransaction(action: CXStartCallAction(call: callUUID!, handle: CXHandle(type: .generic, value: "Huddl.ai")))
callController.request(transaction, completion: { error in
print("Error is : \(String(describing: error))")
})
}
func endCallKitCall(userEnded: Bool) {
self.userEnded = userEnded
guard provider != nil else { return }
guard let callController = callController else { return }
if let uuid = callUUID {
let endCallAction = CXEndCallAction(call: uuid)
callController.request(
CXTransaction(action: endCallAction),
completion: { error in
if let error = error {
print("Error: \(error)")
} else {
print("Success")
}
})
}
}
func isCallGoing() -> Bool {
let callController = CXCallController()
if callController.callObserver.calls.count != 0 {
return true
}
return false
}
}
extension AppDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
print("-Provider-providerDidReset")
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
print("-Provider-perform action: CXAnswerCallAction")
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
action.fulfill()
print("-Provider: End Call")
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
action.fulfill()
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 3) {
provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: Date())
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 1.5) {
provider.reportOutgoingCall(with: action.callUUID, connectedAt: Date())
}
}
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
action.fulfill()
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = true
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = false
}
}

WatchConnectivity send message of watch with the launch of a timer on the iphone

I created a timer app that runs on the iphone.
I wish we could control it iPhone and Watch
The controls (Play, Stop, Restart) with the iPhone works fine, my meter is displayed on the Watch.
Watch on the Stop works well, for against the Start does not work, the meter does not turn on iPhone or the Watch.
Restart the works too.
My Label on the iPhone is very slow to change if the information comes from the Watch, but works well in the other direction, toward the iPhone Watch
Have you noticed this problem, it is a problem related to WatchConnectivity
Thanks for your help
Below is my code:
ViewController.swift
import UIKit
import WatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var watchLabel: UILabel!
var session: WCSession!
var timerCount = 0
var timerRunning = false
var timer = NSTimer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
if session.paired != true {
print("Apple Watch is not paired")
}
if session.watchAppInstalled != true {
print("WatchKit app is not installed")
}
} else {
print("WatchConnectivity is not supported on this device")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func startButton(sender: UIButton) {
startPlay()
}
#IBAction func stopButton(sender: UIButton) {
stopPlay()
}
#IBAction func restartButton(sender: UIButton) {
restartPlay()
}
//Receive messages from watch
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
var replyValues = Dictionary<String, AnyObject>()
//let viewController = self.window!.rootViewController as! ViewController
switch message["command"] as! String {
case "start" :
startPlay()
replyValues["status"] = "Playing"
case "stop" :
stopPlay()
replyValues["status"] = "Stopped"
case "restart" :
restartPlay()
replyValues["status"] = "Stopped"
default:
break
}
replyHandler(replyValues)
}
//Counter Timer
func counting(timer:NSTimer) {
self.timerCount++
self.timerLabel.text = String(timerCount)
let requestValues = ["timer" : String(timerCount)]
let session = WCSession.defaultSession()
session.sendMessage(requestValues, replyHandler: nil, errorHandler: { error in print("error: \(error)")})
}
//Fonction Play
func startPlay() {
if timerRunning == false {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("counting:"), userInfo: nil, repeats: true)
self.timerRunning = true
self.watchLabel.text = "START"
}
}
//Fonction Stop
func stopPlay() {
if timerRunning == true {
self.timer.invalidate()
self.timerRunning = false
self.watchLabel.text = "STOP"
}
}
//Fonction Restart
func restartPlay() {
self.timerCount = 0
self.timerLabel.text = "0";
let requestValues = ["timer" : "0"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues, replyHandler: nil, errorHandler: { error in print("error: \(error)")})
}
}
InterfaceController.swift
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
#IBOutlet var watchLabel: WKInterfaceLabel!
#IBOutlet var statusLabel: WKInterfaceLabel!
//Receiving message from iphone
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
self.watchLabel.setText(message["timer"]! as? String)
// self.statusLabel.setText(message["command"]! as? String)
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
#IBAction func startButtonWatch() {
if WCSession.defaultSession().reachable == true {
let requestValues = ["command" : "start"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues, replyHandler: { reply in
self.statusLabel.setText(reply["status"] as? String)
}, errorHandler: { error in
print("error: \(error)")
})
}
}
#IBAction func stopButtonWatch() {
if WCSession.defaultSession().reachable == true {
let requestValues = ["command" : "stop"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues, replyHandler: { reply in
self.statusLabel.setText(reply["status"] as? String)
}, errorHandler: { error in
print("error: \(error)")
})
}
}
#IBAction func restartButtonWatch() {
if WCSession.defaultSession().reachable == true {
let requestValues = ["command" : "restart"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues, replyHandler: { reply in
self.statusLabel.setText(reply["status"] as? String)
}, errorHandler: { error in
print("error: \(error)")
})
}
}
}
You should use this:
func startPlay() {
if timerRunning == false {
//self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("counting:"), userInfo: nil, repeats: true)
self.timer = NSTimer(timeInterval: 1, target: self, selector: "counting:", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes)
self.timerRunning = true
self.watchLabel.text = "Start"
}
}
I cant explain you why we need use NSRunLoop explicitly. I stuck with same timer issue when develop an app with data transfer. Some answers you can find in google by query "nstimer run loop" or here.
And i pref use this for restart:
func restartPlay() {
self.timerCount = 0
self.timerLabel.text = "0";
stopPlay()
startPlay()
self.watchLabel.text = "Restarted"
}
Cheers.
This functional and optimized code :
//Fonction Play
func startPlay() {
if timerRunning == false {
self.mytimer = NSTimer(timeInterval: 1, target: self, selector: "counting:", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(self.mytimer, forMode: NSRunLoopCommonModes)
timerRunning = true
dispatch_async(dispatch_get_main_queue()) {
self.watchLabel.text = "PLAYING"
}
}
}

Resources