Audio session .ended isn't called with two AVPlayers - objective-c

Here are steps to reproduce:
Activate AVAudioSession with .playback category.
Register for AVAudioSession.interruptionNotification
Create two AVPlayers and start them
Interrupt playback by calling Siri/receiving a call by Skype, Cellular and etc.
Expected behavior:
Receiving notification of the audio session interruption with .began state at the start and .ended at the end. Also, as a side effect, Siri doesn't respond to commands.
Real behavior:
Only .began notification is called.
To bring back .ended notification (which is used to continue playback) remove one player.
Question: how to handle the audio session interruption with more than 1 AVPlayer running?
Here I created a simple demo project: https://github.com/denis-obukhov/AVAudioSessionBug
Tested on iOS 14.4
import UIKit
import AVFoundation
class ViewController: UIViewController {
private let player1: AVPlayer? = {
$0.volume = 0.5
return $0
}(AVPlayer())
private let player2: AVPlayer? = {
$0.volume = 0.5
return $0 // return nil for any player to bring back .ended interruption notification
}(AVPlayer())
override func viewDidLoad() {
super.viewDidLoad()
registerObservers()
startAudioSession()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
player1?.replaceCurrentItem(with: makePlayerItem(named: "music1"))
player2?.replaceCurrentItem(with: makePlayerItem(named: "music2"))
[player1, player2].forEach { $0?.play() }
}
private func makePlayerItem(named name: String) -> AVPlayerItem {
let fileURL = Bundle.main.url(
forResource: name,
withExtension: "mp3"
)!
return AVPlayerItem(url: fileURL)
}
private func registerObservers() {
NotificationCenter.default.addObserver(
self, selector: #selector(handleInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil
)
}
private func startAudioSession() {
try? AVAudioSession.sharedInstance().setCategory(.playback)
try? AVAudioSession.sharedInstance().setActive(true)
}
#objc private func handleInterruption(_ notification: Notification) {
print("GOT INTERRUPTION")
guard
let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
else {
return
}
switch type {
case .began:
print("Interruption BEGAN")
[player1, player2].forEach { $0?.pause() }
case .ended:
// This part isn't called if more than 1 player is playing
print("Interruption ENDED")
[player1, player2].forEach { $0?.play() }
#unknown default:
print("Unknown value")
}
}
}

I just ran into the same issue, and it was driving me crazy for a few days. I'm using two AVQueuePlayer (a subclass of AVPlayer) to play two sets of audio sounds on top of each other, and I get the AVAudioSession.interruptionNotification value of .began when there is an incoming call, but there is no .ended notification when the call ends.
That said, I've found that for some reason, .ended is reliably sent if you instead use two instances of AVAudioPlayer. It also works with one instance of AVAudioPlayer mixed with another instance of AVQueuePlayer. But for some reason using two instances of AVQueuePlayer (or AVPlayer) seems to break it.
Did you ever find a solution for this? For my purposes I need queuing of tracks so I must use AVQueuePlayer, so I'll probably file a bug report with Apple.

Related

UIView got rendered before data from API returned using SwiftyJSON and Alamofire

