AVPlayer boundary time observer fails to fire occasionally - ios

I'm using AVPlayer's -addBoundaryTimeOserverForTimes:queue:usingBlock: to execute some code at a specific time in my video (in this case, I want a un-hide a button when my video reaches its duration. Code is as follows:
- (void)viewWillAppear:(BOOL)animated
{
...
_player = [AVPlayer playerWithURL:videoURL];
AVPlayerLayer *newPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
[newPlayerLayer setFrame:_videoView.bounds];
[_videoView.layer addSublayer:newPlayerLayer];
_observer = [_player addBoundaryTimeObserverForTimes:#[[NSValue valueWithCMTime:_player.currentItem.duration]] queue:NULL usingBlock:^{
[someButton setHidden:NO];
}];
...
}
For whatever reason, sometimes the block of code fires and the button becomes visible, and sometimes it doesn't. Haven't been able to find a pattern in this behavior. It happens very often (almost always) in the simulator, and occasionally when on a device. Has anyone encountered this problem? Any ideas what might be going on?
Edit
Also, if I put a breakpoint on the block, it ALWAYS fires.

Main queue sometimes not call.
You can use Sub queue, and call Main queue in Sub-queue's block.
// dispatch queue setting
dispatch_queue_t subQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
// notification setting
__block id blockObserver;
blockObserver = [self.queuePlayer addBoundaryTimeObserverForTimes:boundary
queue:subQueue // if NULL use mainQueue
usingBlock:^{
// do something
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
// do something
});
}];

For those wanting to observe when the player ends:
I browse this question about once a year because I always forget about the fix that works for me. This time around I had this issue on macOS. I am seeing the same behavior, the observer block sometimes does not get called. When I switch back from the app that is being debugged to Xcode the block suddenly fires. This might be related to having a breakpoint set in the block as described by the OP.
Here's the fix however: Simply switch to AVPlayerItemDidPlayToEndTimeNotification as described in this answer. Note however, as the name implies the notification's object is the player's current item not the player itself!
Because this notification triggers at the end time of an item, instead of observing some "boundary time" simply set the item's forwardPlaybackEndTime if you need another time than the item's actual end time, i.e. duration.

Related

working on parent UI from containerview (iOS - objective-c)

I have a method in my main viewcontroller:
- (void)playMusic:(NSString*)songTitle :(NSString*)songArtist :(NSString*)songDuration :(NSString*)songUrl{
[songPlayer pause];
songPlayer = [[AVPlayer alloc]initWithURL:[NSURL URLWithString:songUrl]];
[songPlayer play];
self.songSlider.maximumValue = 100;
self.songSlider.value = 0;
}
I call it from a view inside a containerview like this (from a button click)
[mainController playMusic:songTitle:songArtist :songDuration :songUrl];
songPlayer (it s an AVPlayer) is doing his job well, the song start without problem.
But the songSlider (it s an UISlider) is not updated.
If I call the exam same instructions:
self.songSlider.maximumValue = 100;
self.songSlider.value = 0;
inside viewDidLoad method of my main cotroller, songSlider is updated without problem.
That makes me think the problem is that I call the function from the containerview? in this case how can I fix this? Thank you
I answered a similar question about an hour ago.
I suspect the problem to be that your button click action is not being called on the main thread. I didn't find any resources to indicate if UIControl always fires events on the main thread and thus there is a possibility that your code is running on a background thread. This is important because you cannot make UI modifications on any other thread than the main thread.
Try the following in your button callback
dispatch_sync(dispatch_get_main_queue(), ^{
[mainController playMusic:songTitle:songArtist :songDuration :songUrl];
});

NSOperationQueue's threads just don't die

