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