Re: AVSimplePlayer in Swift?
Re: AVSimplePlayer in Swift?
- Subject: Re: AVSimplePlayer in Swift?
- From: Charles Jenkins <email@hidden>
- Date: Thu, 12 Jan 2017 18:42:25 -0500
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 <email@hidden>
wrote:
> On Jan 8, 2017, at 1:12 PM, Quincey Morris <quinceymorris@
> rivergatesoftware.com> wrote:
>
>
> On Jan 8, 2017, at 05:49 , Charles Jenkins <email@hidden> 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 (email@hidden)
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:
This email sent to email@hidden