1/*
2     File: MovieControllerLayer.m
3
4 Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
5 Inc. ("Apple") in consideration of your agreement to the following
6 terms, and your use, installation, modification or redistribution of
7 this Apple software constitutes acceptance of these terms.  If you do
8 not agree with these terms, please do not use, install, modify or
9 redistribute this Apple software.
10
11 In consideration of your agreement to abide by the following terms, and
12 subject to these terms, Apple grants you a personal, non-exclusive
13 license, under Apple's copyrights in this original Apple software (the
14 "Apple Software"), to use, reproduce, modify and redistribute the Apple
15 Software, with or without modifications, in source and/or binary forms;
16 provided that if you redistribute the Apple Software in its entirety and
17 without modifications, you must retain this notice and the following
18 text and disclaimers in all such redistributions of the Apple Software.
19 Neither the name, trademarks, service marks or logos of Apple Inc. may
20 be used to endorse or promote products derived from the Apple Software
21 without specific prior written permission from Apple.  Except as
22 expressly stated in this notice, no other rights or licenses, express or
23 implied, are granted by Apple herein, including but not limited to any
24 patent rights that may be infringed by your derivative works or by other
25 works in which the Apple Software may be incorporated.
26
27 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
28 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
29 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
30 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
31 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
32
33 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
34 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
37 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
38 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
39 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
40 POSSIBILITY OF SUCH DAMAGE.
41
42 Copyright (C) 2009 Apple Inc. All Rights Reserved.
43
44 */
45
46#import <WebKit/npapi.h>
47#import <WebKit/npfunctions.h>
48#import <WebKit/npruntime.h>
49
50#import <QuartzCore/QuartzCore.h>
51#import <QTKit/QTKit.h>
52
53#import "MovieControllerLayer.h"
54
55// Browser function table
56static NPNetscapeFuncs* browser;
57
58// Structure for per-instance storage
59typedef struct PluginObject
60{
61    NPP npp;
62
63    NPWindow window;
64
65    CALayer *rootLayer;
66    MovieControllerLayer *controllerLayer;
67    QTMovieLayer *movieLayer;
68
69    CALayer *mouseDownLayer;
70
71    NSURL *movieURL;
72    QTMovie *movie;
73} PluginObject;
74
75NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved);
76NPError NPP_Destroy(NPP instance, NPSavedData** save);
77NPError NPP_SetWindow(NPP instance, NPWindow* window);
78NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stype);
79NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason);
80int32_t NPP_WriteReady(NPP instance, NPStream* stream);
81int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, void* buffer);
82void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname);
83void NPP_Print(NPP instance, NPPrint* platformPrint);
84int16_t NPP_HandleEvent(NPP instance, void* event);
85void NPP_URLNotify(NPP instance, const char* URL, NPReason reason, void* notifyData);
86NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value);
87NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value);
88
89#pragma export on
90// Mach-o entry points
91NPError NP_Initialize(NPNetscapeFuncs *browserFuncs);
92NPError NP_GetEntryPoints(NPPluginFuncs *pluginFuncs);
93void NP_Shutdown(void);
94#pragma export off
95
96NPError NP_Initialize(NPNetscapeFuncs* browserFuncs)
97{
98    browser = browserFuncs;
99    return NPERR_NO_ERROR;
100}
101
102NPError NP_GetEntryPoints(NPPluginFuncs* pluginFuncs)
103{
104    pluginFuncs->version = 11;
105    pluginFuncs->size = sizeof(pluginFuncs);
106    pluginFuncs->newp = NPP_New;
107    pluginFuncs->destroy = NPP_Destroy;
108    pluginFuncs->setwindow = NPP_SetWindow;
109    pluginFuncs->newstream = NPP_NewStream;
110    pluginFuncs->destroystream = NPP_DestroyStream;
111    pluginFuncs->asfile = NPP_StreamAsFile;
112    pluginFuncs->writeready = NPP_WriteReady;
113    pluginFuncs->write = (NPP_WriteProcPtr)NPP_Write;
114    pluginFuncs->print = NPP_Print;
115    pluginFuncs->event = NPP_HandleEvent;
116    pluginFuncs->urlnotify = NPP_URLNotify;
117    pluginFuncs->getvalue = NPP_GetValue;
118    pluginFuncs->setvalue = NPP_SetValue;
119
120    return NPERR_NO_ERROR;
121}
122
123void NP_Shutdown(void)
124{
125
126}
127
128NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved)
129{
130    // Create per-instance storage
131    PluginObject *obj = (PluginObject *)malloc(sizeof(PluginObject));
132    bzero(obj, sizeof(PluginObject));
133
134    obj->npp = instance;
135    instance->pdata = obj;
136
137    // Ask the browser if it supports the Core Animation drawing model
138    NPBool supportsCoreAnimation;
139    if (browser->getvalue(instance, NPNVsupportsCoreAnimationBool, &supportsCoreAnimation) != NPERR_NO_ERROR)
140        supportsCoreAnimation = FALSE;
141
142    if (!supportsCoreAnimation)
143        return NPERR_INCOMPATIBLE_VERSION_ERROR;
144
145    // If the browser supports the Core Animation drawing model, enable it.
146    browser->setvalue(instance, NPPVpluginDrawingModel, (void *)NPDrawingModelCoreAnimation);
147
148    // If the browser supports the Cocoa event model, enable it.
149    NPBool supportsCocoa;
150    if (browser->getvalue(instance, NPNVsupportsCocoaBool, &supportsCocoa) != NPERR_NO_ERROR)
151        supportsCocoa = FALSE;
152
153    if (!supportsCocoa)
154        return NPERR_INCOMPATIBLE_VERSION_ERROR;
155
156    browser->setvalue(instance, NPPVpluginEventModel, (void *)NPEventModelCocoa);
157
158    for (int16_t i = 0; i < argc; i++) {
159        if (strcasecmp(argn[i], "movieurl") == 0) {
160            NSString *urlString = [NSString stringWithUTF8String:argv[i]];
161            if (urlString)
162                obj->movieURL = [[NSURL URLWithString:urlString] retain];
163            break;
164        }
165
166    }
167
168    return NPERR_NO_ERROR;
169}
170
171NPError NPP_Destroy(NPP instance, NPSavedData** save)
172{
173    // Free per-instance storage
174    PluginObject *obj = instance->pdata;
175
176    [obj->movie stop];
177    [obj->rootLayer release];
178
179    free(obj);
180
181    return NPERR_NO_ERROR;
182}
183
184NPError NPP_SetWindow(NPP instance, NPWindow* window)
185{
186    PluginObject *obj = instance->pdata;
187    obj->window = *window;
188
189    return NPERR_NO_ERROR;
190}
191
192
193NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stype)
194{
195    *stype = NP_ASFILEONLY;
196    return NPERR_NO_ERROR;
197}
198
199NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason)
200{
201    return NPERR_NO_ERROR;
202}
203
204int32_t NPP_WriteReady(NPP instance, NPStream* stream)
205{
206    return 0;
207}
208
209int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, void* buffer)
210{
211    return 0;
212}
213
214void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname)
215{
216}
217
218void NPP_Print(NPP instance, NPPrint* platformPrint)
219{
220
221}
222
223static void handleMouseDown(PluginObject *obj, NPCocoaEvent *event)
224{
225    CGPoint point = CGPointMake(event->data.mouse.pluginX,
226                                // Flip the y coordinate
227                                obj->window.height - event->data.mouse.pluginY);
228
229    obj->mouseDownLayer = [obj->rootLayer hitTest:point];
230    if (obj->mouseDownLayer == obj->controllerLayer) {
231        [obj->controllerLayer handleMouseDown:[obj->rootLayer convertPoint:point toLayer:obj->controllerLayer]];
232        return;
233    }
234}
235
236static void togglePlayPause(PluginObject *obj)
237{
238    if (!obj->movie)
239        return;
240
241    if ([obj->movie rate] == 0)
242        [obj->movie play];
243    else
244        [obj->movie stop];
245
246}
247
248static void handleMouseUp(PluginObject *obj, NPCocoaEvent *event)
249{
250    CGPoint point = CGPointMake(event->data.mouse.pluginX,
251                                // Flip the y coordinate
252                                obj->window.height - event->data.mouse.pluginY);
253
254    CALayer *mouseDownLayer = obj->mouseDownLayer;
255    obj->mouseDownLayer = nil;
256    if (mouseDownLayer == obj->controllerLayer) {
257        [obj->controllerLayer handleMouseUp:[obj->rootLayer convertPoint:point toLayer:obj->controllerLayer]];
258        return;
259    }
260}
261
262static void handleMouseDragged(PluginObject *obj, NPCocoaEvent *event)
263{
264    CGPoint point = CGPointMake(event->data.mouse.pluginX,
265                                // Flip the y coordinate
266                                obj->window.height - event->data.mouse.pluginY);
267
268    if (obj->mouseDownLayer == obj->controllerLayer) {
269        [obj->controllerLayer handleMouseDragged:[obj->rootLayer convertPoint:point toLayer:obj->controllerLayer]];
270        return;
271    }
272}
273
274static void handleMouseEntered(PluginObject *obj)
275{
276    // Show the controller layer.
277    obj->controllerLayer.opacity = 1.0;
278}
279
280static void handleMouseExited(PluginObject *obj)
281{
282    // Hide the controller layer if the movie is playing.
283    if ([obj->movie rate])
284        obj->controllerLayer.opacity = 0.0;
285}
286
287static int handleKeyDown(PluginObject *obj, NPCocoaEvent *event)
288{
289    NSString *characters = (NSString *)event->data.key.characters;
290
291    if ([characters length] == 1 && [characters characterAtIndex:0] == ' ') {
292        togglePlayPause(obj);
293        return 1;
294    }
295
296    return 0;
297}
298
299
300static int handleScrollEvent(PluginObject *obj, NPCocoaEvent *event)
301{
302    double delta = event->data.mouse.deltaY;
303    if (delta < 0)
304        [obj->movie stepForward];
305    else
306        [obj->movie stepBackward];
307    return 0;
308}
309
310
311
312int16_t NPP_HandleEvent(NPP instance, void* event)
313{
314    PluginObject *obj = instance->pdata;
315
316    NPCocoaEvent *cocoaEvent = event;
317
318    switch(cocoaEvent->type) {
319        case NPCocoaEventMouseDown:
320            handleMouseDown(obj, cocoaEvent);
321            return 1;
322        case NPCocoaEventMouseUp:
323            handleMouseUp(obj, cocoaEvent);
324            return 1;
325        case NPCocoaEventMouseDragged:
326            handleMouseDragged(obj, cocoaEvent);
327            return 1;
328        case NPCocoaEventMouseEntered:
329            handleMouseEntered(obj);
330            return 1;
331        case NPCocoaEventMouseExited:
332            handleMouseExited(obj);
333            return 1;
334        case NPCocoaEventKeyDown:
335            return handleKeyDown(obj, cocoaEvent);
336        case NPCocoaEventScrollWheel:
337            return handleScrollEvent(obj, cocoaEvent);
338    }
339
340    return 0;
341}
342
343void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
344{
345
346}
347
348NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value)
349{
350    PluginObject *obj = instance->pdata;
351
352    switch (variable) {
353        case NPPVpluginCoreAnimationLayer:
354            if (!obj->rootLayer) {
355                // Setup layer hierarchy.
356                obj->rootLayer = [[CALayer layer] retain];
357
358                obj->movieLayer = [QTMovieLayer layer];
359                obj->movieLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
360                [obj->rootLayer addSublayer:obj->movieLayer];
361
362                obj->controllerLayer = [MovieControllerLayer layer];
363                [obj->rootLayer addSublayer:obj->controllerLayer];
364
365                if (obj->movieURL) {
366                    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:obj->movieURL, QTMovieURLAttribute,
367                                                [NSNumber numberWithBool:YES], QTMovieOpenForPlaybackAttribute,
368                                                [NSNumber numberWithBool:YES], QTMovieLoopsAttribute,
369                                                nil];
370                    obj->movie = [QTMovie movieWithAttributes:attributes error:nil];
371
372                    if (obj->movie) {
373                        obj->movieLayer.movie = obj->movie;
374                        [obj->controllerLayer setMovie:obj->movie];
375                    }
376                }
377
378            }
379
380            // Make sure to return a retained layer
381            *((CALayer **)value) = [obj->rootLayer retain];
382
383            return NPERR_NO_ERROR;
384
385        default:
386            return NPERR_GENERIC_ERROR;
387    }
388}
389
390NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value)
391{
392    return NPERR_GENERIC_ERROR;
393}
394