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

Reply via email to