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];
214 - (void)setValue:(NSString *)value forMeta:(NSString *)meta
216 libvlc_meta_t metaName = [VLCMedia stringToMetaType:meta];
217 NSAssert(metaName >= 0, @"Invalid meta");
218 libvlc_media_set_meta(p_md, metaName, [value UTF8String]);
225 if([self retainCount] <= 1)
227 /* We must make sure we won't receive new event after an upcoming dealloc
228 * We also may receive a -retain in some event callback that may occcur
229 * Before libvlc_event_detach. So this can't happen in dealloc */
230 libvlc_event_manager_t * p_em = libvlc_media_event_manager(p_md);
231 libvlc_event_detach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self);
232 libvlc_event_detach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
233 libvlc_event_detach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self);
234 libvlc_event_detach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self);
242 // Testing to see if the pointer exists is not required, if the pointer is null
243 // then the release message is not sent to it.
248 [metaDictionary release];
250 libvlc_media_release( p_md );
255 - (NSString *)description
257 NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
258 return [NSString stringWithFormat:@"<%@ %p> %@", [self className], self, (result ? result : [url absoluteString])];
261 - (NSComparisonResult)compare:(VLCMedia *)media
264 return NSOrderedSame;
266 return NSOrderedDescending;
267 return p_md == [media libVLCMediaDescriptor] ? NSOrderedSame : NSOrderedAscending;
270 @synthesize delegate;
276 // Try figuring out what the length is
277 long long duration = libvlc_media_get_duration( p_md );
280 length = [[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]] retain];
281 return [[length retain] autorelease];
283 return [VLCTime nullTime];
285 return [[length retain] autorelease];
288 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
290 static const long long thread_sleep = 10000;
294 // Force preparsing of this item.
297 // wait until we are preparsed
298 while (!length && ![self isParsed] && [aDate timeIntervalSinceNow] > 0)
300 usleep( thread_sleep );
303 // So we're done waiting, but sometimes we trap the fact that the parsing
304 // was done before the length gets assigned, so lets go ahead and assign
307 return [self length];
310 return [[length retain] autorelease];
315 return libvlc_media_is_parsed( p_md );
319 @synthesize subitems;
320 @synthesize metaDictionary;
325 /******************************************************************************
326 * Implementation VLCMedia (LibVLCBridging)
328 @implementation VLCMedia (LibVLCBridging)
330 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
332 return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
335 - (id)initWithLibVLCMediaDescriptor:(void *)md
337 if (self = [super init])
339 libvlc_media_retain( md );
342 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
343 [self initInternalMediaDescriptor];
348 - (void *)libVLCMediaDescriptor
353 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
355 libvlc_media_t * p_md;
356 p_md = libvlc_media_duplicate( [media libVLCMediaDescriptor] );
358 for( NSString * key in [options allKeys] )
360 if ( [options objectForKey:key] != [NSNull null] )
361 libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=%@", key, [options objectForKey:key]] UTF8String]);
363 libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@", 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 char * p_url = libvlc_media_get_mrl( p_md );
435 url = [[NSURL URLWithString:[NSString stringWithUTF8String:p_url]] retain];
436 if( !url ) /* Attempt to interpret as a file path then */
437 url = [[NSURL fileURLWithPath:[NSString stringWithUTF8String:p_url]] retain];
440 libvlc_media_set_user_data( p_md, (void*)self );
442 libvlc_event_manager_t * p_em = libvlc_media_event_manager( p_md );
443 libvlc_event_attach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self);
444 libvlc_event_attach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
445 libvlc_event_attach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self);
446 libvlc_event_attach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self);
448 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
454 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
455 libvlc_media_list_release( p_mlist );
458 state = LibVLCStateToMediaState(libvlc_media_get_state( p_md ));
461 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
463 char * psz_value = libvlc_media_get_meta( p_md, [VLCMedia stringToMetaType:metaType] );
464 NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
465 NSString * oldValue = [metaDictionary valueForKey:metaType];
468 if ( newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame) )
470 // Only fetch the art if needed. (ie, create the NSImage, if it was requested before)
471 if (isArtFetched && [metaType isEqualToString:VLCMetaInformationArtworkURL])
473 [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
475 withObject:newValue];
478 [metaDictionary setValue:newValue forKeyPath:metaType];
482 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
484 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
489 // Go ahead and load up the art work
490 NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
492 // Don't attempt to fetch artwork from remote. Core will do that alone
493 if ([artUrl isFileURL])
494 art = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease];
497 // If anything was found, lets save it to the meta data dictionary
498 [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
503 - (void)setArtwork:(NSImage *)art
507 [metaDictionary removeObjectForKey:@"artwork"];
511 [metaDictionary setObject:art forKey:@"artwork"];
514 - (void)metaChanged:(NSString *)metaType
516 [self fetchMetaInformationFromLibVLCWithType:metaType];
522 return; /* Nothing to do */
524 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
526 NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
528 [self willChangeValueForKey:@"subitems"];
529 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
530 [self didChangeValueForKey:@"subitems"];
531 libvlc_media_list_release( p_mlist );
534 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
536 [self setState: [newStateAsNumber intValue]];
539 - (id)valueForKeyPath:(NSString *)keyPath
541 if( !isArtFetched && [keyPath isEqualToString:@"metaDictionary.artwork"])
544 /* Force the retrieval of the artwork now that someone asked for it */
545 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
547 else if( !areOthersMetaFetched && [keyPath hasPrefix:@"metaDictionary."])
549 areOthersMetaFetched = YES;
550 /* Force VLCMetaInformationTitle, that will trigger preparsing
551 * And all the other meta will be added through the libvlc event system */
552 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
555 else if( !isArtURLFetched && [keyPath hasPrefix:@"metaDictionary.artworkURL"])
557 isArtURLFetched = YES;
558 /* Force isArtURLFetched, that will trigger artwork download eventually
559 * And all the other meta will be added through the libvlc event system */
560 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
563 return [super valueForKeyPath:keyPath];
567 /******************************************************************************
568 * Implementation VLCMedia (VLCMediaPlayerBridging)
571 @implementation VLCMedia (VLCMediaPlayerBridging)
573 - (void)setLength:(VLCTime *)value
575 if (length && value && [length compare:value] == NSOrderedSame)
579 length = value ? [value retain] : nil;