I am new to swift and I am trying to make a Mac OS app that loops a video from the app's resources using AVPlayer as the background of the window once the app has been launched. When the user selects a menu item/clicks a button the background video will instantly change to a different video from the app's resources and start looping that video as the window's background.
我是swift的新手,我正在尝试创建一个Mac OS应用程序,该应用程序在应用程序启动后使用AVPlayer作为窗口的背景来循环应用程序资源中的视频。当用户选择菜单项/单击按钮时,背景视频将立即从应用程序的资源更改为不同的视频,并开始将该视频作为窗口的背景循环。
I was able to play the first video once the app launches following this tutorial: (https://youtu.be/QgeQc587w70) and I also successfully made the video loop itself seamlessly following this post: (Looping AVPlayer seamlessly).
一旦应用程序按照本教程启动,我就可以播放第一个视频:(https://youtu.be/QgeQc587w70),我也成功地使视频循环本身无缝地跟随这篇文章:(无缝循环AVPlayer)。
The problem I am now facing is changing the video to the other one once a menu item was selected/a button was clicked. The approach I was going for is to change the url and create a new AVPlayer using the new URL and affect it to the playerView.player following this post: (Swift How to update url of video in AVPlayer when clicking on a button?). However every time the menu item is selected the app crashes with the error "thread 1 exc_bad_instruction (code=exc_i386_invop subcode=0x0)". This is apparently caused by the value of playerView being nil. I don't really understand the reason for this as playerView is an AVPlayerView object that I created using the xib file and linked to the swift file by control-dragging and I couldn't seem to find another appropriate method of doing the thing I wanted to do. If you know the reason for this and the way of fixing it please provide me some help or if you know a better method of doing what I've mention above please tell me as well. Any help would be much appreciated!
我现在面临的问题是,一旦选择了菜单项/点击了一个按钮,就将视频更改为另一个。我想要的方法是更改网址并使用新网址创建一个新的AVPlayer,并在此帖子后将其影响到playerView.player :( Swift如何在单击按钮时更新AVPlayer中的视频网址?)。但是,每次选择菜单项时,应用程序都会崩溃并显示错误“thread 1 exc_bad_instruction(code = exc_i386_invop subcode = 0x0)”。这显然是由playerView的值为零引起的。我真的不明白这个的原因,因为playerView是一个AVPlayerView对象,我使用xib文件创建并通过控制拖动链接到swift文件,我似乎无法找到另一种合适的方法来做我想要的事情去做。如果你知道这个的原因和解决方法,请给我一些帮助,或者如果你知道一个更好的方法做我上面提到的,请告诉我。任何帮助将非常感激!
My code is as follow, the line that crashes the app is at the bottom:
我的代码如下,崩溃应用程序的行位于底部:
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
}
}
Additionally, the background video is not supposed to be interactive(E.g. User cannot pause/ fast-forward the video), so any issues that might be caused by user interactivity can be ignored. The purpose of the app is to play a video on the user's desktop creating the exact same effect of running the command:
此外,背景视频不应该是交互式的(例如,用户不能暂停/快进视频),因此可以忽略可能由用户交互性引起的任何问题。该应用程序的目的是在用户的桌面上播放视频,创建与运行命令完全相同的效果:
"/System/Library/Frameworks/ScreenSaver.framework/Resources/ ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine -background" in terminal.
终端中的“/System/Library/Frameworks/ScreenSaver.framework/Resources/ ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine -background”。
Any help would be much appreciated!
任何帮助将非常感激!
1 个解决方案
#1
1
You don't need to create AVPlayer from url. There is AVPlayerItem
class to manipulate player playback queue.
您不需要从URL创建AVPlayer。有AVPlayerItem类来操纵播放器播放队列。
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)
Docs about AVPlayerItem
有关AVPlayerItem的文档
#1
1
You don't need to create AVPlayer from url. There is AVPlayerItem
class to manipulate player playback queue.
您不需要从URL创建AVPlayer。有AVPlayerItem类来操纵播放器播放队列。
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)
Docs about AVPlayerItem
有关AVPlayerItem的文档