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