I want to fetch data using API request.The data is fetch using SwiftyJson and Alamofire. The problem is that the data is fetched but view gets loaded before the values are fetched.How can I Solve the problem?My code is as below:
func fetchData(){
Alamofire.request(favUrl, method: .get, parameters: [:]).responseJSON {
response in
if response.result.isSuccess{
let dataFetched : JSON = JSON(response.result.value!)
//print(dataFetched)
let titleDisp = dataFetched["title"].arrayObject as? [String]
//print(titleDisp)
self.trackList = dataFetched["track_id"].arrayObject as? [String]
print(self.trackList)
}else{
print("Error \(String(describing: response.result.error))")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
It is important to understand that apps run multiple threads. The main thread, also called the UI thread, performs the actions for the visible parts of the app, including showing views, reacting to buttons and so on.
You cannot block the Main thread.
When you make calls to external resources like API calls or loading external images, those get executed on a separate thread. That thread is independent of the main thread. Neither thread waits for the other. The app will still react to buttons while the data is loading. What you are asking for is to prevent showing the view until the data is loaded. You can do this, but you must understand that this could take time depending on your network connection. There are two approaches you can take.
Transition to the view that shows the data but put a "Loading" element on the screen until the data loads then remove the "Loading" element then redisplay the view with the data.
Load the data before you show the view. Make the previous view load the data then segue to the view that has to show the data.
You must also decide if this data loads ONCE or every time the view is displayed. By placing the call in viewDidLoad, the API call will only happen once until the app is restarted. If you want the data to load every time the screen is shown, put the call in viewWillAppear.
// Approach #1
func fetchData(){
self.showSpinner()
Alamofire.request(favUrl, method: .get, parameters: [:]).responseJSON {
response in
self.hideSpinner()
if response.result.isSuccess {
let dataFetched : JSON = JSON(response.result.value!)
//print(dataFetched)
let titleDisp = dataFetched["title"].arrayObject as? [String]
//print(titleDisp)
self.trackList = dataFetched["track_id"].arrayObject as? [String]
print(self.trackList)
// Actually update the relevant screen elements here.
} else {
print("Error \(String(describing: response.result.error))")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//fetchData()
}
override func viewWillAppear() {
super.viewWillAppear()
fetchData()
}
func showSpinner() {
// IMPLEMENT ME
}
func hideSpinner() {
// IMPLEMENT ME
}
Alamofire mathod runs asynchronously. Not in UIThread.Then you have to reload those views after completion of method run.
As Example - tableview
func fetchData(){
Alamofire.request(favUrl, method: .get, parameters: [:]).responseJSON {
response in
if response.result.isSuccess{
let dataFetched : JSON = JSON(response.result.value!)
//print(dataFetched)
let titleDisp = dataFetched["title"].arrayObject as? [String]
//print(titleDisp)
self.trackList = dataFetched["track_id"].arrayObject as? [String]
print(self.trackList)
// In here you have to reload, set your uiviews or all calculation
tableview.reloadData()
}else{
print("Error \(String(describing: response.result.error))")
}
}
}

how to implement multiple local notification not a single notification for swift 3

i have use the single notification , and this is my code:
this is for register the local notification>>>
func registerLocal() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("Yay!")
} else {
print("D'oh")
}
}
}
and this function to schedule the local notification>>>
func scheduleLocal() {
registerCategories()
let center = UNUserNotificationCenter.current()
// not required, but useful for testing!
center.removeAllPendingNotificationRequests()
let content = UNMutableNotificationContent()
content.title = "good morning"
content.body = "ttt123"
content.categoryIdentifier = "alarm"
content.userInfo = ["customData": "fizzbuzz"]
content.sound = UNNotificationSound.default()
var dateComponents = DateComponents()
dateComponents.hour = 23
dateComponents.minute = 18
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
}
func registerCategories() {
let center = UNUserNotificationCenter.current()
center.delegate = self
let show = UNNotificationAction(identifier: "show", title: "Tell me more…", options: .foreground)
let category = UNNotificationCategory(identifier: "alarm", actions: [show], intentIdentifiers: [])
center.setNotificationCategories([category])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
// pull out the buried userInfo dictionary
let userInfo = response.notification.request.content.userInfo
if let customData = userInfo["customData"] as? String {
print("Custom data received: \(customData)")
switch response.actionIdentifier {
case UNNotificationDefaultActionIdentifier:
// the user swiped to unlock; do nothing
print("Default identifier")
case "show":
print("Show more information…")
break
default:
break
}
}
// you need to call the completion handler when you're done
completionHandler()
}
now how can i use this code with the multiple local notification with iOS 10 and different times
thank you .
Use a different request identifier for each notification (otherwise you only see the last notification).
In the above example, ensure request identifier "UUID().uuidString" contains a unique value for each notification request.
You can call the func scheduleLocal() multiple times with different dateComponents to schedule on different dates. Or you can pass a array of dates to this function and run a loop to schedule the notifications according to these dates.
Just make sure that the identifier you pass in UNNotificationRequest(identifier:, content:, trigger:) function is different for each notification.
Hope this helps. :)
Call this function with different parameters. Like i called when app goes background
func applicationDidEnterBackground(_ application: UIApplication) {
setNotification(time: 25,identifier: "notific2")
setNotification(time: 50,identifier: "notific3")
setNotification(time: 90,identifier: "notific4")
}
func setNotification (time : Int,identifier : String)
{
let content = UNMutableNotificationContent()
content.title = "Don't forget"
content.body = "Buy some milk"
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(time),
repeats: false)
let center = UNUserNotificationCenter.current()
// Swift
let request = UNNotificationRequest(identifier: identifier,
content: content, trigger: trigger)
center.add(request, withCompletionHandler: { (error) in
if error != nil {
// Something went wrong
}
})
}
Note that your code should works as expected! however, there is a little pitfall.
I think that I almost faced the exact same issue, after I tried to trace the pending notifications, I noted that the only notification that has been added is the last requested one. That's because you are calling in scheduleLocal() function:
// not required, but useful for testing!
center.removeAllPendingNotificationRequests()
Well, it is good to remove any existing notification reminder requests, this would help preventing unnecessary duplicate notifications, but you should call it only once before calling scheduleLocal():
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
// now you could call this function many times...
scheduleLocal()
simply, you don't want to delete each pending notification directly after adding it, instead you delete the whole previous pending notifications and add new ones.
If you want to use multiple local notification than you have to used different identifier for each request.
let request = UNNotificationRequest(identifier: "Stock Changed", content: content, trigger: nil)
Change "Stock Changed" with your different identifier for each request.
You can use loop for multiple request.

