Is it possible to play a track when the app has exited/gone into a background state?
For example:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSURL *trackURL = [NSURL URLWithString:#"spotify:track:489K1qunRVBm2OpS4XGLNd"];
[[SPSession sharedSession] trackForURL:trackURL callback:^(SPTrack *track) {
if (track != nil) {
[SPAsyncLoading waitUntilLoaded:track timeout:kSPAsyncLoadingDefaultTimeout then:^(NSArray *tracks, NSArray *notLoadedTracks) {
[self.playbackManager playTrack:track callback:^(NSError *error) {
if (error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Cannot Play Track"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
} else {
self.currentTrack = track;
}
}];
}];
}
}];
}
The above code was taken from the Simple Player app provided with cocoalibspotify.
Copying and pasting code like this won't get you far when backgrounding - you can't use UI elements like UIAlertView, for a start.
You need to declare your application as supporting the audio background mode as documented by Apple. Then, when you go into the background you should make a background task to start playback.
Related
I'm having an issue with saving video from a GPUImage videoCamera to the Camera Roll when my app goes into the background. The file is only saved to the camera roll when the app returns to the foreground / is restarted. I'm no doubt making a beginners code error , if anyone can point it out that would be appreciated.
- (void)applicationDidEnterBackground:(UIApplication *)application {
if (isRecording){
[self stopRecording];
};
if (self.isViewLoaded && self.view.window){
[videoCamera stopCameraCapture];
};
runSynchronouslyOnVideoProcessingQueue(^{
glFinish();
});
NSLog(#"applicationDidEnterBackground");
and then
-(void)stopRecording {
[filterBlend removeTarget:movieWriter];
videoCamera.audioEncodingTarget = nil;
[movieWriter finishRecording];
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:#"file.mov"];
ALAssetsLibrary *al = [[ALAssetsLibrary alloc] init];
[al writeVideoAtPathToSavedPhotosAlbum:[NSURL fileURLWithPath:path] completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(#"Error %#", error);
} else {
NSLog(#"Success");
}
}];
isRecording = NO;
NSLog(#"Stop recording");
It was exactly as Brad pointed out in his, as usual, insightful comment, the -writeVideoAtPathToSavedPhotosAlbum:completionBlock: wasn't completing till after the app returned to the foreground, I solved it by adding
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background handler called. Not running background tasks anymore.");
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}];
and
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
Found this solution at http://www.raywenderlich.com/29948/backgrounding-for-ios
I'm adding FBLoginView to my ViewController < FBLoginViewDelegate >:
FBLoginView *loginview = [[FBLoginView alloc] init];
loginview.frame = CGRectOffset(loginview.frame, 5, 5);
loginview.delegate = self;
[self.view addSubview:loginview];
[loginview sizeToFit];
All the necessary fields in plist (FacebookAppID, FacebookDisplayName, URL Schemes) are all set according to the tutorial. The facebook app is also configured according to the tutorial (bundle ID is set, Facebook login is enabled).
But the login still isn't performed. When I press on "log in", I get redirected to the browser with facebook login, but when it's finished, I'm not logged in the app (loginViewFetchedUserInfo:user: isn't called, "log in" hasn't changed to "log out").
What can be the problem?
Everything worked after I implemented the following in the AppDelegate.m (taken from one of the official examples):
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen:
if (!error) {
// We have a valid session
//NSLog(#"User session found");
[FBRequestConnection
startForMeWithCompletionHandler:^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error) {
if (!error) {
self.loggedInUserID = user.id;
self.loggedInSession = FBSession.activeSession;
}
}];
}
break;
case FBSessionStateClosed:
case FBSessionStateClosedLoginFailed:
[FBSession.activeSession closeAndClearTokenInformation];
break;
default:
break;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:FBSessionStateChangedNotification
object:session];
if (error) {
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Error"
message:error.localizedDescription
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
/*
* Opens a Facebook session and optionally shows the login UX.
*/
- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI {
return [FBSession openActiveSessionWithReadPermissions:nil
allowLoginUI:allowLoginUI
completionHandler:^(FBSession *session,
FBSessionState state,
NSError *error) {
[self sessionStateChanged:session
state:state
error:error];
}];
}
/*
*
*/
- (void) closeSession {
[FBSession.activeSession closeAndClearTokenInformation];
}
/*
* If we have a valid session at the time of openURL call, we handle
* Facebook transitions by passing the url argument to handleOpenURL
*/
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
}
You need to add the following to the app delegate
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// Call FBAppCall's handleOpenURL:sourceApplication to handle Facebook app responses
BOOL wasHandled = [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
// You can add your app-specific url handling code here if needed
return wasHandled;
}
You may need to implement the
(BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url method
Be sure to call:
return [FBSession.activeSession handleOpenURL:url];
When applicable.
#Sergey: Are you want to open FBDialog on your native app or in browser? If you to want open in your native app then use "FBSessionLoginBehaviorForcingWebView". Here is my code that I am using:
NSArray *permission = [NSArray arrayWithObjects:kFBEmailPermission,kFBUserPhotosPermission, nil];
FBSession *session = [[FBSession alloc] initWithPermissions:permission];
[FBSession setActiveSession: [[FBSession alloc] initWithPermissions:permission] ];
[[FBSession activeSession] openWithBehavior:FBSessionLoginBehaviorForcingWebView completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
switch (status) {
case FBSessionStateOpen:
[self yourmethod];
break;
case FBSessionStateClosedLoginFailed: {
// prefer to keep decls near to their use
// unpack the error code and reason in order to compute cancel bool
NSString *errorCode = [[error userInfo] objectForKey:FBErrorLoginFailedOriginalErrorCode];
NSString *errorReason = [[error userInfo] objectForKey:FBErrorLoginFailedReason];
BOOL userDidCancel = !errorCode && (!errorReason || [errorReason isEqualToString:FBErrorLoginFailedReasonInlineCancelledValue]);
if(error.code == 2) {
UIAlertView *errorMessage = [[UIAlertView alloc] initWithTitle:#"FBAlertTitle"
message:#"FBAuthenticationErrorMessage"
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[errorMessage performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
errorMessage = nil;
}
}
break;
// presently extension, log-out and invalidation are being implemented in the Facebook class
default:
break; // so we do nothing in response to those state transitions
}
}];
permission = nil;
or you want to open in browser then use following :
In your .h file
#import <FacebookSDK/FacebookSDK.h> and add FBLoginViewDelegate delegate
In you .m file
FBLoginView *loginview = [[FBLoginView alloc] init];
loginview.frame = CGRectOffset(loginview.frame, 5, 5);
loginview.delegate = self;
[self.view addSubview:loginview];
[loginview sizeToFit];
// use following delegate methods
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView {
// first get the buttons set for login mode
}
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
// here we use helper properties of FBGraphUser to dot-through to first_name and
// id properties of the json response from the server; alternatively we could use
// NSDictionary methods such as objectForKey to get values from the my json object
NSLog(#"userprofile:%#",user);
}
- (void)loginViewShowingLoggedOutUser:(FBLoginView *)loginView {
//BOOL canShareAnyhow = [FBNativeDialogs canPresentShareDialogWithSession:nil];
}
- (void)loginView:(FBLoginView *)loginView handleError:(NSError *)error {
// see https://developers.facebook.com/docs/reference/api/errors/ for general guidance on error handling for Facebook API
// our policy here is to let the login view handle errors, but to log the results
NSLog(#"FBLoginView encountered an error=%#", error);
}
I would have thought NSFileManagers method of removeItemAtURL:error: would remove the Core Data log files created when using UIManagedDocuments with iCloud.
What is the best way to make sure all of these log files are removed?
I have used...
- (void)deleteRemnantsOfOldDatabaseDocumentAndItsTransactionLogsWithCompletionHandler:(completion_success_t)completionBlock
{
__weak CloudController *weakSelf = self;
NSURL *databaseStoreFolder = self.iCloudDatabaseStoreFolderURL;
NSURL *transactionLogFolder = self.transactionLogFilesFolderURL;
[self deleteFileAtURL:databaseStoreFolder withCompletionBlock:^(BOOL docSuccess) {
[weakSelf deleteFileAtURL:transactionLogFolder withCompletionBlock:^(BOOL logSuccess) {
completionBlock(docSuccess && logSuccess);
}];
}];
}
In conjunction with...
- (void)deleteFileAtURL:(NSURL *)fileURL withCompletionBlock:(completion_success_t)completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSError *coordinatorError = nil;
__block BOOL success = NO;
[fileCoordinator coordinateWritingItemAtURL:fileURL
options:NSFileCoordinatorWritingForDeleting
error:&coordinatorError
byAccessor:^(NSURL *writingURL) {
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSError *removalError = nil;
if ([fileManager fileExistsAtPath:[writingURL path]]) {
if (![fileManager removeItemAtURL:writingURL error:&removalError]) {
NSLog(#"deleteFileAtURL: removal error: %#", removalError);
} else {
success = YES;
}
}
}];
if (coordinatorError) {
NSLog(#"deleteFileAtURL: coordinator error: %#", coordinatorError);
}
completionBlock(success);
});
}
Note: this was used for a single document toolbox style app, and was intended more for clearing out the iCloud container before creating a brand new document, in an 'apparently' empty iCloud store for the first time. But I'm sure it can be adapted without too much work.
Oops, the above won't make sense/work without:
typedef void (^completion_success_t)(BOOL success);
You can debug the contents of your iCloud container and verify things have been removed by using a method like (which to be honest I've probably lifted from somewhere else and modified):
- (void)logDirectoryHierarchyContentsForURL:(NSURL *)url
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtURL:url
includingPropertiesForKeys:#[NSURLNameKey, NSURLContentModificationDateKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:nil];
NSMutableArray *results = [NSMutableArray array];
for (NSURL *itemURL in directoryEnumerator) {
NSString *fileName;
[itemURL getResourceValue:&fileName forKey:NSURLNameKey error:NULL];
NSDate *modificationDate;
[itemURL getResourceValue:&modificationDate forKey:NSURLContentModificationDateKey error:NULL];
[results addObject:[NSString stringWithFormat:#"%# (%#)", itemURL, modificationDate]];
}
NSLog(#"Directory contents: %#", results);
}
And it's also worth logging onto developer.icloud.com and examining what is actually in the iCloud store. There is sometimes a difference between what is retained in the device ubiquity container, and what is actually in the iCloud server folder structure. Between all of these you can get quite a good idea of what's going on.
I have a GameCenter Sandbox-Account have tested my game, earned achievements, etc.
Now I've made some changes and want to test earning Achievements again!
Do I have to make an entire new Sandbox-Account or is there a way to reset my account?
The following code is from the Apple Documentation.
- (void) resetAchievements
{
// Clear all locally saved achievement objects.
achievementsDictionary = [[NSMutableDictionary alloc] init];
// Clear all progress saved on Game Center
[GKAchievement resetAchievementsWithCompletionHandler:^(NSError *error)
{
if (error != nil)
// handle errors
}];
}
Also have a look at Apple's sample project GKTapper.
// Reset all the achievements for local player
- (void)resetAchievements
{
[GKAchievement resetAchievementsWithCompletionHandler: ^(NSError *error)
{
if (!error) {
[storedAchievements release];
storedAchievements = [[NSMutableDictionary alloc] init];
// overwrite any previously stored file
[self writeStoredAchievements];
} else {
// Error clearing achievements.
}
}];
}
I have a music button in the main UI of iOS and I want the button function as stop music button and at the same time can play the music again. My problem is I can only stop the music but can't play it again. Here is my iOS code in xcode4. How can I define one button for both play and stop music button? I have assigned one button for both stopMusic and playMusic but it doesn't work.
- (IBAction)stopMusic{
self.playBgMusic.enabled = YES;
[self.player stop];
}
- (IBAction)playMusic{
self.playBgMusic.enabled = YES;
[self.player play];
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"music" ofType:#"mp3"];
self.player=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
player.delegate = self;
[player play];
player.numberOfLoops = -1;
[super viewDidLoad];
}
Rather than have a single button try to send two different messages (stopMusic, playMusic), have it send toggleMusic every time. The toggleMusic method can then stop or play the music based on the current state of the player.
- (IBAction)toggleMusic{
if ([self.player isPlaying] == YES) {
[self.player stop];
} else {
[self.player play];
}
self.playBgMusic.enabled = YES;
}