MacOSX/Framework: s/PrivateAPI/Private/
[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 //NSString *VLCMediaSubItemAdded        = @"VLCMediaSubItemAdded";
55 //NSString *VLCMediaSubItemDeleted      = @"VLCMediaSubItemDeleted";
56
57 /* libvlc event callback */
58 //static void HandleMediaSubItemAdded(const libvlc_event_t *event, void *self)
59 //{
60 //  [[VLCEventManager sharedManager] callOnMainThreadObject:self
61 //                                               withMethod:@selector(subItemAdded:)
62 //                                     withArgumentAsObject:(id)event->u.media_descriptor_subitem_added.new_child];
63 //}
64 //
65 //static void HandleMediaSubItemDeleted(const libvlc_event_t *event, void *self)
66 //{
67 //  [[VLCEventManager sharedManager] callOnMainThreadObject:self
68 //                                               withMethod:@selector(subItemDeleted)
69 //                                     withArgumentAsObject:nil];
70 //}
71
72 static void HandleMediaMetaChanged(const libvlc_event_t *event, void *self)
73 {
74     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
75     
76     [[VLCEventManager sharedManager] callOnMainThreadObject:self
77                                                  withMethod:@selector(metaChanged:)
78                                        withArgumentAsObject:[NSNumber numberWithInt:(int)event->u.media_descriptor_meta_changed.meta_type]];
79     [pool release];
80 }
81
82 static void HandleMediaDurationChanged(const libvlc_event_t *event, void *self)
83 {
84     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
85     
86     //[[VLCEventManager sharedManager] callOnMainThreadObject:self
87 //                                                 withMethod:@selector(setLength:)
88 //                                       withArgumentAsObject:[VLCTime timeWithNumber:
89 //                                           [NSNumber numberWithLongLong:event->u.media_descriptor_duration_changed.new_duration]]];
90     [pool release];
91 }
92
93
94 // TODO: Documentation
95 @interface VLCMedia (Private)
96
97 /* Statics */
98 + (libvlc_meta_t)stringToMetaType:(NSString *)string;
99 + (NSString *)metaTypeToString:(libvlc_meta_t)type;
100
101 /* Initializers */
102 - (void)initInternalMediaDescriptor;
103
104 /* Operations */
105 - (BOOL)setMetaValue:(char *)value forKey:(NSString *)key;
106 - (void)fetchMetaInformation;
107 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL;
108 - (void)notifyChangeForKey:(NSString *)key withOldValue:(id)oldValue;
109
110 /* Callback Methods */
111 //- (void)subItemAdded:(libvlc_media_descriptor_t *)child;
112 //- (void)subItemRemoved:(libvlc_media_descriptor_t *)child;
113 - (void)metaChanged:(NSNumber *)metaType;
114
115 @end
116
117 @implementation VLCMedia
118
119 + (id)mediaWithURL:(NSString *)aURL;
120 {
121     // For some unknown reason, compiler kept shooting me a warning saying:
122     //  warning: passing argument 1 of 'initWithURL:' from distinct Objective-C type
123     // Research on the net shows that the error means that the argument passed
124     // is not compatible with the expected argument.  Doesn't make sense, however
125     // the warning goes away when it is casted it with "id".
126     return [[[VLCMedia alloc] initWithURL:(id)aURL] autorelease];
127 }
128
129 - (id)initWithURL:(NSString *)aURL
130 {
131     // Parse the URL
132     NSString *scheme;   // Everything before ://, defaults to file if not present
133     NSString *path;     // Everything after ://
134     NSRange range;
135     
136     range = [aURL rangeOfString:@"://"];
137     if (range.length > 0)
138     {
139         scheme = [aURL substringToIndex:range.location];
140     }
141     else
142     {
143         scheme = @"file";
144         range.location = 0;
145     }
146     path = [aURL substringFromIndex:NSMaxRange(range)];
147     
148     if ([scheme isEqualToString:@"file"]) 
149     {
150         BOOL isDirectory;
151         path = [path stringByExpandingTildeInPath];
152         
153         // Check to see if it's a file or url
154         NSString *videoTSPath = path;
155         if ([[NSFileManager defaultManager] fileExistsAtPath:videoTSPath isDirectory:&isDirectory] && isDirectory)
156         {
157             if ([[videoTSPath lastPathComponent] compare:@"VIDEO_TS"] != NSOrderedSame)
158                 videoTSPath = [videoTSPath stringByAppendingPathComponent:@"VIDEO_TS/"];
159             videoTSPath = [videoTSPath stringByAppendingPathComponent:@"VIDEO_TS.VOB"];
160             
161             // The url is a directory should we check for a DVD directory structure?
162             if ([[NSFileManager defaultManager] fileExistsAtPath:videoTSPath isDirectory:&isDirectory] && !isDirectory)
163                 /* do nothing because this is a DVD */;
164             else
165                 // TODO: Should we search for playable items?
166                 // This is not a playable file
167                 return nil;
168         }
169     }
170     
171     if (self = [super init])
172     {
173         url = [[NSString stringWithFormat:@"%@://%@", scheme, path] retain];
174         
175         libvlc_exception_t ex;
176         libvlc_exception_init(&ex);
177         
178         p_md = libvlc_media_descriptor_new([VLCLibrary sharedInstance],
179                                            [url cString],
180                                            &ex);
181         quit_on_exception(&ex);
182         
183         delegate = nil;
184         metaDictionary = nil;
185         
186         // This value is set whenever the demuxer figures out what the length is.
187         // TODO: Easy way to tell the length of the movie without having to instiate the demuxer.  Maybe cached info?
188         length = nil;
189
190         [self initInternalMediaDescriptor];
191     }
192     return self;
193 }
194
195 - (void)release
196 {
197     @synchronized(self)
198     {
199         if([self retainCount] <= 1)
200         {
201             /* We must make sure we won't receive new event after an upcoming dealloc
202              * We also may receive a -retain in some event callback that may occcur
203              * Before libvlc_event_detach. So this can't happen in dealloc */
204             libvlc_event_manager_t *p_em = libvlc_media_descriptor_event_manager(p_md, NULL);
205 //            libvlc_event_detach(p_em, libvlc_MediaDescriptorSubItemAdded,    HandleMediaSubItemAdded,    self, NULL);
206 //            libvlc_event_detach(p_em, libvlc_MediaDescriptorSubItemDeleted,  HandleMediaSubItemAdded,    self, NULL);
207 //            libvlc_event_detach(p_em, libvlc_MediaDescriptorMetaChanged,     HandleMediaMetaChanged,     self, NULL);
208 //            libvlc_event_detach(p_em, libvlc_MediaDescriptorDurationChanged, HandleMediaDurationChanged, self, NULL);
209         }
210         [super release];
211     }
212 }
213
214 - (void)dealloc
215 {
216     // Testing to see if the pointer exists is not required, if the pointer is null
217     // then the release message is not sent to it.
218     [self setDelegate:nil];
219     [self setLength:nil];
220
221     [url release];
222     [subitems release];
223     [metaDictionary release];
224     
225     libvlc_media_descriptor_release(p_md);
226
227     [super dealloc];
228 }
229
230 - (NSString *)description
231 {
232     NSString *result = nil;
233     if (metaDictionary)
234         result = [metaDictionary objectForKey:VLCMetaInformationTitle];
235     return (result ? result : url);
236 }
237
238 - (NSComparisonResult)compare:(VLCMedia *)media
239 {
240     if (self == media)
241         return NSOrderedSame;
242     else if (!media)
243         return NSOrderedDescending;
244     else
245         return [[self url] compare:[media url]];
246 }
247
248 - (NSString *)url
249 {
250     return [[url copy] autorelease];
251 }
252
253 - (VLCMediaList *)subitems
254 {
255     return subitems;
256 }
257
258 - (VLCTime *)length
259 {
260     // TODO: Value is updated whenever the item is updated in the libvlc 
261     // (var_Addcallback(instance, "item-changed", callback)) would work but 
262     // that's not authorized from here.
263     if (!length) 
264     {
265         // Try figuring out what the length is
266         long long duration = libvlc_media_descriptor_get_duration(p_md, NULL);
267         if (duration > -1) 
268         {
269             [self setLength:[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]]];
270             return [[length retain] autorelease];
271         } 
272     }
273     return [VLCTime nullTime];
274 }
275
276 #define CLOCK_FREQ      1000000
277 #define THREAD_SLEEP    ((long long)(0.010*CLOCK_FREQ))
278
279 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
280 {
281     if (![url hasPrefix:@"file://"])
282         return [self length];
283     else if (!length)
284     {
285         while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
286         {
287             usleep(THREAD_SLEEP);
288         }
289         
290         // So we're done waiting, but sometimes we trap the fact that the parsing
291         // was done before the length gets assigned, so lets go ahead and assign
292         // it ourselves.
293         if (!length)
294             return [self length];
295     }
296     return [[length retain] autorelease];
297 }
298
299 - (BOOL)isPreparsed
300 {
301     return libvlc_media_descriptor_is_preparsed(p_md, NULL);
302 }
303
304 - (NSDictionary *)metaDictionary
305 {
306     return metaDictionary;
307 }
308
309 - (void)setDelegate:(id)value
310 {
311     delegate = value;
312 }
313
314 - (id)delegate
315 {
316     return delegate;
317 }
318
319 @end
320
321 @implementation VLCMedia (LibVLCBridging)
322
323 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
324 {
325     libvlc_exception_t ex;
326     libvlc_exception_init( &ex );
327     
328     VLCMedia *media = (VLCMedia *)libvlc_media_descriptor_get_user_data(md, &ex);
329     if (!media || libvlc_exception_raised(&ex))
330     {
331         libvlc_exception_clear(&ex);
332         return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
333     }
334     else
335     {
336         return media;
337     }
338 }
339
340 - (id)initWithLibVLCMediaDescriptor:(void *)md
341 {
342     libvlc_exception_t ex;
343     libvlc_exception_init( &ex );
344
345     // Core hacks that allows for native objects to be paired with core objects.  Otherwise, a native object
346     // would be recreated every time we want to address the media descriptor.  This eliminates the need
347     // for maintaining local copies of core objects.
348     if ((self = (id)libvlc_media_descriptor_get_user_data(md, &ex)) && !libvlc_exception_raised(&ex))
349     {
350         return [self retain];
351     }
352     libvlc_exception_clear(&ex);    // Just in case an exception was raised, lets release it
353     
354     if (self = [super init])
355     {
356         char * p_url;
357         
358         p_url = libvlc_media_descriptor_get_mrl(md, &ex);
359         quit_on_exception( &ex );
360         
361         url = [NSString stringWithCString:p_url];
362         
363         libvlc_media_descriptor_retain(md);
364         p_md = md;
365         
366         [self initInternalMediaDescriptor];
367     }
368     return self;
369 }
370
371 - (void *)libVLCMediaDescriptor
372 {
373     return p_md;
374 }
375
376 @end
377
378 @implementation VLCMedia (Private)
379
380 #define VLCStringToMeta( name, string ) if ([VLCMetaInformation##name compare:string] == NSOrderedSame) return libvlc_meta_##name;
381 #define VLCMetaToString( name, type )   if (libvlc_meta_##name == type) return VLCMetaInformation##name;
382
383 + (libvlc_meta_t)stringToMetaType:(NSString *)string
384 {
385     VLCStringToMeta(Title, string);
386     VLCStringToMeta(Artist, string);
387     VLCStringToMeta(Genre, string);
388     VLCStringToMeta(Copyright, string);
389     VLCStringToMeta(Album, string);
390     VLCStringToMeta(TrackNumber, string);
391     VLCStringToMeta(Description, string);
392     VLCStringToMeta(Rating, string);
393     VLCStringToMeta(Date, string);
394     VLCStringToMeta(Setting, string);
395     VLCStringToMeta(URL, string);
396     VLCStringToMeta(Language, string);
397     VLCStringToMeta(NowPlaying, string);
398     VLCStringToMeta(Publisher, string);
399     VLCStringToMeta(ArtworkURL, string);
400     VLCStringToMeta(TrackID, string);
401     return -1;
402 }
403
404 + (NSString *)metaTypeToString:(libvlc_meta_t)type
405 {
406     VLCMetaToString(Title, type);
407     VLCMetaToString(Artist, type);
408     VLCMetaToString(Genre, type);
409     VLCMetaToString(Copyright, type);
410     VLCMetaToString(Album, type);
411     VLCMetaToString(TrackNumber, type);
412     VLCMetaToString(Description, type);
413     VLCMetaToString(Rating, type);
414     VLCMetaToString(Date, type);
415     VLCMetaToString(Setting, type);
416     VLCMetaToString(URL, type);
417     VLCMetaToString(Language, type);
418     VLCMetaToString(NowPlaying, type);
419     VLCMetaToString(Publisher, type);
420     VLCMetaToString(ArtworkURL, type);
421     VLCMetaToString(TrackID, type);
422     return nil;
423 }
424
425 - (void)initInternalMediaDescriptor
426 {
427     libvlc_exception_t ex;
428     libvlc_exception_init(&ex);
429     
430     libvlc_media_descriptor_set_user_data(p_md, (void*)self, &ex);
431     quit_on_exception(&ex);
432     
433     // TODO: Should these events be caught by VLCMediaList's notification hooks?
434     libvlc_event_manager_t *p_em = libvlc_media_descriptor_event_manager(p_md, &ex);
435 //    libvlc_event_attach(p_em, libvlc_MediaDescriptorSubItemAdded,    HandleMediaSubItemAdded,    self, &ex);
436 //    libvlc_event_attach(p_em, libvlc_MediaDescriptorSubItemRemoved,  HandleMediaSubItemRemoved,  self, &ex);
437 //    libvlc_event_attach(p_em, libvlc_MediaDescriptorMetaChanged,     HandleMediaMetaChanged,     self, &ex);
438 //    libvlc_event_attach(p_em, libvlc_MediaDescriptorDurationChanged, HandleMediaDurationChanged, self, &ex);
439     quit_on_exception(&ex);
440     
441     libvlc_media_list_t *p_mlist = libvlc_media_descriptor_subitems(p_md, NULL);
442     if (!p_mlist)
443         subitems = nil;
444     else
445     {
446         [subitems release];
447         subitems = [[VLCMediaList medialistWithLibVLCMediaList:p_mlist] retain];
448         libvlc_media_list_release(p_mlist);
449     }
450     [self fetchMetaInformation];
451 }
452
453 - (BOOL)setMetaValue:(char *)value forKey:(NSString *)key
454 {
455     BOOL result = NO;
456     
457     NSString *oldValue = [metaDictionary valueForKey:key];
458     if ((!oldValue && value) || (oldValue && !value) || (oldValue && value && [oldValue compare:[NSString stringWithCString:value]] != NSOrderedSame))
459     {
460         if (!metaDictionary)
461             metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
462
463         if (value)
464             [metaDictionary setValue:[NSString stringWithCString:value] forKeyPath:key];
465         else
466             [metaDictionary setValue:nil forKeyPath:key];
467         
468         if ([key compare:VLCMetaInformationArtworkURL] == NSOrderedSame)
469         {
470             if ([metaDictionary valueForKey:VLCMetaInformationArtworkURL])
471             {
472                 // Initialize a new thread
473                 [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:) 
474                                          toTarget:self
475                                        withObject:[metaDictionary valueForKey:VLCMetaInformationArtworkURL]];
476             }
477         }
478         result = YES;
479     }
480     free(value);
481     return result;
482 }
483
484 - (void)fetchMetaInformation
485 {
486     // TODO: Only fetch meta data that has been requested.  Just don't fetch
487     // it, just because.
488     
489     [self setMetaValue:libvlc_media_descriptor_get_meta(p_md, libvlc_meta_Title,      NULL) forKey:[VLCMedia metaTypeToString:libvlc_meta_Title]];
490     [self setMetaValue:libvlc_media_descriptor_get_meta(p_md, libvlc_meta_Artist,     NULL) forKey:[VLCMedia metaTypeToString:libvlc_meta_Artist]];
491     [self setMetaValue:libvlc_media_descriptor_get_meta(p_md, libvlc_meta_ArtworkURL, NULL) forKey:[VLCMedia metaTypeToString:libvlc_meta_ArtworkURL]];
492 }
493
494 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
495 {
496     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
497     @try
498     {
499         // Go ahead and load up the art work
500         NSURL *artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
501         NSImage *art  = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease]; 
502         
503         // If anything was found, lets save it to the meta data dictionary
504         if (art)
505         {
506             @synchronized(metaDictionary)
507             {
508                 [metaDictionary setObject:art forKey:VLCMetaInformationArtwork];
509             }
510             
511             // Let the world know that there is new art work available
512             [self notifyChangeForKey:VLCMetaInformationArtwork withOldValue:nil];
513         }
514     }
515     @finally 
516     {
517         [pool release];
518     }
519 }
520
521 - (void)notifyChangeForKey:(NSString *)key withOldValue:(id)oldValue
522 {
523     // Send out a formal notification
524     [[NSNotificationCenter defaultCenter] postNotificationName:VLCMediaMetaChanged
525                                                         object:self
526                                                       userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
527                                                           key, @"key",
528                                                           oldValue, @"oldValue", 
529                                                           nil]];
530     
531     // Now notify the delegate
532     if (delegate && [delegate respondsToSelector:@selector(media:metaValueChangedFrom:forKey:)])
533         [delegate media:self metaValueChangedFrom:oldValue forKey:key];
534 }
535
536 //- (void)subItemAdded:(libvlc_media_descriptor_t *)child
537 //{
538 //  // TODO: SubItemAdded Notification
539 ////    if (!subitems)
540 ////    {
541 ////        subitems = [[VLCMediaList alloc] initWithLibVLCMediaList:];
542 ////    }
543 //}
544 //
545 //- (void)subItemRemoved:(libvlc_media_descriptor_t *)child
546 //{
547 //  // TODO: SubItemAdded Notification
548 //  //    if (!subitems)
549 //  //    {
550 //  //      subitems = [[VLCMediaList alloc] initWithLibVLCMediaList:];
551 //  //    }
552 //}
553
554 - (void)metaChanged:(NSNumber *)metaType
555 {
556     // TODO: Only retrieve the meta that was changed
557     // Can we figure out what piece was changed instead of retreiving everything?
558     NSString *key = [VLCMedia metaTypeToString:[metaType intValue]];
559     id oldValue = (metaDictionary ? [metaDictionary valueForKey:key] : nil);
560
561     // Update the meta data
562     if ([self setMetaValue:libvlc_media_descriptor_get_meta(p_md, [metaType intValue], NULL) forKey:key])
563         // There was actually a change, send out the notifications
564         [self notifyChangeForKey:key withOldValue:oldValue];
565 }
566
567 @end
568
569 @implementation VLCMedia (VLCMediaPlayerBridging)
570
571 - (void)setLength:(VLCTime *)value
572 {
573     if (length != value)
574     {
575         if (length && value && [length compare:value] == NSOrderedSame)
576             return;
577         
578         [self willChangeValueForKey:@"length"];
579
580         if (length) {
581             [length release];
582             length = nil;
583         }
584         
585         if (value)
586             length = [value retain];
587
588         [self didChangeValueForKey:@"length"];
589     }
590 }
591
592 @end