Swift Mac OS - 如何在单击按钮时使用 AVPlayer 播放另一个 video/change 视频的 URL?

Swift Mac OS - How to play another video/change the URL of the video with AVPlayer upon a button click?

我是 swift 的新手,我正在尝试制作一个 Mac OS 应用程序,使用 AVPlayer 作为 [=46] 的背景从应用程序的资源中循环播放视频=] 应用程序启动后。当用户选择菜单 item/clicks 按钮时,背景视频将立即更改为与应用资源不同的视频,并开始循环播放该视频作为 window 的背景。

按照本教程启动应用程序后,我能够播放第一个视频:(https://youtu.be/QgeQc587w70) and I also successfully made the video loop itself seamlessly following this post: (Looping AVPlayer seamlessly)。

我现在面临的问题是在单击菜单项 selected/a 按钮后将视频更改为另一个视频。我想要的方法是更改​​ url 并使用新的 URL 创建一个新的 AVPlayer 并将其影响到 playerView.player 之后 post: ().但是,每次选择菜单项时,应用程序都会崩溃并出现错误 "thread 1 exc_bad_instruction (code=exc_i386_invop subcode=0x0)"。这显然是由 playerView 的值为 nil 引起的。我真的不明白这是为什么,因为 playerView 是我使用 xib 文件创建并通过控制拖动链接到 swift 文件的 AVPlayerView 对象,我似乎找不到另一种合适的方法我想做的事。如果您知道造成这种情况的原因以及解决方法,请为我​​提供一些帮助,或者如果您知道更好的方法来执行我上面提到的操作,请也告诉我。任何帮助将不胜感激!

我的代码如下,导致应用崩溃的行在底部:

import Cocoa
import AppKit
import AVKit
import AVFoundation

struct videoVariables {
    static var videoName = "Test_Video" //declaring the video name as a global variable
}

    var videoIsPlaying = true
    var theURL = Bundle.main.url(forResource:videoVariables.videoName, withExtension: "mp4") //creating the video url
    var player = AVPlayer.init(url: theURL!)

class BackgroundWindow: NSWindowController {    
    @IBOutlet weak var playerView: AVPlayerView! // AVPlayerView Linked using control-drag from xib file
    @IBOutlet var mainWindow: NSWindow!
    @IBOutlet weak var TempBG: NSImageView!
    override var windowNibName : String! {
        return "BackgroundWindow"
    }


//function used for resizing the temporary background image and the playerView to the window’s size
    func resizeBG() {
        var scrn: NSScreen = NSScreen.main()!
        var rect: NSRect = scrn.frame
        var height = rect.size.height
        var width = rect.size.width
        TempBG.setFrameSize(NSSize(width: Int(width), height: Int(height)))
        TempBG.frame.origin = CGPoint(x: 0, y: 0)
        playerView!.setFrameSize(NSSize(width: Int(width), height: Int(height)))
        playerView!.frame.origin = CGPoint(x: 0, y: 0)
    }

    override func windowDidLoad() {
        super.windowDidLoad()   
        self.window?.titleVisibility = NSWindowTitleVisibility.hidden //hide window’s title
        self.window?.styleMask = NSBorderlessWindowMask //hide window’s border
        self.window?.hasShadow = false //hide window’s shadow
        self.window?.level = Int(CGWindowLevelForKey(CGWindowLevelKey.desktopWindow)) //set window’s layer as desktopWindow layer
        self.window?.center()
        self.window?.makeKeyAndOrderFront(nil)
        NSApp.activate(ignoringOtherApps: true)
        if let screen = NSScreen.main() {
            self.window?.setFrame(screen.visibleFrame, display: true, animate: false) //resizing the window to cover the whole screen
        }
        resizeBG() //resizing the temporary background image and the playerView to the window’s size
        startVideo() //start playing and loop the first video as the window’s background  
    }

//function used for starting the video again once it has been played fully
    func playerItemDidReachEnd(notification: NSNotification) {
        playerView.player?.seek(to: kCMTimeZero)
        playerView.player?.play()
    }

//function used for starting and looping the video    
    func startVideo() {
        //set the seeking time to be 2ms ahead to prevent a black screen every time the video loops
        let playAhead = CMTimeMake(2, 100); 
            //loops the video
            NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object:
                playerView.player?.currentItem, queue: nil, using: { (_) in
                    DispatchQueue.main.async {

                        self.playerView.player?.seek(to: playAhead)
                        self.playerView.player?.play()
                    }
                })

        var playerLayer: AVPlayerLayer?
        playerLayer = AVPlayerLayer(player: player)
        playerView?.player = player
        print(playerView?.player)
        playerLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
        player.play()


    }


//changing the url to the new url and create a new AVPlayer then affect it to the playerView.player once the menu item is being selected
    @IBAction func renderBG(_ sender: NSMenuItem) {
        videoVariables.videoName = "Test_Video_2"
        var theNewURL = Bundle.main.url(forResource:videoVariables.videoName, withExtension: "mp4")
        player = AVPlayer.init(url: theNewURL!)

        //!!this line crashes the app with the error "thread 1 exc_bad_instruction (code=exc_i386_invop subcode=0x0)" every time the menu item is being selected!!
        playerView.player = player       
    }

}

此外,背景视频不应该是交互式的(例如,用户不能暂停/快进视频),因此可以忽略可能由用户交互引起的任何问题。该应用程序的目的是在用户的桌面上播放视频,以创建与 运行 命令完全相同的效果:

"/System/Library/Frameworks/ScreenSaver.framework/Resources/ ScreenSaverEngine。app/Contents/MacOS/ScreenSaverEngine - 终端中的背景“。

如有任何帮助,我们将不胜感激!

您不需要从 url 创建 AVPlayer。有AVPlayerItem class 来操作播放器播放队列。

let firstAsset = AVURLAsset(url: firstVideoUrl)
let firstPlayerItem = AVPlayerItem(asset: firstAsset)

let player = AVPlayer(playerItem: firstPlayerItem)

let secondAsset = AVURLAsset(url: secondVideoUrl)    
let secondPlayerItem = AVPlayerItem(asset: secondAsset)

player.replaceCurrentItem(with: secondPlayerItem)

关于AVPlayerItem

的文档