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