]> git.sesse.net Git - vlc/blob - extras/MacOSX/Framework/Sources/VLCMedia.m
MacOSX/Framework/VLCMedia.m: Make sure we compare the media_descriptor and not the...
[vlc] / 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     libvlc_media_descriptor_t * anOtherMd = [media libVLCMediaDescriptor];
267     /* We can release, we'll just use ptr */
268     libvlc_media_descriptor_release(anOtherMd);
269     if (self == media || p_md == anOtherMd)
270         return NSOrderedSame;
271     else if (!media)
272         return NSOrderedDescending;
273     return NSOrderedAscending;
274 }
275
276 @synthesize delegate;
277
278 - (VLCTime *)length
279 {
280     if (!length) 
281     {
282         // Try figuring out what the length is
283         long long duration = libvlc_media_descriptor_get_duration( p_md, NULL );
284         if (duration > -1) 
285         {
286             [self setLength:[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]]];
287             return [[length retain] autorelease];
288         } 
289     }
290     return [VLCTime nullTime];
291 }
292
293 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
294 {
295     static const long long thread_sleep = 10000;
296
297     if (!length)
298     {
299         while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
300         {
301             usleep( thread_sleep );
302         }
303         
304         // So we're done waiting, but sometimes we trap the fact that the parsing
305         // was done before the length gets assigned, so lets go ahead and assign
306         // it ourselves.
307         if (!length)
308             return [self length];
309     }
310
311     return [[length retain] autorelease];
312 }
313
314 - (BOOL)isPreparsed
315 {
316     return libvlc_media_descriptor_is_preparsed( p_md, NULL );
317 }
318
319 @synthesize url;
320 @synthesize subitems;
321 @synthesize metaDictionary;
322 @synthesize state;
323
324 @end
325
326 /******************************************************************************
327  * Implementation VLCMedia (LibVLCBridging)
328  */
329 @implementation VLCMedia (LibVLCBridging)
330
331 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
332 {
333     return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
334 }
335
336 - (id)initWithLibVLCMediaDescriptor:(void *)md
337 {
338     if (self = [super init])
339     {
340         libvlc_exception_t ex;
341         libvlc_exception_init( &ex );
342                 
343         libvlc_media_descriptor_retain( md );
344         p_md = md;
345         
346         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
347         [self initInternalMediaDescriptor];
348     }
349     return self;
350 }
351
352 - (void *)libVLCMediaDescriptor 
353 {
354     return p_md;
355 }
356
357 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
358 {
359     libvlc_media_descriptor_t * p_md;
360     p_md = libvlc_media_descriptor_duplicate( [media libVLCMediaDescriptor] );
361     for( NSString * key in [options allKeys] )
362     {
363         NSLog(@"Adding %@", [NSString stringWithFormat:@"--%@ %@", key, [options objectForKey:key]]);
364         libvlc_media_descriptor_add_option(p_md, [[NSString stringWithFormat:@"%@=#%@", key, [options objectForKey:key]] UTF8String], NULL);
365     }
366     return [VLCMedia mediaWithLibVLCMediaDescriptor:p_md];
367 }
368
369 @end
370
371 /******************************************************************************
372  * Implementation VLCMedia (Private)
373  */
374 @implementation VLCMedia (Private)
375
376 + (libvlc_meta_t)stringToMetaType:(NSString *)string
377 {
378     static NSDictionary * stringToMetaDictionary = nil;
379     // TODO: Thread safe-ize
380     if( !stringToMetaDictionary )
381     {
382 #define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
383         stringToMetaDictionary =
384             [[NSDictionary dictionaryWithObjectsAndKeys:
385                 VLCStringToMeta(Title),
386                 VLCStringToMeta(Artist),
387                 VLCStringToMeta(Genre),
388                 VLCStringToMeta(Copyright),
389                 VLCStringToMeta(Album),
390                 VLCStringToMeta(TrackNumber),
391                 VLCStringToMeta(Description),
392                 VLCStringToMeta(Rating),
393                 VLCStringToMeta(Date),
394                 VLCStringToMeta(Setting),
395                 VLCStringToMeta(URL),
396                 VLCStringToMeta(Language),
397                 VLCStringToMeta(NowPlaying),
398                 VLCStringToMeta(Publisher),
399                 VLCStringToMeta(ArtworkURL),
400                 VLCStringToMeta(TrackID),
401                 nil] retain];
402 #undef VLCStringToMeta
403     }
404     NSNumber * number = [stringToMetaDictionary objectForKey:string];
405     return number ? [number intValue] : -1;
406 }
407
408 + (NSString *)metaTypeToString:(libvlc_meta_t)type
409 {
410 #define VLCMetaToString( name, type )   if (libvlc_meta_##name == type) return VLCMetaInformation##name;
411     VLCMetaToString(Title, type);
412     VLCMetaToString(Artist, type);
413     VLCMetaToString(Genre, type);
414     VLCMetaToString(Copyright, type);
415     VLCMetaToString(Album, type);
416     VLCMetaToString(TrackNumber, type);
417     VLCMetaToString(Description, type);
418     VLCMetaToString(Rating, type);
419     VLCMetaToString(Date, type);
420     VLCMetaToString(Setting, type);
421     VLCMetaToString(URL, type);
422     VLCMetaToString(Language, type);
423     VLCMetaToString(NowPlaying, type);
424     VLCMetaToString(Publisher, type);
425     VLCMetaToString(ArtworkURL, type);
426     VLCMetaToString(TrackID, type);
427 #undef VLCMetaToString
428     return nil;
429 }
430
431 - (void)initInternalMediaDescriptor
432 {
433     libvlc_exception_t ex;
434     libvlc_exception_init( &ex );
435
436     char * p_url = libvlc_media_descriptor_get_mrl( p_md, &ex );
437     catch_exception( &ex );
438
439     url = [[NSURL URLWithString:[NSString stringWithUTF8String:p_url]] retain];
440     if( !url ) /* Attempt to interpret as a file path then */
441         url = [[NSURL fileURLWithPath:[NSString stringWithUTF8String:p_url]] retain];
442     free( p_url );
443
444     libvlc_media_descriptor_set_user_data( p_md, (void*)self, &ex );
445     catch_exception( &ex );
446
447     libvlc_event_manager_t * p_em = libvlc_media_descriptor_event_manager( p_md, &ex );
448     libvlc_event_attach(p_em, libvlc_MediaDescriptorMetaChanged,     HandleMediaMetaChanged,     self, &ex);
449 //    libvlc_event_attach(p_em, libvlc_MediaDescriptorDurationChanged, HandleMediaDurationChanged, self, &ex);
450     libvlc_event_attach(p_em, libvlc_MediaDescriptorStateChanged,    HandleMediaStateChanged,    self, &ex);
451     libvlc_event_attach(p_em, libvlc_MediaDescriptorSubItemAdded,    HandleMediaSubItemAdded,    self, &ex);
452     catch_exception( &ex );
453     
454     libvlc_media_list_t * p_mlist = libvlc_media_descriptor_subitems( p_md, NULL );
455
456     if (!p_mlist)
457         subitems = nil;
458     else
459     {
460         subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
461         libvlc_media_list_release( p_mlist );
462     }
463
464     state = LibVLCStateToMediaState(libvlc_media_descriptor_get_state( p_md, NULL ));
465     /* Force VLCMetaInformationTitle, that will trigger preparsing
466      * And all the other meta will be added through the libvlc event system */
467     [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
468 }
469
470 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
471 {
472     char * psz_value = libvlc_media_descriptor_get_meta( p_md, [VLCMedia stringToMetaType:metaType], NULL);
473     NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
474     NSString * oldValue = [metaDictionary valueForKey:metaType];
475     free(psz_value);
476
477     if ( !(newValue && oldValue && [oldValue compare:newValue] == NSOrderedSame) )
478     {
479         if ([metaType isEqualToString:VLCMetaInformationArtworkURL])
480         {
481             [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:) 
482                                          toTarget:self
483                                        withObject:newValue];
484             return;
485         }
486
487         [metaDictionary setValue:newValue forKeyPath:metaType];
488     }
489 }
490
491 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
492 {
493     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
494     
495     // Go ahead and load up the art work
496     NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
497     NSImage * art  = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease]; 
498         
499     // If anything was found, lets save it to the meta data dictionary
500     if (art)
501     {
502         [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
503     }
504
505     [pool release];
506 }
507
508 - (void)setArtwork:(NSImage *)art
509 {
510     [metaDictionary setObject:art forKey:@"artwork"];
511 }
512
513 - (void)metaChanged:(NSString *)metaType
514 {
515     [self fetchMetaInformationFromLibVLCWithType:metaType];
516 }
517
518 - (void)subItemAdded
519 {
520     if( subitems )
521         return; /* Nothing to do */
522
523     libvlc_media_list_t * p_mlist = libvlc_media_descriptor_subitems( p_md, NULL );
524
525     NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
526
527     [self willChangeValueForKey:@"subitems"];
528     subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
529     [self didChangeValueForKey:@"subitems"];
530     libvlc_media_list_release( p_mlist );
531 }
532
533 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
534 {
535     [self setState: [newStateAsNumber intValue]];
536 }
537 @end
538
539 /******************************************************************************
540  * Implementation VLCMedia (VLCMediaPlayerBridging)
541  */
542
543 @implementation VLCMedia (VLCMediaPlayerBridging)
544
545 - (void)setLength:(VLCTime *)value
546 {
547     if (length && value && [length compare:value] == NSOrderedSame)
548         return;
549         
550     [length release];       
551     length = value ? [value retain] : nil;
552 }
553
554 @end