AVFoundation
AVPlayer
video looping
iOS development
Swift programming

Looping a video with AVFoundation AVPlayer?

Master System Design with Codemia

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

Introduction

Looping a video with AVPlayer is a common iOS task for splash screens, onboarding backgrounds, and ambient media. The most important design choice is whether you want a modern seamless loop using AVPlayerLooper or a manual restart approach for older or more customized playback logic.

The Best Modern Approach: AVQueuePlayer Plus AVPlayerLooper

For most current iOS code, the cleanest solution is AVPlayerLooper working with an AVQueuePlayer. It handles repetition for you and avoids the awkward "seek to start after finish" pattern.

swift
1import UIKit
2import AVFoundation
3
4final class LoopingVideoViewController: UIViewController {
5    private var queuePlayer: AVQueuePlayer?
6    private var playerLayer: AVPlayerLayer?
7    private var playerLooper: AVPlayerLooper?
8
9    override func viewDidLoad() {
10        super.viewDidLoad()
11
12        guard let url = Bundle.main.url(forResource: "demo", withExtension: "mp4") else {
13            return
14        }
15
16        let item = AVPlayerItem(url: url)
17        let player = AVQueuePlayer()
18
19        self.queuePlayer = player
20        self.playerLooper = AVPlayerLooper(player: player, templateItem: item)
21
22        let layer = AVPlayerLayer(player: player)
23        layer.frame = view.bounds
24        layer.videoGravity = .resizeAspectFill
25        view.layer.addSublayer(layer)
26        self.playerLayer = layer
27
28        player.play()
29    }
30
31    override func viewDidLayoutSubviews() {
32        super.viewDidLayoutSubviews()
33        playerLayer?.frame = view.bounds
34    }
35}

This setup is usually what you want for a smooth loop with minimal custom code.

Manual Looping with End-of-Playback Notification

If you only have an AVPlayer and want full control, you can observe the end of the item and seek back to .zero.

swift
1import UIKit
2import AVFoundation
3
4final class ManualLoopViewController: UIViewController {
5    private var player: AVPlayer?
6    private var playerLayer: AVPlayerLayer?
7
8    override func viewDidLoad() {
9        super.viewDidLoad()
10
11        guard let url = Bundle.main.url(forResource: "demo", withExtension: "mp4") else {
12            return
13        }
14
15        let item = AVPlayerItem(url: url)
16        let player = AVPlayer(playerItem: item)
17        self.player = player
18
19        let layer = AVPlayerLayer(player: player)
20        layer.frame = view.bounds
21        view.layer.addSublayer(layer)
22        self.playerLayer = layer
23
24        NotificationCenter.default.addObserver(
25            self,
26            selector: #selector(restartVideo),
27            name: .AVPlayerItemDidPlayToEndTime,
28            object: item
29        )
30
31        player.play()
32    }
33
34    @objc private func restartVideo() {
35        player?.seek(to: .zero)
36        player?.play()
37    }
38
39    deinit {
40        NotificationCenter.default.removeObserver(self)
41    }
42}

This works, but it can be slightly less seamless than AVPlayerLooper, especially for short clips.

Choosing Between the Two

Use AVPlayerLooper when:

  • you want a simple repeating clip
  • seamless playback matters
  • you are already comfortable using AVQueuePlayer

Use manual notification-based restarting when:

  • you need custom logic at each loop boundary
  • you want to update UI or analytics every time the clip completes
  • the playback flow is more than just endless looping

Both are valid, but AVPlayerLooper is the cleaner default.

Playing in a Reusable View

If the video is just a background element, it is often cleaner to wrap the player layer in a dedicated UIView subclass instead of putting all playback logic in a view controller.

swift
1import UIKit
2import AVFoundation
3
4final class LoopingVideoView: UIView {
5    private let player = AVQueuePlayer()
6    private var looper: AVPlayerLooper?
7    private let playerLayer = AVPlayerLayer()
8
9    override init(frame: CGRect) {
10        super.init(frame: frame)
11        playerLayer.player = player
12        playerLayer.videoGravity = .resizeAspectFill
13        layer.addSublayer(playerLayer)
14    }
15
16    required init?(coder: NSCoder) {
17        fatalError("init(coder:) has not been implemented")
18    }
19
20    override func layoutSubviews() {
21        super.layoutSubviews()
22        playerLayer.frame = bounds
23    }
24
25    func configure(url: URL) {
26        let item = AVPlayerItem(url: url)
27        looper = AVPlayerLooper(player: player, templateItem: item)
28        player.play()
29    }
30}

That pattern is easier to reuse across screens.

Common Pitfalls

  • Forgetting to retain the AVPlayerLooper, which can stop the loop when the looper is deallocated.
  • Leaving the AVPlayerLayer frame stale instead of updating it during layout changes.
  • Using notification-based looping without removing observers in more complex view lifecycles.
  • Picking manual restart when you really wanted the smoother behavior of AVPlayerLooper.
  • Looping large high-bitrate assets for decorative UI, which wastes battery and memory on mobile devices.

Summary

  • 'AVPlayerLooper with AVQueuePlayer is usually the cleanest way to loop video on iOS'
  • Manual restart with AVPlayerItemDidPlayToEndTime still works when you need custom loop-boundary logic
  • Retain both the player and the looper as properties
  • Keep the player layer frame in sync with the view layout
  • Optimize looped videos for UI usage so playback stays smooth and efficient

Course illustration
Course illustration

All Rights Reserved.