MacOSX/Framework/VLCMediaPlayer.m: Don't put code that should be in libvlc in the...
[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
63     [[VLCEventManager sharedManager] callOnMainThreadObject:self 
64                                                  withMethod:@selector(mediaPlayerTimeChanged:) 
65                                        withArgumentAsObject:[NSNumber numberWithLongLong:event->u.media_instance_time_changed.new_time]];
66
67     [[VLCEventManager sharedManager] callOnMainThreadDelegateOfObject:self
68                                                    withDelegateMethod:@selector(mediaPlayerTimeChanged:)
69                                                  withNotificationName:VLCMediaPlayerTimeChanged];
70     [pool release];
71 }
72
73 static void HandleMediaPositionChanged(const libvlc_event_t * event, void * self)
74 {
75     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
76
77     [[VLCEventManager sharedManager] callOnMainThreadObject:self 
78                                                  withMethod:@selector(mediaPlayerPositionChanged:) 
79                                        withArgumentAsObject:[NSNumber numberWithFloat:event->u.media_instance_position_changed.new_position]];
80     [pool release];
81 }
82
83 static void HandleMediaInstanceStateChanged(const libvlc_event_t * event, void * self)
84 {
85     VLCMediaPlayerState newState;
86
87     if( event->type == libvlc_MediaInstancePlayed )
88         newState = VLCMediaPlayerStatePlaying;
89     else if( event->type == libvlc_MediaInstancePaused )
90         newState = VLCMediaPlayerStatePaused;
91     else if( event->type == libvlc_MediaInstanceReachedEnd )
92         newState = VLCMediaPlayerStateStopped;
93     else
94     {
95         NSLog(@"%s: Unknown event", __FUNCTION__);
96         return;
97     }
98
99     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
100
101     [[VLCEventManager sharedManager] callOnMainThreadObject:self 
102                                                  withMethod:@selector(mediaPlayerStateChanged:) 
103                                        withArgumentAsObject:[NSNumber numberWithInt:newState]];
104
105     [[VLCEventManager sharedManager] callOnMainThreadDelegateOfObject:self
106                                                    withDelegateMethod:@selector(mediaPlayerStateChanged:)
107                                                  withNotificationName:VLCMediaPlayerStateChanged];
108
109     [pool release];
110
111 }
112
113
114 // TODO: Documentation
115 @interface VLCMediaPlayer (Private)
116 - (id)initWithDrawable:(id)aDrawable;
117 - (void)setDrawable:(id)aDrawable;
118
119 - (void)registerObservers;
120 - (void)unregisterObservers;
121 - (void)mediaPlayerTimeChanged:(NSNumber *)newTime;
122 - (void)mediaPlayerPositionChanged:(NSNumber *)newTime;
123 - (void)mediaPlayerStateChanged:(NSNumber *)newState;
124 @end
125
126 @implementation VLCMediaPlayer
127
128 /* Bindings */
129 + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
130 {
131     static NSDictionary * dict = nil;
132     NSSet * superKeyPaths;
133     if( !dict )
134     {
135         dict = [[NSDictionary dictionaryWithObjectsAndKeys:
136             [NSSet setWithObject:@"state"], @"playing",
137             [NSSet setWithObjects:@"state", @"media", nil], @"seekable",
138             [NSSet setWithObjects:@"state", @"media", nil], @"canPause",
139             [NSSet setWithObjects:@"state", @"media", nil], @"description",
140             nil] retain];
141     }
142     if( (superKeyPaths = [super keyPathsForValuesAffectingValueForKey: key]) )
143     {
144         NSMutableSet * ret = [NSMutableSet setWithSet:[dict objectForKey: key]];
145         [ret unionSet:superKeyPaths];
146         return ret;
147     }
148     return [dict objectForKey: key];
149 }
150
151 /* Contructor */
152 - (id)init
153 {
154     return [self initWithDrawable:nil];
155 }
156
157 - (id)initWithVideoView:(VLCVideoView *)aVideoView
158 {
159     return [self initWithDrawable: aVideoView];
160 }
161
162 - (id)initWithVideoLayer:(VLCVideoLayer *)aVideoLayer
163 {
164     return [self initWithDrawable: aVideoLayer];
165 }
166
167 - (void)release
168 {
169     @synchronized(self)
170     {
171         if([self retainCount] <= 1)
172         {
173             /* We must make sure we won't receive new event after an upcoming dealloc
174              * We also may receive a -retain in some event callback that may occcur
175              * Before libvlc_event_detach. So this can't happen in dealloc */
176             [self unregisterObservers];
177         }
178         [super release];
179     }
180 }
181
182 - (void)dealloc
183 {
184     // Always get rid of the delegate first so we can stop sending messages to it
185     // TODO: Should we tell the delegate that we're shutting down?
186     delegate = nil;
187
188     libvlc_media_instance_release((libvlc_media_instance_t *)instance);
189     
190     // Get rid of everything else
191     [media release];
192     [cachedTime release];
193
194     [super dealloc];
195 }
196
197 - (void)setDelegate:(id)value
198 {
199     delegate = value;
200 }
201
202 - (id)delegate
203 {
204     return delegate;
205 }
206
207 - (void)setVideoView:(VLCVideoView *)aVideoView
208 {    
209     [self setDrawable: aVideoView];
210 }
211
212 - (void)setVideoLayer:(VLCVideoLayer *)aVideoLayer
213 {
214     [self setDrawable: aVideoLayer];
215 }
216
217 - (void)setVideoAspectRatio:(char *)value
218 {
219     libvlc_video_set_aspect_ratio( instance, value, NULL );
220 }
221
222 - (char *)videoAspectRatio
223 {
224     libvlc_exception_t ex;
225     libvlc_exception_init( &ex );
226     char * result = libvlc_video_get_aspect_ratio( instance, &ex );
227     catch_exception( &ex );
228     return result;
229 }
230
231 - (void)setVideoSubTitles:(int)value
232 {
233     libvlc_video_set_spu( instance, value, NULL );
234 }
235
236 - (int)videoSubTitles
237 {
238     libvlc_exception_t ex;
239     libvlc_exception_init( &ex );
240     int result = libvlc_video_get_spu( instance, &ex );
241     catch_exception( &ex );
242     return result;
243 }
244
245 - (void)setVideoCropGeometry:(char *)value
246 {
247     libvlc_video_set_crop_geometry( instance, value, NULL );
248 }
249
250 - (char *)videoCropGeometry
251 {
252     libvlc_exception_t ex;
253     libvlc_exception_init( &ex );
254     char * result = libvlc_video_get_crop_geometry( instance, &ex );
255     catch_exception( &ex );
256     return result;
257 }
258
259 - (void)setVideoTeleText:(int)value
260 {
261     libvlc_video_set_teletext( instance, value, NULL );
262 }
263
264 - (int)videoTeleText
265 {
266     libvlc_exception_t ex;
267     libvlc_exception_init( &ex );
268     int result = libvlc_video_get_teletext( instance, &ex );
269     catch_exception( &ex );
270     return result;
271 }
272
273 - (void)setRate:(float)value
274 {
275     libvlc_media_instance_set_rate( instance, value, NULL );
276 }
277
278 - (float)rate
279 {
280     libvlc_exception_t ex;
281     libvlc_exception_init( &ex );
282     float result = libvlc_media_instance_get_rate( instance, &ex );
283     catch_exception( &ex );
284     return result;
285 }
286
287 - (NSSize)videoSize
288 {
289     libvlc_exception_t ex;
290     libvlc_exception_init( &ex );
291     NSSize result = NSMakeSize(libvlc_video_get_height((libvlc_media_instance_t *)instance, &ex),
292                                libvlc_video_get_width((libvlc_media_instance_t *)instance, &ex));
293     catch_exception( &ex );
294     return result;    
295 }
296
297 - (BOOL)hasVideoOut
298 {
299     libvlc_exception_t ex;
300     libvlc_exception_init( &ex );
301     BOOL result = libvlc_media_instance_has_vout((libvlc_media_instance_t *)instance, &ex);
302     if (libvlc_exception_raised( &ex ))
303     {
304         libvlc_exception_clear( &ex );
305         return NO;
306     }
307     else
308         return result;
309 }
310
311 - (float)framesPerSecond
312 {
313     libvlc_exception_t ex;
314     libvlc_exception_init( &ex );
315     float result = libvlc_media_instance_get_fps( (libvlc_media_instance_t *)instance, &ex );
316     catch_exception( &ex );
317     return result;
318 }
319
320 - (void)setTime:(VLCTime *)value
321 {
322     libvlc_exception_t ex;
323     libvlc_exception_init( &ex );
324     // Time is managed in seconds, while duration is managed in microseconds
325     // TODO: Redo VLCTime to provide value numberAsMilliseconds, numberAsMicroseconds, numberAsSeconds, numberAsMinutes, numberAsHours
326     libvlc_media_instance_set_time( (libvlc_media_instance_t *)instance, 
327                                     (value ? [[value numberValue] longLongValue] / 1000 : 0),
328                                     &ex );
329     catch_exception( &ex );
330 }
331
332 - (VLCTime *)time
333 {
334     return cachedTime;
335 }
336
337 - (void)setChapter:(int)value;
338 {
339     libvlc_media_instance_set_chapter( instance, value, NULL );
340 }
341
342 - (int)chapter
343 {
344     libvlc_exception_t ex;
345     libvlc_exception_init( &ex );
346     int result = libvlc_media_instance_get_chapter( instance, &ex );
347     catch_exception( &ex );
348     return result;
349 }
350
351 - (int)countOfChapters
352 {
353     libvlc_exception_t ex;
354     libvlc_exception_init( &ex );
355     int result = libvlc_media_instance_get_chapter_count( instance, &ex );
356     catch_exception( &ex );
357     return result;
358 }
359
360 - (void)setAudioTrack:(int)value
361 {
362     libvlc_audio_set_track( instance, value, NULL );
363 }
364
365 - (int)audioTrack
366 {
367     libvlc_exception_t ex;
368     libvlc_exception_init( &ex );
369     int result = libvlc_audio_get_track( instance, &ex );
370     catch_exception( &ex );
371     return result;
372 }
373
374 - (int)countOfAudioTracks
375 {
376     libvlc_exception_t ex;
377     libvlc_exception_init( &ex );
378     int result = libvlc_audio_get_track_count( instance, &ex );
379     catch_exception( &ex );
380     return result;
381 }
382
383 - (void)setAudioChannel:(int)value
384 {
385     libvlc_audio_set_channel( instance, value, NULL );
386 }
387
388 - (int)audioChannel
389 {
390     libvlc_exception_t ex;
391     libvlc_exception_init( &ex );
392     int result = libvlc_audio_get_channel( instance, &ex );
393     catch_exception( &ex );
394     return result;
395 }
396
397 - (void)setMedia:(VLCMedia *)value
398 {
399     // We only know how to play media files...not media resources with subitems
400     if (media != value && [media subitems] == nil)
401     {
402         if (media && [media compare:value] == NSOrderedSame)
403             return;
404         
405         [media release];
406         media = [value retain];
407
408         libvlc_exception_t ex;
409         libvlc_exception_init( &ex );
410         libvlc_media_instance_set_media_descriptor( instance, [media libVLCMediaDescriptor], &ex );
411         catch_exception( &ex );
412     }
413 }
414
415 - (VLCMedia *)media
416 {
417     return media;
418 }
419
420 - (BOOL)play
421 {    
422     libvlc_exception_t ex;
423     libvlc_exception_init( &ex );
424     libvlc_media_instance_play( (libvlc_media_instance_t *)instance, &ex );
425     catch_exception( &ex );
426     return YES;
427 }
428
429 - (void)pause
430 {
431     if( [NSThread isMainThread] )
432     {
433         /* Hack because we create a dead lock here, when the vout is stopped
434          * and tries to recontact us on the main thread */
435         /* FIXME: to do this properly we need to do some locking. We may want 
436          * to move that to libvlc */
437         [self performSelectorInBackground:@selector(pause) withObject:nil];
438         return;
439     }
440
441     // Return if there is no media available or if the stream is not paused or 
442     // playing something else
443     if (!media || (![self isPlaying] && [self state] != VLCMediaPlayerStatePaused))
444         return;
445
446     // Should never get here.
447     if (!instance)
448         return;
449
450
451     // Pause the stream
452     libvlc_exception_t ex;
453     libvlc_exception_init( &ex );
454     libvlc_media_instance_pause( (libvlc_media_instance_t *)instance, &ex );
455     catch_exception( &ex );
456     
457     // TODO: Should we record the time in case the media instance is destroyed
458     // then rebuilt?
459 }
460
461 - (void)stop
462 {
463     if( [NSThread isMainThread] )
464     {
465         /* Hack because we create a dead lock here, when the vout is stopped
466          * and tries to recontact us on the main thread */
467         /* FIXME: to do this properly we need to do some locking. We may want 
468          * to move that to libvlc */
469         [self performSelectorInBackground:@selector(stop) withObject:nil];
470         return;
471     }
472
473     // Return if there is no media available or if the system is not in play status 
474     // or pause status.
475     if (!media || (![self isPlaying] && [self state] != VLCMediaPlayerStatePaused))
476         return;
477     
478     // The following is not implemented in the core, should I fix it or just
479     // compensate?
480     //    libvlc_exception_t ex;
481     //    libvlc_exception_init( &ex );
482     //    libvlc_media_instance_stop((libvlc_media_instance_t *)instance, &ex);
483     //    catch_exception( &ex );
484     
485     // Pause and reposition to the begining of the stream.
486     [self pause];
487     [self setTime:0];
488     // TODO: Should we pause this or destroy the media instance so that it appears as being "stopped"?
489 }
490
491 - (void)fastForward
492 {
493     [self fastForwardAtRate: 2.0];
494 }
495
496 - (void)fastForwardAtRate:(float)rate
497 {
498     [self setRate:rate];
499 }
500
501 - (void)rewind
502 {
503     [self rewindAtRate: 2.0];
504 }
505
506 - (void)rewindAtRate:(float)rate
507 {
508     [self setRate: -rate];
509 }
510
511 - (BOOL)isPlaying
512 {
513     VLCMediaPlayerState state = [self state];
514     return ((state == VLCMediaPlayerStateOpening) || (state == VLCMediaPlayerStateBuffering) ||
515             (state == VLCMediaPlayerStatePlaying));
516 }
517
518 - (BOOL)willPlay
519 {
520     libvlc_exception_t ex;
521     libvlc_exception_init( &ex );
522     BOOL ret = libvlc_media_instance_will_play( (libvlc_media_instance_t *)instance, &ex );
523     if (libvlc_exception_raised(&ex))
524     {
525         libvlc_exception_clear(&ex);
526         return NO;
527     }
528     else
529         return ret;
530 }
531
532 static const VLCMediaPlayerState libvlc_to_local_state[] =
533 {
534     [libvlc_Stopped]    = VLCMediaPlayerStateStopped,
535     [libvlc_Opening]    = VLCMediaPlayerStateOpening,
536     [libvlc_Buffering]  = VLCMediaPlayerStateBuffering,
537     [libvlc_Playing]    = VLCMediaPlayerStatePlaying,
538     [libvlc_Paused]     = VLCMediaPlayerStatePaused,
539     [libvlc_Ended]      = VLCMediaPlayerStateEnded,
540     [libvlc_Error]      = VLCMediaPlayerStateError
541 };
542
543 - (VLCMediaPlayerState)state
544 {
545     return cachedState;
546 }
547
548 - (float)position
549 {
550     return position;
551 }
552
553 - (void)setPosition:(float)newPosition
554 {
555     libvlc_exception_t ex;
556     libvlc_exception_init( &ex );
557     libvlc_media_instance_set_position( instance, newPosition, &ex );
558     catch_exception( &ex );
559 }
560
561 - (BOOL)isSeekable
562 {
563     libvlc_exception_t ex;
564     libvlc_exception_init( &ex );
565     BOOL ret = libvlc_media_instance_is_seekable( instance, &ex );
566     catch_exception( &ex );
567     return ret;
568 }
569
570 - (BOOL)canPause
571 {
572     libvlc_exception_t ex;
573     libvlc_exception_init( &ex );
574     BOOL ret = libvlc_media_instance_can_pause( instance, &ex );
575     catch_exception( &ex );
576     return ret;
577 }
578 @end
579
580 @implementation VLCMediaPlayer (Private)
581 - (id)initWithDrawable:(id)aDrawable
582 {
583     if (self = [super init])
584     {
585         delegate = nil;
586         media = nil;
587         cachedTime = [[VLCTime nullTime] retain];
588         position = 0.0f;
589         cachedState = VLCMediaPlayerStateStopped;
590
591         // Create a media instance, it doesn't matter what library we start off with
592         // it will change depending on the media descriptor provided to the media
593         // instance
594         libvlc_exception_t ex;
595         libvlc_exception_init( &ex );
596         instance = (void *)libvlc_media_instance_new([VLCLibrary sharedInstance], &ex);
597         catch_exception( &ex );
598         
599         [self registerObservers];
600         
601         [self setDrawable:aDrawable];
602     }
603     return self;
604 }
605
606 - (void)setDrawable:(id)aDrawable
607 {
608     // Make sure that this instance has been associated with the drawing canvas.
609     libvlc_exception_t ex;
610     libvlc_exception_init( &ex );
611     libvlc_media_instance_set_drawable ((libvlc_media_instance_t *)instance, 
612                                         (libvlc_drawable_t)aDrawable, 
613                                         &ex);
614     catch_exception( &ex );
615 }
616
617 - (void)registerObservers
618 {
619     libvlc_exception_t ex;
620     libvlc_exception_init( &ex );
621
622     // Attach event observers into the media instance
623     libvlc_event_manager_t * p_em = libvlc_media_instance_event_manager( instance, &ex );
624     libvlc_event_attach( p_em, libvlc_MediaInstancePlayed,          HandleMediaInstanceStateChanged, self, &ex );
625     libvlc_event_attach( p_em, libvlc_MediaInstancePaused,          HandleMediaInstanceStateChanged, self, &ex );
626     libvlc_event_attach( p_em, libvlc_MediaInstanceReachedEnd,      HandleMediaInstanceStateChanged, self, &ex );
627     /* FIXME: We may want to turn that off when none is interested by that */
628     libvlc_event_attach( p_em, libvlc_MediaInstancePositionChanged, HandleMediaPositionChanged,      self, &ex );
629     libvlc_event_attach( p_em, libvlc_MediaInstanceTimeChanged,     HandleMediaTimeChanged,          self, &ex );
630     catch_exception( &ex );
631 }
632
633 - (void)unregisterObservers
634 {
635     libvlc_event_manager_t * p_em = libvlc_media_instance_event_manager( instance, NULL );
636     libvlc_event_detach( p_em, libvlc_MediaInstancePlayed,          HandleMediaInstanceStateChanged, self, NULL );
637     libvlc_event_detach( p_em, libvlc_MediaInstancePaused,          HandleMediaInstanceStateChanged, self, NULL );
638     libvlc_event_detach( p_em, libvlc_MediaInstanceReachedEnd,      HandleMediaInstanceStateChanged, self, NULL );
639     libvlc_event_detach( p_em, libvlc_MediaInstancePositionChanged, HandleMediaTimeChanged,            self, NULL );
640     libvlc_event_detach( p_em, libvlc_MediaInstanceTimeChanged,     HandleMediaTimeChanged,            self, NULL );
641 }
642
643 - (void)mediaPlayerTimeChanged:(NSNumber *)newTime
644 {
645     [self willChangeValueForKey:@"time"];
646     [cachedTime release];
647     cachedTime = [[VLCTime timeWithNumber:newTime] retain];
648     [self didChangeValueForKey:@"time"];
649 }
650
651 - (void)mediaPlayerPositionChanged:(NSNumber *)newPosition
652 {
653     if( [newPosition floatValue] - position < 0.005 && position - [newPosition floatValue] < 0.005 )
654         return; /* Forget that, this is too much precision for our uses */
655     [self willChangeValueForKey:@"position"];
656     position = ((float)((int)([newPosition floatValue]*1000)))/1000.;
657     [self didChangeValueForKey:@"position"];
658 }
659
660 - (void)mediaPlayerStateChanged:(NSNumber *)newState
661 {
662     [self willChangeValueForKey:@"state"];
663     cachedState = [newState intValue];
664     [self didChangeValueForKey:@"state"];
665 }
666 @end