I have a query regarding Music Files.
I want to select 1/more Music files from the Music Library of iPhone/iTouch/iPad and Save in my Documents Folder or Send it to Server.
I roughly went through MPMediaPickerController and AddMusic(Sample Code).
But, I could only get details of selecting Songs and Playing it.
Is it possible to Save those selected Songs, which I can also use to Send to Server ?
Thanks
Yes, it is possible.
I think it will help you
// Have to add this framework
//CoreMedia.framework
//AudioToolbox.framework
//CoreAudio.framework
//MediaPlayer.framework
//AVFoundation.framework
//in UploadAudioViewController.h file
#import <UIKit/UIKit.h>
#import <CoreAudio/CoreAudioTypes.h>
#import <AudioToolbox/AudioToolbox.h>
#import <MediaPlayer/MediaPlayer.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>
#interface UploadAudioViewController : UIViewController<UIActionSheetDelegate,UINavigationControllerDelegate,UIImagePickerControllerDelegate,MPMediaPickerControllerDelegate,AVAudioRecorderDelegate, AVAudioPlayerDelegate,AVAudioSessionDelegate>{
MPMediaItem *song;
NSURL *exportURL;
}
#property (nonatomic, retain) NSData *audioData;
// in UploadAudioViewController.m file
#import "UploadAudioViewController.h"
//#import < AudioToolbox/AudioToolbox.h>
#interface UploadAudioViewController ()
#end
#implementation UploadAudioViewController
#synthesize musicPlayer,audioData;
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.title = #"Upload Audio";
self.musicPlayer = [MPMusicPlayerController iPodMusicPlayer];
self.audioData=nil;
}
#pragma mark Browse Audio from Device
-(void)PickAudioForIndex_iPhone
{
if ([[[UIDevice currentDevice] model] isEqualToString:#"iPhone Simulator"]) {
//device is simulator
UIAlertView *alert1;
alert1 = [[UIAlertView alloc] initWithTitle:#"Alert" message:#"There is no Audio file in the Device" delegate:self cancelButtonTitle:nil otherButtonTitles:#"Ok",nil];
alert1.tag=2;
[alert1 show];
//[alert1 release],alert1=nil;
}else{
MPMediaPickerController *mediaPicker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic];
mediaPicker.delegate = self;
mediaPicker.allowsPickingMultipleItems = NO; // this is the default
[self presentViewController:mediaPicker animated:YES completion:nil];
}
}
#pragma mark Media picker delegate methods
-(void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection {
// We need to dismiss the picker
[self dismissViewControllerAnimated:YES completion:nil];
// Assign the selected item(s) to the music player and start playback.
if ([mediaItemCollection count] < 1) {
return;
}
song = [[mediaItemCollection items] objectAtIndex:0];
[self handleExportTapped];
}
-(void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker {
// User did not select anything
// We need to dismiss the picker
[self dismissViewControllerAnimated:YES completion:nil ];
}
-(void)handleExportTapped{
// get the special URL
if (! song) {
return;
}
//[self startLoaderWithLabel:#"Preparing for upload..."];
NSURL *assetURL = [song valueForProperty:MPMediaItemPropertyAssetURL];
AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetURL options:nil];
NSLog (#"Core Audio %# directly open library URL %#",
coreAudioCanOpenURL (assetURL) ? #"can" : #"cannot",
assetURL);
NSLog (#"compatible presets for songAsset: %#",
[AVAssetExportSession exportPresetsCompatibleWithAsset:songAsset]);
/* approach 1: export just the song itself
*/
AVAssetExportSession *exporter = [[AVAssetExportSession alloc]
initWithAsset: songAsset
presetName: AVAssetExportPresetAppleM4A];
NSLog (#"created exporter. supportedFileTypes: %#", exporter.supportedFileTypes);
exporter.outputFileType = #"com.apple.m4a-audio";
NSString *exportFile = [myDocumentsDirectory() stringByAppendingPathComponent: #"exported.m4a"];
// end of approach 1
// set up export (hang on to exportURL so convert to PCM can find it)
myDeleteFile(exportFile);
//[exportURL release];
exportURL = [NSURL fileURLWithPath:exportFile];
exporter.outputURL = exportURL;
// do the export
[exporter exportAsynchronouslyWithCompletionHandler:^{
int exportStatus = exporter.status;
switch (exportStatus) {
case AVAssetExportSessionStatusFailed: {
// log error to text view
NSError *exportError = exporter.error;
NSLog (#"AVAssetExportSessionStatusFailed: %#", exportError);
//errorView.text = exportError ? [exportError description] : #"Unknown failure";
//errorView.hidden = NO;
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
case AVAssetExportSessionStatusCompleted: {
NSLog (#"AVAssetExportSessionStatusCompleted");
//fileNameLabel.text = [exporter.outputURL lastPathComponent];
// set up AVPlayer
//[self setUpAVPlayerForURL: exporter.outputURL];
///////////////// get audio data from url
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
NSURL *audioUrl = exportURL;
NSLog(#"Audio Url=%#",audioUrl);
self.audioData = [NSData dataWithContentsOfURL:audioUrl];
break;
}
case AVAssetExportSessionStatusUnknown: {
NSLog (#"AVAssetExportSessionStatusUnknown");
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
case AVAssetExportSessionStatusExporting: {
NSLog (#"AVAssetExportSessionStatusExporting");
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
case AVAssetExportSessionStatusCancelled: {
NSLog (#"AVAssetExportSessionStatusCancelled");
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
case AVAssetExportSessionStatusWaiting: {
NSLog (#"AVAssetExportSessionStatusWaiting");
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
default: {
NSLog (#"didn't get export status");
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
}
}];
}
#pragma mark conveniences
NSString* myDocumentsDirectory(){
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [paths objectAtIndex:0];;
}
void myDeleteFile (NSString* path){
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSError *deleteErr = nil;
[[NSFileManager defaultManager] removeItemAtPath:path error:&deleteErr];
if (deleteErr) {
NSLog (#"Can't delete %#: %#", path, deleteErr);
}
}
}
// generic error handler from upcoming "Core Audio" book (thanks, Kevin!)
// if result is nonzero, prints error message and exits program.
static void CheckResult(OSStatus result, const char *operation)
{
if (result == noErr) return;
char errorString[20];
// see if it appears to be a 4-char-code
*(UInt32 *)(errorString + 1) = CFSwapInt32HostToBig(result);
if (isprint(errorString[1]) && isprint(errorString[2]) && isprint(errorString[3]) && isprint(errorString[4])) {
errorString[0] = errorString[5] = '\'';
errorString[6] = '\0';
} else
// no, format it as an integer
sprintf(errorString, "%d", (int)result);
fprintf(stderr, "Error: %s (%s)\n", operation, errorString);
exit(1);
}
#pragma mark core audio test
BOOL coreAudioCanOpenURL (NSURL* url){
OSStatus openErr = noErr;
AudioFileID audioFile = NULL;
openErr = AudioFileOpenURL((__bridge CFURLRef) url,
kAudioFileReadPermission ,
0,
&audioFile);
if (audioFile) {
AudioFileClose (audioFile);
}
return openErr ? NO : YES;
}
I have tried and succeed in exporting the ipod library song . Please find my code below:
extension AudioPostViewController: MPMediaPickerControllerDelegate {
func mediaPicker(mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
dismissViewControllerAnimated(true, completion: {
print("You selected \(mediaItemCollection)")
let item: MPMediaItem = mediaItemCollection.items[0]
let pathURL: NSURL? = item.valueForProperty(MPMediaItemPropertyAssetURL) as? NSURL
if pathURL == nil {
Alert.showPopupWithMessage("Unable to read DRM protected file.")
return
}
let title = item.valueForProperty(MPMediaItemPropertyTitle) as? String ?? "Now Playing..."
print("\(pathURL), title : \(title) ")
// get file extension andmime type
let str = pathURL!.absoluteString
let str2 = str.stringByReplacingOccurrencesOfString("ipod-library://item/item", withString: "")
let arr = str2.componentsSeparatedByString("?")
var mimeType = arr[0]
mimeType = mimeType.stringByReplacingOccurrencesOfString(".", withString: "")
// Export the ipod library as .m4a file to local directory for remote upload
let exportSession = AVAssetExportSession(asset: AVAsset(URL: pathURL!), presetName: AVAssetExportPresetAppleM4A)
exportSession?.shouldOptimizeForNetworkUse = true
exportSession?.outputFileType = AVFileTypeAppleM4A
FilePath.removeAudioFile() // Remove file if it exists
let fileUrl = FilePath.testFilePathURL()
exportSession?.outputURL = fileUrl
exportSession?.exportAsynchronouslyWithCompletionHandler({ () -> Void in
if exportSession!.status == AVAssetExportSessionStatus.Completed {
dispatch_async(dispatch_get_main_queue(), {
// Prepare audio to play
self.audioTitle = title
self.audioPath = fileUrl
self.mimeType = "audio/m4a"
self.audioFileName = "audio.m4a"
UIView.animateWithDuration(0.45, animations: {
if Platform.DeviceType.iPhone4 {
self.libraryBottomSpacing.constant = 5
self.view.layoutIfNeeded()
} else {
self.libraryBottomSpacing.constant = 25
self.view.layoutIfNeeded()
}
}, completion: {
(v: Bool) in
self.loadJukeBox(self.audioPath!, title: title)
})
})
} else {
dispatch_async(dispatch_get_main_queue(), {
Alert.showPopupWithMessage("Unable to read file as its DRM protected.")
})
}
})
})
}
func mediaPickerDidCancel(mediaPicker: MPMediaPickerController) {
dismissViewControllerAnimated(true, completion: nil)
}
}
Is it possible to save those selected songs, which I can also use to send to server?
No, you can only play songs from the iTunes library.
The raw audio files are not accessible, probably due to copyright protection.
Related
I have UIWebView like this:
#import <UIKit/UIKit.h>
#import "DataProvider.h"
#implementation QHArticle
{
UIWebView *_helperWebView;
}
-(instancetype)init
{
self = [super init];
if (self)
{
_helperWebView = [[UIWebView alloc] init];
[_helperWebView loadHTMLString:#"<html><script src='Article.js'></script><body onload='Article.init();'></body></html>" baseURL:[[NSBundle mainBundle] bundleURL]];
}
return self;
}
-(NSString*)callJSMethod:(NSString*)method
{
return [_helperWebView stringByEvaluatingJavaScriptFromString:method];
}
-(NSArray *)listArticle
{
NSString *json = [self callJSMethod:#"(function() {return JSON.stringify(Article._data.news)})()"];
;
NSError *error;
NSArray *result = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error];
return [result subarrayWithRange:NSMakeRange(1, result.count-2)];
}
The codes above is working fine when i tried it. But, after the codes changed to WKWebView like this:
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
#import "DataProvider.h"
#implementation QHArticle
{
WKWebView *_helperWebView;
}
-(instancetype)init
{
self = [super init];
if (self)
{
_helperWebView = [[WKWebView alloc] init];
[_helperWebView loadHTMLString:#"<html><script src='Article.js'></script><body onload='Article.init();'></body></html>" baseURL:[[NSBundle mainBundle] bundleURL]];
}
return self;
}
- (NSString *)callJSMethod:(NSString *)method
{
__block NSString *resultString = nil;
__block BOOL finished = NO;
[_helperWebView evaluateJavaScript:method completionHandler:^(id result, NSError *error) {
if (error == nil) {
if (result != nil) {
resultString = [NSString stringWithFormat:#"%#", result];
}
} else {
NSLog(#"evaluateJavaScript error : %#", error.localizedDescription);
}
finished = YES;
}];
while (!finished)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
return resultString;
}
I got an error message, "evaluateJavaScript error : .... Can't find Article object" and got white blank screen on the phone.
I still can't find the issues after i migrated it to WKWebView, please help.
I just want to use Objective-C, not Swift because this is the legacy code.
Thank you.
The WKWebView won't load anything from a file:// URL.
Start by loading your Article.js script into a string; you can use evaluateJavaScript: to run it.
To inject a string into every page that a particular WKWebView loads, you would create a WKUserScript, attach it to a WKUserContentController, and attach THAT to a WKWebViewConfiguration.
Then create your WKWebView with [WKWebView initWithFrame:configuration:]
Is there an example code for corespotlight search feature - iOS 9 API? Really appreciate if can look at sample code to implement/test.
Create a new iOS project and add CoreSpotlight and MobileCoreServices framework to your project.
Create the actual CSSearchableItem and associating the uniqueIdentifier, domainIdentifier and the attributeSet. Finally index the CSSearchableItem using [[CSSearchableIndex defaultSearchableIndex]...] as show below.
OK!Test the index!
CSSearchableItemAttributeSet *attributeSet;
attributeSet = [[CSSearchableItemAttributeSet alloc]
initWithItemContentType:(NSString *)kUTTypeImage];
attributeSet.title = #"My First Spotlight Search";
attributeSet.contentDescription = #"This is my first spotlight Search";
attributeSet.keywords = #[#"Hello", #"Welcome",#"Spotlight"];
UIImage *image = [UIImage imageNamed:#"searchIcon.png"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attributeSet.thumbnailData = imageData;
CSSearchableItem *item = [[CSSearchableItem alloc]
initWithUniqueIdentifier:#"com.deeplink"
domainIdentifier:#"spotlight.sample"
attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item]
completionHandler: ^(NSError * __nullable error) {
if (!error)
NSLog(#"Search item indexed");
}];
Note: kUTTypeImage requires that you import the MobileCoreServices framework.
To complete the spotlight search functionality, once you have implemented mayqiyue's answer, you'll be able to see the results in the search but on selection of the result simply your app would open not the related view with related content.
In order to do so, go to your AppDelegate.m and add the following method.
-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
//check if your activity has type search action(i.e. coming from spotlight search)
if ([userActivity.activityType isEqualToString:CSSearchableItemActionType ] == YES) {
//the identifier you'll use to open specific views and the content in those views.
NSString * identifierPath = [NSString stringWithFormat:#"%#",[userActivity.userInfo objectForKey:CSSearchableItemActivityIdentifier]];
if (identifierPath != nil) {
// go to YOUR VIEWCONTROLLER
// use notifications or whatever you want to do so
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
MyViewController *myViewController = [storyboard instantiateViewControllerWithIdentifier:#"MyViewController"];
// this notification must be registered in MyViewController
[[NSNotificationCenter defaultCenter] postNotificationName:#"OpenMyViewController" object: myViewController userInfo:nil];
return YES;
}
}
return NO;
}
Make sure to import in AppDelegate.m :
#import <MobileCoreServices/MobileCoreServices.h>
#import <CoreSpotlight/CoreSpotlight.h>
UPDATE for Swift 2.1
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
if #available(iOS 9.0, *) {
if userActivity.activityType == CSSearchableItemActionType {
//the identifier you'll use to open specific views and the content in those views.
let dict = userActivity.userInfo! as NSDictionary
let identifierPath = dict.objectForKey(CSSearchableItemActivityIdentifier) as! String
if identifierPath.characters.count > 0 {
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let mvc: MyViewController = storyboard.instantiateViewControllerWithIdentifier("MyViewController") as! MyViewController
NSNotificationCenter.defaultCenter().postNotificationName("OpenMyViewController", object: mvc, userInfo: nil)
}
return true
}
} else {
// Fallback on earlier versions
return false
}
return false
}
Make sure to import in AppDelegate.swift :
import CoreSpotlight
import MobileCoreServices
I am using similar implementation as mentioned by #mayqiyue but I am also checking the existence of the item variable for backwards compatibility with iOS 8.
- (void)setupCoreSpotlightSearch
{
CSSearchableItemAttributeSet *attibuteSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(__bridge NSString *)kUTTypeImage];
attibuteSet.title = NSLocalizedString(#"Be happy!", #"Be happy!");
attibuteSet.contentDescription = #"Just like that";
attibuteSet.keywords = #[#"example", #"stackoverflow", #"beer"];
UIImage *image = [UIImage imageNamed:#"Image"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attibuteSet.thumbnailData = imageData;
CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:#"1"
domainIdentifier:#"album-1"
attributeSet:attibuteSet];
if (item) {
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(#"Search item indexed");
}
}];
}
}
To handle the tap on the search item from Spotlight you need to implement following method in your AppDelegate:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
if ([userActivity.activityType isEqualToString:CSSearchableItemActionType]) {
NSString *uniqueIdentifier = userActivity.userInfo[CSSearchableItemActivityIdentifier];
// Handle 'uniqueIdentifier'
NSLog(#"uniqueIdentifier: %#", uniqueIdentifier);
}
return YES;
}
Write in your main controller Class
-(void)storeValueForSpotligtSearch {
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
// **Your Model Array that Contain Data Like attributes Make, Model, Variant and Year and Images**
for (MyCatalogeModel *myCatalogeModelObj in yourDataContainer) {
NSMutableArray *arrKeywords = [[NSMutableArray alloc] initWithObjects: myCatalogeModelObj.year, myCatalogeModelObj.make, myCatalogeModelObj.model, myCatalogeModelObj.variant, nil];
NSString *strIdentifier = [NSString stringWithFormat:#"%#.%#",bundleIdentifier, myCatalogeModelObj.carId];
self.userActivity = [[NSUserActivity alloc]initWithActivityType:strIdentifier];
self.userActivity.title = myCatalogeModelObj.year;
self.userActivity.title = myCatalogeModelObj.make;
self.userActivity.title = myCatalogeModelObj.model;
self.userActivity.title = myCatalogeModelObj.variant;
self.userActivity.eligibleForSearch = YES;
self.userActivity.eligibleForPublicIndexing = YES;
self.userActivity.eligibleForHandoff = YES;
CSSearchableItemAttributeSet * attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeJSON];
attributeSet.title = myCatalogeModelObj.make;
attributeSet.thumbnailData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[myCatalogeModelObj.imageArray objectAtIndex:0]]];
attributeSet.contentDescription = [NSString stringWithFormat:#"%# %# %# %#", myCatalogeModelObj.year, myCatalogeModelObj.make, myCatalogeModelObj.model, myCatalogeModelObj.variant];
attributeSet.keywords = arrKeywords;
CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:strIdentifier domainIdentifier:#"spotlight.CARS24ChannelPartnerapp" attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler: ^(NSError * __nullable error) {
}];
self.userActivity.contentAttributeSet = attributeSet;
[self.userActivity becomeCurrent];
[self updateUserActivityState:self.userActivity];
}
}
Write in App Delegate
-(BOOL)application:(nonnull UIApplication *) application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * __nullable))restorationHandler {
#try {
NSString *strIdentifier;
NSNumber *numScreenId;
NSNumberFormatter *numFormatter = [[NSNumberFormatter alloc] init];
NSLog(#"Activity = %#",userActivity.userInfo);
if (userActivity.userInfo[#"vc"]) {
numScreenId = userActivity.userInfo[#"vc"];
}
else{
strIdentifier = [userActivity.userInfo objectForKey:#"kCSSearchableItemActivityIdentifier"];
NSLog(#"strIdentifier : %#",strIdentifier);
NSArray *arr = [strIdentifier componentsSeparatedByString:#"."];
NSString *strScreenId = [arr objectAtIndex:3];
NSLog(#"ID -= %#",strScreenId);
**// On Click in Spotlight search item move your particular view.**
[self moveToParticular:[strScreenId intValue]];
numScreenId = [numFormatter numberFromString:strScreenId];
}
}
#catch (NSException *exception) {}
return YES;
}
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeImage as String)
attributeSet.title = "Searchable Item"
attributeSet.contentDescription = "Code for creating searchable item"
attributeSet.keywords = ["Item","Searchable","Imagine"]
attributeSet.thumbnailURL = NSURL(string: "https://blog.imagine.com/")
let searchableItem = CSSearchableItem(uniqueIdentifier: "com.imagine.objectA", domainIdentifier: "spotlight.search", attributeSet: attributeSet)
CSSearchableIndex.defaultSearchableIndex().indexSearchableItems([searchableItem]) {_ in}
I need to create the render graph for basic recording to a file and playback from that file, using AVAudioFile & AVAudioInputNode.
Below are the main setup methods I started, but the graph is connected just for player.
How do I construct the graph so that [Input], [Player] [Mixers] are connected to achieve Rec/Play?
#pragma mark - Setup
-(void)setupAudioEngine{
//create
_audioEngine = [[AVAudioEngine alloc]init];
_player = [[AVAudioPlayerNode alloc] init];
_inputMixer = [[AVAudioMixerNode alloc]init];
_playerMixer = [[AVAudioMixerNode alloc]init];
//attach player to engine
[_audioEngine attachNode:_player];
[_audioEngine attachNode:_inputMixer];
[_audioEngine attachNode:_playerMixer];
_input = [_audioEngine inputNode];
_mainMixer = [_audioEngine mainMixerNode];
//setup audio file
NSError *error = nil;
//Connect the render grapth
[_audioEngine connect:_input to:_mainMixer format:[_input inputFormatForBus:0]];
[_audioEngine connect:_player to:_mainMixer format:_audioFile.processingFormat];
//Start the Engine
[_audioEngine startAndReturnError:&error];
if(error)
{
NSLog(#"error: %#", [error localizedDescription]);
}
}
- (IBAction)recordAudio:(UIButton *)sender {
NSError *error = nil;
//setup for writing to a file
NSArray *pathComponents = [NSArray arrayWithObjects:
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject],
#"MyAudioMemo.m4a",
nil];
NSURL *inputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
NSDictionary *recordSettings = #{
AVFormatIDKey : #(kAudioFormatMPEG4AAC),
AVSampleRateKey : #44100.0f,
AVNumberOfChannelsKey : #1,
AVEncoderBitDepthHintKey : #16,
AVEncoderAudioQualityKey : #(AVAudioQualityMedium)
};
_audioFile = [[AVAudioFile alloc] initForWriting:inputFileURL settings:recordSettings error:&error];
[_mainMixer installTapOnBus:0 bufferSize:4096 format:[_mainMixer outputFormatForBus:0] block:^(AVAudioPCMBuffer *buffer, AVAudioTime *when) {
}];
}
- (IBAction)stopRec:(id)sender {
[_audioEngine disconnectNodeOutput:_input];
}
- (IBAction)playAudio:(UIButton *)sender {
//schedule play
[_player scheduleFile:_audioFile atTime:nil completionHandler:nil];
[_player play];
}
Rule #1: Not all bitrate/audioformat/samplerate combinations work. Most of them will crash, so stick to the ones that work (I discovered a few, empirically).
This code records and plays .M4A format:
func directoryURL() -> NSURL {
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
filepath = urls[0]
let documentDirectory = urls[0] as NSURL
print("STORAGE DIR: "+documentDirectory.description)
//print("---filepath: "+(filepath?.description)!)
let soundURL = documentDirectory.URLByAppendingPathComponent(m4aName) //.m4a
print("SAVING FILE: "+soundURL!.description)
return soundURL!
}
//INIT AUDIO RECORDER
func initializeAudioSession(){
let recordSettings = [AVSampleRateKey : NSNumber(float: Float(16000.0)),
AVFormatIDKey : NSNumber(int: Int32(kAudioFormatMPEG4AAC)), //.m4a !!
// AVFormatIDKey : NSNumber(int: Int32(kAudioFileMP3Type)),
AVNumberOfChannelsKey : NSNumber(int: 1),
AVEncoderAudioQualityKey : NSNumber(int: Int32(AVAudioQuality.Low.rawValue)) ] //mp3 crashes!!
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try audioRecorder = AVAudioRecorder(URL: self.directoryURL(),
settings: recordSettings)
audioRecorder.delegate = self
audioRecorder.meteringEnabled = true
audioRecorder.prepareToRecord()
print("M4a Recorder Initialized - OK...")
recordSpeechM4A();
stopRecordingCapio();
} catch let error1 as NSError{
print("Init Error: "+error1.description)
}
}
//REC SPEECH AUDIO IN .M4A format
func recordSpeechM4A(){
if !audioRecorder.recording {
isRecording=true //optional variable for tracking
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setActive(true)
print("**** RECORDING ****")
audioRecorder.record()
//Animate a microphone waveform while the user speaks
self.meterTimer = NSTimer.scheduledTimerWithTimeInterval(0.03,
target:self,
selector:#selector(ViewController.updateWaveview2(_:)),
userInfo:nil,
repeats:false)
} catch {
print("RECORDING ERROR")
}
}
}
//STOP RECORDING
func stopRecording(){
audioRecorder.stop()
}
//PLAY
func playRecordedAudio(){
if (!audioRecorder.recording){
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: audioRecorder.url)
audioPlayer.play()
print("PLAYING AUDIO...: "+audioRecorder.url.description)
print("Audio duration: "+audioPlayer.duration.description)
} catch {
print("AUDIO PLAYBACK ERROR")
}
}
}
I've made an implementation for an JSON-RPC (a little bit modified) Server/Client in objective-c with the GCDAsyncSocket library. but the app crashes on responding to an request. without debugging for zombies i'm getting this error:
JSONRPCTestServer(1301,0x7fff7f887960) malloc: *** error for object 0x10014db10: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
the screenshot in xcode shows the error is in the completeCurrentRead method of the GCDAsyncSocket library.
when debugging for zombies it logs this:
2013-02-04 14:36:16.430 JSONRPCTestServer[1367:603] *** -[__NSArrayI release]: message sent to deallocated instance 0x1005b6fd0
and instruments shows this:
as this happens when a response to the rpc-call is of type nsarray i'd guess its this array that is causing the error. the rpc-method is:
-(NSArray *)testArray:(GCDAsyncSocket *)sock {
return [NSArray arrayWithObjects:#"test1",#"test2", nil];
}
The JSON-RPC header is:
#import <Foundation/Foundation.h>
#import "JSONRPCMethod.h"
#import "JSONRPCResponse.h"
#import "JSONRPCError.h"
#import "JSONRPCArgument.h"
#import "JSONRPCRequest.h"
#import "GCDAsyncSocket.h"
#class GCDAsyncSocket;
#protocol JSONRPCResponseDelegate <NSObject>
#optional
-(void)rpcSocket:(GCDAsyncSocket*)sock returnedValue:(id)retVal forMethod:(NSString*)m id:(id)i;
-(void)rpcSocket:(GCDAsyncSocket*)sock returnedError:(JSONRPCError*)err forMethod:(NSString*)m id:(id)i;
-(void)rpcReturnedValue:(id)retVal forMethod:(NSString*)m id:(id)i;
-(void)rpcReturnedError:(JSONRPCError*)err forMethod:(NSString*)m id:(id)i;
#end
#interface JSONRPC : NSObject {
NSMutableArray *supportedMethods;
GCDAsyncSocket *mainSocket;
NSMutableArray *connectedSockets;
NSMutableArray *responseDelegates;
BOOL isServer;
}
+(JSONRPC*)sharedConnection;
-(BOOL)startServer:(NSUInteger)port;
-(BOOL)connectToServer:(NSString*)host port:(NSUInteger)port;
-(BOOL)addMethod:(JSONRPCMethod*)method;
-(void)removeMethod:(JSONRPCMethod*)method;
-(void)removeMethodsWithTarget:(id)target;
-(void)sendRequest:(JSONRPCRequest*)req toSocket:(GCDAsyncSocket*)sock;
-(void)sendRequest:(JSONRPCRequest*)req;
-(void)sendNotification:(JSONRPCRequest*)req toSocket:(GCDAsyncSocket*)sock;
-(void)sendNotification:(JSONRPCRequest*)req;
-(void)sendNotification:(JSONRPCRequest*)req toSocketsWithUserData:(id)userData;
#end
.m is:
#import "JSONRPC.h"
#import "GCDAsyncSocket.h"
#define kGeneralReadTimeout -1.0
#define kGeneralWriteTimeout -1.0
#implementation JSONRPC
- (id)init
{
self = [super init];
if (self) {
isServer = NO;
supportedMethods = [[NSMutableArray alloc] init];
mainSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
connectedSockets = [[NSMutableArray alloc] init];
responseDelegates = [[NSMutableArray alloc] init];
}
return self;
}
+ (JSONRPC *)sharedConnection {
static JSONRPC *sharedSingleton;
#synchronized(self)
{
if (!sharedSingleton)
sharedSingleton = [[JSONRPC alloc] init];
return sharedSingleton;
}
}
-(BOOL)startServer:(NSUInteger)port {
// Now we tell the socket to accept incoming connections.
// We don't care what port it listens on, so we pass zero for the port number.
// This allows the operating system to automatically assign us an available port.
isServer = YES;
NSError *err = nil;
if ([mainSocket acceptOnPort:port error:&err]) {
} else {
DDLogError(#"Error while starting JSON-RPC Server: %#",err);
return NO;
}
DDLogInfo(#"Started JSON-RPC Server on port %hu",[mainSocket localPort]);
return YES;
}
-(BOOL)connectToServer:(NSString *)host port:(NSUInteger)port {
NSError *err = nil;
mainSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
[mainSocket connectToHost:host onPort:port error:&err];
if(err != nil) {
DDLogError(#"Couldn't connect to host %#:%lu (Error: %#)",host,port,err);
return NO;
}
return YES;
}
-(BOOL)addMethod:(JSONRPCMethod *)method {
for (JSONRPCMethod *meth in supportedMethods) {
if([meth.name isEqualToString:method.name]) {
return NO;
}
}
[supportedMethods addObject:method];
return YES;
}
-(void)removeMethod:(JSONRPCMethod *)method {
[supportedMethods removeObject:method];
}
-(void)removeMethodsWithTarget:(id)target {
NSMutableArray *toRemove = [[NSMutableArray alloc] init];
for (JSONRPCMethod *meth in supportedMethods) {
if(meth.target == target) {
[toRemove addObject:meth];
}
}
[supportedMethods removeObjectsInArray:toRemove];
}
-(void)sendRequest:(JSONRPCRequest *)req toSocket:(GCDAsyncSocket*)sock {
[responseDelegates addObject:req];
[req setIdentifier:[NSNumber numberWithUnsignedInteger:[responseDelegates count]-1]];
[self sendPackage:[req dictionary] toSocket:sock];
}
-(void)sendRequest:(JSONRPCRequest *)req {
[self sendRequest:req toSocket:mainSocket];
}
-(void)sendNotification:(JSONRPCRequest *)req toSocket:(GCDAsyncSocket*)sock{
[req setIdentifier:nil];
[self sendPackage:[req dictionary] toSocket:sock];
}
-(void)sendNotification:(JSONRPCRequest *)req {
[self sendNotification:req toSocket:mainSocket];
}
-(void)sendNotification:(JSONRPCRequest *)req toSocketsWithUserData:(id)userData {
NSMutableArray *matchingSockets = [[NSMutableArray alloc] init];
for (GCDAsyncSocket*sock in connectedSockets) {
if(sock.userData == userData) {
[matchingSockets addObject:sock];
}
}
if(matchingSockets.count == 0)
return;
[req setIdentifier:nil];
NSData *pkgData = [self writableDataFromDictionary:[req dictionary]];
for (GCDAsyncSocket*sock in matchingSockets) {
[sock writeData:pkgData withTimeout:kGeneralWriteTimeout tag:0];
}
}
#pragma mark Socket Delegate
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
[connectedSockets addObject:newSocket];
[newSocket readDataToData:[GCDAsyncSocket ZeroData] withTimeout:kGeneralReadTimeout tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
DDLogVerbose(#"socket:didConnectToHost:%# port:%hu", host, port);
[sock readDataToData:[GCDAsyncSocket ZeroData] withTimeout:kGeneralReadTimeout tag:0];
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
DDLogVerbose(#"socketDidDisconnect:%#", err);
if(isServer)
[connectedSockets removeObject:sock];
}
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
// So, we've received something from the client
// As we have to cut out the last 0x00 for JSONSerialization it has to be longer than 1 byte
if(data.length > 1) {
// Shorten out that 0x00
data = [data subdataWithRange:NSMakeRange(0, data.length-1)];
// Try to serialize
NSError *err;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];
DDLogVerbose(#"Dict: %#",dict);
if(err != nil) {
// The package isn't json
JSONRPCResponse *response = [JSONRPCResponse responseWithError:[JSONRPCError invalidRequest]];
[self sendPackage:[response dictionary] toSocket:sock];
} else {
JSONRPCResponse *response = [self handleDictionary:dict fromSocket:sock];
if(response != nil) {
[self sendPackage:[response dictionary] toSocket:sock];
}
}
}
[sock readDataToData:[GCDAsyncSocket ZeroData] withTimeout:kGeneralReadTimeout tag:0];
}
-(JSONRPCResponse*)handleDictionary:(NSDictionary*)dict fromSocket:(GCDAsyncSocket*)sock {
// Check if the "id" is of a correct value/type
id identifier = [dict valueForKey:#"id"];
if(!(identifier == nil || [identifier isKindOfClass:[NSNumber class]])) {
return [JSONRPCResponse responseWithError:[JSONRPCError invalidRequest]];
}
// Handle the package
NSString *methodName = [dict valueForKey:#"method"];
id errorValue = [dict valueForKey:#"error"];
id resultValue = [dict valueForKey:#"result"];
if([methodName isKindOfClass:[NSString class]]) {
// We have a string as method
DDLogInfo(#"Method: %#, object: %#",methodName,[dict valueForKey:#"params"]);
for (JSONRPCMethod *method in supportedMethods) {
if([method.name isEqualToString:methodName]) {
id result = nil;
if(isServer == YES) {
// It is a server and the method needs to know from where the call comes
result = [method invoke:[dict valueForKey:#"params"] fromSocket:sock];
} else {
// It is a client and we don't need to know where the call is from. it can only be the server.
result = [method invoke:[dict valueForKey:#"params"]];
}
if([result isKindOfClass:[JSONRPCError class]]) {
return [JSONRPCResponse responseWithError:result id:identifier];
} else if(result != nil) {
return [JSONRPCResponse responseWithResult:result id:identifier];
} else {
return nil;
}
}
}
} else if(resultValue != nil) {
// We have a response from our partner
//DDLogInfo(#"Result: %#",resultValue);
NSUInteger responseDelegateId = [identifier unsignedIntegerValue];
if(responseDelegateId < [responseDelegates count]) {
JSONRPCRequest *originalRequest = [responseDelegates objectAtIndex:responseDelegateId];
if(originalRequest.sender == nil) {
return nil;
}
#try {
SEL selector;
if(isServer) {
selector = #selector(rpcSocket:returnedValue:forMethod:id:);
} else {
selector = #selector(rpcReturnedValue:forMethod:id:);
}
NSMethodSignature *signature = [originalRequest.sender methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:originalRequest.sender];
[invocation setSelector:selector];
NSUInteger startArg = 2;
if(isServer) {
[invocation setArgument:&sock atIndex:startArg];
startArg++;
}
NSString *method = [originalRequest method];
id orgId = [originalRequest identifier];
[invocation setArgument:&resultValue atIndex:startArg];
[invocation setArgument:&method atIndex:startArg+1];
[invocation setArgument:&orgId atIndex:startArg+2];
[invocation invoke];
}
#catch (NSException *exception) {
DDLogWarn(#"Couldn't find a response: %#",exception);
}
}
} else if(errorValue != nil) {
// We have a string as method
DDLogInfo(#"Error: %#",errorValue);
} else {
return [JSONRPCResponse responseWithError:[JSONRPCError invalidRequest] id:identifier];
}
return nil;
}
-(void)sendPackage:(NSDictionary *)dict toSocket:(GCDAsyncSocket *)sock {
NSData *answerData = [self writableDataFromDictionary:dict];
[sock writeData:answerData withTimeout:kGeneralWriteTimeout tag:0];
}
-(NSData*)writableDataFromDictionary:(NSDictionary*)dict {
NSMutableData *answerData = [[NSMutableData alloc] init];
// Serialize the answer
NSError *err = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&err];
if(err != nil) {
// Log
DDLogError(#"JSON-RPC had an internal error while converting the answer to JSON. The answer-dictionary is: %#",dict);
// Form answer manually
jsonData = [NSData dataWithBytes:"{\"error\":{\"code\":-32700,\"message\":\"Parse error\"}}"
length:49];
}
// Format the answer
[answerData appendData:jsonData];
[answerData appendData:[GCDAsyncSocket ZeroData]];
return answerData;
}
i just don't know how to fix this. why do i keep getting an error with an nsarray but when i return a nsnumber it works? how can i fix this?
I've spent two days googling and reading the Bluetooth programming guide while trying to piece together a small Mac app that will retrieve images from a drop folder and send any new files to a predetermined device over Bluetooth. There doesn't seem to be many good examples available.
I'm at the point where I'm able to spawn the Bluetooth Service Browser and select the device and its OBEX service, establishing a service and creating a connection, but then nothing more happens. Could anyone please point me in the direction of/show me a simple example that would work?
AppDelegate source code enclosed. Thanks for reading!
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
IOBluetoothServiceBrowserController *browser = [IOBluetoothServiceBrowserController serviceBrowserController:0];
[browser runModal];
//IOBluetoothSDPServiceRecord
IOBluetoothSDPServiceRecord *result = [[browser getResults] objectAtIndex:0];
[self describe:result];
if ([[result.device.name substringToIndex:8] isEqualToString:#"Polaroid"]) {
printer = result.device;
serviceRecord = result;
[self testPrint];
}
else {
NSLog(#"%# is not a valid device", result.device.name);
}
}
- (void) testPrint {
currentFilePath = #"/Users/oyvind/Desktop/_DSC8797.jpg";
[self sendFile:currentFilePath];
}
- (void) sendFile:(NSString *)filePath {
IOBluetoothOBEXSession *obexSession = [[IOBluetoothOBEXSession alloc] initWithSDPServiceRecord:serviceRecord];
if( obexSession != nil )
{
NSLog(#"OBEX Session Established");
OBEXFileTransferServices *fst = [OBEXFileTransferServices withOBEXSession:obexSession];
OBEXDelegate *obxd = [[OBEXDelegate alloc] init];
[obxd setFile:filePath];
[fst setDelegate:obxd];
OBEXError cnctResult = [fst connectToObjectPushService];
if( cnctResult != kIOReturnSuccess ) {
NSLog(#"Error creating connection");
return;
}
else {
NSLog(#"OBEX Session Created. Sending file: %#", filePath);
[fst sendFile:filePath];
[printer openConnection];
}
}
else {
NSLog(#"Error creating OBEX session");
NSLog(#"Error sending file");
}
}
#end
OK; here's what ultimately became the core parts of the functionality. The application I made was a sort of print server for Polaroid instant printers that would only accept images over Object Push.
First, ensure watched folder exists.
/*
Looks for a directory named PolaroidWatchFolder in the user's desktop directory
and creates it if it does not exist.
*/
- (void) ensureWatchedFolderExists {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *url = [NSURL URLWithString:#"PolaroidWatchFolder" relativeToURL:[[fileManager URLsForDirectory:NSDesktopDirectory inDomains:NSUserDomainMask] objectAtIndex:0]];
BOOL isDir;
if ([fileManager fileExistsAtPath:[url path] isDirectory:&isDir] && isDir) {
[self log:[NSString stringWithFormat:#"Watched folder exists at %#", [url absoluteURL]]];
watchFolderPath = url;
}
else {
NSError *theError = nil;
if (![fileManager createDirectoryAtURL:url withIntermediateDirectories:NO attributes:nil error:&theError]) {
[self log:[NSString stringWithFormat:#"Watched folder could not be created at %#", [url absoluteURL]]];
}
else {
watchFolderPath = url;
[self log:[NSString stringWithFormat:#"Watched folder created at %#", [url absoluteURL]]];
}
}
}
Then scan for available printers:
/*
Loops through all paired Bluetooth devices and retrieves OBEX Object Push service records
for each device who's name starts with "Polaroid".
*/
- (void) findPairedDevices {
NSArray *pairedDevices = [IOBluetoothDevice pairedDevices];
devicesTested = [NSMutableArray arrayWithCapacity:0];
for (IOBluetoothDevice *device in pairedDevices)
{
if ([self deviceQualifiesForAddOrRenew:device.name])
{
BluetoothPushDevice *pushDevice = [[BluetoothPushDevice new] initWithDevice:device];
if (pushDevice != nil)
{
[availableDevices addObject:pushDevice];
[pushDevice testConnection];
}
}
}
}
That last function call is to the BluetoothPushDevice's built-in method to test the connection. Here is the delegate handler for the response:
- (void) deviceStatusHandler: (NSNotification *)notification {
BluetoothPushDevice *device = [notification object];
NSString *status = [[notification userInfo] objectForKey:#"message"];
if ([devicesTested count] < [availableDevices count] && ![devicesTested containsObject:device.name]) {
[devicesTested addObject:device.name];
}
}
Upon server start, this method will run in response to a timer tick or manual scan:
- (void) checkWatchedFolder {
NSError *error = nil;
NSArray *properties = [NSArray arrayWithObjects: NSURLLocalizedNameKey, NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey, nil];
NSArray *files = [[NSFileManager defaultManager]
contentsOfDirectoryAtURL:watchFolderPath
includingPropertiesForKeys:properties
options:(NSDirectoryEnumerationSkipsHiddenFiles)
error:&error];
if (files == nil) {
[self log:#"Error reading watched folder"];
return;
}
if ([files count] > 0) {
int newFileCount = 0;
for (NSURL *url in files) {
if (![filesInTransit containsObject:[url path]]) {
NSLog(#"New file: %#", [url lastPathComponent]);
[self sendFile:[url path]];
newFileCount++;
}
}
}
}
When new files are found, ww first need to find a device that is not busy recieving a file of printing it:
/*
Loops through all discovered device service records and returns the a new OBEX session for
the first it finds that is not connected (meaning it is not currently in use, connections are
ad-hoc per print).
*/
- (BluetoothPushDevice*) getIdleDevice {
for (BluetoothPushDevice *device in availableDevices) {
if ([device.status isEqualToString:kBluetoothDeviceStatusReady]) {
return device;
}
}
return nil;
}
Then a file is sent with this method:
- (void) sendFile:(NSString *)filePath {
BluetoothPushDevice *device = [self getIdleDevice];
if( device != nil ) {
NSLog(#"%# is available", device.name);
if ([device sendFile:filePath]) {
[self log:[NSString stringWithFormat:#"Sending file: %#", filePath]];
[filesInTransit addObject:filePath];
}
else {
[self log:[NSString stringWithFormat:#"Error sending file: %#", filePath]];
}
}
else {
NSLog(#"No idle devices");
}
}
Upon transfer complete, this delegate method is called:
/*
Responds to BluetoothPushDevice's TransferComplete notification
*/
- (void) transferStatusHandler: (NSNotification *) notification {
NSString *status = [[notification userInfo] objectForKey:#"message"];
NSString *file = ((BluetoothPushDevice*)[notification object]).file;
if ([status isEqualToString:kBluetoothTransferStatusComplete]) {
if ([filesInTransit containsObject:file]) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
[fileManager removeItemAtPath:file error:&error];
if (error != nil) {
[self log:[NSString stringWithFormat:#"**ERROR** File %# could not be deleted (%#)", file, error.description]];
}
[self log:[NSString stringWithFormat:#"File deleted: %#", file]];
[filesInTransit removeObject:file];
}
else {
[self log:[NSString stringWithFormat:#"**ERROR** filesInTransit array does not contain file %#", file]];
}
}
[self updateDeviceStatusDisplay];
}
I hope this helps someone!