I use the below code to run a Soundcloud based URL audio file.
https://api.soundcloud.com/tracks/<Track Name>/stream?client_id=<my Client id>
var podcastPlayer = STKAudioPlayer.init()
podcastPlayer.delegate = self
podcastPlayer.play(urlString)
It does not go past the buffering state
func audioPlayer(_ audioPlayer: STKAudioPlayer, stateChanged state: STKAudioPlayerState, previousState: STKAudioPlayerState) {
if( state == .playing){
JDStatusBarNotification.dismiss()
updateRemoteNotificationDisplay()
}else if(state == .buffering) {
JDStatusBarNotification.show(withStatus: "Buffering!..Please Wait!", styleName: JDStatusBarStyleRed)
JDStatusBarNotification.showActivityIndicator(true, indicatorStyle: .gray)
}else if(state == .stopped) {
JDStatusBarNotification.dismiss()
}
NotificationCenter.default.post(name: Notification.Name(rawValue: NLConstants.NOTIF_PODCAST_UPDATE_UI), object: nil)
}
Related
I am working on a VOIP app and implementing CallKit on iOS.
From this link: https://developer.apple.com/documentation/callkit/cxcall/1649013-hasconnected?language=objc
A call is considered connected when both caller and callee can start
communicating.
What does this mean here? Does it mean that a WebRTC or some other audio/video stream must be started in order for it to return true? or does it mean that if user presses the accept call button it is considered connected?
I have the following code to check status of the call and it is always returning true or whatever i give in resolve:
CXCallObserver *callObserver = [[CXCallObserver alloc] init];
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
BOOL _mybool = false;
for(CXCall *call in callObserver.calls){
if([call.UUID isEqual:[[NSUUID alloc] initWithUUIDString:uuidString]] && !call.hasConnected){
_mybool = true;
resolve(#"true");
}
}
if(!_mybool){
reject(false, false, false);
}
In your code, you are checking the call state in the wrong place. This is creating a problem in your execution.
You have to first setup callobserver with call it. which you have done correctly. But one flag is required didDetectOutgoingCall
func setupCallObserverWithCallKit() {
if #available(iOS 10.0, *) {
didDetectOutgoingCall = false
if callObserver == nil {
callObserver = CXCallObserver()
callObserver!.setDelegate(self, queue: nil)
}
}
}
and then inside callObserver write your logic.
#available(iOS 10.0, *)
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if call.isOutgoing && !didDetectOutgoingCall {
didDetectOutgoingCall = true
// "Call button pressed"
}
if call.hasEnded == true && call.isOutgoing == false || // incoming end
call.hasEnded == true && call.isOutgoing == true { // outgoing end
// Disconnected
}
if call.isOutgoing == true && call.hasConnected == false && call.hasEnded == false {
// "CXCallObserver : Dialing"
//**Write your logic written in for loop here**
}
if call.isOutgoing == false && call.hasConnected == false && call.hasEnded == false {
// "CXCallObserver: Incoming"
}
if call.hasConnected == true && call.hasEnded == false {
// "CXCallObserver: Connected")
}
}
Note: Though this code is in swift language, it is easily understandable and easily convertible to objective-c
//init
if (#available(iOS 10.0, *)) {
self.callObserver = [CXCallObserver new];
[self.callObserver setDelegate:self queue:nil];
}
and implement a delegate method - CXCallObserverDelegate
#pragma mark - CXCallObserverDelegate
- (void)callObserver:(CXCallObserver *)callObserver callChanged:(CXCall *)call {
//what you need))
//call.isOutgoing
//call.hasConnected
//call.hasEnded
//call.onHold
//call.hasEnded
}
In objC the syntax written by Rawendrich for GKTurnBasedEventListener, which was GKTurnBasedEventHandler there at that time, now changed by Apple is as below.
if (!gameCenterAvailable) return;
void (^setGKEventHandlerDelegate)(NSError *) = ^ (NSError *error)
{
GKTurnBasedEventHandler *ev =
[GKTurnBasedEventHandler sharedTurnBasedEventHandler];
ev.delegate = self;
};
NSLog(#"Authenticating local user...");
if ([GKLocalPlayer localPlayer].authenticated == NO) {
[[GKLocalPlayer localPlayer]
authenticateWithCompletionHandler:
setGKEventHandlerDelegate];
} else {
NSLog(#"Already authenticated!");
setGKEventHandlerDelegate(nil);
}
Now after converting this to swift, and with the composition of writing down GKTurnBasedEventListener instead of GKTurnBasedEventHandler, this comes the following way.
// Converted with Swiftify v1.0.6381 - https://objectivec2swift.com/
if !gameCenterAvailable {
return
}
var setGKEventHandlerDelegate: ((_: Error) -> Void)? = {(_ error: Error?) -> Void in
var ev = GKTurnBasedEventHandler.shared()
ev.delegate = self
}
print("Authenticating local user...")
if GKLocalPlayer.localPlayer().authenticated == false {
GKLocalPlayer.localPlayer().authenticate(withCompletionHandler: setGKEventHandlerDelegate)
}
else {
print("Already authenticated!")
setGKEventHandlerDelegate(nil)
}
Unfortunately this is not the right syntax to set delegate of GKTurnBasedEventListener for my ViewController.
Please if anyone of you could solve this for me, because without this I'm not able to read through event listener's default functions.
Cheers!
FYI, if you want a working example of how to use GKLocalPlayerListener during a turn-based GameKit match, you are welcome to take a look at this example project for a turn based game. I hope it helps to see all the pieces in context.
Finally after just about harsh 10 hours, I figured out this problem from Here. Although this syntax is in objC, but there's no problem of converting it to swift from Swiftify.
Although a bit later than the real time, but I'm now able to understand that setting delegate of GKTunBasedEventListener is not like the one we do for UITableViewControllerDelegate.
Here, one must have to first authenticate local player, then after you have to register local player's listener to the ViewController's delegate GKLocalPlayerListener.
One other thing I found on Apple's Documentation:
Do not implement GKChallengeListener, GKInviteEventListener, GKSavedGameListener, and GKTurnBasedEventListener directly; implement GKLocalPlayerListener instead. You can listen for and handle multiple events using GKLocalPlayerListener.
So then on I've implemented in the following way.
import GameKit
class ViewController: UIViewController, GKTurnBasedMatchmakerViewControllerDelegate,
GKLocalPlayerListener {
.....
func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch, didBecomeActive: Bool) {
print("#1")
print(player)
print("#2")
print(match)
print("#3")
print(didBecomeActive)
if match.status == GKTurnBasedMatchStatus.open
{
if GKLocalPlayer.localPlayer() == match.currentParticipant
{
if didBecomeActive
{
// Active now
}
else
{
// Active already
}
}
else
{
// It's someone's turn
if match.matchData != myMatch?.matchData
{
// Match Data being Updated by Someone
print(player.alias ?? "No Name:")
}
}
}
thirdTopLabel.text = match.matchID! + "\n" + didBecomeActive.description
}
....
Now in ViewDidLoad() function put the following code.
// In the ViewDidLoad function
if(!GKLocalPlayer.localPlayer().isAuthenticated)
{
authenticatePlayer { (auth) in
weak var weakSelf = self
weak var weakPlayer = GKLocalPlayer.localPlayer()
if(auth){
weakPlayer?.register(weakSelf!)
self.suthentication = true;
}
else{
print("failed in authentication")
self.suthentication = false;
}
}
}
else
{
// Already Authenticated
GKLocalPlayer.localPlayer().register(self)
localPlayer = GKLocalPlayer.localPlayer()
}
And finally your Authentication function should be like this.
// authenticate local player :: Just Authentication
func authenticatePlayer(completionHandler: #escaping (_ resultedPlaces: Bool) -> Void) {
localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler =
{ (viewController , error ) -> Void in
if viewController != nil
{
self.present(viewController!, animated:true, completion: nil)
}
else
{
if self.localPlayer.isAuthenticated
{
completionHandler(true);
}
else
{
completionHandler(false);
print("not able to authenticate fail")
self.gameCenterEnabled = false
if (error != nil)
{
print("\(error.debugDescription)")
}
else
{
print( "error is nil")
}
}
}
}
}
NOTE: GKLocalPlayerListener won't work on simulator.
In my app I have an Audio player that plays audio in background. Everything works as should be however, when I receive a call or I open another app the audio in background in my app goes silent but the remoteControlReceivedWithEvent function is not called.
My function is:
override func remoteControlReceivedWithEvent(event: UIEvent?) {
if event!.type == UIEventType.RemoteControl {
if event!.subtype == UIEventSubtype.RemoteControlPlay {
//print("received remote play")
NSNotificationCenter.defaultCenter().postNotificationName("AudioPlayerIsPlaying", object: nil)
} else if event!.subtype == UIEventSubtype.RemoteControlPause {
//print("received remote pause")
NSNotificationCenter.defaultCenter().postNotificationName("AudioPlayerIsNotPlaying", object: nil)
} else if event!.subtype == UIEventSubtype.RemoteControlNextTrack{
//print("received next")
nextBtn.sendActionsForControlEvents(UIControlEvents.TouchUpInside)
}else if event!.subtype == UIEventSubtype.RemoteControlPreviousTrack{
//print("received previus")
backBtn.sendActionsForControlEvents(UIControlEvents.TouchUpInside)
}else if event!.subtype == UIEventSubtype.RemoteControlTogglePlayPause{
//print("received toggle")
playBtn.sendActionsForControlEvents(UIControlEvents.TouchUpInside)
}
}
}
How can I tell my app that a call is received or that another app has been opened?
So I've got a MPMoviePlayerController playing a video in the background.
If I tell it to load a different video using remote control notifications it works just fine.
However if I tell it to play the video it doesn't play?
Has anyone else had this problem or found a solution?
Code snippet:
override func canBecomeFirstResponder() -> Bool {
return true
}
override func remoteControlReceivedWithEvent(event: UIEvent!) {
if (event.type == UIEventType.RemoteControl){
if (event.subtype.toRaw() == 100 || event.subtype.toRaw() == 101){
didPressPausePlay(self)
}else if(event.subtype.toRaw() == 104){
didPressNext(self)
}else if(event.subtype.toRaw() == 105){
didPressPrevious(self)
}
}
}
#IBAction func didPressPrevious(sender: AnyObject) {
videoTitle.text = ""
if (currentIndex != 0){
currentIndex--
currentVideo = parsedVideoIds[currentIndex] as NSString
videoPlayerViewController = XCDYouTubeVideoPlayerViewController(videoIdentifier: currentVideo);
videoPlayerViewController.moviePlayer.backgroundPlaybackEnabled = true;
videoPlayerViewController.presentInView(self.view);
videoPlayerViewController.moviePlayer.controlStyle = MPMovieControlStyle.None
self.view.bringSubviewToFront(customControls);
videoPlayerViewController.moviePlayer.play()
currentImage = 0
pauseplayButton.setImage(pauseImage, forState: UIControlState.Normal)
}
}
I left out the rest because it all does the same thing.
Developing an app for an iPhone with audio files that need to be listened too through headphones.
How do I check if headphones aren't plugged in so I can tell the user to plug in headphones.
I have the following code from another thread but the audioSessionGetProperty method is deprecated. Anyone know how to alter the following code to make this work OR have there own code/solution.
Thanks.
- (BOOL)isHeadsetPluggedIn {
UInt32 routeSize = sizeof (CFStringRef);
CFStringRef route;
//Maybe changing it to something like the following would work for iOS7?
//AVAudioSession* session = [AVAudioSession sharedInstance];
//OSStatus error = [session setCategory:kAudioSessionProperty_AudioRoute...?
//the line below is whats giving me the warning
OSStatus error = AudioSessionGetProperty (kAudioSessionProperty_AudioRoute,
&routeSize,
&route);
/* Known values of route:
* "Headset"
* "Headphone"
* "Speaker"
* "SpeakerAndMicrophone"
* "HeadphonesAndMicrophone"
* "HeadsetInOut"
* "ReceiverAndMicrophone"
* "Lineout"
*/
if (!error && (route != NULL)) {
NSString* routeStr = (__bridge NSString*)route;
NSRange headphoneRange = [routeStr rangeOfString : #"Head"];
if (headphoneRange.location != NSNotFound) return YES;
}
return NO;
}
This should work, but I cannot test it right now, I'll do in the evening.
- (BOOL)isHeadsetPluggedIn {
AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute];
for (AVAudioSessionPortDescription* desc in [route outputs]) {
if ([[desc portType] isEqualToString:AVAudioSessionPortHeadphones])
return YES;
}
return NO;
}
Just to extend #Antonio's answer. If you need to detect whether the user has pulled out or plugged in the headphone.
#import <AVFoundation/AVFoundation.h>
// [AVAudioSession sharedInstance]; // #Boris edited: you may need it if there is no `AVAudioSession instance` created before. If doesn't work, uncomment this line.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(audioRouteChangeListenerCallback:)
name:AVAudioSessionRouteChangeNotification
object:nil];
// don't forget to `removeObserver:`
// If the user pulls out he headphone jack, stop playing.
- (void)audioRouteChangeListenerCallback:(NSNotification*)notification
{
NSDictionary *interuptionDict = notification.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
NSLog(#"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
NSLog(#"Headphone/Line plugged in");
break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
NSLog(#"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
NSLog(#"Headphone/Line was pulled. Stopping player....");
break;
case AVAudioSessionRouteChangeReasonCategoryChange:
// called at start - also when other audio wants to play
NSLog(#"AVAudioSessionRouteChangeReasonCategoryChange");
break;
}
}
Swift 3:
To check if headphones are connected
extension AVAudioSession {
static var isHeadphonesConnected: Bool {
return sharedInstance().isHeadphonesConnected
}
var isHeadphonesConnected: Bool {
return !currentRoute.outputs.filter { $0.isHeadphones }.isEmpty
}
}
extension AVAudioSessionPortDescription {
var isHeadphones: Bool {
return portType == AVAudioSessionPortHeadphones
}
}
Then you can just print("isHeadphones connected: \(AVAudioSession.isHeadphonesConnected)")
Listening to Changes
In Swift 3 the syntax is this:
func handleRouteChange(_ notification: Notification) {
guard
let userInfo = notification.userInfo,
let reasonRaw = userInfo[AVAudioSessionRouteChangeReasonKey] as? NSNumber,
let reason = AVAudioSessionRouteChangeReason(rawValue: reasonRaw.uintValue)
else { fatalError("Strange... could not get routeChange") }
switch reason {
case .oldDeviceUnavailable:
print("oldDeviceUnavailable")
case .newDeviceAvailable:
print("newDeviceAvailable")
if AVAudioSession.isHeadphonesConnected {
print("Just connected headphones")
}
case .routeConfigurationChange:
print("routeConfigurationChange")
case .categoryChange:
print("categoryChange")
default:
print("not handling reason")
}
}
func listenForNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange(_:)), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)
}
Notice use of:
if AVAudioSession.isHeadphonesConnected {
print("Just connected headphones")
}
#Warif's code in Swift 2.0 with little changes ...
func audioRouteChangeListenerCallback (notif: NSNotification){
let userInfo:[NSObject:AnyObject] = notif.userInfo!
println("\(userInfo)")
let routChangeReason = UInt((userInfo[AVAudioSessionRouteChangeReasonKey]?.integerValue)!)
switch routChangeReason {
case AVAudioSessionRouteChangeReason.NewDeviceAvailable.rawValue:
self.println("Headphone/Line plugged in");
break;
case AVAudioSessionRouteChangeReason.OldDeviceUnavailable.rawValue:
//If the headphones was pulled move to speaker
do {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.Speaker)
} catch _ {
}
self.println("Headphone/Line was pulled. Stopping player....");
break;
case AVAudioSessionRouteChangeReason.CategoryChange.rawValue:
// called at start - also when other audio wants to play
self.println("AVAudioSessionRouteChangeReasonCategoryChange");
break;
default:
break;
}
}
:D
In Swift (as of 1.2):
func headsetPluggedIn() -> Bool {
let route = AVAudioSession.sharedInstance().currentRoute
return (route.outputs as! [AVAudioSessionPortDescription]).filter({ $0.portType == AVAudioSessionPortHeadphones }).count > 0
}
Swift 3.0 version
Method to check if headphones are plugged or any Bluetooth device with audio output connected
func bluetoothOrHeadphonesConnected() -> Bool {
let outputs = AVAudioSession.sharedInstance().currentRoute.outputs
for output in outputs{
if output.portType == AVAudioSessionPortBluetoothA2DP ||
output.portType == AVAudioSessionPortBluetoothHFP ||
output.portType == AVAudioSessionPortBluetoothLE ||
output.portType == AVAudioSessionPortHeadphones {
return true
}
}
return false
}
It's important to check if the headphones are plugged out while you listen any audio.
private func setupObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(self.audioRouteChangeListener), name: .AVAudioSessionRouteChange, object: nil)
}
func audioRouteChangeListener(notification: Notification) {
guard let audioRouteChangeReason = notification.userInfo![AVAudioSessionRouteChangeReasonKey] as? Int else { return }
switch audioRouteChangeReason {
case AVAudioSessionRouteChangeReason.oldDeviceUnavailable.hashValue:
//plugged out
default:
break
}
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(plugout:) name:AVAudioSessionRouteChangeNotification object:nil];
-(void)plugout:(NSNotification*)notification
{
isRemovedHeadset = YES;
}
and handle your code using this isRemovedHeadset boolean in your
if (moviePlayer.playbackState == MPMoviePlaybackStatePaused)
{
if(isRemovedHeadset)
{
isRemovedHeadset = NO;
[moviePlayer prepareToPlay];
[moviePlayer play];
return;
}
}
#Sajjon solution on Swift 5 with RxSwift
func handleRouteChange(_ notification: Notification) {
guard
let userInfo = notification.userInfo,
let reasonRaw = userInfo[AVAudioSessionRouteChangeReasonKey] as? NSNumber,
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonRaw.uintValue)
else { fatalError("Strange... could not get routeChange") }
switch reason {
case .oldDeviceUnavailable:
print("oldDeviceUnavailable")
case .newDeviceAvailable:
print("newDeviceAvailable")
if AVAudioSession.isHeadphonesConnected {
print("Just connected headphones")
}
case .routeConfigurationChange:
print("routeConfigurationChange")
case .categoryChange:
print("categoryChange")
default:
print("not handling reason")
}
}
func listenForNotifications() {
NotificationCenter.default.rx
.notification(AVAudioSession.routeChangeNotification)
.subscribe(onNext: { (n) in
self.handleRouteChange(n)
})
.disposed(by: disposeBag)
}