Add a sample plugin to demonstrate the mute plugin API.
authoradachan@apple.com <adachan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 4 Dec 2014 06:42:38 +0000 (06:42 +0000)
committeradachan@apple.com <adachan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 4 Dec 2014 06:42:38 +0000 (06:42 +0000)
https://bugs.webkit.org/show_bug.cgi?id=139231

Reviewed by Anders Carlsson.

* NetscapeMuteAPIPlugin/AudioPlayer.h: Added.
* NetscapeMuteAPIPlugin/AudioPlayer.mm: Added.
(-[AudioPlayer initWithURL:]):
(-[AudioPlayer dealloc]):
(-[AudioPlayer isReadyToPlay]):
(-[AudioPlayer isPlaying]):
(-[AudioPlayer setPlaying:]):
(-[AudioPlayer isMuted]):
(-[AudioPlayer setMuted:]):
(-[AudioPlayer observeValueForKeyPath:ofObject:change:context:]):
* NetscapeMuteAPIPlugin/English.lproj/InfoPlist.strings: Added.
* NetscapeMuteAPIPlugin/Info.plist: Added.
* NetscapeMuteAPIPlugin/MenuHandler.h: Added.
* NetscapeMuteAPIPlugin/MenuHandler.m: Added.
(-[MenuHandler _play:]):
(-[MenuHandler _pause:]):
(-[MenuHandler validateUserInterfaceItem:]):
(-[MenuHandler initWithAudioPlayer:]):
(-[MenuHandler dealloc]):
(-[MenuHandler menu]):
* NetscapeMuteAPIPlugin/NetscapeMuteAPIPlugin.xcodeproj/project.pbxproj: Added.
* NetscapeMuteAPIPlugin/main.m: Added.
(-[PluginObject initWithNPP:audioURL:]):
(-[PluginObject dealloc]):
(-[PluginObject menuHandler]):
(-[PluginObject isPlayingAudio]):
(-[PluginObject isMuted]):
(-[PluginObject setMuted:]):
(-[PluginObject readyStateDidChangeForAudioPlayer:]):
(-[PluginObject mutedStateDidChangeForAudioPlayer:]):
(-[PluginObject playStateDidChangeForAudioPlayer:]):
(-[PluginObject _invalidateDisplayString]):
(NP_Initialize):
(NP_GetEntryPoints):
(NP_Shutdown):
(NPP_New):
(NPP_Destroy):
(NPP_SetWindow):
(NPP_NewStream):
(NPP_DestroyStream):
(NPP_WriteReady):
(NPP_Write):
(NPP_StreamAsFile):
(NPP_Print):
(handleDraw):
(invalidatePlugin):
(handleMouseEvent):
(NPP_HandleEvent):
(NPP_URLNotify):
(NPP_GetValue):
(NPP_SetValue):
* NetscapeMuteAPIPlugin/test.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@176785 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Examples/ChangeLog
Examples/NetscapeMuteAPIPlugin/AudioPlayer.h [new file with mode: 0644]
Examples/NetscapeMuteAPIPlugin/AudioPlayer.mm [new file with mode: 0644]
Examples/NetscapeMuteAPIPlugin/English.lproj/InfoPlist.strings [new file with mode: 0644]
Examples/NetscapeMuteAPIPlugin/Info.plist [new file with mode: 0644]
Examples/NetscapeMuteAPIPlugin/MenuHandler.h [new file with mode: 0644]
Examples/NetscapeMuteAPIPlugin/MenuHandler.m [new file with mode: 0644]
Examples/NetscapeMuteAPIPlugin/NetscapeMuteAPIPlugin.xcodeproj/project.pbxproj [new file with mode: 0644]
Examples/NetscapeMuteAPIPlugin/main.m [new file with mode: 0644]
Examples/NetscapeMuteAPIPlugin/test.html [new file with mode: 0644]

index 58055e3..4cdeb7f 100644 (file)
@@ -1,3 +1,63 @@
+2014-12-03  Ada Chan  <adachan@apple.com>
+
+        Add a sample plugin to demonstrate the mute plugin API.
+        https://bugs.webkit.org/show_bug.cgi?id=139231
+
+        Reviewed by Anders Carlsson.
+
+        * NetscapeMuteAPIPlugin/AudioPlayer.h: Added.
+        * NetscapeMuteAPIPlugin/AudioPlayer.mm: Added.
+        (-[AudioPlayer initWithURL:]):
+        (-[AudioPlayer dealloc]):
+        (-[AudioPlayer isReadyToPlay]):
+        (-[AudioPlayer isPlaying]):
+        (-[AudioPlayer setPlaying:]):
+        (-[AudioPlayer isMuted]):
+        (-[AudioPlayer setMuted:]):
+        (-[AudioPlayer observeValueForKeyPath:ofObject:change:context:]):
+        * NetscapeMuteAPIPlugin/English.lproj/InfoPlist.strings: Added.
+        * NetscapeMuteAPIPlugin/Info.plist: Added.
+        * NetscapeMuteAPIPlugin/MenuHandler.h: Added.
+        * NetscapeMuteAPIPlugin/MenuHandler.m: Added.
+        (-[MenuHandler _play:]):
+        (-[MenuHandler _pause:]):
+        (-[MenuHandler validateUserInterfaceItem:]):
+        (-[MenuHandler initWithAudioPlayer:]):
+        (-[MenuHandler dealloc]):
+        (-[MenuHandler menu]):
+        * NetscapeMuteAPIPlugin/NetscapeMuteAPIPlugin.xcodeproj/project.pbxproj: Added.
+        * NetscapeMuteAPIPlugin/main.m: Added.
+        (-[PluginObject initWithNPP:audioURL:]):
+        (-[PluginObject dealloc]):
+        (-[PluginObject menuHandler]):
+        (-[PluginObject isPlayingAudio]):
+        (-[PluginObject isMuted]):
+        (-[PluginObject setMuted:]):
+        (-[PluginObject readyStateDidChangeForAudioPlayer:]):
+        (-[PluginObject mutedStateDidChangeForAudioPlayer:]):
+        (-[PluginObject playStateDidChangeForAudioPlayer:]):
+        (-[PluginObject _invalidateDisplayString]):
+        (NP_Initialize):
+        (NP_GetEntryPoints):
+        (NP_Shutdown):
+        (NPP_New):
+        (NPP_Destroy):
+        (NPP_SetWindow):
+        (NPP_NewStream):
+        (NPP_DestroyStream):
+        (NPP_WriteReady):
+        (NPP_Write):
+        (NPP_StreamAsFile):
+        (NPP_Print):
+        (handleDraw):
+        (invalidatePlugin):
+        (handleMouseEvent):
+        (NPP_HandleEvent):
+        (NPP_URLNotify):
+        (NPP_GetValue):
+        (NPP_SetValue):
+        * NetscapeMuteAPIPlugin/test.html: Added.
+
 2014-03-14  Maciej Stachowiak  <mjs@apple.com>
 
         Replace "Apple Computer, Inc." with "Apple Inc." in copyright headers
