Thank you Claude31 and Quinn! That works.
Post
Replies
Boosts
Views
Activity
ah... figuring some stuff out thank to this SO post:
https://stackoverflow.com/questions/47127618/avplayer-wont-let-me-access-url
AHA! Here is the answer... must use "let fileURL = URL(fileURLWithPath: "myPath")... the below code works for Swift 5.3 and uses a button to allow user to choose internal video. See above SO link for things you need to do to play external video.
import Cocoa
import AVKit
import AVFoundation
class ViewController: NSViewController {
@IBOutlet weak var playerView: AVPlayerView!
@IBOutlet weak var chooseVideoButton: NSButton!
var path: String = ""
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func chooseVideoPressed(_ sender: NSButton)
{
let dialog = NSOpenPanel();
dialog.title = "Choose a video";
dialog.showsResizeIndicator = true;
dialog.showsHiddenFiles = false;
dialog.allowsMultipleSelection = false;
dialog.canChooseDirectories = false;
if (dialog.runModal() == NSApplication.ModalResponse.OK) {
let result = dialog.url // Pathname of the file
if (result != nil) {
path = result!.path
}
//let fileURL = URL(string: path)
let fileURL = URL(fileURLWithPath: path)
let avAsset = AVURLAsset(url: fileURL, options: nil)
let playerItem = AVPlayerItem(asset: avAsset)
let videoPlayer = AVPlayer(playerItem: playerItem)
playerView.player = videoPlayer
videoPlayer.play()
} else {
// User clicked on "Cancel"
return
}
}
}
Thank you so much for your reply maven. That worked. I'm relatively new to development and I kind of find it sad and a bit shocking that a function like that can be broken. Although based on the documentation, I never had much hope for .status as it seems to only have three possible states... readyToPlay, failed, and unknown. Doesn't seem like it's possible to know if the video is playing, or paused, or being fast forwarded, etc via the .status keyPath. But rate is just lovely.
I implemented it by adding the observer in my video file open panel modal:
avPlayer?.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil)
and then a func in my viewController.swift file:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "rate" {
if avPlayer?.rate ?? 0 > 0 {
print("video is playing. \(avPlayer?.rate as Any)" )
} else {
print("video is NOT playing.")
}
}
}
OK, after bashing my head against this for 24 solid hours, I have figured it out. The whole protocol / delegate relationship between the main VC and the popover is pretty confusing. The best explanation handy to me was from Matt Neuburg's book Programming Fundamentals with Swift. Here is how I configured my main VC and my popover. Because my popover instantiates from a menuItem, which isn't part of the application scene (?), I cannot drag an IBAction into my VC code. I create an @IBAction in code in my main VC, and then control drag from the menuItem to the first responder cube above the menu bar in the storyboard, then choose the created IBAction from the massive list. Miraculously, that works. SO...
POPOVER CODE:
protocol ReturnFromPopoverDelegate: class
{
func setStartTC(timecode: Timecode)
}
class EnterStartTCPopoverVC: NSViewController
{
weak var delegate: ReturnFromPopoverDelegate?
func controlTextDidEndEditing(_ notification: Notification) {
self.delegate?.setStartTC(timecode: tempTC)
dismiss(self)
}
}
MAIN VC CODE:
class ViewController: NSViewController, ReturnFromPopoverDelegate
{
@IBAction func enterStartTCMenuItemSelected(_ sender: NSMenuItem) {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateController(withIdentifier: "EnterStartTCPopoverVC") as! EnterStartTCPopoverVC
viewController.delegate = self
presentAsModalWindow(viewController)
}
	var startTC: Timecode()
func setStartTC(timecode: Timecode) {
startTC = timecode
printTC(timecode: tempTC) //printTC is a special func of my Timecode class
}
}
Completely mind boggling... I hope this helps you return some data from your alt viewController. Keep at it. :}
Ah, figured it out. I was close. You do subclass AVPlayerView. You must include this bit of code:
override var acceptsFirstResponder: Bool {
get {
return true
}
}
and the big one is you then have to set in the storyboard the class in the identity inspector to this new custom class. Then... code such as...
override func keyDown(with event: NSEvent) {
if self.superview != nil
{
if event.keyCode == 124
{
print("right arrow")
}
}
}
works! :]
It does not print anything. I tried changing it to...
var customPlayerView = CustomAVPlayerView()
override func viewDidLoad() {
super.viewDidLoad()
print(customPlayerView)
customPlayerView.keyboardDelegate = self
}
also prints nothing. But how is "var customPlayerView = CustomAVPlayerView()" not instantiating the class?
Hmm. You are right that viewDidLoad() is not being called. I don't know why. I did change the identity / class of my playerView to my customAVPlayerView class ... but the View Controller still has the class of "ViewController." I don't know how I can show you my storyboard... I don't see an add photo function here.
I just created the project the normal way... xcode 12.3 on catalina 10.15.7. I just opened another app I made the other day and the view does load there. The only thing I can think of is that I have two classes in the same file. My ViewController swift file has a View Controller class, and I'm also subclassing AVPlayerView in the same file, as my code shows. I don't know if that's the right or wrong way to do it. I'm not using any old tutorials.
I just removed the code that subclassed AVPlayerView, and set the identity inspector back to nothing. Now my view IS loading. So it's definitely the fact that I'm subclassing AVPlayerView.
I moved all the subclassing code to another file. Now when I ask in viewDidLoad to print(customPlayerView) I get...
<Player01.CustomAVPlayerView: 0x10100ee00>
!!! We're getting close !!!
print(customPlayerView.keyboardDelegate) is also printing Optional(<Player01.ViewController: 0x100631bf0>), so that seems to be instantiated at least.
However... data is still not being passed to my delegate function in VC. I inserted this line in my keyDown function...
print("from CPV: \(self.keyboardDelegate as Any)")
and it is printing nil. So even though my class is being instantiated, and the delegate set on the VC side... do I have to init the delegate in my custom class?
AH! Okay, problem solved! I still had the old IBOutlet from the original AVPlayerView before I subclassed it. So now I have removed that link, and instead hooked up my customAVPlayerView, and using that as the reference I am able to pass data (via protocol / delegate) to my VC. THANK YOU!!!!
I somehow missed the chapter on all the ways one can pass data between two classes. I kind of find the protocol / delegate thing to be a bit of a PITA. Maybe there are better and easier ways to do this?
Thank you again!
Well, after wrangling with this Document file for a couple days, I've come to the conclusion that the best approach is to toss the thing out, and just write my own code for open, save, save as... etc. functions.
Plus 1 for Apple trying to make things easier, but instead making them damn near impossible. By "hiding" the code in a document based app that does the actual write(to: and Data(contents of:, it makes it impossible to tweak the code to work for your particular application. Not everyone wants to create another run of the mill document app. But if you can't see how the file is getting saved, how the URL is being pulled from the savePanel, how the data is being loaded from the file... you can't alter the code to suit your purposes. Luckily I was able to hodgepodge something together from a variety of internet tutorials, written by people who haven't completely given up on macOS... :[
I think I have figured it out. In the stock out of the box macOS app, when you close the window... it gets deallocated. So you can't create a "new" one. I started reading the Window Programming Guide... so I'm slowly figuring it out. Thank you!!!
Ah I figured it out. I had subclassed AVPlayerView because I didn't like the playback controls, was using keyDown to capture events. However, when you click in a textField, Field Editor takes over and overrides the keyboard with its own behaviors, so anything that you assign via keyDown does nothing. I'd still like to find a way to navigate how to trap that Field Editor key data and re-route it... but so little information on how you would do that. There is some information in the Cocoa Text Architecture Guide on "Intercepting Key Events," ... looks maybe like doCommand or doCommandBySelector but zero context on what to call those on, where they would go, etc. Went with capturing a mouseDown event, that's working for me! Cheers...
In case anyone else is struggling with getting "open recent" to work in a non-document based app... this may help.
You have likely written your own 'open file' function, probably in your ViewController... somewhere in there drop this line:
NSDocumentController.shared.noteNewRecentDocumentURL(myFileURL)
where myFileURL is an unwrapped URL (not NSURL, and definitely not a string). This line instantiates the document controller and should get your URL (now magically shortened to just the file name!) to the open recent menu. You do not need to instantiate the document controller elsewhere, this line does that.
Now that the menu is being fed URLs from your app, you need to create the logic to open a file when the user selects a file to open. In your AppDelegate, create this function:
func application(_ sender: NSApplication, openFile filename: String) -> Bool {
let url = URL(fileURLWithPath: filename)
if let controller = NSApplication.shared.mainWindow?.contentViewController as? ViewController {
let result = controller.openFunction(url: url)
return result
} else {
print("could not instantiate controller object in AppDelegate.")
return false
}
}
Notice: the URL is being returned as a string. The next line turns it back into a URL, and even handles white space, which the usual let url = URL(string: filename) does not. In the next line I will instantiate a handle to my ViewController class, if your file opening logic is elsewhere, instantiate accordingly. The next line calls my 'openFunction' function in my viewController, which takes a URL and returns a bool related to the success of opening the file. This is now functional in my app, hopefully it will work in your own.