Discussion:
AVSimplePlayer in Swift?
Charles Jenkins
2017-01-07 03:32:13 UTC
Permalink
Has anyone recoded Apple's AVSimplePlayer example in Swift?

I’m trying to do that in order to get some AVFoundation experience in
Swift, and I’m having a bit of trouble. I can’t figure out how to do the
bindings to currentTime and duration.


- If I do them the easy way, using didSet clause, everything works and I
can play the video and use the Rewind and Fast Forward buttons to alter
playback rate, but I can’t scrub using the time slider.



- If I imitate the ObjC version and try to do the bindings in IB, the
time slider’s maxValue and value bindings don’t work (won’t compile)
because duration and currentTime are not NSNumbers.



- And if I change duration and currentTime to computed variables based
on NSNumber, the video won’t play at all.


Any suggestions?
--
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 t
Charles Srstka
2017-01-07 03:45:03 UTC
Permalink
Post by Charles Jenkins
- If I imitate the ObjC version and try to do the bindings in IB, the
time slider’s maxValue and value bindings don’t work (won’t compile)
because duration and currentTime are not NSNumbers.
They don’t have to be NSNumbers; KVO automatically wraps primitive number types in NSNumbers for you. What do you mean by “won’t compile”? IB and KVO all occur at runtime, not compile-time.

Anyway, the thing to make sure about is that you’ve declared all KVO-compliant properties with the ‘dynamic’ keyword. If you haven’t, that could easily account for things not working properly.

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 ***@ml-in.narki
Charles Jenkins
2017-01-07 21:24:36 UTC
Permalink
I did try after you asked, and KVO may be working, but the video doesn’t
play. If I delete the KVO bindings from IB and change my code like this:

var currentTime : Double = 0 {
didSet {
timeSlider.doubleValue = currentTime
}
}

var duration: Double = 0
{
didSet {
timeSlider.maxValue = duration
}
}

The video will play and all the buttons will work right. Seriously,
*everything* works except I can’t scrub and I haven’t bothered yet to test
the volume slider.

If I take that working code and the ONLY changes I make are to (a) change
currentTime to the code below and (b) add the binding in IB, the video will
not play. The strange thing is when I do this, I think KVO is actually
working because I get hammered with log messages saying “Seek to 0.0
seconds.”

dynamic var currentTime : Double {
get {
return CMTimeGetSeconds( player.currentTime() )
}
set ( seconds ) {
NSLog( "Seek to \(seconds) seconds" )
let time = CMTimeMakeWithSeconds( seconds, 1 )
player.seek( to: time )
}
}

I thought maybe this meant my periodic time observer was messed up, so I
replaced my equivalent of the ObjC code with this:

myTimeObserver = player.addPeriodicTimeObserver(
forInterval: CMTimeMake( 1, 10 ),
queue: DispatchQueue.main
) {
[weak self] ( time: CMTime ) -> () in
if let weakSelf = self {
let seconds = weakSelf.currentTime // Force a read from player
NSLog( "Asking to set time to \(seconds) seconds" )
weakSelf.currentTime = seconds // Force KVO update
}
}

