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;
77 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL;
78 - (void)setArtwork:(NSImage *)art;
81 - (void)parseIfNeeded;
83 /* Callback Methods */
84 - (void)parsedChanged:(NSNumber *)isParsedAsNumber;
85 - (void)metaChanged:(NSString *)metaType;
87 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber;
90 static VLCMediaState libvlc_state_to_media_state[] =
92 [libvlc_NothingSpecial] = VLCMediaStateNothingSpecial,
93 [libvlc_Stopped] = VLCMediaStateNothingSpecial,
94 [libvlc_Opening] = VLCMediaStateNothingSpecial,
95 [libvlc_Buffering] = VLCMediaStateBuffering,
96 [libvlc_Ended] = VLCMediaStateNothingSpecial,
97 [libvlc_Error] = VLCMediaStateError,
98 [libvlc_Playing] = VLCMediaStatePlaying,
99 [libvlc_Paused] = VLCMediaStatePlaying,
102 static inline VLCMediaState LibVLCStateToMediaState( libvlc_state_t state )
104 return libvlc_state_to_media_state[state];
107 /******************************************************************************
108 * LibVLC Event Callback
110 static void HandleMediaMetaChanged(const libvlc_event_t * event, void * self)
112 if( event->u.media_meta_changed.meta_type == libvlc_meta_Publisher ||
113 event->u.media_meta_changed.meta_type == libvlc_meta_NowPlaying )
115 /* Skip those meta. We don't really care about them for now.
116 * And they occure a lot */
119 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
120 [[VLCEventManager sharedManager] callOnMainThreadObject:self
121 withMethod:@selector(metaChanged:)
122 withArgumentAsObject:[VLCMedia metaTypeToString:event->u.media_meta_changed.meta_type]];
126 static void HandleMediaDurationChanged(const libvlc_event_t * event, void * self)
128 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
130 [[VLCEventManager sharedManager] callOnMainThreadObject:self
131 withMethod:@selector(setLength:)
132 withArgumentAsObject:[VLCTime timeWithNumber:
133 [NSNumber numberWithLongLong:event->u.media_duration_changed.new_duration]]];
137 static void HandleMediaStateChanged(const libvlc_event_t * event, void * self)
139 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
141 [[VLCEventManager sharedManager] callOnMainThreadObject:self
142 withMethod:@selector(setStateAsNumber:)
143 withArgumentAsObject:[NSNumber numberWithInt:
144 LibVLCStateToMediaState(event->u.media_state_changed.new_state)]];
148 static void HandleMediaSubItemAdded(const libvlc_event_t * event, void * self)
150 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
151 [[VLCEventManager sharedManager] callOnMainThreadObject:self
152 withMethod:@selector(subItemAdded)
153 withArgumentAsObject:nil];
157 static void HandleMediaParsedChanged(const libvlc_event_t * event, void * self)
159 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
160 [[VLCEventManager sharedManager] callOnMainThreadObject:self
161 withMethod:@selector(parsedChanged:)
162 withArgumentAsObject:[NSNumber numberWithBool:event->u.media_parsed_changed.new_status]];
167 /******************************************************************************
170 @implementation VLCMedia
171 + (id)mediaWithURL:(NSURL *)anURL;
173 return [[[VLCMedia alloc] initWithURL:anURL] autorelease];
176 + (id)mediaWithPath:(NSString *)aPath;
178 return [[[VLCMedia alloc] initWithPath:aPath] autorelease];
181 + (id)mediaAsNodeWithName:(NSString *)aName;
183 return [[[VLCMedia alloc] initAsNodeWithName:aName] autorelease];
186 - (id)initWithPath:(NSString *)aPath
188 return [self initWithURL:[NSURL fileURLWithPath:aPath isDirectory:NO]];
191 - (id)initWithURL:(NSURL *)anURL
193 if (self = [super init])
195 p_md = libvlc_media_new_location([VLCLibrary sharedInstance],
196 [[anURL absoluteString] UTF8String]);
199 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
201 // This value is set whenever the demuxer figures out what the length is.
202 // TODO: Easy way to tell the length of the movie without having to instiate the demuxer. Maybe cached info?
205 [self initInternalMediaDescriptor];
210 - (id)initAsNodeWithName:(NSString *)aName
212 if (self = [super init])
214 p_md = libvlc_media_new_as_node([VLCLibrary sharedInstance],
218 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
220 // This value is set whenever the demuxer figures out what the length is.
221 // TODO: Easy way to tell the length of the movie without having to instiate the demuxer. Maybe cached info?
224 [self initInternalMediaDescriptor];
229 - (void)setValue:(NSString *)value forMeta:(NSString *)meta
231 libvlc_meta_t metaName = [VLCMedia stringToMetaType:meta];
232 NSAssert(metaName >= 0, @"Invalid meta");
233 libvlc_media_set_meta(p_md, metaName, [value UTF8String]);
238 libvlc_event_manager_t * p_em = libvlc_media_event_manager(p_md);
239 libvlc_event_detach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self);
240 libvlc_event_detach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
241 libvlc_event_detach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self);
242 libvlc_event_detach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self);
243 libvlc_event_detach(p_em, libvlc_MediaParsedChanged, HandleMediaParsedChanged, self);
244 [[VLCEventManager sharedManager] cancelCallToObject:self];
246 // Testing to see if the pointer exists is not required, if the pointer is null
247 // then the release message is not sent to it.
252 [metaDictionary release];
254 libvlc_media_release( p_md );
259 - (NSString *)description
261 NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
262 return [NSString stringWithFormat:@"<%@ %p> %@", [self class], 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 );
284 length = [[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]] retain];
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 parsing of this item.
299 [self parseIfNeeded];
301 // wait until we are preparsed
302 while (!length && !libvlc_media_is_parsed(p_md) && [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];
324 libvlc_media_parse_async(p_md);
327 NSString *VLCMediaTracksInformationCodec = @"codec"; // NSNumber
328 NSString *VLCMediaTracksInformationId = @"id"; // NSNumber
329 NSString *VLCMediaTracksInformationType = @"type"; // NSString
331 NSString *VLCMediaTracksInformationTypeAudio = @"audio";
332 NSString *VLCMediaTracksInformationTypeVideo = @"video";
333 NSString *VLCMediaTracksInformationTypeText = @"text";
334 NSString *VLCMediaTracksInformationTypeUnknown = @"unknown";
336 NSString *VLCMediaTracksInformationCodecProfile = @"profile"; // NSNumber
337 NSString *VLCMediaTracksInformationCodecLevel = @"level"; // NSNumber
339 NSString *VLCMediaTracksInformationAudioChannelsNumber = @"channelsNumber"; // NSNumber
340 NSString *VLCMediaTracksInformationAudioRate = @"rate"; // NSNumber
342 NSString *VLCMediaTracksInformationVideoHeight = @"height"; // NSNumber
343 NSString *VLCMediaTracksInformationVideoWidth = @"width"; // NSNumber
345 - (NSArray *)tracksInformation
347 // Trigger parsing if needed
348 [self parseIfNeeded];
350 libvlc_media_track_info_t *tracksInfo;
351 int count = libvlc_media_get_tracks_info(p_md, &tracksInfo);
352 NSMutableArray *array = [NSMutableArray array];
353 for (int i = 0; i < count; i++) {
354 NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
355 [NSNumber numberWithUnsignedInt:tracksInfo[i].i_codec], VLCMediaTracksInformationCodec,
356 [NSNumber numberWithInt:tracksInfo[i].i_id], VLCMediaTracksInformationId,
357 [NSNumber numberWithInt:tracksInfo[i].i_profile], VLCMediaTracksInformationCodecProfile,
358 [NSNumber numberWithInt:tracksInfo[i].i_level], VLCMediaTracksInformationCodecLevel,
362 switch (tracksInfo[i].i_type) {
363 case libvlc_track_audio:
364 type = VLCMediaTracksInformationTypeAudio;
365 NSNumber *level = [NSNumber numberWithUnsignedInt:tracksInfo[i].u.audio.i_channels];
366 NSNumber *rate = [NSNumber numberWithUnsignedInt:tracksInfo[i].u.audio.i_rate];
367 [dictionary setObject:level forKey:VLCMediaTracksInformationAudioChannelsNumber];
368 [dictionary setObject:rate forKey:VLCMediaTracksInformationAudioRate];
370 case libvlc_track_video:
371 type = VLCMediaTracksInformationTypeVideo;
372 NSNumber *width = [NSNumber numberWithUnsignedInt:tracksInfo[i].u.video.i_width];
373 NSNumber *height = [NSNumber numberWithUnsignedInt:tracksInfo[i].u.video.i_height];
374 [dictionary setObject:width forKey:VLCMediaTracksInformationVideoWidth];
375 [dictionary setObject:height forKey:VLCMediaTracksInformationVideoHeight];
377 case libvlc_track_text:
378 type = VLCMediaTracksInformationTypeText;
379 [dictionary setObject:VLCMediaTracksInformationTypeText forKey:VLCMediaTracksInformationType];
381 case libvlc_track_unknown:
383 type = VLCMediaTracksInformationTypeUnknown;
386 [dictionary setValue:type forKey:VLCMediaTracksInformationType];
388 [array addObject:dictionary];
396 @synthesize subitems;
397 @synthesize metaDictionary;
402 /******************************************************************************
403 * Implementation VLCMedia (LibVLCBridging)
405 @implementation VLCMedia (LibVLCBridging)
407 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
409 return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
412 - (id)initWithLibVLCMediaDescriptor:(void *)md
414 if (self = [super init])
416 libvlc_media_retain( md );
419 metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
420 [self initInternalMediaDescriptor];
425 - (void *)libVLCMediaDescriptor
430 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
432 libvlc_media_t * p_md;
433 p_md = libvlc_media_duplicate( [media libVLCMediaDescriptor] );
435 for( NSString * key in [options allKeys] )
437 if ( [options objectForKey:key] != [NSNull null] )
438 libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=%@", key, [options objectForKey:key]] UTF8String]);
440 libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@", key] UTF8String]);
443 return [VLCMedia mediaWithLibVLCMediaDescriptor:p_md];
448 /******************************************************************************
449 * Implementation VLCMedia (Private)
451 @implementation VLCMedia (Private)
453 + (libvlc_meta_t)stringToMetaType:(NSString *)string
455 static NSDictionary * stringToMetaDictionary = nil;
456 // TODO: Thread safe-ize
457 if( !stringToMetaDictionary )
459 #define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
460 stringToMetaDictionary =
461 [[NSDictionary dictionaryWithObjectsAndKeys:
462 VLCStringToMeta(Title),
463 VLCStringToMeta(Artist),
464 VLCStringToMeta(Genre),
465 VLCStringToMeta(Copyright),
466 VLCStringToMeta(Album),
467 VLCStringToMeta(TrackNumber),
468 VLCStringToMeta(Description),
469 VLCStringToMeta(Rating),
470 VLCStringToMeta(Date),
471 VLCStringToMeta(Setting),
472 VLCStringToMeta(URL),
473 VLCStringToMeta(Language),
474 VLCStringToMeta(NowPlaying),
475 VLCStringToMeta(Publisher),
476 VLCStringToMeta(ArtworkURL),
477 VLCStringToMeta(TrackID),
479 #undef VLCStringToMeta
481 NSNumber * number = [stringToMetaDictionary objectForKey:string];
482 return number ? [number intValue] : -1;
485 + (NSString *)metaTypeToString:(libvlc_meta_t)type
487 #define VLCMetaToString( name, type ) if (libvlc_meta_##name == type) return VLCMetaInformation##name;
488 VLCMetaToString(Title, type);
489 VLCMetaToString(Artist, type);
490 VLCMetaToString(Genre, type);
491 VLCMetaToString(Copyright, type);
492 VLCMetaToString(Album, type);
493 VLCMetaToString(TrackNumber, type);
494 VLCMetaToString(Description, type);
495 VLCMetaToString(Rating, type);
496 VLCMetaToString(Date, type);
497 VLCMetaToString(Setting, type);
498 VLCMetaToString(URL, type);
499 VLCMetaToString(Language, type);
500 VLCMetaToString(NowPlaying, type);
501 VLCMetaToString(Publisher, type);
502 VLCMetaToString(ArtworkURL, type);
503 VLCMetaToString(TrackID, type);
504 #undef VLCMetaToString
508 - (void)initInternalMediaDescriptor
510 char * p_url = libvlc_media_get_mrl( p_md );
512 url = [[NSURL URLWithString:[NSString stringWithUTF8String:p_url]] retain];
513 if( !url ) /* Attempt to interpret as a file path then */
514 url = [[NSURL fileURLWithPath:[NSString stringWithUTF8String:p_url]] retain];
517 libvlc_media_set_user_data( p_md, (void*)self );
519 libvlc_event_manager_t * p_em = libvlc_media_event_manager( p_md );
520 libvlc_event_attach(p_em, libvlc_MediaMetaChanged, HandleMediaMetaChanged, self);
521 libvlc_event_attach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
522 libvlc_event_attach(p_em, libvlc_MediaStateChanged, HandleMediaStateChanged, self);
523 libvlc_event_attach(p_em, libvlc_MediaSubItemAdded, HandleMediaSubItemAdded, self);
524 libvlc_event_attach(p_em, libvlc_MediaParsedChanged, HandleMediaParsedChanged, self);
526 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
532 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
533 libvlc_media_list_release( p_mlist );
536 isParsed = libvlc_media_is_parsed(p_md);
537 state = LibVLCStateToMediaState(libvlc_media_get_state( p_md ));
540 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
542 char * psz_value = libvlc_media_get_meta( p_md, [VLCMedia stringToMetaType:metaType] );
543 NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
544 NSString * oldValue = [metaDictionary valueForKey:metaType];
547 if ( newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame) )
549 // Only fetch the art if needed. (ie, create the NSImage, if it was requested before)
550 if (isArtFetched && [metaType isEqualToString:VLCMetaInformationArtworkURL])
552 [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
554 withObject:newValue];
557 [metaDictionary setValue:newValue forKeyPath:metaType];
561 #if !TARGET_OS_IPHONE
562 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
564 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
569 // Go ahead and load up the art work
570 NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
571 // Don't attempt to fetch artwork from remote. Core will do that alone
572 if ([artUrl isFileURL])
573 art = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease];
576 // If anything was found, lets save it to the meta data dictionary
577 [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
582 - (void)setArtwork:(NSImage *)art
586 [metaDictionary removeObjectForKey:@"artwork"];
590 [metaDictionary setObject:art forKey:@"artwork"];
594 - (void)parseIfNeeded
596 if (![self isParsed])
600 - (void)metaChanged:(NSString *)metaType
602 [self fetchMetaInformationFromLibVLCWithType:metaType];
608 return; /* Nothing to do */
610 libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
612 NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
614 [self willChangeValueForKey:@"subitems"];
615 subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
616 [self didChangeValueForKey:@"subitems"];
617 libvlc_media_list_release( p_mlist );
620 - (void)parsedChanged:(NSNumber *)isParsedAsNumber
622 [self willChangeValueForKey:@"parsed"];
623 isParsed = [isParsedAsNumber boolValue];
624 [self didChangeValueForKey:@"parsed"];
626 // FIXME: Probably don't even call this if there is no delegate.
627 if (!delegate || !isParsed)
630 if ([delegate respondsToSelector:@selector(mediaDidFinishParsing:)]) {
631 [delegate mediaDidFinishParsing:self];
635 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
637 [self setState: [newStateAsNumber intValue]];
641 - (NSDictionary *)metaDictionary
643 if (!areOthersMetaFetched) {
644 areOthersMetaFetched = YES;
645 /* Force VLCMetaInformationTitle, that will trigger preparsing
646 * And all the other meta will be added through the libvlc event system */
647 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
650 if (!isArtURLFetched)
652 isArtURLFetched = YES;
653 /* Force isArtURLFetched, that will trigger artwork download eventually
654 * And all the other meta will be added through the libvlc event system */
655 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
657 return metaDictionary;
662 - (id)valueForKeyPath:(NSString *)keyPath
664 if( !isArtFetched && [keyPath isEqualToString:@"metaDictionary.artwork"])
667 /* Force the retrieval of the artwork now that someone asked for it */
668 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
670 else if( !areOthersMetaFetched && [keyPath hasPrefix:@"metaDictionary."])
672 areOthersMetaFetched = YES;
673 /* Force VLCMetaInformationTitle, that will trigger preparsing
674 * And all the other meta will be added through the libvlc event system */
675 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
678 else if( !isArtURLFetched && [keyPath hasPrefix:@"metaDictionary.artworkURL"])
680 isArtURLFetched = YES;
681 /* Force isArtURLFetched, that will trigger artwork download eventually
682 * And all the other meta will be added through the libvlc event system */
683 [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
685 return [super valueForKeyPath:keyPath];
690 /******************************************************************************
691 * Implementation VLCMedia (VLCMediaPlayerBridging)
694 @implementation VLCMedia (VLCMediaPlayerBridging)
696 - (void)setLength:(VLCTime *)value
698 if (length && value && [length compare:value] == NSOrderedSame)
702 length = value ? [value retain] : nil;