Search code examples
macosavplayerscreensaver

macOS create custom screen saver from .mov file


I am trying to create a custom screen saver using a .mov file.

Xcode - New Project - ScreenSaver

Below is the code using Swift. The problem is that nothing happens - the AV player is not doing anything.

import Foundation
import AVFoundation
import AVKit
import ScreenSaver

class MoonView: ScreenSaverView {
    private var player: AVPlayer!

    override init?(frame: NSRect, isPreview: Bool) {
        super.init(frame: frame, isPreview: isPreview)

        guard let path = Bundle.main.path(forResource: "moon", ofType:"mov") else {
            fatalError("moon.mov not found")
        }
        player = AVPlayer(url: URL(fileURLWithPath: path))
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ rect: NSRect) {

        let playerLayerAV = AVPlayerLayer(player: player)
        playerLayerAV.frame = rect
        player.play()
    }

    override func animateOneFrame() {
        super.animateOneFrame()
        setNeedsDisplay(bounds)
    }

}

Solution

  • Was able to get it working after few days

    import Foundation
    import ScreenSaver
    import AVKit
    
    class MoonView: ScreenSaverView {
        private var player: AVPlayer!
        private var playerLayer: AVPlayerLayer!
                
        override init?(frame: NSRect, isPreview: Bool) {
            super.init(frame: frame, isPreview: isPreview)
            animationTimeInterval = 1.0/30.0
            wantsLayer = true
            player = createAVPlayer()
            playerLayer = createAVPlayerLayer(player: player)
            self.layer = playerLayer
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override func startAnimation() {
            super.startAnimation()
            player.play()
        }
        
        override func stopAnimation() {
            super.stopAnimation()
            player.pause()
        }
            
        func createAVPlayer() -> AVPlayer {
            let moonBundle: Bundle = Bundle(for: MoonView.self)
            guard let url = moonBundle.url(forResource: "moon", withExtension: "mov") else {
                fatalError("moon.mov not found in \(moonBundle.bundlePath)")
            }
            let avPlayer = AVPlayer(url: url)
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(playerItemDidReachEnd),
                                                   name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
                                                   object: nil)
            return avPlayer
        }
        
        func createAVPlayerLayer(player: AVPlayer) -> AVPlayerLayer {
            let avPlayerLayer: AVPlayerLayer = AVPlayerLayer(player: player)
            avPlayerLayer.frame = bounds
            avPlayerLayer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable]
            avPlayerLayer.needsDisplayOnBoundsChange = true
            avPlayerLayer.contentsGravity = .resizeAspect
            avPlayerLayer.backgroundColor = CGColor(red: 0.00, green: 0.01, blue: 0.00, alpha:1.0)
            return avPlayerLayer
        }
        
        // Notification Handling
        @objc func playerItemDidReachEnd(notification: NSNotification) {
            player.seek(to: CMTime.zero)
            player.play()
        }
        
        // Remove Observer
        deinit {
            NotificationCenter.default.removeObserver(self)
        }
    }