diff --git a/Sources/swiftui-loop-videoplayer/fn/fn+.swift b/Sources/swiftui-loop-videoplayer/fn/fn+.swift index e0f4511..28f6c93 100644 --- a/Sources/swiftui-loop-videoplayer/fn/fn+.swift +++ b/Sources/swiftui-loop-videoplayer/fn/fn+.swift @@ -44,31 +44,6 @@ fileprivate func extractExtension(from name: String) -> String? { return nil } -/// Cleans up the resources associated with a video player. -/// This function nullifies references to the player, player looper, and observers to facilitate resource deallocation and prevent memory leaks. -/// - Parameters: -/// - player: A reference to the AVQueuePlayer instance. This parameter is passed by reference to allow the function to nullify the external reference. -/// - playerLooper: A reference to the AVPlayerLooper associated with the player. This is also passed by reference to nullify and help in cleaning up. -/// - statusObserver: A reference to an observer watching the player's status changes. Passing by reference allows the function to dispose of it properly. -/// - errorObserver: A reference to an observer monitoring errors from the player. It is managed in the same way as statusObserver to ensure proper cleanup. -func cleanUp(player: inout AVQueuePlayer?, playerLooper: inout AVPlayerLooper?, errorObserver: inout NSKeyValueObservation?) { - // Invalidate and remove references to observers - errorObserver?.invalidate() - errorObserver = nil - - // Pause the player and release player-related resources - player?.pause() - player = nil - playerLooper?.disableLooping() - playerLooper = nil - - // Debugging statement to confirm cleanup in debug builds - #if DEBUG - print("Cleaned up AVPlayer and observers.") - #endif -} - - /// Detects and returns the appropriate error based on settings and asset. /// - Parameters: /// - settings: The settings for the video player. diff --git a/Sources/swiftui-loop-videoplayer/protocol/player/AbstractPlayer.swift b/Sources/swiftui-loop-videoplayer/protocol/player/AbstractPlayer.swift index 7443ab3..a5c79ef 100644 --- a/Sources/swiftui-loop-videoplayer/protocol/player/AbstractPlayer.swift +++ b/Sources/swiftui-loop-videoplayer/protocol/player/AbstractPlayer.swift @@ -9,7 +9,7 @@ import AVFoundation import CoreImage @available(iOS 14, macOS 11, tvOS 14, *) -@MainActor +@MainActor @preconcurrency public protocol AbstractPlayer: AnyObject { /// Retrieves the current item being played. diff --git a/Sources/swiftui-loop-videoplayer/protocol/view/LoopPlayerViewProtocol.swift b/Sources/swiftui-loop-videoplayer/protocol/view/LoopPlayerViewProtocol.swift index 50757ac..6b832bd 100644 --- a/Sources/swiftui-loop-videoplayer/protocol/view/LoopPlayerViewProtocol.swift +++ b/Sources/swiftui-loop-videoplayer/protocol/view/LoopPlayerViewProtocol.swift @@ -11,7 +11,7 @@ import SwiftUI /// Protocol that defines the common functionalities and properties /// for looping video players on different platforms. @available(iOS 14, macOS 11, tvOS 14, *) -@MainActor +@MainActor @preconcurrency public protocol LoopPlayerViewProtocol { #if canImport(UIKit) diff --git a/Sources/swiftui-loop-videoplayer/protocol/view/LoopingPlayerProtocol.swift b/Sources/swiftui-loop-videoplayer/protocol/view/LoopingPlayerProtocol.swift index 97a71b1..430f65d 100644 --- a/Sources/swiftui-loop-videoplayer/protocol/view/LoopingPlayerProtocol.swift +++ b/Sources/swiftui-loop-videoplayer/protocol/view/LoopingPlayerProtocol.swift @@ -17,7 +17,7 @@ import AppKit /// Conforming types are expected to manage a video player that can loop content continuously, /// handle errors, and notify a delegate of important events. @available(iOS 14, macOS 11, tvOS 14, *) -@MainActor +@MainActor @preconcurrency public protocol LoopingPlayerProtocol: AbstractPlayer, LayerMakerProtocol{ #if canImport(UIKit) @@ -61,7 +61,7 @@ public protocol LoopingPlayerProtocol: AbstractPlayer, LayerMakerProtocol{ func handlePlayerError(_ player: AVPlayer) } -extension LoopingPlayerProtocol { +internal extension LoopingPlayerProtocol { /// Updates the player to play a new asset and handles the playback state. /// @@ -80,22 +80,21 @@ extension LoopingPlayerProtocol { if wasPlaying { player.pause() } - + // Cleaning unloop() - while player.items().count > 0 { - player.advanceToNextItem() - } + clearPlayerQueue() removeAllFilters() + // Replace the current item let newItem = AVPlayerItem(asset: asset) player.replaceCurrentItem(with: newItem) loop() - - player.seek(to: .zero, completionHandler: { _ in + + player.seek(to: .zero, completionHandler: { [weak self] _ in if wasPlaying { - self.player?.play() + self?.play() } }) } @@ -131,7 +130,7 @@ extension LoopingPlayerProtocol { /// - Parameters: /// - player: The AVQueuePlayer to be configured. /// - gravity: The AVLayerVideoGravity determining how the video content should be scaled or fit within the player layer. - internal func configurePlayer(_ player: AVQueuePlayer, gravity: AVLayerVideoGravity) { + func configurePlayer(_ player: AVQueuePlayer, gravity: AVLayerVideoGravity) { player.isMuted = true playerLayer.player = player playerLayer.videoGravity = gravity @@ -162,6 +161,16 @@ extension LoopingPlayerProtocol { self?.handlePlayerError(player) } } + + /// Removes observers for handling errors. + /// + /// This method ensures that the error observer is properly invalidated and the reference is cleared. + /// It is important to call this method to prevent memory leaks and remove any unwanted side effects + /// from obsolete observers. + func removeObservers() { + errorObserver?.invalidate() + errorObserver = nil + } /// Responds to changes in the status of an AVPlayerItem. @@ -184,6 +193,42 @@ extension LoopingPlayerProtocol { delegate?.didReceiveError(.remoteVideoError(error)) } + /// Cleans up the player and its associated resources. + /// + /// This function performs several cleanup tasks to ensure that the player is properly + /// decommissioned. It pauses playback, removes any registered observers, stops any + /// looping, removes all applied filters, and finally nils out the player to release it + /// for garbage collection. This method is marked with `@preconcurrency` to indicate that + /// it is safe to call in both concurrent and non-concurrent environments, preserving + /// compatibility with older code that does not use Swift's new concurrency model. + @preconcurrency + func cleanUp() { + + pause() + + removeObservers() + + unloop() + + clearPlayerQueue() + + removeAllFilters() + + player = nil + + #if DEBUG + print("Cleaned up AVPlayer and observers.") // Debug log for confirming cleanup. + #endif + } + + func clearPlayerQueue() { + guard let items = player?.items() else { return } + for item in items { + // Additional cleanup or processing here + player?.remove(item) + } + } + func setCommand(_ value: PlaybackCommand) { /// Sets the playback command for the video player. /// - Parameter value: The `PlaybackCommand` to set. This can be one of the following: diff --git a/Sources/swiftui-loop-videoplayer/view/loop/player/ios/LoopingPlayerUIView.swift b/Sources/swiftui-loop-videoplayer/view/loop/player/ios/LoopingPlayerUIView.swift index 671df0f..d0ee2f3 100644 --- a/Sources/swiftui-loop-videoplayer/view/loop/player/ios/LoopingPlayerUIView.swift +++ b/Sources/swiftui-loop-videoplayer/view/loop/player/ios/LoopingPlayerUIView.swift @@ -15,7 +15,7 @@ import AVKit import UIKit @available(iOS 14.0, tvOS 14.0, *) -@MainActor +@MainActor @preconcurrency class LoopingPlayerUIView: UIView, LoopingPlayerProtocol { /// `filters` is an array that stores CIFilter objects used to apply different image processing effects @@ -71,9 +71,8 @@ class LoopingPlayerUIView: UIView, LoopingPlayerProtocol { /// /// This method invalidates the status and error observers to prevent memory leaks, /// pauses the player, and clears out player-related references to assist in clean deinitialization. - /// It also conditionally logs the cleanup process during debug mode. deinit { - cleanUp(player: &player, playerLooper: &playerLooper, errorObserver: &errorObserver) + cleanUp() } } #endif diff --git a/Sources/swiftui-loop-videoplayer/view/loop/player/mac/LoopingPlayerNSView.swift b/Sources/swiftui-loop-videoplayer/view/loop/player/mac/LoopingPlayerNSView.swift index 78f09fe..dbd36d2 100644 --- a/Sources/swiftui-loop-videoplayer/view/loop/player/mac/LoopingPlayerNSView.swift +++ b/Sources/swiftui-loop-videoplayer/view/loop/player/mac/LoopingPlayerNSView.swift @@ -17,7 +17,7 @@ import AppKit /// A NSView subclass that loops video using AVFoundation on macOS. /// This class handles the initialization and management of a looping video player with customizable video gravity. @available(macOS 11.0, *) -@MainActor +@MainActor @preconcurrency class LoopingPlayerNSView: NSView, LoopingPlayerProtocol { /// `filters` is an array that stores CIFilter objects used to apply different image processing effects @@ -73,9 +73,8 @@ class LoopingPlayerNSView: NSView, LoopingPlayerProtocol { /// /// This method invalidates the status and error observers to prevent memory leaks, /// pauses the player, and clears out player-related references to assist in clean deinitialization. - /// It also conditionally logs the cleanup process during debug mode. deinit { - cleanUp(player: &player, playerLooper: &playerLooper, errorObserver: &errorObserver) + cleanUp() } } #endif