Not able to get action event on button click in push notification in apple watch device

Working on apple watch notification integration ::- i m not able to get action event of button click in push notification.
//IOS code
1.make function in AppDelegate file for integration custom push notification.
func registerSettingsAndCategories() {
let categories = NSMutableSet()
let acceptAction = UIMutableUserNotificationAction()
acceptAction.title = "View"
acceptAction.identifier = "view"
acceptAction.activationMode = UIUserNotificationActivationMode.Background
acceptAction.authenticationRequired = false
let inviteCategory = UIMutableUserNotificationCategory()
inviteCategory.setActions([acceptAction],forContext: UIUserNotificationActionContext.Default)
inviteCategory.identifier = "myCategory"
categories.addObject(inviteCategory)
// Configure other actions and categories and add them to the set...
let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound],categories: (NSSet(array: [inviteCategory])) as? Set<UIUserNotificationCategory>)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
}
2.in AppDelegate file delegate method for handle notification:
func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [NSObject : AnyObject], completionHandler: () -> Void) {
print("handleActionWithIdentifier Appdel : \(userInfo) \(identifier)")
print(userInfo)
completionHandler()
}
3.In NotificationController file handle notification method in Watchkit Extension target :
override func handleActionWithIdentifier(identifier: String?, forRemoteNotification remoteNotification: [NSObject : AnyObject]) {
print("handleActionWithIdentifier \(remoteNotification)")
if identifier == "myCategory"{
}
}
i know that if i will click on "view" button then handleActionWithIdentifier method should be called but in my case handleActionWithIdentifier method is not called.i m stucked in this issue for last 2 days.
so anyone can help me for resolving this issue?

How to bridge TVML/JavaScriptCore to UIKit/Objective-C (Swift)?

