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