1 /*****************************************************************************
2 * VLCMedia.m: VLCKit.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";
55 /******************************************************************************
56 * @property (readwrite)
58 @interface VLCMedia ()
59 @property (readwrite) VLCMediaState state;
62 /******************************************************************************
65 // TODO: Documentation
66 @interface VLCMedia (Private)
68 + (libvlc_meta_t)stringToMetaType:(NSString *)string;
69 + (NSString *)metaTypeToString:(libvlc_meta_t)type;
72 - (void)initInternalMediaDescriptor;
75 - (void)fetchMetaInformationFromLibVLCWithType:(NSString*)metaType;
76 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL;
77 - (void)setArtwork:(NSImage *)art;
79 /* Callback Methods */
80 - (void)metaChanged:(NSString *)metaType;
82 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber;
85 static VLCMediaState libvlc_state_to_media_state[] =
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,
97 static inline VLCMediaState LibVLCStateToMediaState( libvlc_state_t state )
99 return libvlc_state_to_media_state[state];
102 /******************************************************************************
103 * LibVLC Event Callback
105 static void HandleMediaMetaChanged(const libvlc_event_t * event, void * self)
107 if( event->u.media_meta_changed.meta_type == libvlc_meta_Publisher ||
108 event->u.media_meta_changed.meta_type == libvlc_meta_NowPlaying )
110 /* Skip those meta. We don't really care about them for now.
111 * And they occure a lot */
114 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
115 [[VLCEventManager sharedManager] callOnMainThreadObject:self
116 withMethod:@selector(metaChanged:)
117 withArgumentAsObject:[VLCMedia metaTypeToString:event->u.media_meta_changed.meta_type]];
121 static void HandleMediaDurationChanged(const libvlc_event_t * event, void * self)
123 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
125 [[VLCEventManager sharedManager] callOnMainThreadObject:self
126 withMethod:@selector(setLength:)
127 withArgumentAsObject:[VLCTime timeWithNumber:
128 [NSNumber numberWithLongLong:event->u.media_duration_changed.new_duration]]];
132 static void HandleMediaStateChanged(const libvlc_event_t * event, void * self)
134 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
136 [[VLCEventManager sharedManager] callOnMainThreadObject:self
137 withMethod:@selector(setStateAsNumber:)
138 withArgumentAsObject:[NSNumber numberWithInt:
139 LibVLCStateToMediaState(event->u.media_state_changed.new_state)]];
143 static void HandleMediaSubItemAdded(const libvlc_event_t * event, void * self)
145 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
146 [[VLCEventManager sharedManager] callOnMainThreadObject:self
147 withMethod:@selector(subItemAdded)
148 withArgumentAsObject:nil];
152 /******************************************************************************
155 @implementation VLCMedia
156 + (id)mediaWithURL:(NSURL *)anURL;
158 return [[[VLCMedia alloc] initWithURL:anURL] autorelease];
161 + (id)mediaWithPath:(NSString *)aPath;
163 return [[[VLCMedia alloc] initWithPath:aPath] autorelease];
166 + (id)mediaAsNodeWithName:(NSString *)aName;
168 return [[[VLCMedia alloc] initAsNodeWithName:aName] autorelease];
171 - (id)initWithPath:(NSString *)aPath
173 return [self initWithURL:[NSURL fileURLWithPath:aPath isDirectory:NO]];
176 - (id)initWithURL:(NSURL *)anURL
178 if (self = [super init])
180 libvlc_exception_t ex;
181 libvlc_exception_init(&ex);
183 p_md = libvlc_media_new([VLCLibrary sharedInstance],
184 [[anURL absoluteString] UTF8String],
186 catch_exception(&ex);
189 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
191 // This value is set whenever the demuxer figures out what the length is.
192 // TODO: Easy way to tell the length of the movie without having to instiate the demuxer. Maybe cached info?
195 [self initInternalMediaDescriptor];
200 - (id)initAsNodeWithName:(NSString *)aName
202 if (self = [super init])
204 libvlc_exception_t ex;
205 libvlc_exception_init(&ex);
207 p_md = libvlc_media_new_as_node([VLCLibrary sharedInstance],
210 catch_exception(&ex);
213 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
215 // This value is set whenever the demuxer figures out what the length is.
216 // TODO: Easy way to tell the length of the movie without having to instiate the demuxer. Maybe cached info?
219 [self initInternalMediaDescriptor];
228 if([self retainCount] <= 1)
230 /* We must make sure we won't receive new event after an upcoming dealloc
231 * We also may receive a -retain in some event callback that may occcur
232 * Before libvlc_event_detach. So this can't happen in dealloc */
233 libvlc_event_manager_t * p_em = libvlc_media_event_manager(p_md);
234 libvlc_event_detach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self);
235 libvlc_event_detach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
236 libvlc_event_detach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self);
237 libvlc_event_detach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self);
245 // Testing to see if the pointer exists is not required, if the pointer is null
246 // then the release message is not sent to it.
251 [metaDictionary release];
253 libvlc_media_release( p_md );
258 - (NSString *)description
260 NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
261 return [NSString stringWithFormat:@"<%@ %p> %@", [self className], self, (result ? result : [url absoluteString])];
264 - (NSComparisonResult)compare:(VLCMedia *)media
267 return NSOrderedSame;
269 return NSOrderedDescending;
270 return p_md == [media libVLCMediaDescriptor] ? NSOrderedSame : NSOrderedAscending;
273 @synthesize delegate;
279 // Try figuring out what the length is
280 long long duration = libvlc_media_get_duration( p_md, NULL );
283 length = [[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]] retain];
284 return [[length retain] autorelease];
286 return [VLCTime nullTime];
288 return [[length retain] autorelease];
291 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
293 static const long long thread_sleep = 10000;
297 // Force preparsing of this item.
300 // wait until we are preparsed
301 while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
303 usleep( thread_sleep );
306 // So we're done waiting, but sometimes we trap the fact that the parsing
307 // was done before the length gets assigned, so lets go ahead and assign
310 return [self length];
313 return [[length retain] autorelease];
318 return libvlc_media_is_preparsed( p_md );
322 @synthesize subitems;
323 @synthesize metaDictionary;
328 /******************************************************************************
329 * Implementation VLCMedia (LibVLCBridging)
331 @implementation VLCMedia (LibVLCBridging)
333 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
335 return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
338 - (id)initWithLibVLCMediaDescriptor:(void *)md
340 if (self = [super init])
342 libvlc_media_retain( md );
345 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
346 [self initInternalMediaDescriptor];
351 - (void *)libVLCMediaDescriptor
356 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
358 libvlc_media_t * p_md;
359 p_md = libvlc_media_duplicate( [media libVLCMediaDescriptor] );
361 for( NSString * key in [options allKeys] )
363 libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=#%@", key, [options objectForKey:key]] UTF8String]);
365 return [VLCMedia mediaWithLibVLCMediaDescriptor:p_md];
370 /******************************************************************************
371 * Implementation VLCMedia (Private)
373 @implementation VLCMedia (Private)
375 + (libvlc_meta_t)stringToMetaType:(NSString *)string
377 static NSDictionary * stringToMetaDictionary = nil;
378 // TODO: Thread safe-ize
379 if( !stringToMetaDictionary )
381 #define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
382 stringToMetaDictionary =
383 [[NSDictionary dictionaryWithObjectsAndKeys:
384 VLCStringToMeta(Title),
385 VLCStringToMeta(Artist),
386 VLCStringToMeta(Genre),
387 VLCStringToMeta(Copyright),
388 VLCStringToMeta(Album),
389 VLCStringToMeta(TrackNumber),
390 VLCStringToMeta(Description),
391 VLCStringToMeta(Rating),
392 VLCStringToMeta(Date),
393 VLCStringToMeta(Setting),
394 VLCStringToMeta(URL),
395 VLCStringToMeta(Language),
396 VLCStringToMeta(NowPlaying),
397 VLCStringToMeta(Publisher),
398 VLCStringToMeta(ArtworkURL),
399 VLCStringToMeta(TrackID),
401 #undef VLCStringToMeta
403 NSNumber * number = [stringToMetaDictionary objectForKey:string];
404 return number ? [number intValue] : -1;
407 + (NSString *)metaTypeToString:(libvlc_meta_t)type
409 #define VLCMetaToString( name, type ) if (libvlc_meta_##name == type) return VLCMetaInformation##name;
410 VLCMetaToString(Title, type);
411 VLCMetaToString(Artist, type);
412 VLCMetaToString(Genre, type);
413 VLCMetaToString(Copyright, type);
414 VLCMetaToString(Album, type);
415 VLCMetaToString(TrackNumber, type);
416 VLCMetaToString(Description, type);
417 VLCMetaToString(Rating, type);
418 VLCMetaToString(Date, type);
419 VLCMetaToString(Setting, type);
420 VLCMetaToString(URL, type);
421 VLCMetaToString(Language, type);
422 VLCMetaToString(NowPlaying, type);
423 VLCMetaToString(Publisher, type);
424 VLCMetaToString(ArtworkURL, type);
425 VLCMetaToString(TrackID, type);
426 #undef VLCMetaToString
430 - (void)initInternalMediaDescriptor
432 char * p_url = libvlc_media_get_mrl( p_md );
434 url = [[NSURL URLWithString:[NSString stringWithUTF8String:p_url]] retain];
435 if( !url ) /* Attempt to interpret as a file path then */
436 url = [[NSURL fileURLWithPath:[NSString stringWithUTF8String:p_url]] retain];
439 libvlc_media_set_user_data( p_md, (void*)self );
441 libvlc_event_manager_t * p_em = libvlc_media_event_manager( p_md );
442 libvlc_event_attach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self);
443 libvlc_event_attach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
444 libvlc_event_attach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self);
445 libvlc_event_attach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self);
447 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
453 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
454 libvlc_media_list_release( p_mlist );
457 state = LibVLCStateToMediaState(libvlc_media_get_state( p_md ));
460 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
462 char * psz_value = libvlc_media_get_meta( p_md, [VLCMedia stringToMetaType:metaType] );
463 NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
464 NSString * oldValue = [metaDictionary valueForKey:metaType];
467 if ( newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame) )
469 // Only fetch the art if needed. (ie, create the NSImage, if it was requested before)
470 if (isArtFetched && [metaType isEqualToString:VLCMetaInformationArtworkURL])
472 [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
474 withObject:newValue];
477 [metaDictionary setValue:newValue forKeyPath:metaType];
481 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
483 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
488 // Go ahead and load up the art work
489 NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
491 // Don't attempt to fetch artwork from remote. Core will do that alone
492 if ([artUrl isFileURL])
493 art = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease];
496 // If anything was found, lets save it to the meta data dictionary
497 [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
502 - (void)setArtwork:(NSImage *)art
506 [metaDictionary removeObjectForKey:@"artwork"];
510 [metaDictionary setObject:art forKey:@"artwork"];
513 - (void)metaChanged:(NSString *)metaType
515 [self fetchMetaInformationFromLibVLCWithType:metaType];
521 return; /* Nothing to do */
523 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
525 NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
527 [self willChangeValueForKey:@"subitems"];
528 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
529 [self didChangeValueForKey:@"subitems"];
530 libvlc_media_list_release( p_mlist );
533 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
535 [self setState: [newStateAsNumber intValue]];
538 - (id)valueForKeyPath:(NSString *)keyPath
540 if( !isArtFetched && [keyPath isEqualToString:@"metaDictionary.artwork"])
543 /* Force the retrieval of the artwork now that someone asked for it */
544 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
546 else if( !areOthersMetaFetched && [keyPath hasPrefix:@"metaDictionary."])
548 areOthersMetaFetched = YES;
549 /* Force VLCMetaInformationTitle, that will trigger preparsing
550 * And all the other meta will be added through the libvlc event system */
551 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
554 else if( !isArtURLFetched && [keyPath hasPrefix:@"metaDictionary.artworkURL"])
556 isArtURLFetched = YES;
557 /* Force isArtURLFetched, that will trigger artwork download eventually
558 * And all the other meta will be added through the libvlc event system */
559 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
562 return [super valueForKeyPath:keyPath];
566 /******************************************************************************
567 * Implementation VLCMedia (VLCMediaPlayerBridging)
570 @implementation VLCMedia (VLCMediaPlayerBridging)
572 - (void)setLength:(VLCTime *)value
574 if (length && value && [length compare:value] == NSOrderedSame)
578 length = value ? [value retain] : nil;