MacOSX/Framework: Properly convert libvlc Exception to NSException. Rename quit_on_ex...
[vlc.git] / extras / MacOSX / Framework / Sources / VLCMediaPlayer.m
1 /*****************************************************************************
2  * VLCMediaPlayer.m: VLC.framework VLCMediaPlayer implementation
3  *****************************************************************************
4  * Copyright (C) 2007 Pierre d'Herbemont
5  * Copyright (C) 2007 the VideoLAN team
6  * $Id$
7  *
8  * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
9  *          Faustion Osuna <enrique.osuna # gmail.com>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 #import "VLCLibrary.h"
27 #import "VLCMediaPlayer.h"
28 #import "VLCEventManager.h"
29 #import "VLCLibVLCBridging.h"
30 #import "VLCVideoView.h"
31 #include <vlc/vlc.h>
32
33 /* Notification Messages */
34 NSString * VLCMediaPlayerTimeChanged    = @"VLCMediaPlayerTimeChanged";
35 NSString * VLCMediaPlayerStateChanged   = @"VLCMediaPlayerStateChanged";
36
37 NSString * VLCMediaPlayerStateToString(VLCMediaPlayerState state)
38 {
39     static NSString * stateToStrings[] = {
40         [VLCMediaPlayerStateStopped]      = @"VLCMediaPlayerStateStopped",
41         [VLCMediaPlayerStateOpening]      = @"VLCMediaPlayerStateOpening",
42         [VLCMediaPlayerStateBuffering]    = @"VLCMediaPlayerStateBuffering",
43         [VLCMediaPlayerStateEnded]        = @"VLCMediaPlayerStateEnded",
44         [VLCMediaPlayerStateError]        = @"VLCMediaPlayerStateError",
45         [VLCMediaPlayerStatePlaying]      = @"VLCMediaPlayerStatePlaying",
46         [VLCMediaPlayerStatePaused]       = @"VLCMediaPlayerStatePaused"
47     };
48     return stateToStrings[state];
49 }
50
51 /* libvlc event callback */
52 static void HandleMediaInstanceVolumeChanged(const libvlc_event_t * event, void * self)
53 {
54     [[VLCEventManager sharedManager] callOnMainThreadDelegateOfObject:self
55                                                    withDelegateMethod:@selector(mediaPlayerVolumeChanged:)
56                                                  withNotificationName:VLCMediaPlayerVolumeChanged];
57 }
58
59 static void HandleMediaTimeChanged(const libvlc_event_t * event, void * self)
60 {
61     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
62     [[VLCEventManager sharedManager] callOnMainThreadObject:self 
63                                                  withMethod:@selector(mediaPlayerTimeChanged:) 
64                                        withArgumentAsObject:[NSNumber numberWithLongLong:event->u.media_instance_time_changed.new_time]];
65
66     [[VLCEventManager sharedManager] callOnMainThreadDelegateOfObject:self
67                                                    withDelegateMethod:@selector(mediaPlayerTimeChanged:)
68                                                  withNotificationName:VLCMediaPlayerTimeChanged];
69     [pool release];
70 }
71
72 static void HandleMediaPositionChanged(const libvlc_event_t * event, void * self)
73 {
74     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
75
76     [[VLCEventManager sharedManager] callOnMainThreadObject:self 
77                                                  withMethod:@selector(mediaPlayerPositionChanged:) 
78                                        withArgumentAsObject:[NSNumber numberWithFloat:event->u.media_instance_position_changed.new_position]];
79     [pool release];
80 }
81
82 static void HandleMediaInstanceStateChanged(const libvlc_event_t * event, void * self)
83 {
84     VLCMediaPlayerState newState;
85     
86     if( event->type == libvlc_MediaInstancePlayed )
87         newState = VLCMediaPlayerStatePlaying;
88     else if( event->type == libvlc_MediaInstancePaused )
89         newState = VLCMediaPlayerStatePaused;
90     else if( event->type == libvlc_MediaInstanceReachedEnd )
91         newState = VLCMediaPlayerStateStopped;
92     else
93     {
94         NSLog(@"%s: Unknown event", __FUNCTION__);
95         return;
96     }
97
98     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
99
100     [[VLCEventManager sharedManager] callOnMainThreadObject:self 
101                                                  withMethod:@selector(mediaPlayerStateChanged:) 
102                                        withArgumentAsObject:[NSNumber numberWithInt:newState]];
103
104     [[VLCEventManager sharedManager] callOnMainThreadDelegateOfObject:self
105                                                    withDelegateMethod:@selector(mediaPlayerStateChanged:)
106                                                  withNotificationName:VLCMediaPlayerStateChanged];
107
108     [pool release];
109
110 }
111
112
113 // TODO: Documentation
114 @interface VLCMediaPlayer (Private)
115 - (void)registerObservers;
116 - (void)unregisterObservers;
117 - (void)mediaPlayerTimeChanged:(NSNumber *)newTime;
118 - (void)mediaPlayerPositionChanged:(NSNumber *)newTime;
119 - (void)mediaPlayerStateChanged:(NSNumber *)newState;
120 @end
121
122 @implementation VLCMediaPlayer
123
124 - (id)init
125 {
126     return [self initWithVideoView:nil];
127 }
128
129 - (id)initWithVideoView:(VLCVideoView *)aVideoView
130 {
131     if (self = [super init])
132     {
133         [VLCMediaPlayer setKeys:[NSArray arrayWithObject:@"state"] triggerChangeNotificationsForDependentKey:@"playing"];
134         [VLCMediaPlayer setKeys:[NSArray arrayWithObjects:@"state", @"media", nil] triggerChangeNotificationsForDependentKey:@"seekable"];
135         delegate = nil;
136         media = nil;
137         cachedTime = [[VLCTime nullTime] retain];
138         position = 0.0f;
139         cachedState = VLCMediaPlayerStateStopped;
140
141         // Create a media instance, it doesn't matter what library we start off with
142         // it will change depending on the media descriptor provided to the media
143         // instance
144         libvlc_exception_t ex;
145         libvlc_exception_init( &ex );
146         instance = (void *)libvlc_media_instance_new([VLCLibrary sharedInstance], &ex);
147         catch_exception( &ex );
148         
149         [self registerObservers];
150         
151         [self setVideoView:aVideoView];
152     }
153     return self;
154 }
155
156 - (void)release
157 {
158     @synchronized(self)
159     {
160         if([self retainCount] <= 1)
161         {
162             /* We must make sure we won't receive new event after an upcoming dealloc
163              * We also may receive a -retain in some event callback that may occcur
164              * Before libvlc_event_detach. So this can't happen in dealloc */
165             [self unregisterObservers];
166         }
167         [super release];
168     }
169 }
170
171 - (void)dealloc
172 {
173     // Always get rid of the delegate first so we can stop sending messages to it
174     // TODO: Should we tell the delegate that we're shutting down?
175     delegate = nil;
176
177     libvlc_media_instance_release((libvlc_media_instance_t *)instance);
178     
179     // Get rid of everything else
180     [media release];
181     [cachedTime release];
182
183     [super dealloc];
184 }
185
186 - (void)setDelegate:(id)value
187 {
188     delegate = value;
189 }
190
191 - (id)delegate
192 {
193     return delegate;
194 }
195
196 - (void)setVideoView:(VLCVideoView *)value
197 {
198     videoView = value;
199     
200     // Make sure that this instance has been associated with the drawing canvas.
201     libvlc_exception_t ex;
202     libvlc_exception_init( &ex );
203     libvlc_media_instance_set_drawable ((libvlc_media_instance_t *)instance, 
204                                         (libvlc_drawable_t)videoView, 
205                                         &ex);
206     catch_exception( &ex );
207 }
208
209 - (VLCVideoView *)videoView
210 {
211     return videoView;
212 }
213
214 - (void)setFullscreen:(BOOL)value
215 {
216     libvlc_set_fullscreen(instance, value, NULL);
217 }
218
219 - (BOOL)fullscreen
220 {
221     libvlc_exception_t ex;
222     libvlc_exception_init( &ex );
223     int result = libvlc_get_fullscreen( instance, &ex );
224     catch_exception( &ex );
225     return result;
226 }
227
228 - (void)setVideoAspectRatio:(char *)value
229 {
230     libvlc_video_set_aspect_ratio( instance, value, NULL );
231 }
232
233 - (char *)videoAspectRatio
234 {
235     libvlc_exception_t ex;
236     libvlc_exception_init( &ex );
237     char * result = libvlc_video_get_aspect_ratio( instance, &ex );
238     catch_exception( &ex );
239     return result;
240 }
241
242 - (void)setVideoSubTitles:(int)value
243 {
244     libvlc_video_set_spu( instance, value, NULL );
245 }
246
247 - (int)videoSubTitles
248 {
249     libvlc_exception_t ex;
250     libvlc_exception_init( &ex );
251     int result = libvlc_video_get_spu( instance, &ex );
252     catch_exception( &ex );
253     return result;
254 }
255
256 - (void)setVideoCropGeometry:(char *)value
257 {
258     libvlc_video_set_crop_geometry( instance, value, NULL );
259 }
260
261 - (char *)videoCropGeometry
262 {
263     libvlc_exception_t ex;
264     libvlc_exception_init( &ex );
265     char * result = libvlc_video_get_crop_geometry( instance, &ex );
266     catch_exception( &ex );
267     return result;
268 }
269
270 - (void)setVideoTeleText:(int)value
271 {
272     libvlc_video_set_teletext( instance, value, NULL );
273 }
274
275 - (int)videoTeleText
276 {
277     libvlc_exception_t ex;
278     libvlc_exception_init( &ex );
279     int result = libvlc_video_get_teletext( instance, &ex );
280     catch_exception( &ex );
281     return result;
282 }
283
284 - (void)setRate:(int)value
285 {
286     libvlc_media_instance_set_rate( instance, value, NULL );
287 }
288
289 - (int)rate
290 {
291     libvlc_exception_t ex;
292     libvlc_exception_init( &ex );
293     float result = libvlc_media_instance_get_rate( instance, &ex );
294     catch_exception( &ex );
295     return result;
296 }
297
298 - (NSSize)videoSize
299 {
300     libvlc_exception_t ex;
301     libvlc_exception_init( &ex );
302     NSSize result = NSMakeSize(libvlc_video_get_height((libvlc_media_instance_t *)instance, &ex),
303                                libvlc_video_get_width((libvlc_media_instance_t *)instance, &ex));
304     catch_exception( &ex );
305     return result;    
306 }
307
308 - (BOOL)hasVideoOut
309 {
310     libvlc_exception_t ex;
311     libvlc_exception_init( &ex );
312     BOOL result = libvlc_media_instance_has_vout((libvlc_media_instance_t *)instance, &ex);
313     if (libvlc_exception_raised( &ex ))
314     {
315         libvlc_exception_clear( &ex );
316         return NO;
317     }
318     else
319         return result;
320 }
321
322 - (float)framesPerSecond
323 {
324     libvlc_exception_t ex;
325     libvlc_exception_init( &ex );
326     float result = libvlc_media_instance_get_fps( (libvlc_media_instance_t *)instance, &ex );
327     catch_exception( &ex );
328     return result;
329 }
330
331 - (void)setTime:(VLCTime *)value
332 {
333     libvlc_exception_t ex;
334     libvlc_exception_init( &ex );
335     // Time is managed in seconds, while duration is managed in microseconds
336     // TODO: Redo VLCTime to provide value numberAsMilliseconds, numberAsMicroseconds, numberAsSeconds, numberAsMinutes, numberAsHours
337     libvlc_media_instance_set_time( (libvlc_media_instance_t *)instance, 
338                                     (value ? [[value numberValue] longLongValue] / 1000 : 0),
339                                     &ex );
340     catch_exception( &ex );
341 }
342
343 - (VLCTime *)time
344 {
345     return cachedTime;
346 }
347
348 - (void)setChapter:(int)value;
349 {
350     libvlc_media_instance_set_chapter( instance, value, NULL );
351 }
352
353 - (int)chapter
354 {
355     libvlc_exception_t ex;
356     libvlc_exception_init( &ex );
357     int result = libvlc_media_instance_get_chapter( instance, &ex );
358     catch_exception( &ex );
359     return result;
360 }
361
362 - (int)countOfChapters
363 {
364     libvlc_exception_t ex;
365     libvlc_exception_init( &ex );
366     int result = libvlc_media_instance_get_chapter_count( instance, &ex );
367     catch_exception( &ex );
368     return result;
369 }
370
371 - (void)setAudioTrack:(int)value
372 {
373     libvlc_audio_set_track( instance, value, NULL );
374 }
375
376 - (int)audioTrack
377 {
378     libvlc_exception_t ex;
379     libvlc_exception_init( &ex );
380     int result = libvlc_audio_get_track( instance, &ex );
381     catch_exception( &ex );
382     return result;
383 }
384
385 - (int)countOfAudioTracks
386 {
387     libvlc_exception_t ex;
388     libvlc_exception_init( &ex );
389     int result = libvlc_audio_get_track_count( instance, &ex );
390     catch_exception( &ex );
391     return result;
392 }
393
394 - (void)setAudioChannel:(int)value
395 {
396     libvlc_audio_set_channel( instance, value, NULL );
397 }
398
399 - (int)audioChannel
400 {
401     libvlc_exception_t ex;
402     libvlc_exception_init( &ex );
403     int result = libvlc_audio_get_channel( instance, &ex );
404     catch_exception( &ex );
405     return result;
406 }
407
408 - (void)setMedia:(VLCMedia *)value
409 {
410     // We only know how to play media files...not media resources with subitems
411     if (media != value && [media subitems] == nil)
412     {
413         if (media && [media compare:value] == NSOrderedSame)
414             return;
415         
416         BOOL wasPlaying;
417         if (wasPlaying = [self isPlaying])
418         {
419             [self pause];
420 //            // TODO: Should we wait until it stops playing?
421 //            while ([self isPlaying])
422 //                usleep(1000);
423         }
424         
425         [media release];
426         media = [value retain];
427
428         libvlc_exception_t ex;
429         libvlc_exception_init( &ex );
430         libvlc_media_instance_set_media_descriptor( instance, [media libVLCMediaDescriptor], &ex );
431         catch_exception( &ex );
432     }
433 }
434
435 - (VLCMedia *)media
436 {
437     return media;
438 }
439
440 - (BOOL)play
441 {    
442     libvlc_exception_t ex;
443     libvlc_exception_init( &ex );
444     libvlc_media_instance_play( (libvlc_media_instance_t *)instance, &ex );
445     catch_exception( &ex );
446     return YES;
447 }
448
449 - (void)pause
450 {
451     // Return if there is no media available or if the stream is not paused or 
452     // playing something else
453     if (!media || (![self isPlaying] && [self state] != VLCMediaPlayerStatePaused))
454         return;
455
456     // Should never get here.
457     if (!instance)
458         return;
459
460     // Pause the stream
461     libvlc_exception_t ex;
462     libvlc_exception_init( &ex );
463     libvlc_media_instance_pause( (libvlc_media_instance_t *)instance, &ex );
464     catch_exception( &ex );
465     
466     // TODO: Should we record the time in case the media instance is destroyed
467     // then rebuilt?
468 }
469
470 - (void)stop
471 {
472     // Return if there is no media available or if the system is not in play status 
473     // or pause status.
474     if (!media || (![self isPlaying] && [self state] != VLCMediaPlayerStatePaused))
475         return;
476     
477     // The following is not implemented in the core, should I fix it or just
478     // compensate?
479     //    libvlc_exception_t ex;
480     //    libvlc_exception_init( &ex );
481     //    libvlc_media_instance_stop((libvlc_media_instance_t *)instance, &ex);
482     //    catch_exception( &ex );
483     
484     // Pause and reposition to the begining of the stream.
485     [self pause];
486     [self setTime:0];
487     // TODO: Should we pause this or destroy the media instance so that it appears as being "stopped"?
488 }
489
490 //- (void)fastForward;
491 //- (void)fastForwardAtRate:(int)rate;
492 //- (void)rewind;
493 //- (void)rewindAtRate:(int)rate;
494
495 - (BOOL)isPlaying
496 {
497     VLCMediaPlayerState state = [self state];
498     return ((state == VLCMediaPlayerStateOpening) || (state == VLCMediaPlayerStateBuffering) ||
499             (state == VLCMediaPlayerStatePlaying));
500 }
501
502 - (BOOL)willPlay
503 {
504     libvlc_exception_t ex;
505     libvlc_exception_init( &ex );
506     BOOL ret = libvlc_media_instance_will_play( (libvlc_media_instance_t *)instance, &ex );
507     if (libvlc_exception_raised(&ex))
508     {
509         libvlc_exception_clear(&ex);
510         return NO;
511     }
512     else
513         return ret;
514 }
515
516 static const VLCMediaPlayerState libvlc_to_local_state[] =
517 {
518     [libvlc_Stopped]    = VLCMediaPlayerStateStopped,
519     [libvlc_Opening]    = VLCMediaPlayerStateOpening,
520     [libvlc_Buffering]  = VLCMediaPlayerStateBuffering,
521     [libvlc_Playing]    = VLCMediaPlayerStatePlaying,
522     [libvlc_Paused]     = VLCMediaPlayerStatePaused,
523     [libvlc_Ended]      = VLCMediaPlayerStateEnded,
524     [libvlc_Error]      = VLCMediaPlayerStateError
525 };
526
527 - (VLCMediaPlayerState)state
528 {
529     return cachedState;
530 }
531
532 - (float)position
533 {
534     return position;
535 }
536
537 - (void)setPosition:(float)newPosition
538 {
539     libvlc_exception_t ex;
540     libvlc_exception_init( &ex );
541     libvlc_media_instance_set_position( instance, newPosition, &ex );
542     catch_exception( &ex );
543 }
544
545 - (BOOL)isSeekable
546 {
547     libvlc_exception_t ex;
548     libvlc_exception_init( &ex );
549     BOOL ret = libvlc_media_instance_is_seekable( instance, &ex );
550     catch_exception( &ex );
551     return ret;
552 }
553
554 @end
555
556 @implementation VLCMediaPlayer (Private)
557 - (void)registerObservers
558 {
559     libvlc_exception_t ex;
560     libvlc_exception_init( &ex );
561
562     // Attach event observers into the media instance
563     libvlc_event_manager_t * p_em = libvlc_media_instance_event_manager( instance, &ex );
564     libvlc_event_attach( p_em, libvlc_MediaInstancePlayed,          HandleMediaInstanceStateChanged, self, &ex );
565     libvlc_event_attach( p_em, libvlc_MediaInstancePaused,          HandleMediaInstanceStateChanged, self, &ex );
566     libvlc_event_attach( p_em, libvlc_MediaInstanceReachedEnd,      HandleMediaInstanceStateChanged, self, &ex );
567     /* FIXME: We may want to turn that off when none is interested by that */
568     libvlc_event_attach( p_em, libvlc_MediaInstancePositionChanged, HandleMediaPositionChanged,      self, &ex );
569     libvlc_event_attach( p_em, libvlc_MediaInstanceTimeChanged,     HandleMediaTimeChanged,          self, &ex );
570     catch_exception( &ex );
571 }
572
573 - (void)unregisterObservers
574 {
575     libvlc_event_manager_t * p_em = libvlc_media_instance_event_manager( instance, NULL );
576     libvlc_event_detach( p_em, libvlc_MediaInstancePlayed,          HandleMediaInstanceStateChanged, self, NULL );
577     libvlc_event_detach( p_em, libvlc_MediaInstancePaused,          HandleMediaInstanceStateChanged, self, NULL );
578     libvlc_event_detach( p_em, libvlc_MediaInstanceReachedEnd,      HandleMediaInstanceStateChanged, self, NULL );
579     libvlc_event_detach( p_em, libvlc_MediaInstancePositionChanged, HandleMediaTimeChanged,            self, NULL );
580     libvlc_event_detach( p_em, libvlc_MediaInstanceTimeChanged,     HandleMediaTimeChanged,            self, NULL );
581 }
582
583 - (void)mediaPlayerTimeChanged:(NSNumber *)newTime
584 {
585     [self willChangeValueForKey:@"time"];
586     [cachedTime release];
587     cachedTime = [[VLCTime timeWithNumber:newTime] retain];
588     [self didChangeValueForKey:@"time"];
589 }
590
591 - (void)mediaPlayerPositionChanged:(NSNumber *)newPosition
592 {
593     if( [newPosition floatValue] - position < 0.005 && position - [newPosition floatValue] < 0.005 )
594         return; /* Forget that, this is too much precision for our uses */
595     [self willChangeValueForKey:@"position"];
596     position = ((float)((int)([newPosition floatValue]*1000)))/1000.;
597     [self didChangeValueForKey:@"position"];
598 }
599
600 - (void)mediaPlayerStateChanged:(NSNumber *)newState
601 {
602     [self willChangeValueForKey:@"state"];
603     cachedState = [newState intValue];
604     [self didChangeValueForKey:@"state"];
605 }
606 @end