1 /*****************************************************************************
2 * VLCMedia.m: VLC.framework VLCMedia implementation
3 *****************************************************************************
4 * Copyright (C) 2007 Pierre d'Herbemont
5 * Copyright (C) 2007 the VideoLAN team
8 * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
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.
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.
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 *****************************************************************************/
26 #import "VLCMediaList.h"
27 #import "VLCEventManager.h"
28 #import "VLCLibrary.h"
29 #import "VLCLibVLCBridging.h"
30 #include <vlc/libvlc.h>
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";
52 /* Notification Messages */
53 NSString *VLCMediaMetaChanged = @"VLCMediaMetaChanged";
54 //NSString *VLCMediaSubItemAdded = @"VLCMediaSubItemAdded";
55 //NSString *VLCMediaSubItemDeleted = @"VLCMediaSubItemDeleted";
57 /* libvlc event callback */
58 //static void HandleMediaSubItemAdded(const libvlc_event_t *event, void *self)
60 // [[VLCEventManager sharedManager] callOnMainThreadObject:self
61 // withMethod:@selector(subItemAdded:)
62 // withArgumentAsObject:(id)event->u.media_descriptor_subitem_added.new_child];
65 //static void HandleMediaSubItemDeleted(const libvlc_event_t *event, void *self)
67 // [[VLCEventManager sharedManager] callOnMainThreadObject:self
68 // withMethod:@selector(subItemDeleted)
69 // withArgumentAsObject:nil];
72 static void HandleMediaMetaChanged(const libvlc_event_t *event, void *self)
74 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
76 [[VLCEventManager sharedManager] callOnMainThreadObject:self
77 withMethod:@selector(metaChanged:)
78 withArgumentAsObject:[NSNumber numberWithInt:(int)event->u.media_descriptor_meta_changed.meta_type]];
82 static void HandleMediaDurationChanged(const libvlc_event_t *event, void *self)
84 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
86 //[[VLCEventManager sharedManager] callOnMainThreadObject:self
87 // withMethod:@selector(setLength:)
88 // withArgumentAsObject:[VLCTime timeWithNumber:
89 // [NSNumber numberWithLongLong:event->u.media_descriptor_duration_changed.new_duration]]];
94 // TODO: Documentation
95 @interface VLCMedia (PrivateAPI)
98 + (libvlc_meta_t)stringToMetaType:(NSString *)string;
99 + (NSString *)metaTypeToString:(libvlc_meta_t)type;
102 - (void)initInternalMediaDescriptor;
105 - (BOOL)setMetaValue:(char *)value forKey:(NSString *)key;
106 - (void)fetchMetaInformation;
107 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL;
108 - (void)notifyChangeForKey:(NSString *)key withOldValue:(id)oldValue;
110 /* Callback Methods */
111 //- (void)subItemAdded:(libvlc_media_descriptor_t *)child;
112 //- (void)subItemRemoved:(libvlc_media_descriptor_t *)child;
113 - (void)metaChanged:(NSNumber *)metaType;
117 @implementation VLCMedia
119 + (id)mediaWithURL:(NSString *)aURL;
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];
129 - (id)initWithURL:(NSString *)aURL
132 NSString *scheme; // Everything before ://, defaults to file if not present
133 NSString *path; // Everything after ://
136 range = [aURL rangeOfString:@"://"];
137 if (range.length > 0)
139 scheme = [aURL substringToIndex:range.location];
146 path = [aURL substringFromIndex:NSMaxRange(range)];
148 if ([scheme isEqualToString:@"file"])
151 path = [path stringByExpandingTildeInPath];
153 // Check to see if it's a file or url
154 NSString *videoTSPath = path;
155 if ([[NSFileManager defaultManager] fileExistsAtPath:videoTSPath isDirectory:&isDirectory] && isDirectory)
157 if ([[videoTSPath lastPathComponent] compare:@"VIDEO_TS"] != NSOrderedSame)
158 videoTSPath = [videoTSPath stringByAppendingPathComponent:@"VIDEO_TS/"];
159 videoTSPath = [videoTSPath stringByAppendingPathComponent:@"VIDEO_TS.VOB"];
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 */;
165 // TODO: Should we search for playable items?
166 // This is not a playable file
171 if (self = [super init])
173 url = [[NSString stringWithFormat:@"%@://%@", scheme, path] retain];
175 libvlc_exception_t ex;
176 libvlc_exception_init(&ex);
178 p_md = libvlc_media_descriptor_new([VLCLibrary sharedInstance],
181 quit_on_exception(&ex);
184 metaDictionary = nil;
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?
190 [self initInternalMediaDescriptor];
199 if([self retainCount] <= 1)
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);
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];
223 [metaDictionary release];
225 libvlc_media_descriptor_release(p_md);
230 - (NSString *)description
232 NSString *result = nil;
234 result = [metaDictionary objectForKey:VLCMetaInformationTitle];
235 return (result ? result : url);
238 - (NSComparisonResult)compare:(VLCMedia *)media
241 return NSOrderedSame;
243 return NSOrderedDescending;
245 return [[self url] compare:[media url]];
250 return [[url copy] autorelease];
253 - (VLCMediaList *)subitems
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.
265 // Try figuring out what the length is
266 long long duration = libvlc_media_descriptor_get_duration(p_md, NULL);
269 [self setLength:[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]]];
270 return [[length retain] autorelease];
273 return [VLCTime nullTime];
276 #define CLOCK_FREQ 1000000
277 #define THREAD_SLEEP ((long long)(0.010*CLOCK_FREQ))
279 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
281 if (![url hasPrefix:@"file://"])
282 return [self length];
285 while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
287 usleep(THREAD_SLEEP);
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
294 return [self length];
296 return [[length retain] autorelease];
301 return libvlc_media_descriptor_is_preparsed(p_md, NULL);
304 - (NSDictionary *)metaDictionary
306 return metaDictionary;
309 - (void)setDelegate:(id)value
321 @implementation VLCMedia (LibVLCBridging)
323 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
325 libvlc_exception_t ex;
326 libvlc_exception_init( &ex );
328 VLCMedia *media = (VLCMedia *)libvlc_media_descriptor_get_user_data(md, &ex);
329 if (!media || libvlc_exception_raised(&ex))
331 libvlc_exception_clear(&ex);
332 return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
340 - (id)initWithLibVLCMediaDescriptor:(void *)md
342 libvlc_exception_t ex;
343 libvlc_exception_init( &ex );
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))
350 return [self retain];
352 libvlc_exception_clear(&ex); // Just in case an exception was raised, lets release it
354 if (self = [super init])
358 p_url = libvlc_media_descriptor_get_mrl(md, &ex);
359 quit_on_exception( &ex );
361 url = [NSString stringWithCString:p_url];
363 libvlc_media_descriptor_retain(md);
366 [self initInternalMediaDescriptor];
371 - (void *)libVLCMediaDescriptor
378 @implementation VLCMedia (PrivateAPI)
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;
383 + (libvlc_meta_t)stringToMetaType:(NSString *)string
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);
404 + (NSString *)metaTypeToString:(libvlc_meta_t)type
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);
425 - (void)initInternalMediaDescriptor
427 libvlc_exception_t ex;
428 libvlc_exception_init(&ex);
430 libvlc_media_descriptor_set_user_data(p_md, (void*)self, &ex);
431 quit_on_exception(&ex);
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);
441 libvlc_media_list_t *p_mlist = libvlc_media_descriptor_subitems(p_md, NULL);
447 subitems = [[VLCMediaList medialistWithLibVLCMediaList:p_mlist] retain];
448 libvlc_media_list_release(p_mlist);
450 [self fetchMetaInformation];
453 - (BOOL)setMetaValue:(char *)value forKey:(NSString *)key
457 NSString *oldValue = [metaDictionary valueForKey:key];
458 if ((!oldValue && value) || (oldValue && !value) || (oldValue && value && [oldValue compare:[NSString stringWithCString:value]] != NSOrderedSame))
461 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
464 [metaDictionary setValue:[NSString stringWithCString:value] forKeyPath:key];
466 [metaDictionary setValue:nil forKeyPath:key];
468 if ([key compare:VLCMetaInformationArtworkURL] == NSOrderedSame)
470 if ([metaDictionary valueForKey:VLCMetaInformationArtworkURL])
472 // Initialize a new thread
473 [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
475 withObject:[metaDictionary valueForKey:VLCMetaInformationArtworkURL]];
484 - (void)fetchMetaInformation
486 // TODO: Only fetch meta data that has been requested. Just don't fetch
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]];
494 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
496 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
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];
503 // If anything was found, lets save it to the meta data dictionary
506 @synchronized(metaDictionary)
508 [metaDictionary setObject:art forKey:VLCMetaInformationArtwork];
511 // Let the world know that there is new art work available
512 [self notifyChangeForKey:VLCMetaInformationArtwork withOldValue:nil];
521 - (void)notifyChangeForKey:(NSString *)key withOldValue:(id)oldValue
523 // Send out a formal notification
524 [[NSNotificationCenter defaultCenter] postNotificationName:VLCMediaMetaChanged
526 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
528 oldValue, @"oldValue",
531 // Now notify the delegate
532 if (delegate && [delegate respondsToSelector:@selector(media:metaValueChangedFrom:forKey:)])
533 [delegate media:self metaValueChangedFrom:oldValue forKey:key];
536 //- (void)subItemAdded:(libvlc_media_descriptor_t *)child
538 // // TODO: SubItemAdded Notification
541 //// subitems = [[VLCMediaList alloc] initWithLibVLCMediaList:];
545 //- (void)subItemRemoved:(libvlc_media_descriptor_t *)child
547 // // TODO: SubItemAdded Notification
550 // // subitems = [[VLCMediaList alloc] initWithLibVLCMediaList:];
554 - (void)metaChanged:(NSNumber *)metaType
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);
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];
569 @implementation VLCMedia (VLCMediaPlayerBridging)
571 - (void)setLength:(VLCTime *)value
575 if (length && value && [length compare:value] == NSOrderedSame)
578 [self willChangeValueForKey:@"length"];
586 length = [value retain];
588 [self didChangeValueForKey:@"length"];