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 )
}
}
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.
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
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-***@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/gegs%40ml-in.narkive.net
This email sent to