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 p_md = libvlc_media_new_location([VLCLibrary sharedInstance],
181 [[anURL absoluteString] UTF8String]);
184 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
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];
195 - (id)initAsNodeWithName:(NSString *)aName
197 if (self = [super init])
199 p_md = libvlc_media_new_as_node([VLCLibrary sharedInstance],
203 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
205 // This value is set whenever the demuxer figures out what the length is.
206 // TODO: Easy way to tell the length of the movie without having to instiate the demuxer. Maybe cached info?
209 [self initInternalMediaDescriptor];
218 if([self retainCount] <= 1)
220 /* We must make sure we won't receive new event after an upcoming dealloc
221 * We also may receive a -retain in some event callback that may occcur
222 * Before libvlc_event_detach. So this can't happen in dealloc */
223 libvlc_event_manager_t * p_em = libvlc_media_event_manager(p_md);
224 libvlc_event_detach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self);
225 libvlc_event_detach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
226 libvlc_event_detach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self);
227 libvlc_event_detach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self);
235 // Testing to see if the pointer exists is not required, if the pointer is null
236 // then the release message is not sent to it.
241 [metaDictionary release];
243 libvlc_media_release( p_md );
248 - (NSString *)description
250 NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
251 return [NSString stringWithFormat:@"<%@ %p> %@", [self className], self, (result ? result : [url absoluteString])];
254 - (NSComparisonResult)compare:(VLCMedia *)media
257 return NSOrderedSame;
259 return NSOrderedDescending;
260 return p_md == [media libVLCMediaDescriptor] ? NSOrderedSame : NSOrderedAscending;
263 @synthesize delegate;
269 // Try figuring out what the length is
270 long long duration = libvlc_media_get_duration( p_md );
273 length = [[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]] retain];
274 return [[length retain] autorelease];
276 return [VLCTime nullTime];
278 return [[length retain] autorelease];
281 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
283 static const long long thread_sleep = 10000;
287 // Force preparsing of this item.
290 // wait until we are preparsed
291 while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
293 usleep( thread_sleep );
296 // So we're done waiting, but sometimes we trap the fact that the parsing
297 // was done before the length gets assigned, so lets go ahead and assign
300 return [self length];
303 return [[length retain] autorelease];
308 return libvlc_media_is_preparsed( p_md );
312 @synthesize subitems;
313 @synthesize metaDictionary;
318 /******************************************************************************
319 * Implementation VLCMedia (LibVLCBridging)
321 @implementation VLCMedia (LibVLCBridging)
323 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
325 return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
328 - (id)initWithLibVLCMediaDescriptor:(void *)md
330 if (self = [super init])
332 libvlc_media_retain( md );
335 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
336 [self initInternalMediaDescriptor];
341 - (void *)libVLCMediaDescriptor
346 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
348 libvlc_media_t * p_md;
349 p_md = libvlc_media_duplicate( [media libVLCMediaDescriptor] );
351 for( NSString * key in [options allKeys] )
353 libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=#%@", key, [options objectForKey:key]] UTF8String]);
355 return [VLCMedia mediaWithLibVLCMediaDescriptor:p_md];
360 /******************************************************************************
361 * Implementation VLCMedia (Private)
363 @implementation VLCMedia (Private)
365 + (libvlc_meta_t)stringToMetaType:(NSString *)string
367 static NSDictionary * stringToMetaDictionary = nil;
368 // TODO: Thread safe-ize
369 if( !stringToMetaDictionary )
371 #define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
372 stringToMetaDictionary =
373 [[NSDictionary dictionaryWithObjectsAndKeys:
374 VLCStringToMeta(Title),
375 VLCStringToMeta(Artist),
376 VLCStringToMeta(Genre),
377 VLCStringToMeta(Copyright),
378 VLCStringToMeta(Album),
379 VLCStringToMeta(TrackNumber),
380 VLCStringToMeta(Description),
381 VLCStringToMeta(Rating),
382 VLCStringToMeta(Date),
383 VLCStringToMeta(Setting),
384 VLCStringToMeta(URL),
385 VLCStringToMeta(Language),
386 VLCStringToMeta(NowPlaying),
387 VLCStringToMeta(Publisher),
388 VLCStringToMeta(ArtworkURL),
389 VLCStringToMeta(TrackID),
391 #undef VLCStringToMeta
393 NSNumber * number = [stringToMetaDictionary objectForKey:string];
394 return number ? [number intValue] : -1;
397 + (NSString *)metaTypeToString:(libvlc_meta_t)type
399 #define VLCMetaToString( name, type ) if (libvlc_meta_##name == type) return VLCMetaInformation##name;
400 VLCMetaToString(Title, type);
401 VLCMetaToString(Artist, type);
402 VLCMetaToString(Genre, type);
403 VLCMetaToString(Copyright, type);
404 VLCMetaToString(Album, type);
405 VLCMetaToString(TrackNumber, type);
406 VLCMetaToString(Description, type);
407 VLCMetaToString(Rating, type);
408 VLCMetaToString(Date, type);
409 VLCMetaToString(Setting, type);
410 VLCMetaToString(URL, type);
411 VLCMetaToString(Language, type);
412 VLCMetaToString(NowPlaying, type);
413 VLCMetaToString(Publisher, type);
414 VLCMetaToString(ArtworkURL, type);
415 VLCMetaToString(TrackID, type);
416 #undef VLCMetaToString
420 - (void)initInternalMediaDescriptor
422 char * p_url = libvlc_media_get_mrl( p_md );
424 url = [[NSURL URLWithString:[NSString stringWithUTF8String:p_url]] retain];
425 if( !url ) /* Attempt to interpret as a file path then */
426 url = [[NSURL fileURLWithPath:[NSString stringWithUTF8String:p_url]] retain];
429 libvlc_media_set_user_data( p_md, (void*)self );
431 libvlc_event_manager_t * p_em = libvlc_media_event_manager( p_md );
432 libvlc_event_attach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self);
433 libvlc_event_attach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
434 libvlc_event_attach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self);
435 libvlc_event_attach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self);
437 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
443 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
444 libvlc_media_list_release( p_mlist );
447 state = LibVLCStateToMediaState(libvlc_media_get_state( p_md ));
450 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
452 char * psz_value = libvlc_media_get_meta( p_md, [VLCMedia stringToMetaType:metaType] );
453 NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
454 NSString * oldValue = [metaDictionary valueForKey:metaType];
457 if ( newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame) )
459 // Only fetch the art if needed. (ie, create the NSImage, if it was requested before)
460 if (isArtFetched && [metaType isEqualToString:VLCMetaInformationArtworkURL])
462 [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
464 withObject:newValue];
467 [metaDictionary setValue:newValue forKeyPath:metaType];
471 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
473 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
478 // Go ahead and load up the art work
479 NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
481 // Don't attempt to fetch artwork from remote. Core will do that alone
482 if ([artUrl isFileURL])
483 art = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease];
486 // If anything was found, lets save it to the meta data dictionary
487 [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
492 - (void)setArtwork:(NSImage *)art
496 [metaDictionary removeObjectForKey:@"artwork"];
500 [metaDictionary setObject:art forKey:@"artwork"];
503 - (void)metaChanged:(NSString *)metaType
505 [self fetchMetaInformationFromLibVLCWithType:metaType];
511 return; /* Nothing to do */
513 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
515 NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
517 [self willChangeValueForKey:@"subitems"];
518 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
519 [self didChangeValueForKey:@"subitems"];
520 libvlc_media_list_release( p_mlist );
523 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
525 [self setState: [newStateAsNumber intValue]];
528 - (id)valueForKeyPath:(NSString *)keyPath
530 if( !isArtFetched && [keyPath isEqualToString:@"metaDictionary.artwork"])
533 /* Force the retrieval of the artwork now that someone asked for it */
534 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
536 else if( !areOthersMetaFetched && [keyPath hasPrefix:@"metaDictionary."])
538 areOthersMetaFetched = YES;
539 /* Force VLCMetaInformationTitle, that will trigger preparsing
540 * And all the other meta will be added through the libvlc event system */
541 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
544 else if( !isArtURLFetched && [keyPath hasPrefix:@"metaDictionary.artworkURL"])
546 isArtURLFetched = YES;
547 /* Force isArtURLFetched, that will trigger artwork download eventually
548 * And all the other meta will be added through the libvlc event system */
549 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
552 return [super valueForKeyPath:keyPath];
556 /******************************************************************************
557 * Implementation VLCMedia (VLCMediaPlayerBridging)
560 @implementation VLCMedia (VLCMediaPlayerBridging)
562 - (void)setLength:(VLCTime *)value
564 if (length && value && [length compare:value] == NSOrderedSame)
568 length = value ? [value retain] : nil;