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