37150999b5f4daf78a824e513b688b7ad8bea4cb
[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         BOOL wasPlaying;
406         if (wasPlaying = [self isPlaying])
407         {
408             [self pause];
409 //            // TODO: Should we wait until it stops playing?
410 //            while ([self isPlaying])
411 //                usleep(1000);
412         }
413         
414         [media release];
415         media = [value retain];
416
417         libvlc_exception_t ex;
418         libvlc_exception_init( &ex );
419         libvlc_media_instance_set_media_descriptor( instance, [media libVLCMediaDescriptor], &ex );
420         catch_exception( &ex );
421     }
422 }
423
424 - (VLCMedia *)media
425 {
426     return media;
427 }
428
429 - (BOOL)play
430 {    
431     libvlc_exception_t ex;
432     libvlc_exception_init( &ex );
433     libvlc_media_instance_play( (libvlc_media_instance_t *)instance, &ex );
434     catch_exception( &ex );
435     return YES;
436 }
437
438 - (void)pause
439 {
440     if( [NSThread isMainThread] )
441     {
442         /* Hack because we create a dead lock here, when the vout is stopped
443          * and tries to recontact us on the main thread */
444         /* FIXME: to do this properly we need to do some locking. We may want 
445          * to move that to libvlc */
446         [self performSelectorInBackground:@selector(pause) withObject:nil];
447         return;
448     }
449
450     // Return if there is no media available or if the stream is not paused or 
451     // playing something else
452     if (!media || (![self isPlaying] && [self state] != VLCMediaPlayerStatePaused))
453         return;
454
455     // Should never get here.
456     if (!instance)
457         return;
458
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     if( [NSThread isMainThread] )
473     {
474         /* Hack because we create a dead lock here, when the vout is stopped
475          * and tries to recontact us on the main thread */
476         /* FIXME: to do this properly we need to do some locking. We may want 
477          * to move that to libvlc */
478         [self performSelectorInBackground:@selector(pause) withObject:nil];
479         return;
480     }
481
482     // Return if there is no media available or if the system is not in play status 
483     // or pause status.
484     if (!media || (![self isPlaying] && [self state] != VLCMediaPlayerStatePaused))
485         return;
486     
487     // The following is not implemented in the core, should I fix it or just
488     // compensate?
489     //    libvlc_exception_t ex;
490     //    libvlc_exception_init( &ex );
491     //    libvlc_media_instance_stop((libvlc_media_instance_t *)instance, &ex);
492     //    catch_exception( &ex );
493     
494     // Pause and reposition to the begining of the stream.
495     [self pause];
496     [self setTime:0];
497     // TODO: Should we pause this or destroy the media instance so that it appears as being "stopped"?
498 }
499
500 - (void)fastForward
501 {
502     [self fastForwardAtRate: 2.0];
503 }
504
505 - (void)fastForwardAtRate:(float)rate
506 {
507     [self setRate:rate];
508 }
509
510 - (void)rewind
511 {
512     [self rewindAtRate: 2.0];
513 }
514
515 - (void)rewindAtRate:(float)rate
516 {
517     [self setRate: -rate];
518 }
519
520 - (BOOL)isPlaying
521 {
522     VLCMediaPlayerState state = [self state];
523     return ((state == VLCMediaPlayerStateOpening) || (state == VLCMediaPlayerStateBuffering) ||
524             (state == VLCMediaPlayerStatePlaying));
525 }
526
527 - (BOOL)willPlay
528 {
529     libvlc_exception_t ex;
530     libvlc_exception_init( &ex );
531     BOOL ret = libvlc_media_instance_will_play( (libvlc_media_instance_t *)instance, &ex );
532     if (libvlc_exception_raised(&ex))
533     {
534         libvlc_exception_clear(&ex);
535         return NO;
536     }
537     else
538         return ret;
539 }
540
541 static const VLCMediaPlayerState libvlc_to_local_state[] =
542 {
543     [libvlc_Stopped]    = VLCMediaPlayerStateStopped,
544     [libvlc_Opening]    = VLCMediaPlayerStateOpening,
545     [libvlc_Buffering]  = VLCMediaPlayerStateBuffering,
546     [libvlc_Playing]    = VLCMediaPlayerStatePlaying,
547     [libvlc_Paused]     = VLCMediaPlayerStatePaused,
548     [libvlc_Ended]      = VLCMediaPlayerStateEnded,
549     [libvlc_Error]      = VLCMediaPlayerStateError
550 };
551
552 - (VLCMediaPlayerState)state
553 {
554     return cachedState;
555 }
556
557 - (float)position
558 {
559     return position;
560 }
561
562 - (void)setPosition:(float)newPosition
563 {
564     libvlc_exception_t ex;
565     libvlc_exception_init( &ex );
566     libvlc_media_instance_set_position( instance, newPosition, &ex );
567     catch_exception( &ex );
568 }
569
570 - (BOOL)isSeekable
571 {
572     libvlc_exception_t ex;
573     libvlc_exception_init( &ex );
574     BOOL ret = libvlc_media_instance_is_seekable( instance, &ex );
575     catch_exception( &ex );
576     return ret;
577 }
578
579 - (BOOL)canPause
580 {
581     libvlc_exception_t ex;
582     libvlc_exception_init( &ex );
583     BOOL ret = libvlc_media_instance_can_pause( instance, &ex );
584     catch_exception( &ex );
585     return ret;
586 }
587 @end
588
589 @implementation VLCMediaPlayer (Private)
590 - (id)initWithDrawable:(id)aDrawable
591 {
592     if (self = [super init])
593     {
594         delegate = nil;
595         media = nil;
596         cachedTime = [[VLCTime nullTime] retain];
597         position = 0.0f;
598         cachedState = VLCMediaPlayerStateStopped;
599
600         // Create a media instance, it doesn't matter what library we start off with
601         // it will change depending on the media descriptor provided to the media
602         // instance
603         libvlc_exception_t ex;
604         libvlc_exception_init( &ex );
605         instance = (void *)libvlc_media_instance_new([VLCLibrary sharedInstance], &ex);
606         catch_exception( &ex );
607         
608         [self registerObservers];
609         
610         [self setDrawable:aDrawable];
611     }
612     return self;
613 }
614
615 - (void)setDrawable:(id)aDrawable
616 {
617     // Make sure that this instance has been associated with the drawing canvas.
618     libvlc_exception_t ex;
619     libvlc_exception_init( &ex );
620     libvlc_media_instance_set_drawable ((libvlc_media_instance_t *)instance, 
621                                         (libvlc_drawable_t)aDrawable, 
622                                         &ex);
623     catch_exception( &ex );
624 }
625
626 - (void)registerObservers
627 {
628     libvlc_exception_t ex;
629     libvlc_exception_init( &ex );
630
631     // Attach event observers into the media instance
632     libvlc_event_manager_t * p_em = libvlc_media_instance_event_manager( instance, &ex );
633     libvlc_event_attach( p_em, libvlc_MediaInstancePlayed,          HandleMediaInstanceStateChanged, self, &ex );
634     libvlc_event_attach( p_em, libvlc_MediaInstancePaused,          HandleMediaInstanceStateChanged, self, &ex );
635     libvlc_event_attach( p_em, libvlc_MediaInstanceReachedEnd,      HandleMediaInstanceStateChanged, self, &ex );
636     /* FIXME: We may want to turn that off when none is interested by that */
637     libvlc_event_attach( p_em, libvlc_MediaInstancePositionChanged, HandleMediaPositionChanged,      self, &ex );
638     libvlc_event_attach( p_em, libvlc_MediaInstanceTimeChanged,     HandleMediaTimeChanged,          self, &ex );
639     catch_exception( &ex );
640 }
641
642 - (void)unregisterObservers
643 {
644     libvlc_event_manager_t * p_em = libvlc_media_instance_event_manager( instance, NULL );
645     libvlc_event_detach( p_em, libvlc_MediaInstancePlayed,          HandleMediaInstanceStateChanged, self, NULL );
646     libvlc_event_detach( p_em, libvlc_MediaInstancePaused,          HandleMediaInstanceStateChanged, self, NULL );
647     libvlc_event_detach( p_em, libvlc_MediaInstanceReachedEnd,      HandleMediaInstanceStateChanged, self, NULL );
648     libvlc_event_detach( p_em, libvlc_MediaInstancePositionChanged, HandleMediaTimeChanged,            self, NULL );
649     libvlc_event_detach( p_em, libvlc_MediaInstanceTimeChanged,     HandleMediaTimeChanged,            self, NULL );
650 }
651
652 - (void)mediaPlayerTimeChanged:(NSNumber *)newTime
653 {
654     [self willChangeValueForKey:@"time"];
655     [cachedTime release];
656     cachedTime = [[VLCTime timeWithNumber:newTime] retain];
657     [self didChangeValueForKey:@"time"];
658 }
659
660 - (void)mediaPlayerPositionChanged:(NSNumber *)newPosition
661 {
662     if( [newPosition floatValue] - position < 0.005 && position - [newPosition floatValue] < 0.005 )
663         return; /* Forget that, this is too much precision for our uses */
664     [self willChangeValueForKey:@"position"];
665     position = ((float)((int)([newPosition floatValue]*1000)))/1000.;
666     [self didChangeValueForKey:@"position"];
667 }
668
669 - (void)mediaPlayerStateChanged:(NSNumber *)newState
670 {
671     [self willChangeValueForKey:@"state"];
672     cachedState = [newState intValue];
673     [self didChangeValueForKey:@"state"];
674 }
675 @end