Skip to content

Commit

Permalink
refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
swiftuiux committed Aug 14, 2024
1 parent ce5ae1b commit 44d3c50
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 43 deletions.
25 changes: 0 additions & 25 deletions Sources/swiftui-loop-videoplayer/fn/fn+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
///
Expand All @@ -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()
}
})
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

0 comments on commit 44d3c50

Please sign in to comment.