Sources/VLCMediaPlayer.m: Position getters and setters.
[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 #include <vlc/vlc.h>
31
32 /* Notification Messages */
33 NSString *VLCMediaPlayerTimeChanged   = @"VLCMediaPlayerTimeChanged";
34 NSString *VLCMediaPlayerStateChanged  = @"VLCMediaPlayerStateChanged";
35
36 NSString *VLCMediaPlayerStateToString(VLCMediaPlayerState state)
37 {
38     static NSString *stateToStrings[] = {
39         [VLCMediaPlayerStateStopped]      = @"VLCMediaPlayerStateStopped",
40         [VLCMediaPlayerStateOpening]      = @"VLCMediaPlayerStateOpening",
41         [VLCMediaPlayerStateBuffering]    = @"VLCMediaPlayerStateBuffering",
42         [VLCMediaPlayerStateEnded]        = @"VLCMediaPlayerStateEnded",
43         [VLCMediaPlayerStateError]        = @"VLCMediaPlayerStateError",
44         [VLCMediaPlayerStatePlaying]      = @"VLCMediaPlayerStatePlaying",
45         [VLCMediaPlayerStatePaused]       = @"VLCMediaPlayerStatePaused"
46     };
47     return stateToStrings[state];
48 }
49
50 /* libvlc event callback */
51 static void HandleMediaInstanceVolumeChanged(const libvlc_event_t *event, void *self)
52 {
53     [[VLCEventManager sharedManager] callOnMainThreadDelegateOfObject:self
54                                                    withDelegateMethod:@selector(mediaPlayerVolumeChanged:)
55                                                  withNotificationName:VLCMediaPlayerVolumeChanged];
56 }
57
58 static void HandleMediaTimeChanged(const libvlc_event_t * event, void * self)
59 {
60     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
61     NSLog(@"time!");
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 + (void)initialize {
124     [self setKeys:[NSArray arrayWithObject:@"state"] triggerChangeNotificationsForDependentKey:@"playing"];
125 }
126
127 - (id)init
128 {
129     return [self initWithVideoView:nil];
130 }
131
132 - (id)initWithVideoView:(VLCVideoView *)aVideoView
133 {
134     if (self = [super init])
135     {
136         delegate = nil;
137         media = nil;
138         cachedTime = [[VLCTime nullTime] retain];
139         position = 0.0f;
140         cachedState = VLCMediaPlayerStateStopped;
141
142         // Create a media instance, it doesn't matter what library we start off with
143         // it will change depending on the media descriptor provided to the media
144         // instance
145         libvlc_exception_t ex;
146         libvlc_exception_init( &ex );
147         instance = (void *)libvlc_media_instance_new([VLCLibrary sharedInstance], &ex);
148         quit_on_exception( &ex );
149         
150         [self registerObservers];
151         
152         [self setVideoView:aVideoView];
153     }
154     return self;
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 *)value
198 {
199     videoView = value;
200     
201     // Make sure that this instance has been associated with the drawing canvas.
202     libvlc_exception_t ex;
203     libvlc_exception_init( &ex );
204     libvlc_media_instance_set_drawable ((libvlc_media_instance_t *)instance, 
205                                         (libvlc_drawable_t)videoView, 
206                                         &ex);
207     quit_on_exception( &ex );
208 }
209
210 - (VLCVideoView *)videoView
211 {
212     return videoView;
213 }
214
215 - (void)setFullscreen:(BOOL)value
216 {
217     libvlc_set_fullscreen(instance, value, NULL);
218 }
219
220 - (BOOL)fullscreen
221 {
222     libvlc_exception_t ex;
223     libvlc_exception_init( &ex );
224     int result = libvlc_get_fullscreen( instance, &ex );
225     quit_on_exception( &ex );
226     return result;
227 }
228
229 - (void)setVideoAspectRatio:(char *)value
230 {
231     libvlc_video_set_aspect_ratio( instance, value, NULL );
232 }
233
234 - (char *)videoAspectRatio
235 {
236     libvlc_exception_t ex;
237     libvlc_exception_init( &ex );
238     char *result = libvlc_video_get_aspect_ratio( instance, &ex );
239     quit_on_exception( &ex );
240     return result;
241 }
242
243 - (void)setVideoSubTitles:(int)value
244 {
245     libvlc_video_set_spu( instance, value, NULL );
246 }
247
248 - (int)videoSubTitles
249 {
250     libvlc_exception_t ex;
251     libvlc_exception_init( &ex );
252     int result = libvlc_video_get_spu( instance, &ex );
253     quit_on_exception( &ex );
254     return result;
255 }
256
257 - (void)setVideoCropGeometry:(char *)value
258 {
259     libvlc_video_set_crop_geometry( instance, value, NULL );
260 }
261
262 - (char *)videoCropGeometry
263 {
264     libvlc_exception_t ex;
265     libvlc_exception_init( &ex );
266     char *result = libvlc_video_get_crop_geometry( instance, &ex );
267     quit_on_exception( &ex );
268     return result;
269 }
270
271 - (void)setVideoTeleText:(int)value
272 {
273     libvlc_video_set_teletext( instance, value, NULL );
274 }
275
276 - (int)videoTeleText
277 {
278     libvlc_exception_t ex;
279     libvlc_exception_init( &ex );
280     int result = libvlc_video_get_teletext( instance, &ex );
281     quit_on_exception( &ex );
282     return result;
283 }
284
285 - (void)setRate:(int)value
286 {
287     libvlc_media_instance_set_rate( instance, value, NULL );
288 }
289
290 - (int)rate
291 {
292     libvlc_exception_t ex;
293     libvlc_exception_init( &ex );
294     float result = libvlc_media_instance_get_rate( instance, &ex );
295     quit_on_exception( &ex );
296     return result;
297 }
298
299 - (NSSize)videoSize
300 {
301     libvlc_exception_t ex;
302     libvlc_exception_init( &ex );
303     NSSize result = NSMakeSize(libvlc_video_get_height((libvlc_media_instance_t *)instance, &ex),
304                                libvlc_video_get_width((libvlc_media_instance_t *)instance, &ex));
305     quit_on_exception( &ex );
306     return result;    
307 }
308
309 - (BOOL)hasVideoOut
310 {
311     libvlc_exception_t ex;
312     libvlc_exception_init( &ex );
313     BOOL result = libvlc_media_instance_has_vout((libvlc_media_instance_t *)instance, &ex);
314     if (libvlc_exception_raised( &ex ))
315     {
316         libvlc_exception_clear( &ex );
317         return NO;
318     }
319     else
320         return result;
321 }
322
323 - (float)framesPerSecond
324 {
325     libvlc_exception_t ex;
326     libvlc_exception_init( &ex );
327     float result = libvlc_media_instance_get_fps( (libvlc_media_instance_t *)instance, &ex );
328     quit_on_exception( &ex );
329     return result;
330 }
331
332 - (void)setTime:(VLCTime *)value
333 {
334     libvlc_exception_t ex;
335     libvlc_exception_init( &ex );
336     // Time is managed in seconds, while duration is managed in microseconds
337     // TODO: Redo VLCTime to provide value numberAsMilliseconds, numberAsMicroseconds, numberAsSeconds, numberAsMinutes, numberAsHours
338     libvlc_media_instance_set_time( (libvlc_media_instance_t *)instance, 
339                                     (value ? [[value numberValue] longLongValue] / 1000 : 0),
340                                     &ex );
341     quit_on_exception( &ex );
342 }
343
344 - (VLCTime *)time
345 {
346     return cachedTime;
347 }
348
349 - (void)setChapter:(int)value;
350 {
351     libvlc_media_instance_set_chapter( instance, value, NULL );
352 }
353
354 - (int)chapter
355 {
356     libvlc_exception_t ex;
357     libvlc_exception_init( &ex );
358     int result = libvlc_media_instance_get_chapter( instance, &ex );
359     quit_on_exception( &ex );
360     return result;
361 }
362
363 - (int)countOfChapters
364 {
365     libvlc_exception_t ex;
366     libvlc_exception_init( &ex );
367     int result = libvlc_media_instance_get_chapter_count( instance, &ex );
368     quit_on_exception( &ex );
369     return result;
370 }
371
372 - (void)setAudioTrack:(int)value
373 {
374     libvlc_audio_set_track( instance, value, NULL );
375 }
376
377 - (int)audioTrack
378 {
379     libvlc_exception_t ex;
380     libvlc_exception_init( &ex );
381     int result = libvlc_audio_get_track( instance, &ex );
382     quit_on_exception( &ex );
383     return result;
384 }
385
386 - (int)countOfAudioTracks
387 {
388     libvlc_exception_t ex;
389     libvlc_exception_init( &ex );
390     int result = libvlc_audio_get_track_count( instance, &ex );
391     quit_on_exception( &ex );
392     return result;
393 }
394
395 - (void)setAudioChannel:(int)value
396 {
397     libvlc_audio_set_channel( instance, value, NULL );
398 }
399
400 - (int)audioChannel
401 {
402     libvlc_exception_t ex;
403     libvlc_exception_init( &ex );
404     int result = libvlc_audio_get_channel( instance, &ex );
405     quit_on_exception( &ex );
406     return result;
407 }
408
409 - (void)setMedia:(VLCMedia *)value
410 {
411     // We only know how to play media files...not media resources with subitems
412     if (media != value && [media subitems] == nil)
413     {
414         if (media && [media compare:value] == NSOrderedSame)
415             return;
416         
417         BOOL wasPlaying;
418         if (wasPlaying = [self isPlaying])
419         {
420             [self pause];
421 //            // TODO: Should we wait until it stops playing?
422 //            while ([self isPlaying])
423 //                usleep(1000);
424         }
425         
426         [self willChangeValueForKey:@"media"];
427         [media release];
428         media = [value retain];
429         [self didChangeValueForKey:@"media"];
430
431         libvlc_exception_t ex;
432         libvlc_exception_init( &ex );
433         libvlc_media_instance_set_media_descriptor( instance, [media libVLCMediaDescriptor], &ex );
434         quit_on_exception( &ex );
435         
436         if (media) {
437             if (wasPlaying)
438                 [self play];
439         }
440     }
441 }
442
443 - (VLCMedia *)media
444 {
445     return media;
446 }
447
448 - (BOOL)play
449 {
450     // Return if there is no media available or if the stream is already playing something
451     if (!media || [self isPlaying])
452         return [self isPlaying];
453     
454     libvlc_exception_t ex;
455     libvlc_exception_init( &ex );
456
457     libvlc_media_instance_play( (libvlc_media_instance_t *)instance, &ex );
458     quit_on_exception( &ex );
459
460     return YES;
461 }
462
463 - (void)pause
464 {
465     // Return if there is no media available or if the stream is not paused or 
466     // playing something else
467     if (!media || (![self isPlaying] && [self state] != VLCMediaPlayerStatePaused))
468         return;
469
470     // Should never get here.
471     if (!instance)
472         return;
473
474     // Pause the stream
475     libvlc_exception_t ex;
476     libvlc_exception_init( &ex );
477     libvlc_media_instance_pause( (libvlc_media_instance_t *)instance, &ex );
478     quit_on_exception( &ex );
479     
480     // TODO: Should we record the time in case the media instance is destroyed
481     // then rebuilt?
482 }
483
484 - (void)stop
485 {
486     // Return if there is no media available or if the system is not in play status 
487     // or pause status.
488     if (!media || (![self isPlaying] && [self state] != VLCMediaPlayerStatePaused))
489         return;
490     
491     // The following is not implemented in the core, should I fix it or just
492     // compensate?
493     //    libvlc_exception_t ex;
494     //    libvlc_exception_init( &ex );
495     //    libvlc_media_instance_stop((libvlc_media_instance_t *)instance, &ex);
496     //    quit_on_exception( &ex );
497     
498     // Pause and reposition to the begining of the stream.
499     [self pause];
500     [self setTime:0];
501     // TODO: Should we pause this or destroy the media instance so that it appears as being "stopped"?
502 }
503
504 //- (void)fastForward;
505 //- (void)fastForwardAtRate:(int)rate;
506 //- (void)rewind;
507 //- (void)rewindAtRate:(int)rate;
508
509 - (BOOL)isPlaying
510 {
511     VLCMediaPlayerState state = [self state];
512     return ((state == VLCMediaPlayerStateOpening) || (state == VLCMediaPlayerStateBuffering) ||
513             (state == VLCMediaPlayerStatePlaying));
514 }
515
516 - (BOOL)willPlay
517 {
518     libvlc_exception_t ex;
519     libvlc_exception_init( &ex );
520     BOOL ret = libvlc_media_instance_will_play( (libvlc_media_instance_t *)instance, &ex );
521     if (libvlc_exception_raised(&ex))
522     {
523         libvlc_exception_clear(&ex);
524         return NO;
525     }
526     else
527         return ret;
528 }
529
530 static const VLCMediaPlayerState libvlc_to_local_state[] =
531 {
532     [libvlc_Stopped]    = VLCMediaPlayerStateStopped,
533     [libvlc_Opening]    = VLCMediaPlayerStateOpening,
534     [libvlc_Buffering]  = VLCMediaPlayerStateBuffering,
535     [libvlc_Playing]    = VLCMediaPlayerStatePlaying,
536     [libvlc_Paused]     = VLCMediaPlayerStatePaused,
537     [libvlc_Ended]      = VLCMediaPlayerStateEnded,
538     [libvlc_Error]      = VLCMediaPlayerStateError
539 };
540
541 - (VLCMediaPlayerState)state
542 {
543     return cachedState;
544 }
545 - (float)position
546 {
547     return position;
548 }
549 - (void)setPosition:(float)newPosition
550 {
551     libvlc_exception_t ex;
552     libvlc_exception_init( &ex );
553     libvlc_media_instance_set_position( instance, newPosition, &ex );
554     quit_on_exception( &ex );
555 }
556 @end
557
558 @implementation VLCMediaPlayer (Private)
559 - (void)registerObservers
560 {
561     libvlc_exception_t ex;
562     libvlc_exception_init( &ex );
563
564     // Attach event observers into the media instance
565     libvlc_event_manager_t *p_em = libvlc_media_instance_event_manager( instance, &ex );
566     libvlc_event_attach( p_em, libvlc_MediaInstancePlayed,          HandleMediaInstanceStateChanged, self, &ex );
567     libvlc_event_attach( p_em, libvlc_MediaInstancePaused,          HandleMediaInstanceStateChanged, self, &ex );
568     libvlc_event_attach( p_em, libvlc_MediaInstanceReachedEnd,      HandleMediaInstanceStateChanged, self, &ex );
569     /* FIXME: We may want to turn that off when none is interested by that */
570     libvlc_event_attach( p_em, libvlc_MediaInstancePositionChanged, HandleMediaPositionChanged,      self, &ex );
571     libvlc_event_attach( p_em, libvlc_MediaInstanceTimeChanged,     HandleMediaTimeChanged,          self, &ex );
572     quit_on_exception( &ex );
573 }
574
575 - (void)unregisterObservers
576 {
577     libvlc_event_manager_t *p_em = libvlc_media_instance_event_manager( instance, NULL );
578     libvlc_event_detach( p_em, libvlc_MediaInstancePlayed,          HandleMediaInstanceStateChanged, self, NULL );
579     libvlc_event_detach( p_em, libvlc_MediaInstancePaused,          HandleMediaInstanceStateChanged, self, NULL );
580     libvlc_event_detach( p_em, libvlc_MediaInstanceReachedEnd,      HandleMediaInstanceStateChanged, self, NULL );
581     libvlc_event_detach( p_em, libvlc_MediaInstancePositionChanged, HandleMediaTimeChanged,            self, NULL );
582     libvlc_event_detach( p_em, libvlc_MediaInstanceTimeChanged,     HandleMediaTimeChanged,            self, NULL );
583 }
584
585 - (void)mediaPlayerTimeChanged:(NSNumber *)newTime
586 {
587     [self willChangeValueForKey:@"time"];
588     [cachedTime release];
589     cachedTime = [[VLCTime timeWithNumber:newTime] retain];
590     [self didChangeValueForKey:@"time"];
591 }
592
593 - (void)mediaPlayerPositionChanged:(NSNumber *)newPosition
594 {
595     if( [newPosition floatValue] - position < 0.005 && position - [newPosition floatValue] < 0.005 )
596         return; /* Forget that, this is too much precision for our uses */
597     [self willChangeValueForKey:@"position"];
598     position = ((float)((int)([newPosition floatValue]*1000)))/1000.;
599     [self didChangeValueForKey:@"position"];
600 }
601
602 - (void)mediaPlayerStateChanged:(NSNumber *)newState
603 {
604     [self willChangeValueForKey:@"state"];
605     cachedState = [newState intValue];
606     [self didChangeValueForKey:@"state"];
607 }
608 @end