Skip to content

Instantly share code, notes, and snippets.

@arthurdapaz
Forked from michaelevensen/AVPlayerCrossFade.swift
Created February 26, 2020 15:47
Show Gist options
  • Save arthurdapaz/047f5820553b1fcbde78995ae0690562 to your computer and use it in GitHub Desktop.
Save arthurdapaz/047f5820553b1fcbde78995ae0690562 to your computer and use it in GitHub Desktop.
Handles cross-fading between two individual AVPlayers, creates a smooth, undulating loop between two AVPlayerItem's.
import UIKit
import AVFoundation
class ViewController: UIViewController {
// Duplicate players to handle optional cross-fading.
let playerQueue = [AVPlayer(), AVPlayer()]
var timeObserverToken: Any?
// Duration for overlapping cross-fade
var crossFadeDuration: Double = 5.0
/* Returns current AVPlayer */
var currentPlayer: AVPlayer {
return self.playingCopy ? self.playerQueue.last! : self.playerQueue.first!
}
// Init optional cross-fading
var playingCopy: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://storage.googleapis.com/slow-69a4e.appspot.com/audio%2F10%20sec_Ocean%2010ms%20fade.mp3?GoogleAccessId=slow-69a4e%40appspot.gserviceaccount.com&Expires=1735689600&Signature=GoDFIQ96SLBtxlQFEz5B5RfUlxrD5SuIy0ZUAC7FITburO5skPskUDj7nDWhkH%2Fn7%2FmuVaKnX9VaLTRQ5BLAxaSLpY5NuGecoPq%2BScdOLrW%2FlCGx0AATsV9fe5rxTRTZzLtv6vj9fLnQEvleZGqYdqBhBw94uDI51N0w2OLCCv8nGBgixrlfNgkSTLJkT541Nc%2FpxzPXyozhjMTFshdTeMEvpLvkzCM9BuSx7ZFBon0HBJ3UlQFz%2FicYUqz5CeTF%2F6n%2BuOX2ujnRk0nbOdVOv%2BIhPeEJispw6Rxz9ludyckMSEdDaBv0DF3Zen6uxnyHL6i5p%2FAZPW33sMnuvA%2FVyQ%3D%3D") else {
return
}
// Setup and start main player
self.currentPlayer.replaceCurrentItem(with: AVPlayerItem(url: url))
// Setup copy
if let currentItem = self.currentPlayer.currentItem {
let copy = AVPlayerItem(asset: currentItem.asset)
self.playerQueue.last?.replaceCurrentItem(with: copy)
}
// Add volume ramps
self.addVolumeRamps(with: self.crossFadeDuration)
// Add time observer
self.addPeriodicTimeObserver(for: self.currentPlayer)
// Start playback
self.currentPlayer.play()
}
/// Add volume ramps for all players in queue.
fileprivate func addVolumeRamps(with duration: Double) {
for player in self.playerQueue {
player.currentItem?.addFadeInOut(duration: duration)
}
}
/// Add periodic time observer for current player.
fileprivate func addPeriodicTimeObserver(for player: AVPlayer) {
self.timeObserverToken = self.currentPlayer.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: .main) { [weak self] (currentTime) in
if let currentItem = self?.currentPlayer.currentItem, currentItem.status == .readyToPlay, let crossFadeDuration = self?.crossFadeDuration {
let totalDuration = currentItem.asset.duration
/**
Logic for Looping
*/
// Start cross-fade logic towards end of playback
if (CMTimeCompare(currentTime, totalDuration - CMTimeMakeWithSeconds(crossFadeDuration, preferredTimescale: CMTimeScale(NSEC_PER_SEC))) > 0) {
// Handle crossfade
self?.handleCrossFade()
}
}
}
}
/// Cycles between players in Queue
fileprivate func handleCrossFade() {
// Remove current player observer (if any)
self.removePeriodicTimeObserver(for: self.currentPlayer)
// Invert current player
self.playingCopy = !self.playingCopy
// Start observing inverted player
self.addPeriodicTimeObserver(for: self.currentPlayer)
// Play
self.currentPlayer.seek(to: .zero)
self.currentPlayer.play()
}
/// Remove periodic time observer
fileprivate func removePeriodicTimeObserver(for player: AVPlayer) {
if let timeObserverToken = timeObserverToken {
player.removeTimeObserver(timeObserverToken)
self.timeObserverToken = nil
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment