AVPlayer
Delegate
Objective-C
iPhone Development
Audio Playback

No AVPlayer Delegate? How to track when song finished playing? Objective C iPhone development

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

AVPlayer does not provide a direct delegate callback for playback completion, unlike AVAudioPlayer. The correct completion mechanism is observing AVPlayerItemDidPlayToEndTimeNotification on the current player item. Reliable behavior also requires observer cleanup and handling item replacement.

Completion Tracking with Notification Center

Register for end-of-playback notification on the active AVPlayerItem.

objective-c
1#import <AVFoundation/AVFoundation.h>
2
3@interface PlayerController : NSObject
4@property (nonatomic, strong) AVPlayer *player;
5@property (nonatomic, strong) AVPlayerItem *currentItem;
6@end
7
8@implementation PlayerController
9
10- (void)playURL:(NSURL *)url {
11    self.currentItem = [AVPlayerItem playerItemWithURL:url];
12    self.player = [AVPlayer playerWithPlayerItem:self.currentItem];
13
14    [[NSNotificationCenter defaultCenter] addObserver:self
15                                             selector:@selector(itemDidFinish:)
16                                                 name:AVPlayerItemDidPlayToEndTimeNotification
17                                               object:self.currentItem];
18
19    [self.player play];
20}
21
22- (void)itemDidFinish:(NSNotification *)note {
23    NSLog(@"Playback finished");
24}
25
26@end

This is the standard replacement for delegate-style completion.

Remove Observers Correctly

If you change tracks and forget observer cleanup, callbacks may fire multiple times or for stale items.

objective-c
1- (void)replaceWithURL:(NSURL *)url {
2    if (self.currentItem) {
3        [[NSNotificationCenter defaultCenter] removeObserver:self
4                                                        name:AVPlayerItemDidPlayToEndTimeNotification
5                                                      object:self.currentItem];
6    }
7
8    AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
9    self.currentItem = item;
10
11    [[NSNotificationCenter defaultCenter] addObserver:self
12                                             selector:@selector(itemDidFinish:)
13                                                 name:AVPlayerItemDidPlayToEndTimeNotification
14                                               object:item];
15
16    [self.player replaceCurrentItemWithPlayerItem:item];
17    [self.player play];
18}

Always remove observers in dealloc for safety.

Handle Failure and Interruptions

Playback completion is only one terminal state. Also observe failure notifications and audio session interruptions.

objective-c
1[[NSNotificationCenter defaultCenter] addObserver:self
2                                         selector:@selector(itemFailed:)
3                                             name:AVPlayerItemFailedToPlayToEndTimeNotification
4                                           object:self.currentItem];
5
6- (void)itemFailed:(NSNotification *)note {
7    NSError *err = note.userInfo[AVPlayerItemFailedToPlayToEndTimeErrorKey];
8    NSLog(@"Playback failed: %@", err.localizedDescription);
9}

Handling these states improves user experience during network and session disruptions.

Looping and Playlist Progression

In completion callback, decide whether to loop or advance playlist.

Loop current track:

objective-c
1- (void)itemDidFinish:(NSNotification *)note {
2    AVPlayerItem *item = note.object;
3    [item seekToTime:kCMTimeZero completionHandler:^(BOOL done) {
4        if (done) [self.player play];
5    }];
6}

Playlist progression should replace item and update index atomically to avoid race conditions.

Progress Updates Are Separate

addPeriodicTimeObserverForInterval is useful for progress UI, not completion detection.

objective-c
1id token = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 2)
2                                                     queue:dispatch_get_main_queue()
3                                                usingBlock:^(CMTime t) {
4    NSLog(@"%.2f", CMTimeGetSeconds(t));
5}];
6self.timeObserverToken = token;

Remove this token when no longer needed.

Background Audio and Session Configuration

If your app should keep playing while backgrounded, configure audio session category before creating player objects. Without this setup, playback can pause when app state changes, and completion callback behavior may look inconsistent during testing.

objective-c
1#import <AVFoundation/AVFoundation.h>
2
3- (BOOL)configureAudioSession:(NSError **)error {
4    AVAudioSession *session = [AVAudioSession sharedInstance];
5    BOOL ok = [session setCategory:AVAudioSessionCategoryPlayback error:error];
6    if (!ok) return NO;
7    return [session setActive:YES error:error];
8}

Also enable the Background Modes capability for audio in project settings. Session setup and notification handling should be treated as one system, because both affect end-of-track behavior users observe.

Prefer Main Queue for UI Side Effects

Completion callback may trigger UI updates such as resetting play button state or moving to next row in a playlist table. Keep UI mutations on main queue to avoid race conditions.

objective-c
1- (void)itemDidFinish:(NSNotification *)note {
2    dispatch_async(dispatch_get_main_queue(), ^{
3        // Update controls and labels safely on main thread.
4        self.playButton.enabled = YES;
5        self.statusLabel.text = @"Finished";
6    });
7}

For pure playback engine state, background queues can be fine, but UI work should stay on main thread.

Testing Playback Completion

A practical test plan:

  • play short local file and assert one completion callback
  • replace item mid-play and assert no stale completion callback
  • simulate failure path and confirm error handling

Playback code is stateful, so deterministic tests prevent regressions during refactor.

Common Pitfalls

  • Expecting AVPlayer delegate callback for completion.
  • Observing completion without scoping notification object to current item.
  • Forgetting observer removal on item replacement.
  • Treating periodic time observer as completion event.
  • Handling completion only and ignoring failure or interruption states.

Summary

  • Use AVPlayerItemDidPlayToEndTimeNotification for completion tracking.
  • Scope observers to active player item and clean up on replacement.
  • Handle failures and interruptions, not only successful completion.
  • Keep progress updates and completion logic as separate concerns.
  • Add deterministic tests for item lifecycle and callback behavior.

Course illustration
Course illustration

All Rights Reserved.