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.
248 [self setLength:nil];
252 [metaDictionary release];
254 libvlc_media_release( p_md );
259 - (NSString *)description
261 NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
262 return [NSString stringWithFormat:@"<%@ %p> %@", [self className], self, (result ? result : [url absoluteString])];
265 - (NSComparisonResult)compare:(VLCMedia *)media
268 return NSOrderedSame;
270 return NSOrderedDescending;
271 return p_md == [media libVLCMediaDescriptor] ? NSOrderedSame : NSOrderedAscending;
274 @synthesize delegate;
280 // Try figuring out what the length is
281 long long duration = libvlc_media_get_duration( p_md, NULL );
284 [self setLength:[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]]];
285 return [[length retain] autorelease];
287 return [VLCTime nullTime];
289 return [[length retain] autorelease];
292 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
294 static const long long thread_sleep = 10000;
298 // Force preparsing of this item.
301 // wait until we are preparsed
302 while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
304 usleep( thread_sleep );
307 // So we're done waiting, but sometimes we trap the fact that the parsing
308 // was done before the length gets assigned, so lets go ahead and assign
311 return [self length];
314 return [[length retain] autorelease];
319 return libvlc_media_is_preparsed( p_md );
323 @synthesize subitems;
324 @synthesize metaDictionary;
329 /******************************************************************************
330 * Implementation VLCMedia (LibVLCBridging)
332 @implementation VLCMedia (LibVLCBridging)
334 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
336 return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
339 - (id)initWithLibVLCMediaDescriptor:(void *)md
341 if (self = [super init])
343 libvlc_media_retain( md );
346 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
347 [self initInternalMediaDescriptor];
352 - (void *)libVLCMediaDescriptor
357 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
359 libvlc_media_t * p_md;
360 p_md = libvlc_media_duplicate( [media libVLCMediaDescriptor] );
362 for( NSString * key in [options allKeys] )
364 libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=#%@", key, [options objectForKey:key]] UTF8String]);
366 return [VLCMedia mediaWithLibVLCMediaDescriptor:p_md];
371 /******************************************************************************
372 * Implementation VLCMedia (Private)
374 @implementation VLCMedia (Private)
376 + (libvlc_meta_t)stringToMetaType:(NSString *)string
378 static NSDictionary * stringToMetaDictionary = nil;
379 // TODO: Thread safe-ize
380 if( !stringToMetaDictionary )
382 #define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
383 stringToMetaDictionary =
384 [[NSDictionary dictionaryWithObjectsAndKeys:
385 VLCStringToMeta(Title),
386 VLCStringToMeta(Artist),
387 VLCStringToMeta(Genre),
388 VLCStringToMeta(Copyright),
389 VLCStringToMeta(Album),
390 VLCStringToMeta(TrackNumber),
391 VLCStringToMeta(Description),
392 VLCStringToMeta(Rating),
393 VLCStringToMeta(Date),
394 VLCStringToMeta(Setting),
395 VLCStringToMeta(URL),
396 VLCStringToMeta(Language),
397 VLCStringToMeta(NowPlaying),
398 VLCStringToMeta(Publisher),
399 VLCStringToMeta(ArtworkURL),
400 VLCStringToMeta(TrackID),
402 #undef VLCStringToMeta
404 NSNumber * number = [stringToMetaDictionary objectForKey:string];
405 return number ? [number intValue] : -1;
408 + (NSString *)metaTypeToString:(libvlc_meta_t)type
410 #define VLCMetaToString( name, type ) if (libvlc_meta_##name == type) return VLCMetaInformation##name;
411 VLCMetaToString(Title, type);
412 VLCMetaToString(Artist, type);
413 VLCMetaToString(Genre, type);
414 VLCMetaToString(Copyright, type);
415 VLCMetaToString(Album, type);
416 VLCMetaToString(TrackNumber, type);
417 VLCMetaToString(Description, type);
418 VLCMetaToString(Rating, type);
419 VLCMetaToString(Date, type);
420 VLCMetaToString(Setting, type);
421 VLCMetaToString(URL, type);
422 VLCMetaToString(Language, type);
423 VLCMetaToString(NowPlaying, type);
424 VLCMetaToString(Publisher, type);
425 VLCMetaToString(ArtworkURL, type);
426 VLCMetaToString(TrackID, type);
427 #undef VLCMetaToString
431 - (void)initInternalMediaDescriptor
433 libvlc_exception_t ex;
434 libvlc_exception_init( &ex );
436 char * p_url = libvlc_media_get_mrl( p_md );
438 url = [[NSURL URLWithString:[NSString stringWithUTF8String:p_url]] retain];
439 if( !url ) /* Attempt to interpret as a file path then */
440 url = [[NSURL fileURLWithPath:[NSString stringWithUTF8String:p_url]] retain];
443 libvlc_media_set_user_data( p_md, (void*)self );
445 libvlc_event_manager_t * p_em = libvlc_media_event_manager( p_md );
446 libvlc_event_attach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self, &ex);
447 libvlc_event_attach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self, &ex);
448 libvlc_event_attach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self, &ex);
449 libvlc_event_attach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self, &ex);
451 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
457 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
458 libvlc_media_list_release( p_mlist );
461 state = LibVLCStateToMediaState(libvlc_media_get_state( p_md ));
464 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
466 char * psz_value = libvlc_media_get_meta( p_md, [VLCMedia stringToMetaType:metaType] );
467 NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
468 NSString * oldValue = [metaDictionary valueForKey:metaType];
471 if ( newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame) )
473 // Only fetch the art if needed. (ie, create the NSImage, if it was requested before)
474 if (isArtFetched && [metaType isEqualToString:VLCMetaInformationArtworkURL])
476 [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
478 withObject:newValue];
481 [metaDictionary setValue:newValue forKeyPath:metaType];
485 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
487 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
492 // Go ahead and load up the art work
493 NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
495 // Don't attempt to fetch artwork from remote. Core will do that alone
496 if ([artUrl isFileURL])
497 art = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease];
500 // If anything was found, lets save it to the meta data dictionary
501 [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
506 - (void)setArtwork:(NSImage *)art
510 [metaDictionary removeObjectForKey:@"artwork"];
514 [metaDictionary setObject:art forKey:@"artwork"];
517 - (void)metaChanged:(NSString *)metaType
519 [self fetchMetaInformationFromLibVLCWithType:metaType];
525 return; /* Nothing to do */
527 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
529 NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
531 [self willChangeValueForKey:@"subitems"];
532 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
533 [self didChangeValueForKey:@"subitems"];
534 libvlc_media_list_release( p_mlist );
537 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
539 [self setState: [newStateAsNumber intValue]];
542 - (id)valueForKeyPath:(NSString *)keyPath
544 if( !isArtFetched && [keyPath isEqualToString:@"metaDictionary.artwork"])
547 /* Force the retrieval of the artwork now that someone asked for it */
548 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
550 else if( !areOthersMetaFetched && [keyPath hasPrefix:@"metaDictionary."])
552 areOthersMetaFetched = YES;
553 /* Force VLCMetaInformationTitle, that will trigger preparsing
554 * And all the other meta will be added through the libvlc event system */
555 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
558 else if( !isArtURLFetched && [keyPath hasPrefix:@"metaDictionary.artworkURL"])
560 isArtURLFetched = YES;
561 /* Force isArtURLFetched, that will trigger artwork download eventually
562 * And all the other meta will be added through the libvlc event system */
563 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
566 return [super valueForKeyPath:keyPath];
570 /******************************************************************************
571 * Implementation VLCMedia (VLCMediaPlayerBridging)
574 @implementation VLCMedia (VLCMediaPlayerBridging)
576 - (void)setLength:(VLCTime *)value
578 if (length && value && [length compare:value] == NSOrderedSame)
582 length = value ? [value retain] : nil;