I am using Zxing to scan the data matrix codes. I imported the zxing from github . When the app starts , the camera is scanning the code repeatedly as long as the camera is placed on a barcode.I want to stop the scanning once the barcode is decoded and I want to perform a task and then again start scanning. I stopped the scanning but unable to start it. Here is what I have done to stop the scanning.
Here is my ViewController.m
- (void)captureResult:(ZXCapture *)capture result:(ZXResult *)result {
if (!result) return;
// We got a result. Display information about the result onscreen.
NSString *formatString = [self barcodeFormatToString:result.barcodeFormat];
NSString *display = [NSString stringWithFormat:#"Scanned!\n\nFormat: %#\n\nContents:\n%#", formatString, result.text];
//here i called the stop method
[self.capture stop];
//i want to start scanning again ,so i created this method
[self afterScan];
}
Now once the barcode is decoded the camera is stopped. Now i want to implement this method
-(void) afterScan{
// UIAlertVIew " code is decoded "
// store in database
// again start scanning
[self.capture start];
}
The problem is the camera is not starting again.
The start and stop methods in the ZXing are as follows:
- (void)start {
if (self.hardStop) {
return;
}
if (self.delegate || self.luminanceLayer || self.binaryLayer) {
[self output];
}
if (!self.session.running) {
static int i = 0;
if (++i == -2) {
abort();
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.session startRunning];
});
}
self.running = YES;
}
- (void)stop {
if (!self.running) {
return;
}
if (self.session.running) {
[self.layer removeFromSuperlayer];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.session stopRunning];
//[self.session startRunning];
});
}
self.running = NO;
}
Could you please help me in solving this issue.
Thanks in advance.
When I did it, I used a BOOL property.
So put one in your view controller like this:
#property (nonatomic, assign) BOOL hasScannedResult;
Then you need an if() condition check to ensure your method doesn't get called repeatedly.
- (void)captureResult:(ZXCapture *)capture result:(ZXResult *)result {
if(self.hasScannedResult == NO)
{
self.hasScannedResult = YES;
// do something with result
}
}
Now when you want to scan again, reset the BOOL flag:
-(void)startScan
{
// reset BOOL flag to enable scanning
self.hasScannedResult = NO;
// open the scanner
}
I stopped the camera by calling the [capture stopReading];
and I started it again by calling the [capture startReading];
Related
I am trying to write some code to scan for bluetooth devices for a while, and then return the array of discovered peripherals through a block-based callback.
Blocking code should not be an issue as the code will be called asynchronously.
After reading up on the API documentation my initial plan of attack was to write an implementation for CBCentralManagerDelegate, use an init method to give it a block-based callback to call once the CBManagerState is PoweredOn, and then initialize this class with a callback that triggers the scanning and extracts the discovered Peripherals.
The issue is... it doesn't work. Except when it does.
Now I could work out a workaround to reach my goal, but for the sake of learning and understanding I am very interested in where exactly the issue originates from.
typedef void (^SomeBlock)(CBCentralManager*);
#interface TEST : NSObject <CBCentralManagerDelegate>
#property CBCentralManager* manager;
#property SomeBlock onPoweredOn;
#property NSMutableArray<CBPeripheral*>* peripherals;
- (void) init: (SomeBlock) onPoweredOn;
- (void) startScan;
- (void) stopScan;
#end
#implementation TEST
- (void) init: (SomeBlock) onPoweredOn {
NSLog(#"%#", #"init");
self.onPoweredOn = onPoweredOn;
self.manager = [CBCentralManager alloc];
dispatch_queue_attr_t attr = DISPATCH_QUEUE_CONCURRENT;
dispatch_queue_t queue =dispatch_queue_create("BTManagerHandler", attr);
self.manager = [self.manager initWithDelegate: self queue: queue];
}
- (void) startScan {
NSLog(#"%#", #"startScan");
[self.manager scanForPeripheralsWithServices: nil options: nil];
}
- (void) stopScan {
NSLog(#"%#", #"stopScan ");
[self.manager stopScan];
}
- (void) centralManagerDidUpdateState: (nonnull CBCentralManager *) manager {
NSLog(#"%#", #"centralManagerDidUpdateState:");
switch (manager.state) {
case CBManagerStateUnknown:
NSLog(#"%#", #"CBManagerStateUnknown:");
break;
case CBManagerStateResetting:
NSLog(#"%#", #"CBManagerStateResetting:");
break;
case CBManagerStateUnsupported:
NSLog(#"%#", #"CBManagerStateUnsupported:");
break;
case CBManagerStateUnauthorized:
NSLog(#"%#", #"CBManagerStateUnauthorized:");
break;
case CBManagerStatePoweredOff:
NSLog(#"%#", #"CBManagerStatePoweredOff:");
break;
case CBManagerStatePoweredOn:
NSLog(#"%#", #"CBManagerStatePoweredOn:");
if (self.onPoweredOn != nil) self.onPoweredOn(manager);
break;
}
}
- (void) centralManager: (nonnull CBCentralManager*) central didDiscoverPeripheral: (nonnull CBPeripheral*) peripheral advertisementData: (nonnull NSDictionary<NSString*, id>*) advertisementData RSSI: (nonnull NSNumber*) RSSI {
NSLog(#"%#", #"centralManager:didDiscoverPeripheral:advertisementData:RSSI:");
if (self.peripherals == nil) self.peripherals = [NSMutableArray array];
for (CBPeripheral* _peripheral in self.peripherals) {
if (peripheral.identifier == _peripheral.identifier) return;
}
[self.peripherals addObject: peripheral];
}
#end
+ (void) discoverDevices {
TEST* test = nil;
#try {
test = [TEST alloc];
SomeBlock onPoweredOn = ^(CBCentralManager* manager) {
NSLog(#"%#", #"_onPoweredOn_");
[test startScan];
[NSThread sleepForTimeInterval: 10.0];
[managerHandler stopScan];
NSArray<CBPeripheral*>* discoveredPeripherals = managerHandler.peripherals;
// do stuff with discoveredPeripherals
};
[test init: onPoweredOn];
} #catch(NSException* e) {
// exception handling
} #finally {
// cleanup
}
}
I would expect the above code to work, but it doesn't.
The 'onPoweredOn' callback and the 'startScan' method are called correctly, but the 'centralManager:didDiscoverPeripheral:advertisementData:RSSI:' method is never called.
After some trial and error I found that the following works:
+ (void) discoverDevices {
TEST* test = nil;
#try {
test = [TEST alloc];
SomeBlock onPoweredOn = ^(CBCentralManager* manager) {
NSLog(#"%#", #"_onPoweredOn_");
[test startScan];
};
[test init: onPoweredOn];
[NSThread sleepForTimeInterval: 10.0];
[managerHandler stopScan];
NSArray<CBPeripheral*>* discoveredPeripherals = managerHandler.peripherals;
// do stuff with discoveredPeripherals
} #catch(NSException* e) {
// exception handling
} #finally {
// cleanup
}
}
After some more trial and error I narrowed it down to one line of code:
+ (void) discoverDevices {
TEST* test = nil;
#try {
test = [TEST alloc];
SomeBlock onPoweredOn = ^(CBCentralManager* manager) {
NSLog(#"%#", #"_onPoweredOn_");
[test startScan];
[NSThread sleepForTimeInterval: 10.0]; // <<=== this line! ===
};
[test init: onPoweredOn];
[NSThread sleepForTimeInterval: 10.0];
[managerHandler stopScan];
NSArray<CBPeripheral*>* discoveredPeripherals = managerHandler.peripherals;
// do stuff with discoveredPeripherals
} #catch(NSException* e) {
// exception handling
} #finally {
// cleanup
}
}
This suggests that that using a [NSThread sleepForTimeInterval:] blocks the discovery of bluetooth devices... but tat seems illogical to me because the same code works without the block-based callback:
+ (void) discoverDevices {
TEST* test = nil;
#try {
test = [TEST alloc];
[test init: nil];
[NSThread sleepForTimeInterval: 1.0];
[test startScan];
[NSThread sleepForTimeInterval: 10.0];
[managerHandler stopScan];
NSArray<CBPeripheral*>* discoveredPeripherals = managerHandler.peripherals;
// do stuff with discoveredPeripherals
} #catch(NSException* e) {
// exception handling
} #finally {
// cleanup
}
}
Conclusion: combining CBCentralManager, block-based callbacks and [NSThread sleepForTimeInterval:] leads to unexpected behaviour??
but why? what's so special about this specific combination?
I have a resume function that reestablishes a network connection before it gathers updated parameters for the app.
Issue is that it seems to be trying to get some parameters before the network link has been reestablished.
Is there a way i can pause until the connection is made?
The getParameters func is on a background thread using:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void) { //code }
The initConnection is primary thread
Here is the func:
- (void)screenUnlocked {
[self initConnection];
CDCChannelCollectionView *scrollView;
if ([self channelView]) {
scrollView = [[self channelView] scrollView];
for (CDCChannelStrip* strip in [scrollView visibleCells]) {
[self getParameters:[NSNumber numberWithInteger:strip.channelNumber]];
}
}
}
edit:
Something like this?
- (void)initConnection: completion:(void (^)(void))completionBlock {
if (![self isDemo]) {
[self setTcpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:TCP]];
[[self tcpControl] setDelegate:self];
[self setUdpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:UDP]];
[[self udpControl] setDelegate:self];
if (successful) {
completionBlock();
}
}
}
For block syntax you can follow:
//Block funtion with void block return type.
- (void)initConnection:(void(^)(void))completionBlock {
if (![self isDemo]) {
[self setTcpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:TCP]];
[[self tcpControl] setDelegate:self];
[self setUdpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:UDP]];
[[self udpControl] setDelegate:self];
if (successful) {
//call completion when connection is made.
completionBlock();
}
}
}
calling function as:
//Calling initConnection funtion.
[self initConnection:^{
NSLog(#"complition success");
}];
- (void)main
{
IDBAssert0(self.bestCapture.webpCandidate);
self.finished = NO;
self.executing = YES;
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
UIImage *possiblycorrupted = [UIImage imageWithWebPData:self.bestCapture.webpCandidate];
NSTimeInterval webpInterval = [NSDate timeIntervalSinceReferenceDate]-now;
NSDLog(#"it tooke %.2f sec to unpack webp", webpInterval);
self.microblinkCandidate = possiblycorrupted; // data superclass nsoperation processes
[super main];
}
first thing main in the base class does naturally is setting finished to no and executing to yes:
- (void)main
{
self.finished = NO;
self.executing = YES;
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
start = now;
CGSize size = [self.microblinkCandidate size];
IDBAssert0(size.width && size.height);
IDBAssert0(self.microblink);
// this starts async processing
[self.microblink processImage:self.microblinkCandidate
scanningRegion:CGRectMake(0.0, 0.0, 1.0, 1.0)
delegate:self];
while (![self isCancelled])
{
sleep(1);
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
if(now - start > 5) {
// #5677 microblink watchdog to detect hangs
[self cancel];
break;
}
}
[self done];
}
cause it's not an abstract and will be used on its own as well.
the loop is for debug/watchdog purposes only
in the normal operation it's not tripped an operation is done
if this callback:
- (void)scanningViewController: (UIViewController<PPScanningViewController>*)scanningViewController
didOutputResults:(NSArray*)results
{
if([results count]>0) {
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
NSDLog(#"found barcode in %.1fs", now - start);
self.microblinkSuccessHandler();
}else{
IDBAssert0(self.microblinkFailureHandler);
self.microblinkFailureHandler();
}
[self done];
}
is invoked when "processImage:" will have finished (in a timely fashion).
the very base class is
#implementation IDBAsynchronousOperation
#synthesize executing = _executing;
#synthesize finished = _finished;
-(BOOL)isFinished
{
return _finished;
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
-(BOOL)isExecuting
{
return _executing;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (instancetype)init
{
self = [super init];
if (self) {
// self.completionBlock = ^{
// NSDLog(#"image barcode search has finished");
// };
IDBAssert0(sizeof(_executing)<2);
}
return self;
}
-(BOOL)isAsynchronous
{
return YES;
}
#end
You certainly can (and we often do) subclass your own concrete NSOperation subclass.
To make the base class subclassable, you want to make sure that you only perform self.executing = true once. Right now, the main in both the base class and the subclass do it, and you'll therefore be doing it twice. The typical solution is to pull it out of both of those main implementations and do it in start of the base class. Apple suggests that you do this stuff in start, anyway.
Thus having removed the self.finished and self.executing stuff from both main implementations, you can then implement start:
- (void)start {
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
Note, you don't have to call self.finished = false when the operation is starting because that will send an unnecessary KVO.
An unrelated observation:
If you keep the while loop in the base class, I'd suggest exiting the loop if either [self isCancelled] or if the processImage delegate completion methods was called (perhaps you can update some state property to designate when that delegate method was called). Right now, if the processImage finishes before the timeout, it will keep the operation running for the full 5 seconds.
Personally, depending upon how processImage was designed, I probably be inclined excise the while loop entirely. You generally want to avoid any polling like this at all. I might, for example, put the [self done] in the appropriate delegate method and then set up a timer or dispatch_after for the timeout.
- (void)main {
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
start = now;
CGSize size = [self.microblinkCandidate size];
IDBAssert0(size.width && size.height);
IDBAssert0(self.microblink);
// this starts async processing
[self.microblink processImage:self.microblinkCandidate
scanningRegion:CGRectMake(0.0, 0.0, 1.0, 1.0)
delegate:self];
// cancel upon timeout
typeof(self) __weak weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
typeof(self) __strong strongSelf = weakSelf;
if ([strongSelf isExecuting]) {
[strongSelf cancel];
[strongSelf done]; // if canceling calls the delegate method that calls `done`, then you don't need this here
}
});
}
Using Ray Wenderlich's QRCode reader from Chapter 22 of iOS7 Tutorials, I am successfully reading QRCodes for my current app. I am now extending it that upon successfully reading a QRCode, I want to store the stringValue of the AVMetadataMachineReadableCodeObject that was read, segue to a new view, and use that data on the new view, more or less exactly how most QRCode reader apps (like RedLaser, etc...) process barcodes and QRCodes.
However, I call [captureSession stopRunning] (so that it does not read any more QRCodes and trigger additional segues) and there is a 10+ second hang. I have tried to implement an async call per this SO question, however to no avail. I have also looked at these SO Questions and they seem not to be appropriate for this purpose.
Does anyone have an idea how to remove this hanging?
Here is the code:
#import "BMQRCodeReaderViewController.h"
#import "NSString+containsString.h"
#import "BMManualExperimentDataEntryViewController.h"
#import AVFoundation;
#interface BMQRCodeReaderViewController ()
<AVCaptureMetadataOutputObjectsDelegate>
#end
#implementation BMQRCodeReaderViewController {
AVCaptureSession *_captureSession;
AVCaptureDevice *_videoDevice;
AVCaptureDeviceInput *_videoInput;
AVCaptureVideoPreviewLayer *_previewLayer;
BOOL _running;
AVCaptureMetadataOutput *_metadataOutput;
}
- (void)setupCaptureSession { // 1
if (_captureSession) return;
// 2
_videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (!_videoDevice) {
NSLog(#"No video camera on this device!"); return;
}
// 3
_captureSession = [[AVCaptureSession alloc] init];
// 4
_videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:_videoDevice error:nil];
// 5
if ([_captureSession canAddInput:_videoInput]) { [_captureSession addInput:_videoInput];
}
// 6
_previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_metadataOutput = [[AVCaptureMetadataOutput alloc] init];
dispatch_queue_t metadataQueue = dispatch_queue_create("com.razeware.ColloQR.metadata", 0);
[_metadataOutput setMetadataObjectsDelegate:self queue:metadataQueue];
if ([_captureSession canAddOutput:_metadataOutput]) { [_captureSession addOutput:_metadataOutput];
}
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection {
// This fancy BOOL is just helping me fire the segue when the correct string is found
__block NSNumber *didFind = [NSNumber numberWithBool:NO];
[metadataObjects enumerateObjectsUsingBlock:^(AVMetadataObject *obj, NSUInteger idx, BOOL *stop) {
AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)obj;
NSLog(#"Metadata: %#", readableObject);
// [ containsString is a category I extended for NSString, just FYI
if ([readableObject.stringValue containsString:#"CorrectString"]) {
didFind = [NSNumber numberWithBool:YES];
NSLog(#"Found it");
_testName = #"NameOfTest";
*stop = YES;
return;
}
}];
if ([didFind boolValue]) {
NSLog(#"Confirming we found it");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self stopRunning];
});
_labelTestName.text = _testName;
[self performSegueWithIdentifier:#"segueFromFoundQRCode" sender:self];
}
else {
NSLog(#"Did not find it");
}
}
- (void)startRunning {
if (_running)
return;
[_captureSession startRunning];
_metadataOutput.metadataObjectTypes = _metadataOutput.availableMetadataObjectTypes;
_running = YES;
}
- (void)stopRunning {
if (!_running) return;
[_captureSession stopRunning];
_running = NO;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupCaptureSession];
[self setupNavBar];
[self startRunning];
_previewLayer.frame = _previewView.bounds;
[_previewView.layer addSublayer:_previewLayer];
}
I have successively solved the issue. The issue was that the delegate method call
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
is running in the background. This was determined with a [NSThread isMainThread] call, which it fails.
The solution is to find the proper stringValue from the QRCode, stop your captureSession in the background, THEN call your segue on the main thread. The solution looks as such:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection {
// This fancy BOOL is just helping me fire the segue when the correct string is found
__block NSNumber *didFind = [NSNumber numberWithBool:NO];
[metadataObjects enumerateObjectsUsingBlock:^(AVMetadataObject *obj, NSUInteger idx, BOOL *stop) {
AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)obj;
NSLog(#"Metadata: %#", readableObject);
if ([NSThread isMainThread]) {
NSLog(#"Yes Main Thread");
}
else {
NSLog(#"Not main thread");
}
// [ containsString is a category I extended for NSString, just FYI
if ([readableObject.stringValue containsString:#"Biomeme"]) {
//NSLog(#"this is a test: %#", getTestName);
didFind = [NSNumber numberWithBool:YES];
NSLog(#"Found it");
_testName = readableObject.stringValue;
*stop = YES;
return;
}
}];
if ([didFind boolValue]) {
NSLog(#"Confirming we found it");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSDate *start = [NSDate date];
[self stopRunning];
NSLog(#"time took: %f", -[start timeIntervalSinceNow]);
// *** Here is the key, make your segue on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"segueFromFoundQRCode" sender:self];
_labelTestName.text = _testName;
});
});
}
else {
NSLog(#"Did not find it");
}
}
I am creating this app were there is background music playing, but I want it so the user can stop the music with a UISwitch if they dont want background music. I already have the code working for the music to play and stop (Code below) with the switch bu my question is this. When i switch to a different view (one that the switch isnt on) and the music is playing, then go back to the view. The switch is off, when i turn it back on (even thought the music is already playing), it will play it again and they will overlap each other (same music file).
Code for the switch and music player...
-(IBAction)play:(id)sender {
if (audioControlSwitch.on) {
[sound setTextColor:[UIColor blueColor]];
[sound setText:#"Sound On"];
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/Tone 2.m4a", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer1.numberOfLoops = 100000000000000000;
[audioPlayer1 play];
} else {
[sound setTextColor:[UIColor darkGrayColor]];
[sound setText:#"Sound Off"];
[audioPlayer1 stop];
}
}
in yourViewController.h
#interface yourViewController : NSObject <AVAudioPlayerDelegate> {
BOOL inBackground;
}
- (void)registerForBackgroundNotifications;
in yourViewController.m
#synthesize inBackground;
#pragma mark background notifications
- (void)registerForBackgroundNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(setInBackgroundFlag)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(clearInBackgroundFlag)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
- (void)setInBackgroundFlag
{
inBackground = true;
}
- (void)clearInBackgroundFlag
{
inBackground = false;
}
- (void)updateViewForPlayerStateInBackground:(AVAudioPlayer *)p
{
if (p.playing)
{
// Do something
}
else
{
// Do something else
}
}
#pragma mark AVAudioPlayer delegate methods
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)p successfully:(BOOL)flag
{
if (flag == NO)
NSLog(#"Playback finished unsuccessfully");
[p setCurrentTime:0.];
if (inBackground)
{
[self updateViewForPlayerStateInBackground:p];
}
else
{
}
}
- (void)playerDecodeErrorDidOccur:(AVAudioPlayer *)p error:(NSError *)error
{
NSLog(#"ERROR IN DECODE: %#\n", error);
}
// we will only get these notifications if playback was interrupted
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)p
{
NSLog(#"Interruption begin. Updating UI for new state");
// the object has already been paused, we just need to update UI
if (inBackground)
{
[self updateViewForPlayerStateInBackground:p];
}
else
{
}
}
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)p
{
NSLog(#"Interruption ended. Resuming playback");
[self startPlaybackForPlayer:p];
}
-(void)startPlaybackForPlayer:(AVAudioPlayer*)p
{
if ([p play])
{
}
else
NSLog(#"Could not play %#\n", p.url);
}
#end