515342b5e9cdfe4ce62d2446ff613ad0702965bb
[vlc.git] / extras / MacOSX / Framework / Sources / VLCMedia.m
1 /*****************************************************************************
2  * VLCMedia.m: VLC.framework VLCMedia 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  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #import "VLCMedia.h"
26 #import "VLCMediaList.h"
27 #import "VLCEventManager.h"
28 #import "VLCLibrary.h"
29 #import "VLCLibVLCBridging.h"
30 #include <vlc/libvlc.h>
31
32 /* Meta Dictionary Keys */
33 NSString * VLCMetaInformationTitle          = @"title";
34 NSString * VLCMetaInformationArtist         = @"artist";
35 NSString * VLCMetaInformationGenre          = @"genre";
36 NSString * VLCMetaInformationCopyright      = @"copyright";
37 NSString * VLCMetaInformationAlbum          = @"album";
38 NSString * VLCMetaInformationTrackNumber    = @"trackNumber";
39 NSString * VLCMetaInformationDescription    = @"description";
40 NSString * VLCMetaInformationRating         = @"rating";
41 NSString * VLCMetaInformationDate           = @"date";
42 NSString * VLCMetaInformationSetting        = @"setting";
43 NSString * VLCMetaInformationURL            = @"url";
44 NSString * VLCMetaInformationLanguage       = @"language";
45 NSString * VLCMetaInformationNowPlaying     = @"nowPlaying";
46 NSString * VLCMetaInformationPublisher      = @"publisher";
47 NSString * VLCMetaInformationEncodedBy      = @"encodedBy";
48 NSString * VLCMetaInformationArtworkURL     = @"artworkURL";
49 NSString * VLCMetaInformationArtwork        = @"artwork";
50 NSString * VLCMetaInformationTrackID        = @"trackID";
51
52 /* Notification Messages */
53 NSString * VLCMediaMetaChanged              = @"VLCMediaMetaChanged";
54
55 /******************************************************************************
56  * Interface (Private)
57  */
58 // TODO: Documentation
59 @interface VLCMedia (Private)
60 /* Statics */
61 + (libvlc_meta_t)stringToMetaType:(NSString *)string;
62 + (NSString *)metaTypeToString:(libvlc_meta_t)type;
63
64 /* Initializers */
65 - (void)initInternalMediaDescriptor;
66
67 /* Operations */
68 - (void)fetchMetaInformationFromLibVLCWithType:(NSString*)metaType;
69 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL;
70
71 /* Callback Methods */
72 - (void)metaChanged:(NSString *)metaType;
73 - (void)subItemAdded;
74 @end
75
76 static VLCMediaState libvlc_state_to_media_state[] =
77 {
78     [libvlc_NothingSpecial] = VLCMediaStateNothingSpecial,
79     [libvlc_Stopped]        = VLCMediaStateNothingSpecial,
80     [libvlc_Opening]        = VLCMediaStateNothingSpecial,
81     [libvlc_Buffering]      = VLCMediaStateBuffering,
82     [libvlc_Ended]          = VLCMediaStateNothingSpecial,
83     [libvlc_Error]          = VLCMediaStateError,
84     [libvlc_Playing]        = VLCMediaStatePlaying,
85     [libvlc_Paused]         = VLCMediaStatePlaying,
86 };
87
88 static inline VLCMediaState LibVLCStateToMediaState( libvlc_state_t state )
89 {
90     return libvlc_state_to_media_state[state];
91 }
92
93 /******************************************************************************
94  * LibVLC Event Callback
95  */
96 static void HandleMediaMetaChanged(const libvlc_event_t * event, void * self)
97 {
98     if( event->u.media_descriptor_meta_changed.meta_type == libvlc_meta_Publisher ||
99         event->u.media_descriptor_meta_changed.meta_type == libvlc_meta_NowPlaying )
100     {
101         /* Skip those meta. We don't really care about them for now.
102          * And they occure a lot */
103         return;
104     }
105     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
106     [[VLCEventManager sharedManager] callOnMainThreadObject:self
107                                                  withMethod:@selector(metaChanged:)
108                                        withArgumentAsObject:[VLCMedia metaTypeToString:event->u.media_descriptor_meta_changed.meta_type]];
109     [pool release];
110 }
111
112 //static void HandleMediaDurationChanged(const libvlc_event_t * event, void * self)
113 //{
114 //    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
115 //    
116 //    [[VLCEventManager sharedManager] callOnMainThreadObject:self
117 //                                                 withMethod:@selector(setLength:)
118 //                                       withArgumentAsObject:[VLCTime timeWithNumber:
119 //                                           [NSNumber numberWithLongLong:event->u.media_descriptor_duration_changed.new_duration]]];
120 //    [pool release];
121 //}
122
123 static void HandleMediaStateChanged(const libvlc_event_t * event, void * self)
124 {
125     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
126     [[VLCEventManager sharedManager] callOnMainThreadObject:self
127                                                  withMethod:@selector(setState:)
128                                        withArgumentAsObject:[NSNumber numberWithInt:
129                                             LibVLCStateToMediaState(event->u.media_descriptor_state_changed.new_state)]];
130     [pool release];
131 }
132
133 static void HandleMediaSubItemAdded(const libvlc_event_t * event, void * self)
134 {
135     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
136     [[VLCEventManager sharedManager] callOnMainThreadObject:self
137                                                  withMethod:@selector(subItemAdded)
138                                        withArgumentAsObject:nil];
139     [pool release];
140 }
141
142 /******************************************************************************
143  * Implementation
144  */
145 @implementation VLCMedia
146 + (id)mediaWithURL:(NSURL *)anURL;
147 {
148     return [[[VLCMedia alloc] initWithURL:anURL] autorelease];
149 }
150
151 + (id)mediaWithPath:(NSString *)aPath;
152 {
153     return [[[VLCMedia alloc] initWithPath:aPath] autorelease];
154 }
155
156 + (id)mediaAsNodeWithName:(NSString *)aName;
157 {
158     return [[[VLCMedia alloc] initAsNodeWithName:aName] autorelease];
159 }
160
161 - (id)initWithURL:(NSURL *)anURL
162 {
163     return [self initWithPath:[anURL path]];
164 }
165
166 - (id)initWithPath:(NSString *)aPath
167 {        
168     if (self = [super init])
169     {
170         libvlc_exception_t ex;
171         libvlc_exception_init(&ex);
172         
173         p_md = libvlc_media_descriptor_new([VLCLibrary sharedInstance],
174                                            [aPath UTF8String],
175                                            &ex);
176         quit_on_exception(&ex);
177         
178         url = [aPath copy];
179         delegate = nil;
180         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
181         
182         // This value is set whenever the demuxer figures out what the length is.
183         // TODO: Easy way to tell the length of the movie without having to instiate the demuxer.  Maybe cached info?
184         length = nil;
185
186         [self initInternalMediaDescriptor];
187     }
188     return self;
189 }
190
191 - (id)initAsNodeWithName:(NSString *)aName
192 {        
193     if (self = [super init])
194     {
195         libvlc_exception_t ex;
196         libvlc_exception_init(&ex);
197         
198         p_md = libvlc_media_descriptor_new_as_node([VLCLibrary sharedInstance],
199                                                    [aName UTF8String],
200                                                    &ex);
201         quit_on_exception(&ex);
202         
203         url = [aName copy];
204         delegate = nil;
205         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
206         
207         // This value is set whenever the demuxer figures out what the length is.
208         // TODO: Easy way to tell the length of the movie without having to instiate the demuxer.  Maybe cached info?
209         length = nil;
210         
211         [self initInternalMediaDescriptor];
212     }
213     return self;
214 }
215
216 - (void)release
217 {
218     @synchronized(self)
219     {
220         if([self retainCount] <= 1)
221         {
222             /* We must make sure we won't receive new event after an upcoming dealloc
223              * We also may receive a -retain in some event callback that may occcur
224              * Before libvlc_event_detach. So this can't happen in dealloc */
225             libvlc_event_manager_t * p_em = libvlc_media_descriptor_event_manager(p_md, NULL);
226             libvlc_event_detach(p_em, libvlc_MediaDescriptorMetaChanged,     HandleMediaMetaChanged,     self, NULL);
227 //            libvlc_event_detach(p_em, libvlc_MediaDescriptorDurationChanged, HandleMediaDurationChanged, self, NULL);
228             libvlc_event_detach(p_em, libvlc_MediaDescriptorStateChanged,    HandleMediaStateChanged,    self, NULL);
229             libvlc_event_detach(p_em, libvlc_MediaDescriptorSubItemAdded,    HandleMediaSubItemAdded,    self, NULL);
230         }
231         [super release];
232     }
233 }
234
235 - (void)dealloc
236 {
237     // Testing to see if the pointer exists is not required, if the pointer is null
238     // then the release message is not sent to it.
239     delegate = nil;
240     [self setLength:nil];
241
242     [url release];
243     [subitems release];
244     [metaDictionary release];
245
246     libvlc_media_descriptor_release( p_md );
247
248     [super dealloc];
249 }
250
251 - (NSString *)description
252 {
253     NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
254     return [NSString stringWithFormat:@"<%@ %p> %@", [self className], self, (result ? result : url)];
255 }
256
257 - (NSComparisonResult)compare:(VLCMedia *)media
258 {
259     if (self == media)
260         return NSOrderedSame;
261     else if (!media)
262         return NSOrderedDescending;
263     else
264         return [[self url] compare:[media url]];
265 }
266
267 @synthesize delegate;
268
269 - (VLCTime *)length
270 {
271     if (!length) 
272     {
273         // Try figuring out what the length is
274         long long duration = libvlc_media_descriptor_get_duration( p_md, NULL );
275         if (duration > -1) 
276         {
277             [self setLength:[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]]];
278             return [[length retain] autorelease];
279         } 
280     }
281     return [VLCTime nullTime];
282 }
283
284 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
285 {
286     static const long long thread_sleep = 10000;
287
288     if (!length)
289     {
290         while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
291         {
292             usleep( thread_sleep );
293         }
294         
295         // So we're done waiting, but sometimes we trap the fact that the parsing
296         // was done before the length gets assigned, so lets go ahead and assign
297         // it ourselves.
298         if (!length)
299             return [self length];
300     }
301
302     return [[length retain] autorelease];
303 }
304
305 - (BOOL)isPreparsed
306 {
307     return libvlc_media_descriptor_is_preparsed( p_md, NULL );
308 }
309
310 @synthesize url;
311 @synthesize subitems;
312 @synthesize metaDictionary;
313 @synthesize state;
314 @end
315
316 /******************************************************************************
317  * Implementation VLCMedia (LibVLCBridging)
318  */
319 @implementation VLCMedia (LibVLCBridging)
320
321 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
322 {
323     return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
324 }
325
326 - (id)initWithLibVLCMediaDescriptor:(void *)md
327 {
328     if (self = [super init])
329     {
330         libvlc_exception_t ex;
331         libvlc_exception_init( &ex );
332         char * p_url;
333         
334         p_url = libvlc_media_descriptor_get_mrl( md, &ex );
335         quit_on_exception( &ex );
336         
337         url = [[NSString stringWithUTF8String:p_url] retain];
338         free( p_url );
339         
340         libvlc_media_descriptor_retain( md );
341         p_md = md;
342         
343         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
344         [self initInternalMediaDescriptor];
345     }
346     return self;
347 }
348
349 - (void *)libVLCMediaDescriptor 
350 {
351     return p_md;
352 }
353 @end
354
355 /******************************************************************************
356  * Implementation VLCMedia (Private)
357  */
358 @implementation VLCMedia (Private)
359
360 + (libvlc_meta_t)stringToMetaType:(NSString *)string
361 {
362     static NSDictionary * stringToMetaDictionary = nil;
363     // TODO: Thread safe-ize
364     if( !stringToMetaDictionary )
365     {
366 #define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
367         stringToMetaDictionary =
368             [[NSDictionary dictionaryWithObjectsAndKeys:
369                 VLCStringToMeta(Title),
370                 VLCStringToMeta(Artist),
371                 VLCStringToMeta(Genre),
372                 VLCStringToMeta(Copyright),
373                 VLCStringToMeta(Album),
374                 VLCStringToMeta(TrackNumber),
375                 VLCStringToMeta(Description),
376                 VLCStringToMeta(Rating),
377                 VLCStringToMeta(Date),
378                 VLCStringToMeta(Setting),
379                 VLCStringToMeta(URL),
380                 VLCStringToMeta(Language),
381                 VLCStringToMeta(NowPlaying),
382                 VLCStringToMeta(Publisher),
383                 VLCStringToMeta(ArtworkURL),
384                 VLCStringToMeta(TrackID),
385                 nil] retain];
386 #undef VLCStringToMeta
387     }
388     NSNumber * number = [stringToMetaDictionary objectForKey:string];
389     return number ? [number intValue] : -1;
390 }
391
392 + (NSString *)metaTypeToString:(libvlc_meta_t)type
393 {
394 #define VLCMetaToString( name, type )   if (libvlc_meta_##name == type) return VLCMetaInformation##name;
395     VLCMetaToString(Title, type);
396     VLCMetaToString(Artist, type);
397     VLCMetaToString(Genre, type);
398     VLCMetaToString(Copyright, type);
399     VLCMetaToString(Album, type);
400     VLCMetaToString(TrackNumber, type);
401     VLCMetaToString(Description, type);
402     VLCMetaToString(Rating, type);
403     VLCMetaToString(Date, type);
404     VLCMetaToString(Setting, type);
405     VLCMetaToString(URL, type);
406     VLCMetaToString(Language, type);
407     VLCMetaToString(NowPlaying, type);
408     VLCMetaToString(Publisher, type);
409     VLCMetaToString(ArtworkURL, type);
410     VLCMetaToString(TrackID, type);
411 #undef VLCMetaToString
412     return nil;
413 }
414
415 - (void)initInternalMediaDescriptor
416 {
417     libvlc_exception_t ex;
418     libvlc_exception_init( &ex );
419
420     libvlc_media_descriptor_set_user_data( p_md, (void*)self, &ex );
421     quit_on_exception( &ex );
422
423     libvlc_event_manager_t * p_em = libvlc_media_descriptor_event_manager( p_md, &ex );
424     libvlc_event_attach(p_em, libvlc_MediaDescriptorMetaChanged,     HandleMediaMetaChanged,     self, &ex);
425 //    libvlc_event_attach(p_em, libvlc_MediaDescriptorDurationChanged, HandleMediaDurationChanged, self, &ex);
426     libvlc_event_attach(p_em, libvlc_MediaDescriptorStateChanged,    HandleMediaStateChanged,    self, &ex);
427     libvlc_event_attach(p_em, libvlc_MediaDescriptorSubItemAdded,    HandleMediaSubItemAdded,    self, &ex);
428     quit_on_exception( &ex );
429     
430     libvlc_media_list_t * p_mlist = libvlc_media_descriptor_subitems( p_md, NULL );
431
432     if (!p_mlist)
433         subitems = nil;
434     else
435     {
436         subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
437         libvlc_media_list_release( p_mlist );
438     }
439
440     state = LibVLCStateToMediaState(libvlc_media_descriptor_get_state( p_md, NULL ));
441     /* Force VLCMetaInformationTitle, that will trigger preparsing
442      * And all the other meta will be added through the libvlc event system */
443     [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
444 }
445
446 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
447 {
448     char * psz_value = libvlc_media_descriptor_get_meta( p_md, [VLCMedia stringToMetaType:metaType], NULL);
449     NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
450     NSString * oldValue = [metaDictionary valueForKey:metaType];
451     free(psz_value);
452
453     if ( !(newValue && oldValue && [oldValue compare:newValue] == NSOrderedSame) )
454     {
455         if ([metaType isEqualToString:VLCMetaInformationArtworkURL])
456         {
457             [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:) 
458                                          toTarget:self
459                                        withObject:newValue];
460             return;
461         }
462
463         [metaDictionary setValue:newValue forKeyPath:metaType];
464     }
465 }
466
467 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
468 {
469     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
470     
471     // Go ahead and load up the art work
472     NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
473     NSImage * art  = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease]; 
474         
475     // If anything was found, lets save it to the meta data dictionary
476     if (art)
477     {
478         @synchronized(metaDictionary) 
479         {
480             [metaDictionary setObject:art forKey:VLCMetaInformationArtwork];
481         }
482     }
483
484     [pool release];
485 }
486
487 - (void)metaChanged:(NSString *)metaType
488 {
489     [self fetchMetaInformationFromLibVLCWithType:metaType];
490 }
491
492 - (void)subItemAdded
493 {
494     if( subitems )
495         return; /* Nothing to do */
496
497     libvlc_media_list_t * p_mlist = libvlc_media_descriptor_subitems( p_md, NULL );
498
499     NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
500
501     [self willChangeValueForKey:@"subitems"];
502     subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
503     [self didChangeValueForKey:@"subitems"];
504     libvlc_media_list_release( p_mlist );
505 }
506 @end
507
508 /******************************************************************************
509  * Implementation VLCMedia (VLCMediaPlayerBridging)
510  */
511
512 @implementation VLCMedia (VLCMediaPlayerBridging)
513
514 - (void)setLength:(VLCTime *)value
515 {
516     if (length && value && [length compare:value] == NSOrderedSame)
517         return;
518         
519     [length release];       
520     length = value ? [value retain] : nil;
521 }
522
523 - (void)setState:(NSNumber *)newStateAsNumber
524 {        
525     state = [newStateAsNumber intValue];
526 }
527 @end