Replaying AVPlayerItem / AVPlayer without re-downloading

You can call the seekToTime method when your player received AVPlayerItemDidPlayToEndTimeNotification

func itemDidFinishPlaying() {
    self.player.seek(to: CMTime.zero)
    self.player.play()
}

Apple recommends using AVQueueplayer with an AVPlayerLooper.

Here's Apple's (slightly revised) sample code:

AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] init];    

AVAsset *asset = // AVAsset with its 'duration' property value loaded
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];

 // Create a new player looper with the queue player and template item
self.playerLooper = [AVPlayerLooper playerLooperWithPlayer:queuePlayer
                                              templateItem:playerItem];

 // Begin looping playback
[queuePlayer play];

The AVPlayerLooper does all the event listening and playing for you, and the queue player is used to create what they call a "treadmill pattern". This pattern is essentially chaining multiple instances of the same AVAssetItem in a queue player and moving each finished asset back to the beginning of the queue.

The advantage of this approach is that it enables the framework to preroll the next asset (which is the same asset in this case, but its start still needs prerolling) before it arrives, reducing latency between the asset's end and looped start.

This is described in greater detail at ~15:00 in the video here: https://developer.apple.com/videos/play/wwdc2016/503/