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