So far tvOS supports two ways to make tv apps, TVML and UIKit, and there is no official mentions about how to mix up things to make a TVML (that is basically XML) User Interface with the native counter part for the app logic and I/O (like playback, streaming, iCloud persistence, etc).
So, which is the best solution to mix TVML and UIKit in a new tvOS app?
In the following I have tried a solution following code snippets adapted from Apple Forums and related questions about JavaScriptCore to ObjC/Swift binding.
This is a simple wrapper class in your Swift project.
import UIKit
import TVMLKit
#objc protocol MyJSClass : JSExport {
func getItem(key:String) -> String?
func setItem(key:String, data:String)
}
class MyClass: NSObject, MyJSClass {
func getItem(key: String) -> String? {
return "String value"
}
func setItem(key: String, data: String) {
print("Set key:\(key) value:\(data)")
}
}
where the delegate must conform a TVApplicationControllerDelegate:
typealias TVApplicationDelegate = AppDelegate
extension TVApplicationDelegate : TVApplicationControllerDelegate {
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext) {
let myClass: MyClass = MyClass();
jsContext.setObject(myClass, forKeyedSubscript: "objectwrapper");
}
func appController(appController: TVApplicationController, didFailWithError error: NSError) {
let title = "Error Launching Application"
let message = error.localizedDescription
let alertController = UIAlertController(title: title, message: message, preferredStyle:.Alert ) self.appController?.navigationController.presentViewController(alertController, animated: true, completion: { () -> Void in
})
}
func appController(appController: TVApplicationController, didStopWithOptions options: [String : AnyObject]?) {
}
func appController(appController: TVApplicationController, didFinishLaunchingWithOptions options: [String : AnyObject]?) {
}
}
At this point the javascript is very simple like. Take a look at the methods with named parameters, you will need to change the javascript counter part method name:
App.onLaunch = function(options) {
var text = objectwrapper.getItem()
// keep an eye here, the method name it changes when you have named parameters, you need camel case for parameters:
objectwrapper.setItemData("test", "value")
}
App. onExit = function() {
console.log('App finished');
}
Now, supposed that you have a very complex js interface to export like
#protocol MXMJSProtocol<JSExport>
- (void)boot:(JSValue *)status network:(JSValue*)network user:(JSValue*)c3;
- (NSString*)getVersion;
#end
#interface MXMJSObject : NSObject<MXMJSProtocol>
#end
#implementation MXMJSObject
- (NSString*)getVersion {
return #"0.0.1";
}
you can do like
JSExportAs(boot,
- (void)boot:(JSValue *)status network:(JSValue*)network user:(JSValue*)c3 );
At this point in the JS Counter part you will not do the camel case:
objectwrapper.bootNetworkUser(statusChanged,networkChanged,userChanged)
but you are going to do:
objectwrapper.boot(statusChanged,networkChanged,userChanged)
Finally, look at this interface again:
- (void)boot:(JSValue *)status network:(JSValue*)network user:(JSValue*)c3;
The value JSValue* passed in. is a way to pass completion handlers between ObjC/Swift and JavaScriptCore. At this point in the native code you do all call with arguments:
dispatch_async(dispatch_get_main_queue(), ^{
NSNumber *state = [NSNumber numberWithInteger:status];
[networkChanged.context[#"setTimeout"]
callWithArguments:#[networkChanged, #0, state]];
});
In my findings, I have seen that the MainThread will hang if you do not dispatch on the main thread and async. So I will call the javascript "setTimeout" call that calls the completion handler callback.
So the approach I have used here is:
Use JSExportAs to take car of methods with named parameters and avoid to camel case javascript counterparts like callMyParam1Param2Param3
Use JSValue as parameter to get rid of completion handlers. Use callWithArguments on the native side. Use javascript functions on the JS side;
dispatch_async for completion handlers, possibly calling a setTimeout 0-delayed in the JavaScript side, to avoid the UI to freeze.
[UPDATE]
I have updated this question in order to be more clear. I'm finding a technical solution for bridging TVML and UIKit in order to
Understand the best programming model with JavaScriptCode
Have the right bridge from JavaScriptCore to ObjectiveC and
viceversa
Have the best performances when calling JavaScriptCode from Objective-C
This WWDC Video explains how to communicate between JavaScript and Obj-C
Here is how I communicate from Swift to JavaScript:
//when pushAlertInJS() is called, pushAlert(title, description) will be called in JavaScript.
func pushAlertInJS(){
//allows us to access the javascript context
appController!.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
//get a handle on the "pushAlert" method that you've implemented in JavaScript
let pushAlert = evaluation.objectForKeyedSubscript("pushAlert")
//Call your JavaScript method with an array of arguments
pushAlert.callWithArguments(["Login Failed", "Incorrect Username or Password"])
}, completion: {(Bool) -> Void in
//evaluation block finished running
})
}
Here is how I communicate from JavaScript to Swift (it requires some setup in Swift):
//call this method once after setting up your appController.
func createSwiftPrint(){
//allows us to access the javascript context
appController?.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
//this is the block that will be called when javascript calls swiftPrint(str)
let swiftPrintBlock : #convention(block) (String) -> Void = {
(str : String) -> Void in
//prints the string passed in from javascript
print(str)
}
//this creates a function in the javascript context called "swiftPrint".
//calling swiftPrint(str) in javascript will call the block we created above.
evaluation.setObject(unsafeBitCast(swiftPrintBlock, AnyObject.self), forKeyedSubscript: "swiftPrint" as (NSCopying & NSObjectProtocol)?)
}, completion: {(Bool) -> Void in
//evaluation block finished running
})
}
[UPDATE] For those of you who would like to know what "pushAlert" would look like on the javascript side, I'll share an example implemented in application.js
var pushAlert = function(title, description){
var alert = createAlert(title, description);
alert.addEventListener("select", Presenter.load.bind(Presenter));
navigationDocument.pushDocument(alert);
}
// This convenience funnction returns an alert template, which can be used to present errors to the user.
var createAlert = function(title, description) {
var alertString = `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<alertTemplate>
<title>${title}</title>
<description>${description}</description>
</alertTemplate>
</document>`
var parser = new DOMParser();
var alertDoc = parser.parseFromString(alertString, "application/xml");
return alertDoc
}
You sparked an idea that worked...almost. Once you have displayed a native view, there is no straightforward method as-of-yet to push an TVML-based view onto the navigation stack. What I have done at this time is:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.appController?.navigationController.popViewControllerAnimated(true)
dispatch_async(dispatch_get_main_queue()) {
tvmlContext!.evaluateScript("showTVMLView()")
}
...then on the JavaScript side:
function showTVMLView() {setTimeout(function(){_showTVMLView();}, 100);}
function _showTVMLView() {//push the next document onto the stack}
This seems to be the cleanest way to move execution off the main thread and onto the JSVirtualMachine thread and avoid the UI lockup. Notice that I had to pop at the very least the current native view controller, as it was getting sent a deadly selector otherwise.

KeyDown actions in OSX?

I have the code all done for my keydown actions, but i dont know what to do with the first responder that every site i go to seems to skim over. Can anyone tell me how to set it up to recognise keydown actions in cocoa objectivec?
Thanks
First, keyDown: is an event message, not an action message. Note that its argument is an NSEvent, not a UI object of some sort (such as an NSControl or NSMenuItem).
Action messages go down the responder chain, in which case the “first responder” is not special. Each responder will hand any action message it doesn't know how to handle off to its next responder. This is the “responder chain”. The first responder is simply whatever responder is at the head of the responder chain—i.e., is first. You would simply need to be in that chain, behind anything that doesn't know how to respond to the action being passed down it.
But since this is an event message, things are different. You need to be the key view, which is the first responder.
And that's all there is to it. You need to respond to the keyDown: message (and possibly related ones) in a view, and that view needs to be the first responder to receive the message.
The NSResponder class reference and Cocoa Event-Handling Guide will tell you more.
Here is what I've done and it works well. (Swift 3 for macOS Sierra)
override func viewDidLoad() {
keyIsDown = false // variable defined in the NSViewController
NSEvent.addLocalMonitorForEvents(matching: .keyUp) { (aEvent) -> NSEvent? in
self.keyUp(with: aEvent)
return aEvent
}
NSEvent.addLocalMonitorForEvents(matching: .keyDown) { (aEvent) -> NSEvent? in
self.keyDown(with: aEvent)
return aEvent
}
}
Now I also override these:
override var acceptsFirstResponder: Bool { return true }
override func becomeFirstResponder() -> Bool { return true }
override func resignFirstResponder() -> Bool { return true }
And these:
override func keyUp(with event: NSEvent) {
keyIsDown = false
if event.keyCode == 1 {
print("s key released")
}
}
override func keyDown(with event: NSEvent) {
if keyIsDown == true {
return
}
keyIsDown = true
// Whatever you'd like to do (check to see which key released, etc.)
}
That should get you started.