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)initWithURL:(NSURL *)anURL
173 return [self initWithPath:[anURL path]];
176 - (id)initWithPath:(NSString *)aPath
178 if (self = [super init])
180 libvlc_exception_t ex;
181 libvlc_exception_init(&ex);
183 p_md = libvlc_media_new([VLCLibrary sharedInstance],
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, NULL);
234 libvlc_event_detach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self, NULL);
235 // libvlc_event_detach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self, NULL);
236 libvlc_event_detach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self, NULL);
237 libvlc_event_detach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self, NULL);
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];
288 return [VLCTime nullTime];
291 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
293 static const long long thread_sleep = 10000;
297 while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
299 usleep( thread_sleep );
302 // So we're done waiting, but sometimes we trap the fact that the parsing
303 // was done before the length gets assigned, so lets go ahead and assign
306 return [self length];
309 return [[length retain] autorelease];
314 return libvlc_media_is_preparsed( p_md, NULL );
318 @synthesize subitems;
319 @synthesize metaDictionary;
324 /******************************************************************************
325 * Implementation VLCMedia (LibVLCBridging)
327 @implementation VLCMedia (LibVLCBridging)
329 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
331 return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
334 - (id)initWithLibVLCMediaDescriptor:(void *)md
336 if (self = [super init])
338 libvlc_exception_t ex;
339 libvlc_exception_init( &ex );
341 libvlc_media_retain( md );
344 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
345 [self initInternalMediaDescriptor];
350 - (void *)libVLCMediaDescriptor
355 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
357 libvlc_media_t * p_md;
358 p_md = libvlc_media_duplicate( [media libVLCMediaDescriptor] );
359 for( NSString * key in [options allKeys] )
361 libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=#%@", key, [options objectForKey:key]] UTF8String], NULL);
363 return [VLCMedia mediaWithLibVLCMediaDescriptor:p_md];
368 /******************************************************************************
369 * Implementation VLCMedia (Private)
371 @implementation VLCMedia (Private)
373 + (libvlc_meta_t)stringToMetaType:(NSString *)string
375 static NSDictionary * stringToMetaDictionary = nil;
376 // TODO: Thread safe-ize
377 if( !stringToMetaDictionary )
379 #define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
380 stringToMetaDictionary =
381 [[NSDictionary dictionaryWithObjectsAndKeys:
382 VLCStringToMeta(Title),
383 VLCStringToMeta(Artist),
384 VLCStringToMeta(Genre),
385 VLCStringToMeta(Copyright),
386 VLCStringToMeta(Album),
387 VLCStringToMeta(TrackNumber),
388 VLCStringToMeta(Description),
389 VLCStringToMeta(Rating),
390 VLCStringToMeta(Date),
391 VLCStringToMeta(Setting),
392 VLCStringToMeta(URL),
393 VLCStringToMeta(Language),
394 VLCStringToMeta(NowPlaying),
395 VLCStringToMeta(Publisher),
396 VLCStringToMeta(ArtworkURL),
397 VLCStringToMeta(TrackID),
399 #undef VLCStringToMeta
401 NSNumber * number = [stringToMetaDictionary objectForKey:string];
402 return number ? [number intValue] : -1;
405 + (NSString *)metaTypeToString:(libvlc_meta_t)type
407 #define VLCMetaToString( name, type ) if (libvlc_meta_##name == type) return VLCMetaInformation##name;
408 VLCMetaToString(Title, type);
409 VLCMetaToString(Artist, type);
410 VLCMetaToString(Genre, type);
411 VLCMetaToString(Copyright, type);
412 VLCMetaToString(Album, type);
413 VLCMetaToString(TrackNumber, type);
414 VLCMetaToString(Description, type);
415 VLCMetaToString(Rating, type);
416 VLCMetaToString(Date, type);
417 VLCMetaToString(Setting, type);
418 VLCMetaToString(URL, type);
419 VLCMetaToString(Language, type);
420 VLCMetaToString(NowPlaying, type);
421 VLCMetaToString(Publisher, type);
422 VLCMetaToString(ArtworkURL, type);
423 VLCMetaToString(TrackID, type);
424 #undef VLCMetaToString
428 - (void)initInternalMediaDescriptor
430 libvlc_exception_t ex;
431 libvlc_exception_init( &ex );
435 char * p_url = libvlc_media_get_mrl( p_md, &ex );
436 catch_exception( &ex );
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, &ex );
444 catch_exception( &ex );
446 libvlc_event_manager_t * p_em = libvlc_media_event_manager( p_md, &ex );
447 libvlc_event_attach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self, &ex);
448 // libvlc_event_attach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self, &ex);
449 libvlc_event_attach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self, &ex);
450 libvlc_event_attach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self, &ex);
451 catch_exception( &ex );
453 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md, NULL );
459 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
460 libvlc_media_list_release( p_mlist );
463 state = LibVLCStateToMediaState(libvlc_media_get_state( p_md, NULL ));
465 /* Force VLCMetaInformationTitle, that will trigger preparsing
466 * And all the other meta will be added through the libvlc event system */
467 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
470 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
472 char * psz_value = libvlc_media_get_meta( p_md, [VLCMedia stringToMetaType:metaType], NULL);
473 NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
474 NSString * oldValue = [metaDictionary valueForKey:metaType];
477 if ( newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame) )
479 if ([metaType isEqualToString:VLCMetaInformationArtworkURL])
481 [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
483 withObject:newValue];
487 [metaDictionary setValue:newValue forKeyPath:metaType];
491 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
493 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
498 // Go ahead and load up the art work
499 NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
501 // Don't attempt to fetch artwork from remote. Core will do that alone
502 if ([artUrl isFileURL])
503 art = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease];
506 // If anything was found, lets save it to the meta data dictionary
507 [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
512 - (void)setArtwork:(NSImage *)art
516 [metaDictionary removeObjectForKey:@"artwork"];
520 [metaDictionary setObject:art forKey:@"artwork"];
523 - (void)metaChanged:(NSString *)metaType
525 [self fetchMetaInformationFromLibVLCWithType:metaType];
531 return; /* Nothing to do */
533 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md, NULL );
535 NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
537 [self willChangeValueForKey:@"subitems"];
538 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
539 [self didChangeValueForKey:@"subitems"];
540 libvlc_media_list_release( p_mlist );
543 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
545 [self setState: [newStateAsNumber intValue]];
548 - (id)valueForKeyPath:(NSString *)keyPath
550 if( !artFetched && [keyPath isEqualToString:@"metaDictionary.artwork"])
553 /* Force the retrieval of the artwork now that someone asked for it */
554 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
556 return [super valueForKeyPath:keyPath];
560 /******************************************************************************
561 * Implementation VLCMedia (VLCMediaPlayerBridging)
564 @implementation VLCMedia (VLCMediaPlayerBridging)
566 - (void)setLength:(VLCTime *)value
568 if (length && value && [length compare:value] == NSOrderedSame)
572 length = value ? [value retain] : nil;