diff --git a/Examples/NetscapeMuteAPIPlugin/AudioPlayer.h b/Examples/NetscapeMuteAPIPlugin/AudioPlayer.h
new file mode 100644 (file)
index 0000000..e8dd6bc
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ IMPORTANT:  This Apple software is supplied to you by Apple Inc. ("Apple") in
+ consideration of your agreement to the following terms, and your use, installation,
+ modification or redistribution of this Apple software constitutes acceptance of these
+ terms.  If you do not agree with these terms, please do not use, install, modify or
+ redistribute this Apple software.
+
+ In consideration of your agreement to abide by the following terms, and subject to these
+ terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in
+ this original Apple software (the "Apple Software"), to use, reproduce, modify and
+ redistribute the Apple Software, with or without modifications, in source and/or binary
+ forms; provided that if you redistribute the Apple Software in its entirety and without
+ modifications, you must retain this notice and the following text and disclaimers in all
+ such redistributions of the Apple Software.  Neither the name, trademarks, service marks
+ or logos of Apple Inc. may be used to endorse or promote products derived from
+ the Apple Software without specific prior written permission from Apple. Except as expressly
+ stated in this notice, no other rights or licenses, express or implied, are granted by Apple
+ herein, including but not limited to any patent rights that may be infringed by your
+ derivative works or by other works in which the Apple Software may be incorporated.
+
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES,
+ EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS
+ USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
+ REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND
+ WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
+ OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <WebKit/npfunctions.h>
+
+@class AVPlayer;
+@class AVPlayerItem;
+@class AudioPlayer;
+
+@protocol AudioPlayerDelegate <NSObject>
+
+- (void)readyStateDidChangeForAudioPlayer:(AudioPlayer *)audioPlayer;
+- (void)mutedStateDidChangeForAudioPlayer:(AudioPlayer *)audioPlayer;
+- (void)playStateDidChangeForAudioPlayer:(AudioPlayer *)audioPlayer;
+
+@end
+
+@interface AudioPlayer : NSObject {
+    BOOL _playing;
+    AVPlayer *_audioPlayer;
+    AVPlayerItem *_playerItem;
+    id<AudioPlayerDelegate> _audioPlayerDelegate;
+}
+
+- (instancetype)initWithURL:(NSURL *)url;
+
+@property (nonatomic, weak) id<AudioPlayerDelegate> audioPlayerDelegate;
+@property (nonatomic, readonly, getter=isReadyToPlay) BOOL readyToPlay;
+@property (nonatomic, getter=isPlaying) BOOL playing;
+@property (nonatomic, getter=isMuted) BOOL muted;
+
+@end
diff --git a/Examples/NetscapeMuteAPIPlugin/AudioPlayer.mm b/Examples/NetscapeMuteAPIPlugin/AudioPlayer.mm
new file mode 100644 (file)
index 0000000..751231c
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ IMPORTANT:  This Apple software is supplied to you by Apple Inc. ("Apple") in
+ consideration of your agreement to the following terms, and your use, installation,
+ modification or redistribution of this Apple software constitutes acceptance of these
+ terms.  If you do not agree with these terms, please do not use, install, modify or
+ redistribute this Apple software.
+
+ In consideration of your agreement to abide by the following terms, and subject to these
+ terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in
+ this original Apple software (the "Apple Software"), to use, reproduce, modify and
+ redistribute the Apple Software, with or without modifications, in source and/or binary
+ forms; provided that if you redistribute the Apple Software in its entirety and without
+ modifications, you must retain this notice and the following text and disclaimers in all
+ such redistributions of the Apple Software.  Neither the name, trademarks, service marks
+ or logos of Apple Inc. may be used to endorse or promote products derived from
+ the Apple Software without specific prior written permission from Apple. Except as expressly
+ stated in this notice, no other rights or licenses, express or implied, are granted by Apple
+ herein, including but not limited to any patent rights that may be infringed by your
+ derivative works or by other works in which the Apple Software may be incorporated.
+
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES,
+ EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS
+ USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
+ REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND
+ WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
+ OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "AudioPlayer.h"
+
+#import <AVFoundation/AVFoundation.h>
+
+static void *itemStatusKVOContext = &itemStatusKVOContext;
+
+@implementation AudioPlayer
+
+@synthesize audioPlayerDelegate=_audioPlayerDelegate;
+
+- (instancetype)initWithURL:(NSURL *)url
+{
+    if (!url.absoluteString.length)
+        return nil;
+
+    if (!(self = [super init]))
+        return nil;
+
+    AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
+    _playerItem = [[AVPlayerItem alloc] initWithAsset:avAsset];
+    [_playerItem addObserver:self forKeyPath:@"status" options:0 context:itemStatusKVOContext];
+    _audioPlayer = [[AVPlayer alloc] initWithPlayerItem:_playerItem];
+
+    return self;
+}
+
+- (void)dealloc
+{
+    [_playerItem removeObserver:self forKeyPath:@"status"];
+    [_audioPlayer release];
+    [_playerItem release];
+    [super dealloc];
+}
+
+- (BOOL)isReadyToPlay
+{
+    return _audioPlayer.currentItem.status == AVPlayerItemStatusReadyToPlay;
+}
+
+- (BOOL)isPlaying
+{
+    return _playing;
+}
+
+- (void)setPlaying:(BOOL)playing
+{
+    if (_playing == playing)
+        return;
+
+    if (playing && !self.isReadyToPlay)
+        return;
+
+    _playing = playing;
+
+    if (_playing)
+        [_audioPlayer play];
+    else
+        [_audioPlayer pause];
+
+    [_audioPlayerDelegate playStateDidChangeForAudioPlayer:self];
+}
+
+- (BOOL)isMuted
+{
+    return _audioPlayer.isMuted;
+}
+
+- (void)setMuted:(BOOL)muted
+{
+    if (self.isMuted == muted)
+        return;
+
+    _audioPlayer.muted = muted;
+    [_audioPlayerDelegate mutedStateDidChangeForAudioPlayer:self];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+
+    if (context == &itemStatusKVOContext) {
+        dispatch_async(dispatch_get_main_queue(), ^{ [_audioPlayerDelegate readyStateDidChangeForAudioPlayer:self]; });
+        return;
+    }
+    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+}
+@end
diff --git a/Examples/NetscapeMuteAPIPlugin/English.lproj/InfoPlist.strings b/Examples/NetscapeMuteAPIPlugin/English.lproj/InfoPlist.strings
new file mode 100644 (file)
index 0000000..5e45963
Binary files /dev/null and b/Examples/NetscapeMuteAPIPlugin/English.lproj/InfoPlist.strings differ
diff --git a/Examples/NetscapeMuteAPIPlugin/Info.plist b/Examples/NetscapeMuteAPIPlugin/Info.plist
new file mode 100644 (file)
index 0000000..7d235cc
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>English</string>
+       <key>CFBundleExecutable</key>
+       <string>NetscapeMuteAPIPlugin</string>
+       <key>CFBundleIconFile</key>
+       <string></string>
+       <key>CFBundleIdentifier</key>
+       <string>com.apple.netscapemuteapiplugin</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundlePackageType</key>
+       <string>BRPL</string>
+       <key>CFBundleShortVersionString</key>
+       <string>1.0</string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>CFBundleVersion</key>
+       <string>1.0</string>
+       <key>WebPluginDescription</key>
+       <string>Simple Netscape plug-in that uses the Mute API</string>
+       <key>WebPluginMIMETypes</key>
+       <dict>
+               <key>test/x-netscape-mute-api-plugin</key>
+               <dict>
+                       <key>WebPluginTypeDescription</key>
+                       <string>Netscape Cocoa Plug-in</string>
+               </dict>
+       </dict>
+       <key>WebPluginName</key>
+       <string>Netscape Mute API Plug-in</string>
+</dict>
+</plist>
diff --git a/Examples/NetscapeMuteAPIPlugin/MenuHandler.h b/Examples/NetscapeMuteAPIPlugin/MenuHandler.h
new file mode 100644 (file)
index 0000000..3dd5887
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ IMPORTANT:  This Apple software is supplied to you by Apple Inc. ("Apple") in
+ consideration of your agreement to the following terms, and your use, installation, 
+ modification or redistribution of this Apple software constitutes acceptance of these 
+ terms.  If you do not agree with these terms, please do not use, install, modify or 
+ redistribute this Apple software.
+ In consideration of your agreement to abide by the following terms, and subject to these 
+ terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in 
+ this original Apple software (the "Apple Software"), to use, reproduce, modify and 
+ redistribute the Apple Software, with or without modifications, in source and/or binary 
+ forms; provided that if you redistribute the Apple Software in its entirety and without 
+ modifications, you must retain this notice and the following text and disclaimers in all 
+ such redistributions of the Apple Software.  Neither the name, trademarks, service marks 
+ or logos of Apple Inc. may be used to endorse or promote products derived from 
+ the Apple Software without specific prior written permission from Apple. Except as expressly
+ stated in this notice, no other rights or licenses, express or implied, are granted by Apple
+ herein, including but not limited to any patent rights that may be infringed by your 
+ derivative works or by other works in which the Apple Software may be incorporated.
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES, 
+ EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, 
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS 
+ USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL 
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
+ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, 
+ REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND 
+ WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR 
+ OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@class AudioPlayer;
+
+@interface MenuHandler : NSObject {
+    NSMenu *_menu;
+    AudioPlayer *_audioPlayer;
+}
+
+- (id)initWithAudioPlayer:(AudioPlayer *)audioPlayer;
+- (NSMenu *)menu;
+
+@end
diff --git a/Examples/NetscapeMuteAPIPlugin/MenuHandler.m b/Examples/NetscapeMuteAPIPlugin/MenuHandler.m
new file mode 100644 (file)
index 0000000..0911fff
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ IMPORTANT:  This Apple software is supplied to you by Apple Inc. ("Apple") in
+ consideration of your agreement to the following terms, and your use, installation, 
+ modification or redistribution of this Apple software constitutes acceptance of these 
+ terms.  If you do not agree with these terms, please do not use, install, modify or 
+ redistribute this Apple software.
+ In consideration of your agreement to abide by the following terms, and subject to these 
+ terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in 
+ this original Apple software (the "Apple Software"), to use, reproduce, modify and 
+ redistribute the Apple Software, with or without modifications, in source and/or binary 
+ forms; provided that if you redistribute the Apple Software in its entirety and without 
+ modifications, you must retain this notice and the following text and disclaimers in all 
+ such redistributions of the Apple Software.  Neither the name, trademarks, service marks 
+ or logos of Apple Inc. may be used to endorse or promote products derived from 
+ the Apple Software without specific prior written permission from Apple. Except as expressly
+ stated in this notice, no other rights or licenses, express or implied, are granted by Apple
+ herein, including but not limited to any patent rights that may be infringed by your 
+ derivative works or by other works in which the Apple Software may be incorporated.
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES, 
+ EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, 
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS 
+ USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL 
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
+ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, 
+ REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND 
+ WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR 
+ OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "MenuHandler.h"
+
+#import "AudioPlayer.h"
+
+@implementation MenuHandler
+
+- (void)_play:(id)sender
+{
+    _audioPlayer.playing = YES;
+}
+
+- (void)_pause:(id)sender
+{
+    _audioPlayer.playing = NO;
+}
+
+- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
+{
+    SEL sel = [anItem action];
+    
+    if (sel == @selector(_play:))
+        return _audioPlayer.isReadyToPlay && !_audioPlayer.isPlaying;
+
+    if (sel == @selector(_pause:))
+        return _audioPlayer.isPlaying;
+
+    return NO;
+}
+
+- (id)initWithAudioPlayer:(AudioPlayer *)audioPlayer
+{
+    self = [super init];
+    if (!self)
+        return nil;
+
+    _audioPlayer = [audioPlayer retain];
+
+    // Create the menu
+    _menu = [[NSMenu alloc] initWithTitle:@"Menu"];
+    
+    NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Play" action:@selector(_play:) keyEquivalent:@""];
+    [item setTarget:self];
+    [_menu addItem:item];
+    [item release];
+
+    item = [[NSMenuItem alloc] initWithTitle:@"Pause" action:@selector(_pause:) keyEquivalent:@""];
+    [item setTarget:self];
+    [_menu addItem:item];
+    [item release];
+
+    return self;
+}
+
+- (void)dealloc
+{
+    [_menu release];
+    [_audioPlayer release];
+    
+    [super dealloc];
+}
+
+- (NSMenu *)menu
+{
+    return _menu;
+}
+
+@end
diff --git a/Examples/NetscapeMuteAPIPlugin/NetscapeMuteAPIPlugin.xcodeproj/project.pbxproj b/Examples/NetscapeMuteAPIPlugin/NetscapeMuteAPIPlugin.xcodeproj/project.pbxproj
new file mode 100644 (file)
index 0000000..81e68e9
--- /dev/null
@@ -0,0 +1,263 @@
+// !$*UTF8*$!
+{
+       archiveVersion = 1;
+       classes = {
+       };
+       objectVersion = 46;
+       objects = {
+
+/* Begin PBXBuildFile section */
+               1A624E590DCBC7F6006898C8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A624E580DCBC7F6006898C8 /* main.m */; };
+               1A6250420DCBC81B006898C8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A6250410DCBC81B006898C8 /* Cocoa.framework */; };
+               1AAD6A090DD0DC52003985DA /* MenuHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AAD6A080DD0DC52003985DA /* MenuHandler.m */; };
+               52E2CAF119F856E9001EEB4F /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52E2CAF019F856E9001EEB4F /* AVFoundation.framework */; };
+               52E2CAF419F85A47001EEB4F /* AudioPlayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 52E2CAF319F85A47001EEB4F /* AudioPlayer.mm */; };
+               8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8D5B49A704867FD3000E48DA /* InfoPlist.strings */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+               089C167EFE841241C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+               1A624E580DCBC7F6006898C8 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+               1A6250410DCBC81B006898C8 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
+               1AAD6A070DD0DC52003985DA /* MenuHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MenuHandler.h; sourceTree = "<group>"; };
+               1AAD6A080DD0DC52003985DA /* MenuHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MenuHandler.m; sourceTree = "<group>"; };
+               52E2CAF019F856E9001EEB4F /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = ../../../../../../../../System/Library/Frameworks/AVFoundation.framework; sourceTree = "<group>"; };
+               52E2CAF219F85A47001EEB4F /* AudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioPlayer.h; sourceTree = "<group>"; };
+               52E2CAF319F85A47001EEB4F /* AudioPlayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AudioPlayer.mm; sourceTree = "<group>"; };
+               8D576316048677EA00EA77CD /* NetscapeMuteAPIPlugin.plugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NetscapeMuteAPIPlugin.plugin; sourceTree = BUILT_PRODUCTS_DIR; };
+               8D576317048677EA00EA77CD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+               8D576313048677EA00EA77CD /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               52E2CAF119F856E9001EEB4F /* AVFoundation.framework in Frameworks */,
+                               1A6250420DCBC81B006898C8 /* Cocoa.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+               089C166AFE841209C02AAC07 /* NetscapeMuteAPIPlugin */ = {
+                       isa = PBXGroup;
+                       children = (
+                               52E2CAF019F856E9001EEB4F /* AVFoundation.framework */,
+                               08FB77AFFE84173DC02AAC07 /* Source */,
+                               089C167CFE841241C02AAC07 /* Resources */,
+                               089C1671FE841209C02AAC07 /* External Frameworks and Libraries */,
+                               19C28FB6FE9D52B211CA2CBB /* Products */,
+                       );
+                       name = NetscapeMuteAPIPlugin;
+                       sourceTree = "<group>";
+               };
+               089C1671FE841209C02AAC07 /* External Frameworks and Libraries */ = {
+                       isa = PBXGroup;
+                       children = (
+                               1A6250410DCBC81B006898C8 /* Cocoa.framework */,
+                       );
+                       name = "External Frameworks and Libraries";
+                       sourceTree = "<group>";
+               };
+               089C167CFE841241C02AAC07 /* Resources */ = {
+                       isa = PBXGroup;
+                       children = (
+                               8D576317048677EA00EA77CD /* Info.plist */,
+                               8D5B49A704867FD3000E48DA /* InfoPlist.strings */,
+                       );
+                       name = Resources;
+                       sourceTree = "<group>";
+               };
+               08FB77AFFE84173DC02AAC07 /* Source */ = {
+                       isa = PBXGroup;
+                       children = (
+                               1A624E580DCBC7F6006898C8 /* main.m */,
+                               1AAD6A070DD0DC52003985DA /* MenuHandler.h */,
+                               1AAD6A080DD0DC52003985DA /* MenuHandler.m */,
+                               52E2CAF219F85A47001EEB4F /* AudioPlayer.h */,
+                               52E2CAF319F85A47001EEB4F /* AudioPlayer.mm */,
+                       );
+                       name = Source;
+                       sourceTree = "<group>";
+               };
+               19C28FB6FE9D52B211CA2CBB /* Products */ = {
+                       isa = PBXGroup;
+                       children = (
+                               8D576316048677EA00EA77CD /* NetscapeMuteAPIPlugin.plugin */,
+                       );
+                       name = Products;
+                       sourceTree = "<group>";
+               };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+               8D57630D048677EA00EA77CD /* NetscapeMuteAPIPlugin */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = 149C298308902C53008A9EFC /* Build configuration list for PBXNativeTarget "NetscapeMuteAPIPlugin" */;
+                       buildPhases = (
+                               8D576311048677EA00EA77CD /* Sources */,
+                               8D57630F048677EA00EA77CD /* Resources */,
+                               8D576313048677EA00EA77CD /* Frameworks */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = NetscapeMuteAPIPlugin;
+                       productInstallPath = "$(HOME)/Library/Bundles";
+                       productName = NetscapeMuteAPIPlugin;
+                       productReference = 8D576316048677EA00EA77CD /* NetscapeMuteAPIPlugin.plugin */;
+                       productType = "com.apple.product-type.bundle";
+               };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+               089C1669FE841209C02AAC07 /* Project object */ = {
+                       isa = PBXProject;
+                       attributes = {
+                               LastUpgradeCheck = 0600;
+                       };
+                       buildConfigurationList = 149C298708902C53008A9EFC /* Build configuration list for PBXProject "NetscapeMuteAPIPlugin" */;
+                       compatibilityVersion = "Xcode 3.2";
+                       developmentRegion = English;
+                       hasScannedForEncodings = 1;
+                       knownRegions = (
+                               English,
+                               Japanese,
+                               French,
+                               German,
+                       );
+                       mainGroup = 089C166AFE841209C02AAC07 /* NetscapeMuteAPIPlugin */;
+                       projectDirPath = "";
+                       projectRoot = "";
+                       targets = (
+                               8D57630D048677EA00EA77CD /* NetscapeMuteAPIPlugin */,
+                       );
+               };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+               8D57630F048677EA00EA77CD /* Resources */ = {
+                       isa = PBXResourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+               8D576311048677EA00EA77CD /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               1A624E590DCBC7F6006898C8 /* main.m in Sources */,
+                               52E2CAF419F85A47001EEB4F /* AudioPlayer.mm in Sources */,
+                               1AAD6A090DD0DC52003985DA /* MenuHandler.m in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+               8D5B49A704867FD3000E48DA /* InfoPlist.strings */ = {
+                       isa = PBXVariantGroup;
+                       children = (
+                               089C167EFE841241C02AAC07 /* English */,
+                       );
+                       name = InfoPlist.strings;
+                       sourceTree = "<group>";
+               };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+               149C298408902C53008A9EFC /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               COMBINE_HIDPI_IMAGES = YES;
+                               COPY_PHASE_STRIP = NO;
+                               FRAMEWORK_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
+                               );
+                               GCC_DEBUGGING_SYMBOLS = full;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_ENABLE_FIX_AND_CONTINUE = YES;
+                               GCC_MODEL_TUNING = G5;
+                               GCC_OPTIMIZATION_LEVEL = 0;
+                               INFOPLIST_FILE = Info.plist;
+                               INSTALL_PATH = "$(HOME)/Library/Bundles";
+                               PRODUCT_NAME = NetscapeMuteAPIPlugin;
+                               WRAPPER_EXTENSION = plugin;
+                               ZERO_LINK = YES;
+                       };
+                       name = Debug;
+               };
+               149C298508902C53008A9EFC /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               COMBINE_HIDPI_IMAGES = YES;
+                               FRAMEWORK_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
+                               );
+                               GCC_DEBUGGING_SYMBOLS = full;
+                               GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
+                               GCC_MODEL_TUNING = G5;
+                               GCC_OPTIMIZATION_LEVEL = 0;
+                               INFOPLIST_FILE = Info.plist;
+                               INSTALL_PATH = "$(HOME)/Library/Bundles";
+                               PRODUCT_NAME = NetscapeMuteAPIPlugin;
+                               WRAPPER_EXTENSION = plugin;
+                       };
+                       name = Release;
+               };
+               149C298808902C53008A9EFC /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ARCHS_STANDARD_32_64_BIT_PRE_XCODE_3_1 = "ppc i386 ppc64 x86_64";
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               ONLY_ACTIVE_ARCH = YES;
+                               PREBINDING = NO;
+                       };
+                       name = Debug;
+               };
+               149C298908902C53008A9EFC /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ARCHS_STANDARD_32_64_BIT_PRE_XCODE_3_1 = "ppc i386 ppc64 x86_64";
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               PREBINDING = NO;
+                       };
+                       name = Release;
+               };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+               149C298308902C53008A9EFC /* Build configuration list for PBXNativeTarget "NetscapeMuteAPIPlugin" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               149C298408902C53008A9EFC /* Debug */,
+                               149C298508902C53008A9EFC /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               149C298708902C53008A9EFC /* Build configuration list for PBXProject "NetscapeMuteAPIPlugin" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               149C298808902C53008A9EFC /* Debug */,
+                               149C298908902C53008A9EFC /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+/* End XCConfigurationList section */
+       };
+       rootObject = 089C1669FE841209C02AAC07 /* Project object */;
+}
diff --git a/Examples/NetscapeMuteAPIPlugin/main.m b/Examples/NetscapeMuteAPIPlugin/main.m
new file mode 100644 (file)
index 0000000..86c3340
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+ IMPORTANT:  This Apple software is supplied to you by Apple Inc. ("Apple") in
+ consideration of your agreement to the following terms, and your use, installation, 
+ modification or redistribution of this Apple software constitutes acceptance of these 
+ terms.  If you do not agree with these terms, please do not use, install, modify or 
+ redistribute this Apple software.
+ In consideration of your agreement to abide by the following terms, and subject to these 
+ terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in 
+ this original Apple software (the "Apple Software"), to use, reproduce, modify and 
+ redistribute the Apple Software, with or without modifications, in source and/or binary 
+ forms; provided that if you redistribute the Apple Software in its entirety and without 
+ modifications, you must retain this notice and the following text and disclaimers in all 
+ such redistributions of the Apple Software.  Neither the name, trademarks, service marks 
+ or logos of Apple Inc. may be used to endorse or promote products derived from 
+ the Apple Software without specific prior written permission from Apple. Except as expressly
+ stated in this notice, no other rights or licenses, express or implied, are granted by Apple
+ herein, including but not limited to any patent rights that may be infringed by your 
+ derivative works or by other works in which the Apple Software may be incorporated.
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES, 
+ EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, 
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS 
+ USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL 
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
+ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, 
+ REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND 
+ WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR 
+ OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "AudioPlayer.h"
+#import "MenuHandler.h"
+#import <Cocoa/Cocoa.h>
+#import <WebKit/npapi.h>
+#import <WebKit/npfunctions.h>
+#import <WebKit/npruntime.h>
+
+// Browser function table
+static NPNetscapeFuncs* browser;
+
+// Structure for per-instance storage
+@interface PluginObject : NSObject <AudioPlayerDelegate> {
+    AudioPlayer *_audioPlayer;
+    MenuHandler *_menuHandler;
+}
+
+@property (nonatomic, readonly) NPP npp;
+@property (nonatomic) NPWindow window;
+@property (nonatomic, strong, readonly) NSURL *audioURL;
+@property (nonatomic, copy, readonly) NSString *displayString;
+@property (nonatomic, strong, readonly) MenuHandler *menuHandler;
+@property (nonatomic) BOOL hasFocus;
+@property (nonatomic, readonly, getter=isPlayingAudio) BOOL playingAudio;
+@property (nonatomic, getter=isMuted) BOOL muted;
+
+- (instancetype)initWithNPP:(NPP)npp audioURL:(NSURL *)audioURL;
+
+@end
+
+NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData*);
+NPError NPP_Destroy(NPP instance, NPSavedData**);
+NPError NPP_SetWindow(NPP instance, NPWindow*);
+NPError NPP_NewStream(NPP instance, NPMIMEType, NPStream*, NPBool seekable, uint16_t* stype);
+NPError NPP_DestroyStream(NPP instance, NPStream*, NPReason);
+int32_t NPP_WriteReady(NPP instance, NPStream*);
+int32_t NPP_Write(NPP instance, NPStream*, int32_t offset, int32_t len, void* buffer);
+void NPP_StreamAsFile(NPP instance, NPStream*, const char* fname);
+void NPP_Print(NPP instance, NPPrint* platformPrint);
+int16_t NPP_HandleEvent(NPP instance, void* event);
+void NPP_URLNotify(NPP instance, const char* URL, NPReason, void* notifyData);
+NPError NPP_GetValue(NPP instance, NPPVariable, void *value);
+NPError NPP_SetValue(NPP instance, NPNVariable, void *value);
+
+#pragma export on
+// Mach-o entry points
+NPError NP_Initialize(NPNetscapeFuncs *browserFuncs);
+NPError NP_GetEntryPoints(NPPluginFuncs *);
+void NP_Shutdown(void);
+#pragma export off
+
+static void invalidatePlugin(PluginObject *);
+
+@implementation PluginObject
+
+- (instancetype)initWithNPP:(NPP)npp audioURL:(NSURL *)audioURL
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _npp = npp;
+    _audioURL = [audioURL retain];
+    _audioPlayer = [[AudioPlayer alloc] initWithURL:audioURL];
+    _audioPlayer.audioPlayerDelegate = self;
+
+    NPBool isMuted;
+    if (browser->getvalue(npp, NPNVmuteAudioBool, &isMuted) != NPERR_NO_ERROR)
+        isMuted = FALSE;
+    self.muted = isMuted;
+
+    [self _invalidateDisplayString];
+
+    return self;
+}
+
+- (void)dealloc
+{
+    [_audioURL release];
+    [_menuHandler release];
+    [_audioPlayer release];
+    [_displayString release];
+
+    [super dealloc];
+}
+
+- (MenuHandler *)menuHandler
+{
+    if (!_menuHandler)
+        _menuHandler = [[MenuHandler alloc] initWithAudioPlayer:_audioPlayer];
+
+    return _menuHandler;
+}
+
+- (BOOL)isPlayingAudio
+{
+    return _audioPlayer.isPlaying;
+}
+
+- (BOOL)isMuted
+{
+    return _audioPlayer.isMuted;
+}
+
+- (void)setMuted:(BOOL)muted
+{
+    _audioPlayer.muted = muted;
+}
+
+- (void)readyStateDidChangeForAudioPlayer:(AudioPlayer *)audioPlayer
+{
+    [self _invalidateDisplayString];
+    invalidatePlugin(self);
+}
+
+- (void)mutedStateDidChangeForAudioPlayer:(AudioPlayer *)audioPlayer
+{
+}
+
+- (void)playStateDidChangeForAudioPlayer:(AudioPlayer *)audioPlayer
+{
+    [self _invalidateDisplayString];
+    invalidatePlugin(self);
+    browser->setvalue(_npp, NPPVpluginIsPlayingAudio, _audioPlayer.isPlaying);
+}
+
+- (void)_invalidateDisplayString
+{
+    [_displayString release];
+
+    NSString *prefix;
+    if (!_audioPlayer.isReadyToPlay)
+        prefix = @"Loading: ";
+    else if (_audioPlayer.isPlaying)
+        prefix = @"Playing: ";
+    else
+        prefix = @"Ready to play: ";
+
+    _displayString = [[NSString stringWithFormat:@"%@ %@", prefix, _audioURL.absoluteString] retain];
+}
+
+@end
+
+
+NPError NP_Initialize(NPNetscapeFuncs* browserFuncs)
+{
+    browser = browserFuncs;
+    return NPERR_NO_ERROR;
+}
+
+NPError NP_GetEntryPoints(NPPluginFuncs* pluginFuncs)
+{
+    pluginFuncs->version = 11;
+    pluginFuncs->size = sizeof(pluginFuncs);
+    pluginFuncs->newp = NPP_New;
+    pluginFuncs->destroy = NPP_Destroy;
+    pluginFuncs->setwindow = NPP_SetWindow;
+    pluginFuncs->newstream = NPP_NewStream;
+    pluginFuncs->destroystream = NPP_DestroyStream;
+    pluginFuncs->asfile = NPP_StreamAsFile;
+    pluginFuncs->writeready = NPP_WriteReady;
+    pluginFuncs->write = (NPP_WriteProcPtr)NPP_Write;
+    pluginFuncs->print = NPP_Print;
+    pluginFuncs->event = NPP_HandleEvent;
+    pluginFuncs->urlnotify = NPP_URLNotify;
+    pluginFuncs->getvalue = NPP_GetValue;
+    pluginFuncs->setvalue = NPP_SetValue;
+    
+    return NPERR_NO_ERROR;
+}
+
+void NP_Shutdown(void)
+{
+
+}
+
+NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved)
+{
+    // Create per-instance storage
+    // Ask the browser if it supports the CoreGraphics drawing model
+    NPBool supportsCoreGraphics;
+    if (browser->getvalue(instance, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics) != NPERR_NO_ERROR)
+        supportsCoreGraphics = FALSE;
+    
+    if (!supportsCoreGraphics)
+        return NPERR_INCOMPATIBLE_VERSION_ERROR;
+    
+    // If the browser supports the CoreGraphics drawing model, enable it.
+    browser->setvalue(instance, NPPVpluginDrawingModel, (void *)NPDrawingModelCoreGraphics);
+
+    // If the browser supports the Cocoa event model, enable it.
+    NPBool supportsCocoa;
+    if (browser->getvalue(instance, NPNVsupportsCocoaBool, &supportsCocoa) != NPERR_NO_ERROR)
+        supportsCocoa = FALSE;
+    
+    if (!supportsCocoa)
+        return NPERR_INCOMPATIBLE_VERSION_ERROR;
+    
+    browser->setvalue(instance, NPPVpluginEventModel, (void *)NPEventModelCocoa);
+
+    NSURL *audioURL = nil;
+    for (int16_t i = 0; i < argc; i++) {
+        if (!strcasecmp(argn[i], "audiourl")) {
+            NSString *urlString = [NSString stringWithUTF8String:argv[i]];
+            if (urlString)
+                audioURL = [NSURL URLWithString:urlString];
+            break;
+        }
+    }
+
+    PluginObject *obj = [[PluginObject alloc] initWithNPP:instance audioURL:audioURL];
+    instance->pdata = obj;
+
+    return NPERR_NO_ERROR;
+}
+
+NPError NPP_Destroy(NPP instance, NPSavedData** save)
+{
+    // Free per-instance storage
+    PluginObject *obj = instance->pdata;
+    [obj release];
+
+    return NPERR_NO_ERROR;
+}
+
+NPError NPP_SetWindow(NPP instance, NPWindow* window)
+{
+    PluginObject *obj = instance->pdata;
+    obj.window = *window;
+
+    return NPERR_NO_ERROR;
+}
+
+NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype)
+{
+    *stype = NP_ASFILEONLY;
+    return NPERR_NO_ERROR;
+}
+
+NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason)
+{
+    return NPERR_NO_ERROR;
+}
+
+int32_t NPP_WriteReady(NPP instance, NPStream* stream)
+{
+    return 0;
+}
+
+int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, void* buffer)
+{
+    return 0;
+}
+
+void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname)
+{
+}
+
+void NPP_Print(NPP instance, NPPrint* platformPrint)
+{
+}
+
+static void handleDraw(PluginObject *obj, NPCocoaEvent *event)
+{
+    NSGraphicsContext *oldContext = [[NSGraphicsContext currentContext] retain];
+    
+    NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:event->data.draw.context flipped:YES];
+
+    [NSGraphicsContext setCurrentContext:context];
+    
+    NSRect rect = NSMakeRect(0, 0, obj.window.width, obj.window.height);
+    
+    [[NSColor lightGrayColor] set];
+    [NSBezierPath fillRect:rect];
+
+    // If the plugin has focus, draw a focus indicator
+    if (obj.hasFocus) {
+        [[NSColor blackColor] set];
+        NSBezierPath *path = [NSBezierPath bezierPathWithRect:rect];
+        [path setLineWidth:5];
+        [path stroke];
+    }
+    
+    [obj.displayString drawAtPoint:NSMakePoint(10, 10) withAttributes:nil];
+    
+    [NSGraphicsContext setCurrentContext:oldContext];
+    [oldContext release];
+}
+
+static void invalidatePlugin(PluginObject *obj)
+{
+    NPRect rect;
+    rect.left = 0;
+    rect.top = 0;
+    rect.right = obj.window.width;
+    rect.bottom = obj.window.height;
+    
+    browser->invalidaterect(obj.npp, &rect);
+}
+
+
+static void handleMouseEvent(PluginObject *obj, NPCocoaEvent *event)
+{
+    if (event->data.mouse.buttonNumber == 1)
+        browser->popupcontextmenu(obj.npp, (NPNSMenu *)[obj.menuHandler menu]);
+}
+
+int16_t NPP_HandleEvent(NPP instance, void* event)
+{
+    PluginObject *obj = instance->pdata;
+
+    NPCocoaEvent *cocoaEvent = event;
+    
+    switch (cocoaEvent->type) {
+    case NPCocoaEventFocusChanged:
+        obj.hasFocus = cocoaEvent->data.focus.hasFocus;
+        invalidatePlugin(obj);
+        return 1;
+        
+    case NPCocoaEventDrawRect:
+        handleDraw(obj, cocoaEvent);
+        return 1;
+    
+    case NPCocoaEventMouseDown:
+    case NPCocoaEventMouseUp:
+        
+    // FIXME: NPCocoaEventMouseMoved is currently disabled in order to see other events more clearly
+    // without "drowning" in mouse moved events.
+//        case NPCocoaEventMouseMoved:
+    case NPCocoaEventMouseEntered:
+    case NPCocoaEventMouseExited:
+    case NPCocoaEventMouseDragged:
+    case NPCocoaEventScrollWheel:
+        handleMouseEvent(obj, cocoaEvent);
+        return 1;
+
+    default:
+        break;
+    }
+    
+    return 0;
+}
+
+void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
+{
+
+}
+
+NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value)
+{
+    PluginObject *obj = instance->pdata;
+
+    switch (variable) {
+    case NPPVpluginIsPlayingAudio:
+        if (value)
+            *((NPBool **)value) = obj.isPlayingAudio;
+        return NPERR_NO_ERROR;
+
+    default:
+        return NPERR_GENERIC_ERROR;
+    }
+}
+
+NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value)
+{
+    PluginObject *obj = instance->pdata;
+
+    switch (variable) {
+    case NPNVmuteAudioBool:
+        if (value)
+            obj.muted = *((NPBool *)value);
+        return NPERR_NO_ERROR;
+
+    default:
+        return NPERR_GENERIC_ERROR;
+    }
+}
diff --git a/Examples/NetscapeMuteAPIPlugin/test.html b/Examples/NetscapeMuteAPIPlugin/test.html
new file mode 100644 (file)
index 0000000..32145cc
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
+<html>
+<head>
+<title>Cocoa Mute API Plug-In</title>
+</head>
+<body>
+<p>To test, set audiourl to an absolute URL to an audio file (e.g. mp3).</p>
+<div>
+    <embed width="700" height="50" type="test/x-netscape-mute-api-plugin" audiourl=""></embed>
+</div>
+</body>
+</html>