Last active
September 24, 2024 07:11
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
Where do we find "addFadeInOut" function for current item of the player? Please help us