ios play a system sound - ios

I created a bar code scanner with the apple in build framework AVFoundation. Everything works fine for now.
I want to add a sound that is played when the bar code scan is complete. I my case that would be when a label on my screen is filled with the number scanned.
I know how to play a sound. but the sound gets repeated all the time which is expected cause the method (see code*)is getting called always.
captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
what I do is
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
CGRect highlightViewRect = CGRectZero;
AVMetadataMachineReadableCodeObject *barCodeObject;
NSString *detectionString = nil;
NSArray *barCodeTypes = #[AVMetadataObjectTypeUPCECode,AVMetadataObjectTypeCode39Code,AVMetadataObjectTypeCode39Mod43Code,AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeCode93Code,AVMetadataObjectTypeCode128Code,AVMetadataObjectTypePDF417Code,AVMetadataObjectTypeQRCode,AVMetadataObjectTypeAztecCode];
for (AVMetadataObject *metadata in metadataObjects) {
for (NSString *type in barCodeTypes) {
if ([metadata.type isEqualToString:type])
{
barCodeObject = (AVMetadataMachineReadableCodeObject *)[prevLayer transformedMetadataObjectForMetadataObject:(AVMetadataMachineReadableCodeObject *)metadata];
highlightViewRect = barCodeObject.bounds;
detectionString = [(AVMetadataMachineReadableCodeObject *)metadata stringValue];
break;
}
}
if (detectionString != nil)
{
lblresult.text = detectionString;
break;
}else
lblresult.text = #"(none)";
}
if(detectionString !=nil && ![lblresult.text isEqual:#""]){
[self playSound];
}
highlightView.frame = highlightViewRect;
}
The method which is playing the sound
-(void)playSound{
if(![lblresult.text isEqual:#""]){
AudioServicesPlaySystemSound(systemSoundID);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
AudioServicesDisposeSystemSoundID(systemSoundID);
});
}
}
I really dont know where I should call this method so the sound is played only once when the label is not empty .
Thanks for help and fast answer !

It sounds like the sound is playing every time AVFoundation recognizes the barcode, which is many times a second for as long as your camera is pointed at that barcode.
I assume that you have an AVCaptureSession that you start running at some point (this is what causes captureOutput: to run), as well as a preview layer to see what the camera sees on screen, like this:
#property (strong, nonatomic) AVCaptureSession *session;
#property (strong, nonatomic) AVCaptureVideoPreviewLayer *prevLayer;
...
- (void)viewDidLoad
{
[_session startRunning];
...
If you called stopRunning: right after a successful scan, like this:
if(detectionString !=nil && ![lblresult.text isEqual:#""]){
[self playSound];
[_session stopRunning];
}
then it should only play the sound one time. However,you'll also be left with a preview layer frozen on the last frame it captured, so you may want to consider calling:
[self.prevLayer removeFromSuperlayer];
This would remove the frozen image from the view entirely, and then the preview layer could be added back and scanning could be re-enabled by:
[self.barcodeScanAreaView.layer addSublayer:_prevLayer];
[_session startRunning];
Alternatively, you could include some logic to disable barcode recognition after a scan just occurred. Create a BOOL property and change its value after a successful scan, like:
if(detectionString !=nil && !wasJustScanned && ![lblresult.text isEqual:#""]){
[self playSound];
wasJustScanned = YES;
}
The big question, is, of course, when to re-enable scanning. Perhaps you want the user to confirm that they want to keep what they just scanned (so maybe a button under the view), or perhaps the preview/ scanning view is a modal view that is popped right after a successful scan. I'd recommend looking at existing apps to see what they do. Fundamentally, though, the key to not repeating the beep is by either disabling capture or disabling processing of a captured barcode after the first successful recognition of the barcode.

Related

Using Google ML Kit and Front Camera for PDF 417 bar code scanning on iOS

I am using Google ML Kit and iPad Air 4 for bar code scanning. For PDF417 bar code back camera works fine. I am not able scan PDF417 using front camera. Is there any settings/code I need to do for front camera?
- (void)captureOutput:(AVCaptureOutput *)output
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (imageBuffer) {
// Evaluate `self.currentDetector` once to ensure consistency throughout this method since it
// can be concurrently modified from the main thread.
Detector activeDetector = self.currentDetector;
//[self resetManagedLifecycleDetectorsForActiveDetector:activeDetector];
_lastFrame = sampleBuffer;
MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
UIImageOrientation orientation = [UIUtilities
imageOrientationFromDevicePosition:_isUsingFrontCamera ? AVCaptureDevicePositionFront
: AVCaptureDevicePositionBack];
visionImage.orientation = orientation;
CGFloat imageWidth = CVPixelBufferGetWidth(imageBuffer);
CGFloat imageHeight = CVPixelBufferGetHeight(imageBuffer);
BOOL shouldEnableClassification = NO;
BOOL shouldEnableMultipleObjects = NO;
switch (activeDetector) {
case DetectorOnDeviceBarcode: {
MLKBarcodeScannerOptions *options = [[MLKBarcodeScannerOptions alloc] init];
[self scanBarcodesOnDeviceInImage:visionImage
width:imageWidth
height:imageHeight
options:options];
break;
}
}
} else {
if (kTrace) NSLog(#"%#", #"Failed to get image buffer from sample buffer.");
}
}
Yeah, you need some logic to handle the switch between the front and back cameras. You can check out ML Kit's vision quickstart sample app to see how that's handled (in particular, see all the logic dealing with isUsingFrontCamera in this file).

Create UIButton that responds while a Video is loading

I currently have a screen that loads a video through a method called - loadCurrentVideo and this method usually takes 2-3 seconds to complete while on wifi but 10-20 seconds to complete while on data. I have a button built into the player that exits out of the player when it is tapped, but the button only works after the video is loaded. I want to be able to add a button that exits out of the player whenever I tap it at any point during the loading and playing stages.
While the video is loading, I can't use any of the buttons on the player since i'm assuming the video is still loading and executing the method described above. How can I build a button that is capable of always being available to respond to a tap and interrupts a method while it is executing to return to the previous screen that the app was on?
Below is the method:
- (void) loadCurrentVideo {
__block UIActivityIndicatorView *pw = _pleaseWait;
__block KolorEyes *ke = _koloreyes;
__block UISlider *slider = _timeSlider;
_pleaseWait.hidden = NO;
[_pleaseWait startAnimating];
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url1 = [NSURL URLWithString:url];
[_koloreyes setDisplayDevice:kKE_DISPLAY_HMD forView:_viewRectHandle];
[_koloreyes setCameraControlMode:kKE_CAMERA_TOUCH forView:_viewRectHandle];
err = [_koloreyes setAssetURL:url1
withCompletionHandler:^{
NSLog(#"Video %#", url1.path);
[pw stopAnimating];
pw.hidden = YES;
CMTime duration = [ke getMediaDuration];
if (CMTIME_IS_INDEFINITE(duration)) {
slider.hidden = YES;
}
else {
[slider setMaximumValue:CMTimeGetSeconds(duration)];
}
[ke play];
[self postView];
Log("Kolor Eyes set asset URL completion handler");
}];
if (err != kKE_ERR_NONE) {
[pw stopAnimating];
pw.hidden = YES;
Log("Kolor Eyes setAssetURL failed with error %d", err);
}
});
}

GMSSyncTileLayer tileForX:y:zoom: called only once

Summary:
I subclassed
GMSSyncTileLayer
and overwrote
tileForX:y:zoom:
but its only called once no matter how much I pan.
why?
DETAIL
We have implemented our own TileServer which is behind a secure webservice so we need to pass a login token to get the tiles.
The call is asynchronous POST which is quiet common for a webservice call.
Because I had to pass login token in the NSURLSession header I couldnt just pass GET urls to
GMSTileURLConstructor urls = http://<tileserver>/gettile?x=3&y=4&zoom=5
So I subclassed GMSSyncTileLayer
#interface SNSyncTileLayer : GMSSyncTileLayer
overwrote
- (UIImage *)tileForX:(NSUInteger)x y:(NSUInteger)y zoom:(NSUInteger)zoom {
When tileForX:y:Zoom: is called the first time I call a webservice to get the tile UIImage.
The UIImage returns on a delegate and is stored in a NSDictionary with key in format TILE_x_y_Zoom.
The call to the WS is asynch so - (UIImage *)tileForX:y:Zoom: always returns nil for that tile the first time its called.
What i've noticed is that tileForX:y:Zoom: is never called again no matter how much I pan back and forth.
For instance at the current zoom I pan across europe.
I see tileForX:y:Zoom: being called once and ws calls being mad and images being stored in my dictionary.
But if i keep panning at the same zoom I come back to Europe and tileForX:y:Zoom: is not called again.
Fix one - clear cache when new tile downloaded
I tried creating a delegate on SNSyncTileLayer and everytime a new tile downloaded it called:
[self.snSyncTileLayer clearTileCache];
But this wipes out ALL tiles and reloads them so as you pan you get a terrible flashing.
My only idea next is to measure how much the map has panned and if its more than half a width or height then to call clearTileCache.
SO the big question why isnt tileForX:y:Zoom: called everytime?
My overridden class
//
// SNSyncTileLayer.m
//
#import "SNSyncTileLayer.h"
#import "SNAppDelegate.h"
#import "GoogleTileRequest.h"
#import "SNGoogleTileRequest.h"
#import "GoogleTileImageResult.h"
#interface SNSyncTileLayer()<SNSeaNetWebServicesManagerDelegate>{
BOOL _debugOn;
}
#property (nonatomic, retain) NSMutableDictionary * tileImageCacheDict;
#end
#implementation SNSyncTileLayer
- (instancetype)init
{
self = [super init];
if (self) {
_tileImageCacheDict = [NSMutableDictionary dictionary];
_debugOn = TRUE;
}
return self;
}
- (UIImage *)tileForX:(NSUInteger)x y:(NSUInteger)y zoom:(NSUInteger)zoom {
if(_debugOn)ImportantLog(#"tileForX:(%lu) y:(%lu) zoom:(%lu)", (unsigned long)x,(unsigned long)y,(unsigned long)zoom);
UIImage *tileImage_ = nil;
//tileImage_ = [UIImage imageNamed:#"EmptyTile1.png"];
NSString * keyForTile_ = [NSString stringWithFormat:#"TILE_%lu_%lu_%lu", (unsigned long)x,(unsigned long)y,(unsigned long)zoom];
id dictObj_ = [self.tileImageCacheDict objectForKey:keyForTile_];
if (dictObj_) {
if([dictObj_ isMemberOfClass:[NSNull class]])
{
if(_debugOn)DebugLog(#"tile has been called before but image not downloaded yet:[%#]",keyForTile_);
}
else if([dictObj_ isMemberOfClass:[UIImage class]])
{
if(_debugOn)DebugLog(#"cached image found in dict_ return it:[%#]",keyForTile_);
tileImage_ = (UIImage *)dictObj_;
}
else{
ErrorLog(#"ITEM IN self.tileImageCacheDict not NSNull or UIImage:[%#]", dictObj_);
}
}else{
if(_debugOn)ImportantLog(#"tileForX: CACHED IMAGE NOT FOUND: DOWNLOAD IT[%#]",keyForTile_);
//-----------------------------------------------------------------------------------
//add in temp object - tyring to check if tileForX:Y:Zoom is called more than once
[self.tileImageCacheDict setObject:[NSNull null] forKey:keyForTile_];
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
SNAppDelegate *appDelegate = (SNAppDelegate *)[[UIApplication sharedApplication] delegate];
GoogleTileRequest * googleTileRequest_ = [[GoogleTileRequest alloc]init];
googleTileRequest_.X = [NSNumber numberWithInteger:x];
googleTileRequest_.Y = [NSNumber numberWithInteger:y];
googleTileRequest_.Zoom = [NSNumber numberWithInteger:zoom];
#pragma mark TODO - NOW - thur11dec - load from settings
googleTileRequest_.MapType = #"Dark";
//for general errors
appDelegate.snSeaNetWebServicesManager.delegate = self;
//Request should know what class to return too
googleTileRequest_.delegateForRequest = self;
[appDelegate.snSeaNetWebServicesManager ITileController_GoogleTile:googleTileRequest_];
//-----------------------------------------------------------------------------------
return kGMSTileLayerNoTile;
//-----------------------------------------------------------------------------------
}
return tileImage_;
}
#pragma mark -
#pragma mark SNSeaNetWebServicesManagerDelegate
#pragma mark -
-(void) snSeaNetWebServicesManager:(SNSeaNetWebServicesManager *)SNSeaNetWebServicesManager
wsReponseReceivedForRequest:(SNWebServiceRequest *)snWebServiceRequest_
error:(NSError *)error
{
#pragma mark TODO - NOW - thur11dec2014
if(error){
ErrorLog(#"error:%#",error);
}else{
if(snWebServiceRequest_){
if([snWebServiceRequest_ isMemberOfClass:[SNGoogleTileRequest class]])
{
//Result is JSONModel ivar in Request
if(snWebServiceRequest_.resultObject){
GoogleTileImageResult * googleTileImageResult_= (GoogleTileImageResult *)snWebServiceRequest_.resultObject;
UIImage * responseImage_ = googleTileImageResult_.responseImage;
if(responseImage_){
//-----------------------------------------------------------------------------------
//build the key from the parameters
if(snWebServiceRequest_.bodyJsonModel){
NSDictionary *paramsDict = [snWebServiceRequest_.bodyJsonModel toDictionary];
if(paramsDict){
NSString *keyX_ = [paramsDict objectForKey:#"X"];
NSString *keyY_ = [paramsDict objectForKey:#"Y"];
NSString *keyZoom_ = [paramsDict objectForKey:#"Zoom"];
if(keyX_){
if(keyY_){
if(keyZoom_){
NSString * keyForTile_ = [NSString stringWithFormat:#"TILE_%#_%#_%#", keyX_,keyY_,keyZoom_];
if(_debugOn)ImportantLog(#"TILE DOWNLOADED ADD TO CACHE[%#]",keyForTile_);
[self.tileImageCacheDict setObject:responseImage_ forKey:keyForTile_];
//if(_debugOn)DebugLog(#"[[self.tileImageCacheDict allKeys]count]:%lu", (unsigned long)[[self.tileImageCacheDict allKeys]count]);
//-----------------------------------------------------------------------------------
//I ADDED THIS SO delegate could clearTileCache but causes flashing as ALL tiles get reloaded visible ones and ones downloaded but not on map
if(self.delegate){
if([self.delegate respondsToSelector:#selector(snSyncTileLayer:tileDownloadedForX:Y:Zoom:)]){
[self.delegate snSyncTileLayer:self
tileDownloadedForX:keyX_
Y:keyY_
Zoom:keyZoom_
];
}else {
ErrorLog(#"<%# %#:(%d)> %s delegate[%#] doesnt implement snSyncTileLayer:tileDownloadedForX:Y:Zoom:", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, __PRETTY_FUNCTION__ ,self.delegate);
}
}else{
ErrorLog(#"<%# %#:(%d)> %s self.delegate is nil", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, __PRETTY_FUNCTION__);
}
//-----------------------------------------------------------------------------------
}else{
ErrorLog(#"keyZoom_ is nil");
}
}else{
ErrorLog(#"keyY_ is nil");
}
}else{
ErrorLog(#"keyX_ is nil");
}
}else{
ErrorLog(#"paramsDict is nil");
}
}else{
ErrorLog(#"self.downloadingTasksDictionary is nil");
}
//-----------------------------------------------------------------------------------
}else{
ErrorLog(#"responseImage_ is nil");
}
}else{
ErrorLog(#"snWebServiceRequest_.resultJsonModel is nil");
}
}
else {
ErrorLog(#"UNHANDLED snWebServiceRequest_:%#", snWebServiceRequest_.class);
}
}else{
ErrorLog(#"snWebServiceRequest_ is nil");
}
}
}
#end
I haven't done this so I'm not sure, but my guess from reading the documentation is that you should be subclassing GMSTileLayer instead of GMSSyncTileLayer.
GMSSyncTileLayer is designed for cases where you are able to synchronously (ie immediately) return the tile for that location. By returning kGMSTileLayerNoTile, you are specifically indicating that 'there is no tile here', and so it never calls your class again for that location, as you've already responded that there is no tile there. (BTW, your description says you are returning nil, which indicates a transient error, but your code is actually returning kGMSTileLayerNoTile).
The GMSTileLayer class is designed for the asynchronous approach that you're using. If you subclass GMSTileLayer, your requestTileForX:y:zoom:receiver: method should start the background process to fetch the tile. When the tile request succeeds, then it is passed off to the GMSTileReceiver that was provided in that method (you should keep a copy of that receiver along with your request).

Audio streaming playback: unable to change play/stop button image

I'm developing a radio streaming app with XCode 4.5 for iOS 6 and mainly using storyboards.
I successfully made it to be able to play in background. So wherever I go from my app, whether to other tabs or even after clicking the Home button on the simulator,it keeps playing.
I'm using Matt Gallagher's audio streamer, which I include in my app delegate .m file below
#pragma mark - audio streaming
- (void)playAudio:(indoRadio *)np withButton:(NSString *)currentButton
{
if ([currentButton isEqual:#"playbutton.png"])
{
[self.nowplayingVC.downloadSourceField resignFirstResponder];
[self createStreamer:np.URL];
[self setButtonImageNamed:#"loadingbutton.png"];
[audioStreamer start];
}
else
{
[audioStreamer stop];
}
}
- (void)createStreamer:(NSString *)url
{
if (audioStreamer)
{
return;
}
[self destroyStreamer];
NSString *escapedValue = (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(nil,(CFStringRef)url,NULL,NULL,kCFStringEncodingUTF8);
NSURL *streamurl = [NSURL URLWithString:escapedValue];
audioStreamer = [[AudioStreamer alloc] initWithURL:streamurl];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(playbackStateChanged:)
name:ASStatusChangedNotification
object:audioStreamer];
}
- (void)playbackStateChanged:(NSNotification *)aNotification
{
NSLog(#"playback state changed? %d",[audioStreamer isWaiting]);
if ([audioStreamer isWaiting])
{
[self setButtonImageNamed:#"loadingbutton.png"];
}
else if ([audioStreamer isPlaying])
{
[self setButtonImageNamed:#"stopbutton.png"];
}
else if ([audioStreamer isIdle])
{
[self destroyStreamer];
[self setButtonImageNamed:#"playbutton.png"];
}
}
- (void)destroyStreamer
{
if (audioStreamer)
{
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:ASStatusChangedNotification
object:audioStreamer];
[audioStreamer stop];
audioStreamer = nil;
}
}
- (void)setButtonImageNamed:(NSString *)imageName
{
if (!imageName)
{
imageName = #"playButton";
}
self.nowplayingVC.currentImageName = imageName;
UIImage *image = [UIImage imageNamed:imageName];
[self.nowplayingVC.playBtn.layer removeAllAnimations];
[self.nowplayingVC.playBtn setImage:image forState:0];
if ([imageName isEqual:#"loadingbutton.png"])
{
[self.nowplayingVC spinButton];
}
}
The problem I got is that when I click the play button, it starts the audio streamers but doesn't change into either loading button or stop button. This makes me unable to stop the audio since the button image is not changed. And since I put an NSLog inside the playbackStateChanged: method,I know that the playback state did change. It seems like the setButtonImagedNamed: method is not firing. Any thoughts?
Please have look of my answer:Disabled buttons not changing image in
For a button there is normal, selected, highlighted and disabled state.
So in your case you have to handle the normal and selected state.
In the xib, set image to a button for normal and selected state.
Now in the function of playAudio instead of checking the image name check the button state as below:
- (void)playAudio:(indoRadio *)np withButton:(NSString *)currentButton
{
if (playButton.selected)
{
//Stop the streaming
//set selection NO
[playButton setSelected:NO];
}
else
{
//Start the streaming
//set selection YES
[playButton setSelected:YES];
}
}
playButton is the IBOutlet of play button.
As you have set the images in xib, so the image automatically get changed as per the state of the image
In case you have added the playBtn programmatically, check if the button is declared as weak or its getting released somewhere. If it is weak, make it strong.
In case the button is in the nib file, then the problem is in the IBOutlet. Redo the connection properly.
It turned out that the delegate didn't know which nowplayingVC I was calling. #sarp-kaya's question about Calling a UIViewController method from app delegate has inspired me.
I actually have put this code in my view controller viewDidLoad:
myAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
but I forgot to add this line:
appDelegate.nowplayingVC = self;
So, all I need to do is adding that one line and it works now. okay, so silly of me for not noticing such a basic thing. But thanks for your helps :D

Game Center : match delegate’s not called after finding a match

I'm trying to use game center : multi player
Till now, players are Authenticating to Game center, they can send/read scores, and acheivements.
For multiplayer features, I tried both methods :
- using Game center interface to find a match.
- Find a Match Programmatically.
For both ways I have the following issue: the match delegate’s match:player:didChangeState: method is not called.
In apple docs, it's stated that this delegate is called if one player is connected or disconnected.
In my case this delegate is never called. I think that I'm missing a step.
here after the implementation of my delegate (as specified in apple doc).
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state
{
switch (state)
{
case GKPlayerStateConnected:
// handle a new player connection.
break;
case GKPlayerStateDisconnected:
// a player just disconnected.
break;
}
if (!self.matchStarted && match.expectedPlayerCount == 0)
{
self.matchStarted = YES;
// handle initial match negotiation.
}
}
and also the code to find a match.
-(void) findProgrammaticMatch
{
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
[[GKMatchmaker sharedMatchmaker] findMatchForRequest:request
withCompletionHandler:^(GKMatch *FoundMatch, NSError *error)
{
if (error)
{
// Process the error.
StatusLabel.text = #"Match Not Found";
}
else if (FoundMatch != nil)
{
MultiPlayerMatch = FoundMatch; // Use a retaining property to retain the match.
StatusLabel.text = #"Match Found";
MultiPlayerMatch.delegate = self; // start!
// Start the match.
// Start the game using the match.
[self StartMatch];
}
}];
}
Thanks for your help.
It was working all along. The only difference is that... when you use invites the event "didChangeState" doesn't get called. You're connected without notice and you can start to receive data. I never tried to send/receive data because I was expecting the event first, but i did send something by mistake one time, and it worked.
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *) match {
//Dismiss window
[self dismissModalViewControllerAnimated:YES];
//Retain match
self.myMatch = match;
//Delegate
myMatch.delegate = self;
//Flag
matchStarted = TRUE;
//Other stuff
}
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
//This code gets called only on auto-match
}
The above code works as expected.
I think didChangeState: GKPlayerStateConnected may only happen if a player was GKPlayerStateUnknown and then comes back, or if they are added/invited back to a match in progress.

Resources