Skip to content

Instantly share code, notes, and snippets.

@michaelevensen
Last active November 10, 2023 07:48
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save michaelevensen/eb23087ae14f51d3de12644b1459363b to your computer and use it in GitHub Desktop.
Save michaelevensen/eb23087ae14f51d3de12644b1459363b 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
}
}
}
@IrfanSaeculum
Copy link

Where do we find "addFadeInOut" function for current item of the player? Please help us

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment