Here is the code I promised to post. This version is using didSet instead of KVO. In both cases, if I imitate the way AVSimplePlayer seeks in the setter for currentTime, the video won’t play.
// // ViewController.swift import Cocoa import AVFoundation // Swift offers the #keyPath() directive to prevent errors due to typos; // unfortunately it doesn't always work. So I will forget it and make my // own symbols, whose names must agree with the names of AVFoundation // object properties in the ViewController fileprivate struct KeyPath { static let playerCurrentItem = "player.currentItem" static let playerRate = "player.rate" static let playerStatus = "player.currentItem.status" static let playerVolume = "player.volume" static let playerLayerReady = "playerLayer.readyForDisplay" } fileprivate var statusContext = "Status" fileprivate var rateContext = "Rate" fileprivate var readyContext = "Ready" fileprivate let playStr = "PLAY" fileprivate let pauseStr = "PAUSE" fileprivate let sampleVideoLocation = "file:///Users/Charles/Desktop/Samples/David.m4v" class ViewController: NSViewController { @IBOutlet weak var workspace: NSBox! @IBOutlet weak var timePassed: NSTextField! @IBOutlet weak var timeRemaining: NSTextField! @IBOutlet weak var timeSlider: NSSlider! @IBOutlet weak var folderImage: NSImageView! @IBOutlet weak var volumeSlider: NSSlider! @IBOutlet weak var pausePlayButton: NSButton! @IBOutlet weak var rewindButton: NSButton! @IBOutlet weak var fastForwardButton: NSButton! var timeObserverToken: Any? static let keyPathsForValuesAffectingDuration = Set<String>( [ KeyPath.playerCurrentItem, KeyPath.playerStatus ] ) static let keyPathsForValuesAffectingVolume = Set<String>( [ KeyPath.playerVolume ] ) let player = AVPlayer() var playerLayer : AVPlayerLayer? var volume: Float = 0 { didSet { player.volume = volume volumeSlider.doubleValue = Double( volume ) } } var currentTime : Double = 0 { didSet { let seconds = currentTime /* // Uncomment these lines to prevent the video from playing NSLog( "Seek to \(seconds) seconds" ) let time = CMTimeMakeWithSeconds( seconds, 1 ) if player.currentTime() != time { player.seek( to: time ) } */ timeSlider.doubleValue = seconds } } var duration: Double = 0 { didSet { timeSlider.maxValue = duration } } override func viewDidLoad() { super.viewDidLoad() timePassed.stringValue = "" timeRemaining.stringValue = "" volumeSlider.maxValue = 1.0 volume = 1.0 // Add status and rate observers addObserver( self, forKeyPath: KeyPath.playerRate, options: .new, context: &rateContext ) addObserver( self, forKeyPath: KeyPath.playerStatus, options: .new, context: &statusContext ) pausePlayButton.title = playStr // Create an asset with our URL, // asynchronously load its tracks, // and determine whether it's playable let url = URL( string: sampleVideoLocation ) play( url: url! ) } private func play( url: URL ) { let keys = [ "playable", "hasProtectedContent", "tracks" ] let asset = AVAsset( url: url ) asset.loadValuesAsynchronously( forKeys: keys ) { DispatchQueue.main.async { self.play( asset: asset, keys: keys ) } } } private func play( asset: AVAsset, keys: [String] ) { if asset.isPlayable == false { NSLog( "Unplayable media." ) return } if asset.hasProtectedContent == true { NSLog( "Protected content--this program won't be allowed to play it." ) return } let tracks = asset.tracks( withMediaType: AVMediaTypeVideo ) if tracks.count < 1 { NSLog( "No video tracks found." ) return } // Create an AVPlayerLayer and add it to the // player view if there is video, but hide it // until it is ready for display let playerLayer = AVPlayerLayer( player: player ) if let workspaceLayer = workspace.layer { playerLayer.frame = workspaceLayer.bounds playerLayer.autoresizingMask = [ .layerWidthSizable, .layerHeightSizable ] playerLayer.isHidden = true workspaceLayer.addSublayer( playerLayer ) self.playerLayer = playerLayer addObserver( self, forKeyPath: KeyPath.playerLayerReady, options: [ .initial, .new ], context: &readyContext ) // Create new AVPlayerItem and make it our // player's current tiem let item = AVPlayerItem( asset: asset ) player.replaceCurrentItem( with: item ) timeObserverToken = player.addPeriodicTimeObserver( forInterval: CMTimeMake( 1, 10 ), queue: DispatchQueue.main ) { [weak self] ( time: CMTime ) -> () in let seconds = CMTimeGetSeconds( time ) NSLog( "Periodic time observer asking to set time to \(seconds) seconds" ) if let weakSelf = self { weakSelf.currentTime = seconds } } } } override func observeValue( forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer? ) { if context == &statusContext { // Status changed if let change = change, let statusInt = ( change[ NSKeyValueChangeKey.newKey ] as AnyObject ).integerValue { let status = AVPlayerStatus( rawValue: statusInt ) let enable = ( status == AVPlayerStatus.readyToPlay ) pausePlayButton.isEnabled = enable fastForwardButton.isEnabled = enable rewindButton.isEnabled = enable } } else if context == &rateContext { // Rate changed if let change = change, let rate = ( change[ NSKeyValueChangeKey.newKey ] as AnyObject ).floatValue { let title = ( rate == 1.0 ) ? pauseStr : playStr pausePlayButton.title = title } } else if context == &readyContext { // Ready condition changed playerLayer?.isHidden = false if let item = player.currentItem { self.duration = item.duration.seconds self.currentTime = 0 } } else { // We don't handle this change, but our // parent class might super.observeValue( forKeyPath: keyPath, of: object, change: change, context: context ) } } func close() { player.pause() if let timeObserverToken = timeObserverToken { player.removeTimeObserver( timeObserverToken ) } removeObserver( self, forKeyPath: KeyPath.playerRate ) removeObserver( self, forKeyPath: KeyPath.playerStatus ) if playerLayer != nil { removeObserver( self, forKeyPath: KeyPath.playerLayerReady ) } } func adjustSpeed( step: Float ) { let rate = player.rate if ( step < 0 && rate > step ) || ( step > 0 && rate < step ) { player.rate = step } else { player.rate = rate + step } } @IBAction func rewind( _ sender: Any ) { adjustSpeed( step: -2.0 ) } @IBAction func togglePausePlay( _ sender: Any ) { if player.rate != 1.0 { player.rate = 1.0 if currentTime >= duration { currentTime = 0.0 } player.play() } else { player.pause() } } @IBAction func fastForward( _ sender: Any ) { adjustSpeed( step: 2.0 ) } } On Sun, Jan 8, 2017 at 5:13 PM, Charles Srstka <cocoa...@charlessoft.com> wrote: > On Jan 8, 2017, at 1:12 PM, Quincey Morris <quinceymorris@ > rivergatesoftware.com> wrote: > > > On Jan 8, 2017, at 05:49 , Charles Jenkins <cejw...@gmail.com> wrote: > > > changing to CDouble didn’t help > > > This is one of those cases where I regretted pressing “Send” just 2 > minutes later. What I wrote was a thought process that didn’t make complete > sense. > > There are 4 possibilities and you’ll need to narrow it down to one: > > 1. The property type is incorrect. You could try changing it to an > explicit NSNumber, which is the type that the binding actually requires. > > > This is not the problem; KVO works just fine with a property typed as > Double as long as the property is marked ‘dynamic', which my sample code > demonstrates (for some reason, the mailing list software separated the > “.zip” from the rest of the link, so just add “.zip” to the end manually to > download it). > > 2. The property accessors are not using the correct calling convention > (@objc). If they’re in a view controller subclass (which is an @objc) > object, they’ll normally be @objc, but there are some situations (e.g. > declaring them private) that may make them native Swift. Leaving off the > “dynamic” would come under this case too, but this was covered already. > > > 3. The property declaration is fine, but IB is broken and doesn’t > recognize the property as compatible. It may simply fail to set up the > binding properly, even though it would work if it did. You could try > leaving it unbound, and set up the binding at run time. > > 4. The property is fine, and something else is wrong. > > Finally, I’d note that the discussion in this thread had jumped back and > forth between bindings and KVO. I’ve lost track of whether you’re saying > that KVO isn’t working here (Charles posted sample code that he said > works), or whether bindings aren’t working here. > > > I think that seeing a simplified version of the code that isn’t working > would be the easiest way to debug this at this point. OP, would you mind > posting it? > > Charles > > -- Charles _______________________________________________ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com