And the resulting log messages prove, I think, that player.currentTime()
really does sit at 0.0 while I’m using KVO for the slider.
Charles,
Thank you for the reply. “Won’t compile” was incorrect shorthand for a
In the AVSimplePlayer demo, the timeSlider has two bindings: Value is
bound to File’s Owner.currentTime and MaxValue is bound to File’s
Owner.duration. These are double properties in ObjC. Here is an example of
- (double)currentTime
{
return CMTimeGetSeconds(self.player.currentTime);
}
- (void)setCurrentTime:(double)time
{
[self.player seekToTime:CMTimeMakeWithSeconds(time, 1)
toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
}
In my project using Swift, I haven’t found a way to set File’s Owner in
IB, so I choose my binding object to be “View Controller,” because Xcode
automatically generated my view controller class with the name
ViewController. I have the currentTime and duration properties in my Swift
file, so I try to make my timeSlider mimic Apple’s two bindings: Value is
bound to ViewController.currentTime and MaxValue is bound to
ViewController.duration. I know I haven’t mistyped them because when I
clear the Model Key Path boxes, I can use a pulldown to select the
properties. But when I select them, a red stop sign appears in each Model
Key Path box, and when I hover over it I see this message like this: “The
Value binding expects to be bound to an object of type NSNumber, but
currentTime is of type Double.”
dynamic var currentTime: Double {
get {
return CMTimeGetSeconds( player.currentTime() )
}
set ( seconds ) {
let time = CMTimeMakeWithSeconds( seconds, 1 )
player.seek( to: time )
}
}
To be clear, my ViewController object is an NSViewController subclass, so
I think NSObject conformance is a given. What else could be wrong?
Have you just tried compiling and running it and seeing if it works? I’ve
found that little stop sign in the Model Key Path box to produce false
positives from time to time, and this sounds like one. The KVO system wraps
primitive types in objects, and for Double, that’ll be NSNumber. As long as
your properties are dynamic, it should work.
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
Charles Srstka
2017-01-07 22:01:36 UTC
Permalink
Post by Charles Jenkins
var currentTime : Double = 0 {
didSet {
timeSlider.doubleValue = currentTime
}
}
var duration: Double = 0
{
didSet {
timeSlider.maxValue = duration
}
}
The video will play and all the buttons will work right. Seriously, *everything* works except I can’t scrub and I haven’t bothered yet to test the volume slider.
If I take that working code and the ONLY changes I make are to (a) change currentTime to the code below and (b) add the binding in IB, the video will not play. The strange thing is when I do this, I think KVO is actually working because I get hammered with log messages saying “Seek to 0.0 seconds.”
dynamic var currentTime : Double {
get {
return CMTimeGetSeconds( player.currentTime() )
}
set ( seconds ) {
NSLog( "Seek to \(seconds) seconds" )
let time = CMTimeMakeWithSeconds( seconds, 1 )
player.seek( to: time )
}
}
myTimeObserver = player.addPeriodicTimeObserver(
forInterval: CMTimeMake( 1, 10 ),
queue: DispatchQueue.main
) {
[weak self] ( time: CMTime ) -> () in
if let weakSelf = self {
let seconds = weakSelf.currentTime // Force a read from player
NSLog( "Asking to set time to \(seconds) seconds" )
weakSelf.currentTime = seconds // Force KVO update
}
}
And the resulting log messages prove, I think, that player.currentTime() really does sit at 0.0 while I’m using KVO for the slider.
That’s odd that the currentTime variable would stay at 0.0. Have you checked “Continuous” for your slider in IB?

If it helps, I made a simple example project connecting a slider and Swift via KVO. You can drag the slider around and watch the image update as you do:

http://www.charlessoft.com/extraneous_stuff/Slider_Test <http://www.charlessoft.com/extraneous_stuff/Slider_Test>.zip

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-
Quincey Morris
2017-01-08 09:33:28 UTC
Permalink
Post by Charles Jenkins
If I take that working code and the ONLY changes I make are to (a) change
currentTime to the code below and (b) add the binding in IB, the video will
not play. The strange thing is when I do this, I think KVO is actually
working because I get hammered with log messages saying “Seek to 0.0
seconds.”
dynamic var currentTime : Double {
Is this issue resolved?

If not, it seems to me there’s a problem that “Double” is a Swift type, and it’s not the same as the Obj-C “double” type. AFAIK, these 2 types are never identical, although there is bridging at the SDK and API levels. However, I doubt that KVC knows about the Swift types, so it’s not going to do the automatic translation to NSNumber that happens in the Obj-C version of your code.

If I’m right, then solution might be to declare your properties as type “CDouble”, which *is* the same as the C (and hence Obj-C) “double” type.

_______________________________________________

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 ema
Quincey Morris
2017-01-08 19:12:35 UTC
Permalink
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.

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.

_______________________________________________

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 ***@ml-in.n
Charles Srstka
2017-01-08 22:13:27 UTC
Permalink
Post by Quincey Morris
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).
Post by Quincey Morris
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

_______________________________________________

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 gegs
Charles Jenkins
2017-01-09 12:58:50 UTC
Permalink
I will post code later today. In the meantime, I think I know what’s
happening, but not yet why. In short, the IB message is worthless, and KVO
is actually working. Ironically, proved that to myself by ditching it
altogether and switching currentTime back to using a local (non-computed)
property and didSet, but this time, in didSet I imitated the code in the
ObjC setCurrentTime() a little more closely.

What I found is if I comment out my call to player.seek(), the video plays
through and all the controls work (except of course no scrubbing). The
count of seconds reported by the periodic time observer (hereafter, PTO)
always increases.

But if I uncomment the player.seek() line, the video seems to be frozen on
the first frame and all the PTO requests are to seek back to 0.0.

I think what happens is, the video actually is playing, but the PTO checks
when it’s at 0.0, the player internally moves on, and then my KVO or didSet
requests a seek back to 0.0. And the process repeats ad infinitum. The PTO
is always a tiny bit behind where the video actually is, so it just keeps
requesting seeks backward.

It appears that if my setter for currentTime does the same thing as the
ObjC one, I’m toast.
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.narkiv
Charles Jenkins
2017-01-12 23:42:25 UTC
Permalink
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
Uli Kusterer
2017-02-11 13:19:27 UTC
Permalink
Post by Charles Jenkins
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.
Just a thought: Does AVSimplePlayer[ObjC] actually call the getters and setters, or does it assign directly to the ivars? I don't have access to the code right now.

In the latter case, there would be no KVO notification, whereas in your case didSet always runs. Also, AFAIR KVO or at least bindings have loop prevention. So if you're triggering a notification on a particular object/property and while that is being handled in -observeValueForKeyPath:..., it will not send a second notification.

Did you move code from the -observeValueForKeyPath:... into didSet?

Cheers,
-- Uli Kusterer
"The Witnesses of TeachText are everywhere..."
http://www.zathras.de


_______________________________________________

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 ***@ml-

Loading...