Sorry, it's a bit wordy, but I wanted to make sure I was clear! ;-)
I have an iOS app that uses FFMPEG for streaming RTSP. I've multi-threaded FFMPEG using NSOperationQueue such that most its work, other than painting the image to the screen, of course, happens in background threads.
Works great! ...except for the fact that threads the NSOperationQueue creates never die!
I init the Queue in the class' init method with:
self->opQ = [[NSOperationQueue alloc] init];
[self->opQ setMaxConcurrentOperationCount:1];
I add methods to the Queue using blocks:
[self->opQ addOperationWithBlock:^{
[self haveConnectedSuccessfullyOperation];
}];
Or
[self->opQ addOperationWithBlock:^{
if (SOME_CONDITION) {
[self performSelectorOnMainThread:#selector(DO_SOME_CRAP) withObject:nil waitUntilDone:NO];
}
}];
Later, when I need to tear down the RTSP stream, in addition to telling FFMPEG to shut down, I call:
[self->opQ cancelAllOperations];
Which does indeed stop the threads from doing any work , but never actually destroys them. Below, you'll see a screen shot of threads that are doing nothing at all. This is what my threads look like after starting/stoping FFMPEG several times.
I seem to remember reading in Apple's documentation that NSOperations and the threads they are run on are destroyed once they are done executing, unless otherwise referenced. This doesn't appear to be the case.
Do I just need to destroy the NSOperationQueue, then re-init it when I need to start up FFMPEG again (I just realized I haven't tried this)? Anyone know how I need to kill these extra threads?
THANKS!
I solved it by creating NSBlockOperations so that I could monitor the isCancelled state, while also making the new NSBlockOperations' content more intelligent, such that I simplified the routine that would add the operations to the queue.
... Plus, I made an NSOperationQueue n00b mistake: I was adding operations to the queue on a looping basis, which fired up to 30 times per second (matching the video's frame rate). Now, however, the operation is added to the queue only once and the looping behavior is contained within the operation instead of having the loop add the operation to the queue.
Previously, I had something like this (pseudo code, since I don't have the project with me):
NSTimer *frameRateTimeout = [NSTimer scheduledTimerWithTimeInterval:1/DESIRED_FRAMES_PER_SECOND target:self selector:#selector(ADD_OPERATION_TO_QUEUE_METHOD:) userInfo:nil repeats:YES];
-(void)ADD_OPERATION_TO_QUEUE_METHOD:(NSTimer *)timer {
[opQ addOperation:displayFrame];
}
Which worked well, as the OS would correctly manage the queue, but it was not very efficient, and kept those threads alive forever.
Now, it's more like:
-(id)init {
self = [super init];
if (self) {
// alloc/init operation queue
...
// alloc/init 'displayFrame'
displayFrame = [NSBlockOperation blockOperationWithBlock:^{
while (SOME_CONDITION && ![displayFrame isCancelled]) {
if (playVideo) {
// DO STUFF
[NSThread sleepForTimeInterval:FRAME_RATE];
}
else { // teardown stream
// DO STUFF
break;
}
}
}];
}
return self;
}
- (void)Some_method_called_after_getting_video_ready_to_play {
[opQ addOperation:displayFrame];
}
Thanks, Jacob Relkin, for responding to my post.
If anyone needs further clarification, let me know, and I'll post better code once I have the project in my hands again.

How to solve delay of progressView's movement animation

Currently, I'm downloading data from a web server by calling a method fetchProducts. This is done in another separate thread. As I successfully download fifty items inside the method stated above, I post a notification to the [NSNotification defaultCenter] through the method call postNotificationName: object: which is being listened to by the Observer. Take note that this Observer is another ViewController with the selector updateProductsBeingDownloadedCount:. Now as the Observer gets the notification, I set the property of my progressView and a label that tells the progress. Below is the code I do this change in UI.
dispatch_async(dispatch_get_main_queue(), ^{
if ([notif.name isEqualToString:#"DownloadingProducts"]) {
[self.progressBar setProgress:self.progress animated:YES];
NSLog(#"SetupStore: progress bar value is %.0f", self.progressBar.progress);
self.progressLabel.text = [NSString stringWithFormat:#"Downloading %.0f%% done...", self.progress * 100];
NSLog(#"SetupStore: progress label value is %#", self.progressLabel.text);
[self.view reloadInputViews];
}
});
The idea is to move the progressView simultaneously as more items were being downloaded until it is finished. In my case, the progressView's animation will just start right after the items were already downloaded, hence a delay. Kindly enlighten me on this.

How to hide a UIPopoverController while a lengthy operation is to be performed?

After selecting an option from a popover controller, the delegate is informed that a selection has been made.
I want to dismiss the popover, have it removed from the screen, and display an activity indicator to the user.
Unfortunately, the code below the dismissPopover runs before the popover actually disappears, resulting in a long wait without anything appearing to be happening.
- (void)itemSelected:(int)option {
[popController dismissPopoverAnimated:YES];
activityIndicator.hidden = NO;
[activityIndicator startAnimating];
switch (option) {
case 0:
// Do something that takes some time
break;
case 1:
// Do something that takes even longer
break;
}
}
What's the best way to return control back to the calling ViewController after dismissing the popover?
The problem is that when you change the UI, it doesn't happen instantly. The changes are actually queued up to occur next time the main event loop finishes. Since that usually happens right away, we usually don't have to worry about the difference. All UI updates happen on the main thread, and since your long operations are also on the main thread, the app never gets around to updating the UI until the long operations are done.
One solution would be to use Grand Central Dispatch to offload those operations to another thread, which will allow the main thread to continue executing (and the UI to continue updating) until the operation is done.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[self performReallyLongOperation];
});
dispatch_release(queue);
You can use UIPopOverController's delegate method popoverControllerDidDismissPopover to execute your code after the popover is done dismissing:
Header
<UIPopoverControllerDelegate>
Implementation
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
activityIndicator.hidden = NO;
[activityIndicator startAnimating];
switch (option) {
case 0:
// Do something that takes some time
break;
case 1:
// Do something that takes even longer
break;
}
}

waiting in the fixed place in the code for a touch on the screen

For example in a thread (because I can't wait in main loop) I have this code :
-(void) game {
for (Players player in players) {
if (player.type == IA) { // computer plays
answer = [player play];
else {
[ui showQuestion]; // user plays with the touch screen
// here waiting for the answer touch
answer = ???????????????? // return from waiting after touch callback
}
[answersArray addObject:answer];
}
answer = [self bestAnswer : answersArray];
[ui showTheBestAnswer : answer];
}
Is there a solution to wait for an UI Event in a fixed code place ?
without blocking the main loop of course.
Thank you very much for your help,
jpdms
First of all, I highly recommend that you read Apple's Concurrency Programming Guide, included with the Xcode documentation. In general, there are much better alternatives to threads, especially in the example you provide.
However:
If the game method is executing on a separate thread, then the proper way to signal the thread is using NSCondition. Create an instance and make sure both the code above and the touch handler has access to it.
NSCondition *playerDidTouchCondition = [[NSCondition alloc] init];
In the game method, you wait on the condition like this:
[ui showQuestion];
[playerDidTouchCondition lock];
[playerDidTouchCondition wait];
[playerDidTouchCondition unlock];
// do something with answer
Your game thread will sleep until the condition has been signaled. In your touch handler you would do this:
answer = whatever the user did
[playerDidTouchCondition lock];
[playerDidTouchCondition signal]; // wake up one of the sleeping threads
[playerDidTouchCondition unlock];
The example code you have above really doesn't demonstrate the need for a separate thread, however. You could very easily store a currentPlayerIndex somewhere and proceed to the next player inside of the button handler for the answer button.
Also, you MUST ensure that any UI updates are actually happening on the main thread. I hope that your lines like [ui showQuestion] are queuing calls on the main thread. In Cocoa you can do this easily with something like: [ui performSelectorOnMainThread:#selector(showQuestion)];
You really, really, really should not be using a separate thread for this.

Resources