MacOSX/Framework/VLCMedia: make the url property an NSURL.
[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         catch_exception(&ex);
177         
178         delegate = nil;
179         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
180         
181         // This value is set whenever the demuxer figures out what the length is.
182         // TODO: Easy way to tell the length of the movie without having to instiate the demuxer.  Maybe cached info?
183         length = nil;
184
185         [self initInternalMediaDescriptor];
186     }
187     return self;
188 }
189
190 - (id)initAsNodeWithName:(NSString *)aName
191 {        
192     if (self = [super init])
193     {
194         libvlc_exception_t ex;
195         libvlc_exception_init(&ex);
196         
197         p_md = libvlc_media_descriptor_new_as_node([VLCLibrary sharedInstance],
198                                                    [aName UTF8String],
199                                                    &ex);
200         catch_exception(&ex);
201
202         delegate = nil;
203         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
204         
205         // This value is set whenever the demuxer figures out what the length is.
206         // TODO: Easy way to tell the length of the movie without having to instiate the demuxer.  Maybe cached info?
207         length = nil;
208         
209         [self initInternalMediaDescriptor];
210     }
211     return self;
212 }
213
214 - (void)release
215 {
216     @synchronized(self)
217     {
218         if([self retainCount] <= 1)
219         {
220             /* We must make sure we won't receive new event after an upcoming dealloc
221              * We also may receive a -retain in some event callback that may occcur
222              * Before libvlc_event_detach. So this can't happen in dealloc */
223             libvlc_event_manager_t * p_em = libvlc_media_descriptor_event_manager(p_md, NULL);
224             libvlc_event_detach(p_em, libvlc_MediaDescriptorMetaChanged,     HandleMediaMetaChanged,     self, NULL);
225 //            libvlc_event_detach(p_em, libvlc_MediaDescriptorDurationChanged, HandleMediaDurationChanged, self, NULL);
226             libvlc_event_detach(p_em, libvlc_MediaDescriptorStateChanged,    HandleMediaStateChanged,    self, NULL);
227             libvlc_event_detach(p_em, libvlc_MediaDescriptorSubItemAdded,    HandleMediaSubItemAdded,    self, NULL);
228         }
229         [super release];
230     }
231 }
232
233 - (void)dealloc
234 {
235     // Testing to see if the pointer exists is not required, if the pointer is null
236     // then the release message is not sent to it.
237     delegate = nil;
238     [self setLength:nil];
239
240     [url release];
241     [subitems release];
242     [metaDictionary release];
243
244     libvlc_media_descriptor_release( p_md );
245
246     [super dealloc];
247 }
248
249 - (NSString *)description
250 {
251     NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
252     return [NSString stringWithFormat:@"<%@ %p> %@", [self className], self, (result ? result : [url absoluteString])];
253 }
254
255 - (NSComparisonResult)compare:(VLCMedia *)media
256 {
257     if (self == media)
258         return NSOrderedSame;
259     else if (!media)
260         return NSOrderedDescending;
261     else
262         return [[[self url] absoluteString] compare:[[media url] absoluteString]];
263 }
264
265 @synthesize delegate;
266
267 - (VLCTime *)length
268 {
269     if (!length) 
270     {
271         // Try figuring out what the length is
272         long long duration = libvlc_media_descriptor_get_duration( p_md, NULL );
273         if (duration > -1) 
274         {
275             [self setLength:[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]]];
276             return [[length retain] autorelease];
277         } 
278     }
279     return [VLCTime nullTime];
280 }
281
282 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
283 {
284     static const long long thread_sleep = 10000;
285
286     if (!length)
287     {
288         while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
289         {
290             usleep( thread_sleep );
291         }
292         
293         // So we're done waiting, but sometimes we trap the fact that the parsing
294         // was done before the length gets assigned, so lets go ahead and assign
295         // it ourselves.
296         if (!length)
297             return [self length];
298     }
299
300     return [[length retain] autorelease];
301 }
302
303 - (BOOL)isPreparsed
304 {
305     return libvlc_media_descriptor_is_preparsed( p_md, NULL );
306 }
307
308 @synthesize url;
309 @synthesize subitems;
310 @synthesize metaDictionary;
311 @synthesize state;
312 @end
313
314 /******************************************************************************
315  * Implementation VLCMedia (LibVLCBridging)
316  */
317 @implementation VLCMedia (LibVLCBridging)
318
319 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
320 {
321     return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
322 }
323
324 - (id)initWithLibVLCMediaDescriptor:(void *)md
325 {
326     if (self = [super init])
327     {
328         libvlc_exception_t ex;
329         libvlc_exception_init( &ex );
330                 
331         libvlc_media_descriptor_retain( md );
332         p_md = md;
333         
334         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
335         [self initInternalMediaDescriptor];
336     }
337     return self;
338 }
339
340 - (void *)libVLCMediaDescriptor 
341 {
342     return p_md;
343 }
344 @end
345
346 /******************************************************************************
347  * Implementation VLCMedia (Private)
348  */
349 @implementation VLCMedia (Private)
350
351 + (libvlc_meta_t)stringToMetaType:(NSString *)string
352 {
353     static NSDictionary * stringToMetaDictionary = nil;
354     // TODO: Thread safe-ize
355     if( !stringToMetaDictionary )
356     {
357 #define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
358         stringToMetaDictionary =
359             [[NSDictionary dictionaryWithObjectsAndKeys:
360                 VLCStringToMeta(Title),
361                 VLCStringToMeta(Artist),
362                 VLCStringToMeta(Genre),
363                 VLCStringToMeta(Copyright),
364                 VLCStringToMeta(Album),
365                 VLCStringToMeta(TrackNumber),
366                 VLCStringToMeta(Description),
367                 VLCStringToMeta(Rating),
368                 VLCStringToMeta(Date),
369                 VLCStringToMeta(Setting),
370                 VLCStringToMeta(URL),
371                 VLCStringToMeta(Language),
372                 VLCStringToMeta(NowPlaying),
373                 VLCStringToMeta(Publisher),
374                 VLCStringToMeta(ArtworkURL),
375                 VLCStringToMeta(TrackID),
376                 nil] retain];
377 #undef VLCStringToMeta
378     }
379     NSNumber * number = [stringToMetaDictionary objectForKey:string];
380     return number ? [number intValue] : -1;
381 }
382
383 + (NSString *)metaTypeToString:(libvlc_meta_t)type
384 {
385 #define VLCMetaToString( name, type )   if (libvlc_meta_##name == type) return VLCMetaInformation##name;
386     VLCMetaToString(Title, type);
387     VLCMetaToString(Artist, type);
388     VLCMetaToString(Genre, type);
389     VLCMetaToString(Copyright, type);
390     VLCMetaToString(Album, type);
391     VLCMetaToString(TrackNumber, type);
392     VLCMetaToString(Description, type);
393     VLCMetaToString(Rating, type);
394     VLCMetaToString(Date, type);
395     VLCMetaToString(Setting, type);
396     VLCMetaToString(URL, type);
397     VLCMetaToString(Language, type);
398     VLCMetaToString(NowPlaying, type);
399     VLCMetaToString(Publisher, type);
400     VLCMetaToString(ArtworkURL, type);
401     VLCMetaToString(TrackID, type);
402 #undef VLCMetaToString
403     return nil;
404 }
405
406 - (void)initInternalMediaDescriptor
407 {
408     libvlc_exception_t ex;
409     libvlc_exception_init( &ex );
410
411     char * p_url = libvlc_media_descriptor_get_mrl( p_md, &ex );
412     catch_exception( &ex );
413
414     url = [[NSURL URLWithString:[NSString stringWithUTF8String:p_url]] retain];
415     if( !url ) /* Attempt to interpret as a file path then */
416         url = [[NSURL fileURLWithPath:[NSString stringWithUTF8String:p_url]] retain];
417     free( p_url );
418
419     libvlc_media_descriptor_set_user_data( p_md, (void*)self, &ex );
420     catch_exception( &ex );
421
422     libvlc_event_manager_t * p_em = libvlc_media_descriptor_event_manager( p_md, &ex );
423     libvlc_event_attach(p_em, libvlc_MediaDescriptorMetaChanged,     HandleMediaMetaChanged,     self, &ex);
424 //    libvlc_event_attach(p_em, libvlc_MediaDescriptorDurationChanged, HandleMediaDurationChanged, self, &ex);
425     libvlc_event_attach(p_em, libvlc_MediaDescriptorStateChanged,    HandleMediaStateChanged,    self, &ex);
426     libvlc_event_attach(p_em, libvlc_MediaDescriptorSubItemAdded,    HandleMediaSubItemAdded,    self, &ex);
427     catch_exception( &ex );
428     
429     libvlc_media_list_t * p_mlist = libvlc_media_descriptor_subitems( p_md, NULL );
430
431     if (!p_mlist)
432         subitems = nil;
433     else
434     {
435         subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
436         libvlc_media_list_release( p_mlist );
437     }
438
439     state = LibVLCStateToMediaState(libvlc_media_descriptor_get_state( p_md, NULL ));
440     /* Force VLCMetaInformationTitle, that will trigger preparsing
441      * And all the other meta will be added through the libvlc event system */
442     [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
443 }
444
445 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
446 {
447     char * psz_value = libvlc_media_descriptor_get_meta( p_md, [VLCMedia stringToMetaType:metaType], NULL);
448     NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
449     NSString * oldValue = [metaDictionary valueForKey:metaType];
450     free(psz_value);
451
452     if ( !(newValue && oldValue && [oldValue compare:newValue] == NSOrderedSame) )
453     {
454         if ([metaType isEqualToString:VLCMetaInformationArtworkURL])
455         {
456             [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:) 
457                                          toTarget:self
458                                        withObject:newValue];
459             return;
460         }
461
462         [metaDictionary setValue:newValue forKeyPath:metaType];
463     }
464 }
465
466 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
467 {
468     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
469     
470     // Go ahead and load up the art work
471     NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
472     NSImage * art  = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease]; 
473         
474     // If anything was found, lets save it to the meta data dictionary
475     if (art)
476     {
477         @synchronized(metaDictionary) 
478         {
479             [metaDictionary setObject:art forKey:VLCMetaInformationArtwork];
480         }
481     }
482
483     [pool release];
484 }
485
486 - (void)metaChanged:(NSString *)metaType
487 {
488     [self fetchMetaInformationFromLibVLCWithType:metaType];
489 }
490
491 - (void)subItemAdded
492 {
493     if( subitems )
494         return; /* Nothing to do */
495
496     libvlc_media_list_t * p_mlist = libvlc_media_descriptor_subitems( p_md, NULL );
497
498     NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
499
500     [self willChangeValueForKey:@"subitems"];
501     subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
502     [self didChangeValueForKey:@"subitems"];
503     libvlc_media_list_release( p_mlist );
504 }
505 @end
506
507 /******************************************************************************
508  * Implementation VLCMedia (VLCMediaPlayerBridging)
509  */
510
511 @implementation VLCMedia (VLCMediaPlayerBridging)
512
513 - (void)setLength:(VLCTime *)value
514 {
515     if (length && value && [length compare:value] == NSOrderedSame)
516         return;
517         
518     [length release];       
519     length = value ? [value retain] : nil;
520 }
521
522 - (void)setState:(NSNumber *)newStateAsNumber
523 {        
524     state = [newStateAsNumber intValue];
525 }
526 @end