osxvideosink: fix setting window handle after transition
[platform/upstream/gstreamer.git] / sys / osxvideo / osxvideosink.m
1 /* GStreamer
2  * OSX video sink
3  * Copyright (C) 2004-6 Zaheer Abbas Merali <zaheerabbas at merali dot org>
4  * Copyright (C) 2007,2008,2009 Pioneers of the Inevitable <songbird@songbirdnest.com>
5  * 
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  * The development of this code was made possible due to the involvement of
22  * Pioneers of the Inevitable, the creators of the Songbird Music player.
23  * 
24  */
25
26 /**
27  * SECTION:element-osxvideosink
28  *
29  * The OSXVideoSink renders video frames to a MacOSX window. The video output
30  * must be directed to a window embedded in an existing NSApp.
31  *
32  * When the NSView to be embedded is created an element #GstMessage with a 
33  * name of 'have-ns-view' will be created and posted on the bus. 
34  * The pointer to the NSView to embed will be in the 'nsview' field of that 
35  * message. The application MUST handle this message and embed the view
36  * appropriately.
37  */
38
39 #include "config.h"
40 #include <gst/video/videooverlay.h>
41 #include <gst/video/navigation.h>
42
43 #include "osxvideosink.h"
44 #include <unistd.h>
45 #import "cocoawindow.h"
46
47 GST_DEBUG_CATEGORY (gst_debug_osx_video_sink);
48 #define GST_CAT_DEFAULT gst_debug_osx_video_sink
49
50 #ifdef RUN_NS_APP_THREAD
51 #include <pthread.h>
52 extern  void _CFRunLoopSetCurrent(CFRunLoopRef rl);
53 extern pthread_t _CFMainPThread;
54 #endif
55
56 static GstStaticPadTemplate gst_osx_video_sink_sink_template_factory =
57 GST_STATIC_PAD_TEMPLATE ("sink",
58     GST_PAD_SINK,
59     GST_PAD_ALWAYS,
60     GST_STATIC_CAPS ("video/x-raw, "
61         "framerate = (fraction) [ 0, MAX ], "
62         "width = (int) [ 1, MAX ], "
63         "height = (int) [ 1, MAX ], "
64 #if G_BYTE_ORDER == G_BIG_ENDIAN
65        "format = (string) YUY2")
66 #else
67         "format = (string) UYVY")
68 #endif
69     );
70
71 enum
72 {
73   ARG_0,
74   ARG_EMBED,
75   ARG_FORCE_PAR,
76 };
77
78 static void gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink);
79
80 static GstVideoSinkClass *parent_class = NULL;
81
82 /* Helper to trigger calls from the main thread */
83 static void
84 gst_osx_video_sink_call_from_main_thread(GstOSXVideoSink *osxvideosink,
85     NSObject * object, SEL function, NSObject *data, BOOL waitUntilDone)
86 {
87
88   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
89
90   [object performSelector:function onThread:osxvideosink->ns_app_thread
91           withObject:data waitUntilDone:waitUntilDone];
92   [pool release];
93 }
94
95 /* Poll for cocoa events */
96 static void
97 run_ns_app_loop (void) {
98   NSEvent *event;
99   NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init];
100   NSDate *pollTime = nil;
101
102 #ifdef RUN_NS_APP_THREAD
103   /* when running the loop in a thread we want to sleep as long as possible */
104   pollTime = [NSDate distantFuture];
105 #else
106   pollTime = [NSDate distantPast];
107 #endif
108
109   do {
110       event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:pollTime
111           inMode:NSDefaultRunLoopMode dequeue:YES];
112       [NSApp sendEvent:event];
113     }
114   while (event != nil);
115   [pool release];
116 }
117
118 static void
119 gst_osx_videosink_check_main_run_loop (GstOSXVideoSink *sink)
120 {
121   /* check if the main run loop is running */
122   gboolean is_running;
123
124   if (sink->mrl_check_done) {
125     return;
126   }
127   /* the easy way */
128   is_running = [[NSRunLoop mainRunLoop] currentMode] != nil;
129   if (is_running) {
130     goto exit;
131   } else {
132     /* the previous check doesn't always work with main loops that run
133      * cocoa's main run loop manually, like the gdk one, giving false
134      * negatives. This check defers a call to the main thread and waits to
135      * be awaken by this function. */
136     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
137     GstOSXVideoSinkObject * object = (GstOSXVideoSinkObject *) sink->osxvideosinkobject;
138     gint64 abstime;
139
140     g_mutex_lock (&sink->mrl_check_lock);
141     [object performSelectorOnMainThread:
142           @selector(checkMainRunLoop)
143           withObject:nil waitUntilDone:NO];
144     /* Wait 100 ms */
145     abstime = g_get_monotonic_time () + 100 * 1000;
146     is_running = g_cond_wait_until (&sink->mrl_check_cond,
147         &sink->mrl_check_lock, abstime);
148     g_mutex_unlock (&sink->mrl_check_lock);
149
150     [pool release];
151   }
152
153 exit:
154   {
155   GST_DEBUG_OBJECT(sink, "The main runloop %s is running",
156       is_running ? "" : " not ");
157   sink->main_run_loop_running = is_running;
158   sink->mrl_check_done = TRUE;
159   }
160 }
161
162 static void
163 gst_osx_video_sink_run_cocoa_loop (GstOSXVideoSink * sink )
164 {
165   /* Cocoa applications require a main runloop running to dispatch UI
166    * events and process deferred calls to the main thread through
167    * perfermSelectorOnMainThread.
168    * Since the sink needs to create it's own Cocoa window when no
169    * external NSView is passed to the sink through the GstVideoOverlay API,
170    * we need to run the cocoa mainloop somehow.
171    */
172   if (!sink->main_run_loop_running) {
173 #ifdef RUN_NS_APP_THREAD
174     /* run the main runloop in a separate thread */
175
176     /* override [NSThread isMainThread] with our own implementation so that we can
177      * make it believe our dedicated thread is the main thread 
178      */
179     Method origIsMainThread = class_getClassMethod([NSThread class],
180         NSSelectorFromString(@"isMainThread"));
181     Method ourIsMainThread = class_getClassMethod([GstOSXVideoSinkObject class],
182         NSSelectorFromString(@"isMainThread"));
183
184     method_exchangeImplementations(origIsMainThread, ourIsMainThread);
185
186     sink->ns_app_thread = [[NSThread alloc]
187         initWithTarget:sink->osxvideosinkobject
188         selector:@selector(nsAppThread) object:nil];
189     [sink->ns_app_thread start];
190
191     g_mutex_lock (&sink->loop_thread_lock);
192     while (!sink->app_started)
193       g_cond_wait (&sink->loop_thread_cond, &sink->loop_thread_lock);
194     g_mutex_unlock (&sink->loop_thread_lock);
195 #else
196   /* assume that there is a GMainLoop and iterate the main runloop from there
197    */
198     sink->cocoa_timeout = g_timeout_add (10,
199         (GSourceFunc) run_ns_app_loop, NULL);
200 #endif
201   }
202 }
203
204 static void
205 gst_osx_video_sink_stop_cocoa_loop (GstOSXVideoSink * osxvideosink)
206 {
207 #ifndef RUN_NS_APP_THREAD
208   if (osxvideosink->cocoa_timeout)
209     g_source_remove(osxvideosink->cocoa_timeout);
210 #endif
211 }
212
213 /* This function handles osx window creation */
214 static gboolean
215 gst_osx_video_sink_osxwindow_create (GstOSXVideoSink * osxvideosink, gint width,
216     gint height)
217 {
218   NSRect rect;
219   GstOSXWindow *osxwindow = NULL;
220   GstStructure *s;
221   GstMessage *msg;
222   gboolean res = TRUE;
223   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
224
225   g_return_val_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink), FALSE);
226
227   GST_DEBUG_OBJECT (osxvideosink, "Creating new OSX window");
228
229   osxvideosink->osxwindow = osxwindow = g_new0 (GstOSXWindow, 1);
230
231   osxwindow->width = width;
232   osxwindow->height = height;
233   osxwindow->closed = FALSE;
234   osxwindow->internal = FALSE;
235
236   /* Allocate our GstGLView for the window, and then tell the application
237    * about it (hopefully it's listening...) */
238   rect.origin.x = 0.0;
239   rect.origin.y = 0.0;
240   rect.size.width = (float) osxwindow->width;
241   rect.size.height = (float) osxwindow->height;
242   osxwindow->gstview =[[GstGLView alloc] initWithFrame:rect];
243
244   s = gst_structure_new ("have-ns-view",
245      "nsview", G_TYPE_POINTER, osxwindow->gstview,
246      nil);
247
248   msg = gst_message_new_element (GST_OBJECT (osxvideosink), s);
249   gst_element_post_message (GST_ELEMENT (osxvideosink), msg);
250
251   GST_INFO_OBJECT (osxvideosink, "'have-ns-view' message sent");
252
253   osxvideosink->ns_app_thread = [NSThread mainThread];
254   gst_osx_videosink_check_main_run_loop (osxvideosink);
255   gst_osx_video_sink_run_cocoa_loop (osxvideosink);
256   [osxwindow->gstview setMainThread:osxvideosink->ns_app_thread];
257
258   /* check if have-ns-view was handled and osxwindow->gstview was added to a
259    * superview
260    */
261   if ([osxwindow->gstview haveSuperview] == NO) {
262     /* have-ns-view wasn't handled, post prepare-xwindow-id */
263     if (osxvideosink->superview == NULL) {
264       GST_INFO_OBJECT (osxvideosink, "emitting prepare-xwindow-id");
265       gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (osxvideosink));
266     }
267
268     if (osxvideosink->superview != NULL) {
269       /* prepare-xwindow-id was handled, we have the superview in
270        * osxvideosink->superview. We now add osxwindow->gstview to the superview
271        * from the main thread
272        */
273       GST_INFO_OBJECT (osxvideosink, "we have a superview, adding our view to it");
274       gst_osx_video_sink_call_from_main_thread(osxvideosink, osxwindow->gstview,
275           @selector(addToSuperview:), osxvideosink->superview, NO);
276
277     } else {
278       if (osxvideosink->embed) {
279         /* the view wasn't added to a superview. It's possible that the
280          * application handled have-ns-view, stored our view internally and is
281          * going to add it to a superview later (webkit does that now).
282          */
283         GST_INFO_OBJECT (osxvideosink, "no superview");
284       } else {
285         gst_osx_video_sink_call_from_main_thread(osxvideosink,
286           osxvideosink->osxvideosinkobject,
287           @selector(createInternalWindow), nil, YES);
288         GST_INFO_OBJECT (osxvideosink, "No superview, creating an internal window.");
289       }
290     }
291   }
292   [osxwindow->gstview setNavigation: GST_NAVIGATION(osxvideosink)];
293   [osxvideosink->osxwindow->gstview setKeepAspectRatio: osxvideosink->keep_par];
294
295   [pool release];
296
297   return res;
298 }
299
300 static void
301 gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink)
302 {
303   NSAutoreleasePool *pool;
304
305   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
306   pool = [[NSAutoreleasePool alloc] init];
307
308   gst_osx_video_sink_call_from_main_thread(osxvideosink,
309       osxvideosink->osxvideosinkobject,
310       @selector(destroy), (id) nil, YES);
311   gst_osx_video_sink_stop_cocoa_loop (osxvideosink);
312   [pool release];
313 }
314
315 /* This function resizes a GstXWindow */
316 static void
317 gst_osx_video_sink_osxwindow_resize (GstOSXVideoSink * osxvideosink,
318     GstOSXWindow * osxwindow, guint width, guint height)
319 {
320   GstOSXVideoSinkObject *object = osxvideosink->osxvideosinkobject;
321
322   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
323   g_return_if_fail (osxwindow != NULL);
324   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
325
326   osxwindow->width = width;
327   osxwindow->height = height;
328
329   GST_DEBUG_OBJECT (osxvideosink, "Resizing window to (%d,%d)", width, height);
330
331   /* Directly resize the underlying view */
332   GST_DEBUG_OBJECT (osxvideosink, "Calling setVideoSize on %p", osxwindow->gstview);
333   gst_osx_video_sink_call_from_main_thread(osxvideosink, object,
334       @selector(resize), (id)nil, YES);
335
336   [pool release];
337 }
338
339 static gboolean
340 gst_osx_video_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
341 {
342   GstOSXVideoSink *osxvideosink;
343   GstStructure *structure;
344   gboolean res, result = FALSE;
345   gint video_width, video_height;
346
347   osxvideosink = GST_OSX_VIDEO_SINK (bsink);
348
349   GST_DEBUG_OBJECT (osxvideosink, "caps: %" GST_PTR_FORMAT, caps);
350
351   structure = gst_caps_get_structure (caps, 0);
352   res = gst_structure_get_int (structure, "width", &video_width);
353   res &= gst_structure_get_int (structure, "height", &video_height);
354
355   if (!res) {
356     goto beach;
357   }
358
359   GST_DEBUG_OBJECT (osxvideosink, "our format is: %dx%d video",
360       video_width, video_height);
361
362   GST_VIDEO_SINK_WIDTH (osxvideosink) = video_width;
363   GST_VIDEO_SINK_HEIGHT (osxvideosink) = video_height;
364
365   gst_osx_video_sink_osxwindow_resize (osxvideosink, osxvideosink->osxwindow,
366       video_width, video_height);
367   result = TRUE;
368
369 beach:
370   return result;
371
372 }
373
374 static GstStateChangeReturn
375 gst_osx_video_sink_change_state (GstElement * element,
376     GstStateChange transition)
377 {
378   GstOSXVideoSink *osxvideosink;
379   GstStateChangeReturn ret;
380
381   osxvideosink = GST_OSX_VIDEO_SINK (element);
382
383   GST_DEBUG_OBJECT (osxvideosink, "%s => %s", 
384         gst_element_state_get_name(GST_STATE_TRANSITION_CURRENT (transition)),
385         gst_element_state_get_name(GST_STATE_TRANSITION_NEXT (transition)));
386
387   switch (transition) {
388     case GST_STATE_CHANGE_NULL_TO_READY:
389       break;
390     case GST_STATE_CHANGE_READY_TO_PAUSED:
391       /* Creating our window and our image */
392       GST_VIDEO_SINK_WIDTH (osxvideosink) = 320;
393       GST_VIDEO_SINK_HEIGHT (osxvideosink) = 240;
394       if (!gst_osx_video_sink_osxwindow_create (osxvideosink,
395           GST_VIDEO_SINK_WIDTH (osxvideosink),
396           GST_VIDEO_SINK_HEIGHT (osxvideosink))) {
397         ret = GST_STATE_CHANGE_FAILURE;
398         goto done;
399       }
400       break;
401     default:
402       break;
403   }
404
405   ret = (GST_ELEMENT_CLASS (parent_class))->change_state (element, transition);
406
407   switch (transition) {
408     case GST_STATE_CHANGE_PAUSED_TO_READY:
409       GST_VIDEO_SINK_WIDTH (osxvideosink) = 0;
410       GST_VIDEO_SINK_HEIGHT (osxvideosink) = 0;
411       osxvideosink->app_started = FALSE;
412       gst_osx_video_sink_osxwindow_destroy (osxvideosink);
413       break;
414     case GST_STATE_CHANGE_READY_TO_NULL:
415       break;
416     default:
417       break;
418   }
419
420 done:
421   return ret;
422 }
423
424 static GstFlowReturn
425 gst_osx_video_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
426 {
427   GstOSXVideoSink *osxvideosink;
428   GstBufferObject* bufferobject;
429   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
430
431   osxvideosink = GST_OSX_VIDEO_SINK (bsink);
432
433   GST_DEBUG ("show_frame");
434   bufferobject = [[GstBufferObject alloc] initWithBuffer:buf];
435   gst_osx_video_sink_call_from_main_thread(osxvideosink,
436       osxvideosink->osxvideosinkobject,
437       @selector(showFrame:), bufferobject, NO);
438   [pool release];
439   return GST_FLOW_OK;
440 }
441
442 /* Buffer management */
443
444
445
446 /* =========================================== */
447 /*                                             */
448 /*              Init & Class init              */
449 /*                                             */
450 /* =========================================== */
451
452 static void
453 gst_osx_video_sink_set_property (GObject * object, guint prop_id,
454     const GValue * value, GParamSpec * pspec)
455 {
456   GstOSXVideoSink *osxvideosink;
457
458   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
459
460   osxvideosink = GST_OSX_VIDEO_SINK (object);
461
462   switch (prop_id) {
463     case ARG_EMBED:
464       osxvideosink->embed = g_value_get_boolean(value);
465       break;
466     case ARG_FORCE_PAR:
467       osxvideosink->keep_par = g_value_get_boolean(value);
468       if (osxvideosink->osxwindow)
469         [osxvideosink->osxwindow->gstview
470             setKeepAspectRatio: osxvideosink->keep_par];
471       break;
472     default:
473       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
474       break;
475   }
476 }
477
478 static void
479 gst_osx_video_sink_get_property (GObject * object, guint prop_id,
480     GValue * value, GParamSpec * pspec)
481 {
482   GstOSXVideoSink *osxvideosink;
483
484   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
485
486   osxvideosink = GST_OSX_VIDEO_SINK (object);
487
488   switch (prop_id) {
489     case ARG_EMBED:
490       g_value_set_boolean (value, osxvideosink->embed);
491       break;
492     case ARG_FORCE_PAR:
493       g_value_set_boolean (value, osxvideosink->keep_par);
494       break;
495     default:
496       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
497       break;
498   }
499 }
500
501
502 static void
503 gst_osx_video_sink_init (GstOSXVideoSink * sink)
504 {
505   sink->osxwindow = NULL;
506   sink->superview = NULL;
507   sink->osxvideosinkobject = [[GstOSXVideoSinkObject alloc] initWithSink:sink];
508 #ifdef RUN_NS_APP_THREAD
509   g_mutex_init (&sink->loop_thread_lock);
510   g_cond_init (&sink->loop_thread_cond);
511 #endif
512   g_mutex_init (&sink->mrl_check_lock);
513   g_cond_init (&sink->mrl_check_cond);
514   sink->mrl_check_done = FALSE;
515   sink->main_run_loop_running = FALSE;
516   sink->app_started = FALSE;
517   sink->keep_par = FALSE;
518 }
519
520 static void
521 gst_osx_video_sink_base_init (gpointer g_class)
522 {
523   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
524
525   gst_element_class_set_static_metadata (element_class, "OSX Video sink",
526       "Sink/Video", "OSX native videosink",
527       "Zaheer Abbas Merali <zaheerabbas at merali dot org>");
528
529   gst_element_class_add_pad_template (element_class,
530       gst_static_pad_template_get (&gst_osx_video_sink_sink_template_factory));
531 }
532
533 static void
534 gst_osx_video_sink_finalize (GObject *object)
535 {
536   GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (object);
537
538   if (osxvideosink->superview)
539     [osxvideosink->superview release];
540
541   if (osxvideosink->osxvideosinkobject)
542     [(GstOSXVideoSinkObject*)(osxvideosink->osxvideosinkobject) release];
543
544   g_mutex_clear (&osxvideosink->mrl_check_lock);
545   g_cond_clear (&osxvideosink->mrl_check_cond);
546
547   G_OBJECT_CLASS (parent_class)->finalize (object);
548 }
549
550 static void
551 gst_osx_video_sink_class_init (GstOSXVideoSinkClass * klass)
552 {
553   GObjectClass *gobject_class;
554   GstElementClass *gstelement_class;
555   GstBaseSinkClass *gstbasesink_class;
556
557   gobject_class = (GObjectClass *) klass;
558   gstelement_class = (GstElementClass *) klass;
559   gstbasesink_class = (GstBaseSinkClass *) klass;
560
561
562   parent_class = g_type_class_ref (GST_TYPE_VIDEO_SINK);
563
564   gobject_class->set_property = gst_osx_video_sink_set_property;
565   gobject_class->get_property = gst_osx_video_sink_get_property;
566   gobject_class->finalize = gst_osx_video_sink_finalize;
567
568   gstbasesink_class->set_caps = gst_osx_video_sink_setcaps;
569   gstbasesink_class->preroll = gst_osx_video_sink_show_frame;
570   gstbasesink_class->render = gst_osx_video_sink_show_frame;
571   gstelement_class->change_state = gst_osx_video_sink_change_state;
572
573   /**
574    * GstOSXVideoSink:embed
575    *
576    * Set to #TRUE if you are embedding the video window in an application.
577    *
578    **/
579
580   g_object_class_install_property (gobject_class, ARG_EMBED,
581       g_param_spec_boolean ("embed", "embed", "For ABI compatiblity only, do not use",
582           FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
583
584   /**
585    * GstOSXVideoSink:force-aspect-ratio
586    *
587    * When enabled, scaling will respect original aspect ratio.
588    *
589    **/
590
591   g_object_class_install_property (gobject_class, ARG_FORCE_PAR,
592       g_param_spec_boolean ("force-aspect-ratio", "force aspect ration",
593           "When enabled, scaling will respect original aspect ration",
594           TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
595 }
596
597 static void
598 gst_osx_video_sink_navigation_send_event (GstNavigation * navigation,
599     GstStructure * structure)
600 {
601   GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (navigation);
602   GstPad *peer;
603   GstEvent *event;
604   GstVideoRectangle src, dst, result;
605   NSRect bounds;
606   gdouble x, y, xscale = 1.0, yscale = 1.0;
607
608   peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (osxvideosink));
609
610   if (!peer || !osxvideosink->osxwindow)
611     return;
612
613   event = gst_event_new_navigation (structure);
614
615   bounds = [osxvideosink->osxwindow->gstview getDrawingBounds];
616
617   if (osxvideosink->keep_par) {
618     /* We get the frame position using the calculated geometry from _setcaps
619        that respect pixel aspect ratios */
620     src.w = GST_VIDEO_SINK_WIDTH (osxvideosink);
621     src.h = GST_VIDEO_SINK_HEIGHT (osxvideosink);
622     dst.w = bounds.size.width;
623     dst.h = bounds.size.height;
624
625     gst_video_sink_center_rect (src, dst, &result, TRUE);
626     result.x += bounds.origin.x;
627     result.y += bounds.origin.y;
628   } else {
629     result.x = bounds.origin.x;
630     result.y = bounds.origin.y;
631     result.w = bounds.size.width;
632     result.h = bounds.size.height;
633   }
634
635   /* We calculate scaling using the original video frames geometry to include
636      pixel aspect ratio scaling. */
637   xscale = (gdouble) osxvideosink->osxwindow->width / result.w;
638   yscale = (gdouble) osxvideosink->osxwindow->height / result.h;
639
640   /* Converting pointer coordinates to the non scaled geometry */
641   if (gst_structure_get_double (structure, "pointer_x", &x)) {
642     x = MIN (x, result.x + result.w);
643     x = MAX (x - result.x, 0);
644     gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
645         (gdouble) x * xscale, NULL);
646   }
647   if (gst_structure_get_double (structure, "pointer_y", &y)) {
648     y = MIN (y, result.y + result.h);
649     y = MAX (y - result.y, 0);
650     gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE,
651         (gdouble) y * yscale, NULL);
652   }
653
654   gst_pad_send_event (peer, event);
655   gst_object_unref (peer);
656 }
657
658 static void
659 gst_osx_video_sink_navigation_init (GstNavigationInterface * iface)
660 {
661   iface->send_event = gst_osx_video_sink_navigation_send_event;
662 }
663
664 static void
665 gst_osx_video_sink_set_window_handle (GstVideoOverlay * overlay, guintptr handle_id)
666 {
667   GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (overlay);
668   gulong window_id = (gulong) handle_id;
669
670   if (osxvideosink->superview) {
671     GST_INFO_OBJECT (osxvideosink, "old xwindow id %p", osxvideosink->superview);
672     if (osxvideosink->osxwindow) {
673       gst_osx_video_sink_call_from_main_thread(osxvideosink,
674           osxvideosink->osxwindow->gstview,
675           @selector(removeFromSuperview:), (id)nil, YES);
676     }
677     [osxvideosink->superview release];
678
679   }
680
681   GST_INFO_OBJECT (osxvideosink, "set xwindow id 0x%lx", window_id);
682   osxvideosink->superview = [((NSView *) window_id) retain];
683   if (osxvideosink->osxwindow) {
684       gst_osx_video_sink_call_from_main_thread(osxvideosink,
685         osxvideosink->osxwindow->gstview,
686         @selector(addToSuperview:), osxvideosink->superview, YES);
687     if (window_id) {
688       osxvideosink->osxwindow->internal = FALSE;
689     }
690   }
691
692 }
693
694 static void
695 gst_osx_video_sink_xoverlay_init (GstVideoOverlayInterface * iface)
696 {
697   iface->set_window_handle = gst_osx_video_sink_set_window_handle;
698   iface->expose = NULL;
699   iface->handle_events = NULL;
700 }
701
702 /* ============================================================= */
703 /*                                                               */
704 /*                       Public Methods                          */
705 /*                                                               */
706 /* ============================================================= */
707
708 /* =========================================== */
709 /*                                             */
710 /*          Object typing & Creation           */
711 /*                                             */
712 /* =========================================== */
713
714 GType
715 gst_osx_video_sink_get_type (void)
716 {
717   static GType osxvideosink_type = 0;
718
719   if (!osxvideosink_type) {
720     static const GTypeInfo osxvideosink_info = {
721       sizeof (GstOSXVideoSinkClass),
722       gst_osx_video_sink_base_init,
723       NULL,
724       (GClassInitFunc) gst_osx_video_sink_class_init,
725       NULL,
726       NULL,
727       sizeof (GstOSXVideoSink),
728       0,
729       (GInstanceInitFunc) gst_osx_video_sink_init,
730     };
731
732     static const GInterfaceInfo overlay_info = {
733       (GInterfaceInitFunc) gst_osx_video_sink_xoverlay_init,
734       NULL,
735       NULL,
736     };
737
738     static const GInterfaceInfo navigation_info = {
739       (GInterfaceInitFunc) gst_osx_video_sink_navigation_init,
740       NULL,
741       NULL,
742     };
743     osxvideosink_type = g_type_register_static (GST_TYPE_VIDEO_SINK,
744         "GstOSXVideoSink", &osxvideosink_info, 0);
745
746     g_type_add_interface_static (osxvideosink_type, GST_TYPE_VIDEO_OVERLAY,
747         &overlay_info);
748     g_type_add_interface_static (osxvideosink_type, GST_TYPE_NAVIGATION,
749         &navigation_info);
750   }
751
752   return osxvideosink_type;
753 }
754
755 @implementation GstWindowDelegate
756 - (id) initWithSink: (GstOSXVideoSink *) sink
757 {
758   self = [super init];
759   self->osxvideosink = sink;
760   return self;
761 }
762
763 - (void)windowWillClose:(NSNotification *)notification {
764   /* Only handle close events if the window was closed manually by the user
765    * and not becuase of a state change state to READY */
766   if (!osxvideosink->osxwindow->closed) {
767     osxvideosink->osxwindow->closed = TRUE;
768     GST_ELEMENT_ERROR (osxvideosink, RESOURCE, NOT_FOUND, ("Output window was closed"), (NULL));
769     gst_osx_video_sink_osxwindow_destroy(osxvideosink);
770   }
771 }
772
773 @end
774
775 @ implementation GstOSXVideoSinkObject
776
777 -(id) initWithSink: (GstOSXVideoSink*) sink
778 {
779   self = [super init];
780   self->osxvideosink = sink;
781   return self;
782 }
783
784 -(void) createInternalWindow
785 {
786   GstOSXWindow *osxwindow = osxvideosink->osxwindow;
787   ProcessSerialNumber psn;
788   NSRect rect;
789   unsigned int mask;
790
791   osxwindow->internal = TRUE;
792
793   mask =  NSTitledWindowMask             |
794           NSClosableWindowMask           |
795           NSResizableWindowMask          |
796           NSTexturedBackgroundWindowMask |
797           NSMiniaturizableWindowMask;
798
799   rect.origin.x = 100.0;
800   rect.origin.y = 100.0;
801   rect.size.width = (float) osxwindow->width;
802   rect.size.height = (float) osxwindow->height;
803
804 #ifndef RUN_NS_APP_THREAD
805   if (!osxvideosink->app_started) {
806     [NSApplication sharedApplication];
807     [NSApp finishLaunching];
808     osxvideosink->app_started = TRUE;
809   }
810 #endif
811
812   if (!GetCurrentProcess(&psn)) {
813       TransformProcessType(&psn, kProcessTransformToForegroundApplication);
814       SetFrontProcess(&psn);
815   }
816
817   osxwindow->win =[[GstOSXVideoSinkWindow alloc]
818                        initWithContentNSRect: rect
819                        styleMask: mask
820                        backing: NSBackingStoreBuffered
821                        defer: NO
822                        screen: nil];
823   GST_DEBUG("VideoSinkWindow created, %p", osxwindow->win);
824   [osxwindow->win makeKeyAndOrderFront:NSApp];
825   osxwindow->gstview =[osxwindow->win gstView];
826   [osxwindow->win setDelegate:[[GstWindowDelegate alloc]
827       initWithSink:osxvideosink]];
828
829 }
830
831 #ifdef RUN_NS_APP_THREAD
832 + (BOOL) isMainThread
833 {
834   /* FIXME: ideally we should return YES only for ->ns_app_thread here */
835   return YES;
836 }
837 #endif
838
839 - (void) resize
840 {
841   GstOSXWindow *osxwindow = osxvideosink->osxwindow;
842
843   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
844
845   GST_INFO_OBJECT (osxvideosink, "resizing");
846   NSSize size = {osxwindow->width, osxwindow->height};
847   if (osxwindow->internal) {
848     [osxwindow->win setContentSize:size];
849   }
850   if (osxwindow->gstview) {
851       [osxwindow->gstview setVideoSize :(int)osxwindow->width :(int)osxwindow->height];
852   }
853   GST_INFO_OBJECT (osxvideosink, "done");
854
855   [pool release];
856 }
857
858 - (void) showFrame: (GstBufferObject *) object
859 {
860   GstMapInfo info;
861   guint8 *viewdata;
862   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
863   GstBuffer *buf = object->buf;
864
865   if (osxvideosink->osxwindow != NULL)
866   {
867     gst_buffer_map (buf, &info, GST_MAP_READ);
868     viewdata = (guint8 *) [osxvideosink->osxwindow->gstview getTextureBuffer];
869
870     memcpy (viewdata, info.data, info.size);
871     [osxvideosink->osxwindow->gstview displayTexture];
872     gst_buffer_unmap (buf, &info);
873   }
874
875   [object release];
876
877   [pool release];
878 }
879
880 -(void) destroy
881 {
882   NSAutoreleasePool *pool;
883   GstOSXWindow *osxwindow;
884
885   pool = [[NSAutoreleasePool alloc] init];
886
887   osxwindow = osxvideosink->osxwindow;
888   osxvideosink->osxwindow = NULL;
889
890   if (osxwindow) {
891     if (osxvideosink->superview) {
892       [osxwindow->gstview removeFromSuperview];
893     }
894     [osxwindow->gstview release];
895     if (osxwindow->internal) {
896       if (!osxwindow->closed) {
897         osxwindow->closed = TRUE;
898         [osxwindow->win release];
899       }
900     }
901
902     g_free (osxwindow);
903   }
904   [pool release];
905 }
906
907 #ifdef RUN_NS_APP_THREAD
908 -(void) nsAppThread
909 {
910   NSAutoreleasePool *pool;
911   GstOSXVideoSink *sink = osxvideosink;
912
913   /* set the main runloop as the runloop for the current thread. This has the
914    * effect that calling NSApp nextEventMatchingMask:untilDate:inMode:dequeue
915    * runs the main runloop.
916    */
917   _CFRunLoopSetCurrent(CFRunLoopGetMain());
918
919   /* this is needed to make IsMainThread checks in core foundation work from the
920    * current thread
921    */
922   _CFMainPThread = pthread_self();
923
924   pool = [[NSAutoreleasePool alloc] init];
925
926   [NSApplication sharedApplication];
927   [NSApp finishLaunching];
928
929   g_mutex_lock (&sink->loop_thread_lock);
930   sink->app_started = TRUE;
931   g_cond_signal (&sink->loop_thread_cond);
932   g_mutex_unlock (&sink->loop_thread_lock);
933
934   /* run the loop */
935   run_ns_app_loop ();
936
937   [pool release];
938 }
939 #endif
940
941 -(void) checkMainRunLoop
942 {
943   g_mutex_lock (&osxvideosink->mrl_check_lock);
944   g_cond_signal (&osxvideosink->mrl_check_cond);
945   g_mutex_unlock (&osxvideosink->mrl_check_lock);
946 }
947
948 @end
949
950 @ implementation GstBufferObject
951 -(id) initWithBuffer: (GstBuffer*) buffer
952 {
953   self = [super init];
954   gst_buffer_ref(buffer);
955   self->buf = buffer;
956   return self;
957 }
958
959 -(void) dealloc{
960   gst_buffer_unref(buf);
961   [super dealloc];
962 }
963 @end
964
965 static gboolean
966 plugin_init (GstPlugin * plugin)
967 {
968
969   if (!gst_element_register (plugin, "osxvideosink",
970           GST_RANK_PRIMARY, GST_TYPE_OSX_VIDEO_SINK))
971     return FALSE;
972
973   GST_DEBUG_CATEGORY_INIT (gst_debug_osx_video_sink, "osxvideosink", 0,
974       "osxvideosink element");
975
976   return TRUE;
977 }
978
979 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
980     GST_VERSION_MINOR,
981     osxvideo,
982     "OSX native video output plugin",
983     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)