]> git.sesse.net Git - vlc/blob - projects/macosx/framework/Sources/VLCMedia.m
VLCMedia: Don't use setLength in -length. It is bad for bindings.
[vlc] / projects / macosx / framework / Sources / VLCMedia.m
1 /*****************************************************************************
2  * VLCMedia.m: VLCKit.framework VLCMedia implementation
3  *****************************************************************************
4  * Copyright (C) 2007 Pierre d'Herbemont
5  * Copyright (C) 2007 the VideoLAN team
6  * $Id$
7  *
8  * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
9  *
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.
14  *
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.
19  *
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  *****************************************************************************/
24
25 #import "VLCMedia.h"
26 #import "VLCMediaList.h"
27 #import "VLCEventManager.h"
28 #import "VLCLibrary.h"
29 #import "VLCLibVLCBridging.h"
30 #include <vlc/libvlc.h>
31
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";
51
52 /* Notification Messages */
53 NSString * VLCMediaMetaChanged              = @"VLCMediaMetaChanged";
54
55 /******************************************************************************
56  * @property (readwrite)
57  */
58 @interface VLCMedia ()
59 @property (readwrite) VLCMediaState state;
60 @end
61
62 /******************************************************************************
63  * Interface (Private)
64  */
65 // TODO: Documentation
66 @interface VLCMedia (Private)
67 /* Statics */
68 + (libvlc_meta_t)stringToMetaType:(NSString *)string;
69 + (NSString *)metaTypeToString:(libvlc_meta_t)type;
70
71 /* Initializers */
72 - (void)initInternalMediaDescriptor;
73
74 /* Operations */
75 - (void)fetchMetaInformationFromLibVLCWithType:(NSString*)metaType;
76 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL;
77 - (void)setArtwork:(NSImage *)art;
78
79 /* Callback Methods */
80 - (void)metaChanged:(NSString *)metaType;
81 - (void)subItemAdded;
82 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber;
83 @end
84
85 static VLCMediaState libvlc_state_to_media_state[] =
86 {
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,
95 };
96
97 static inline VLCMediaState LibVLCStateToMediaState( libvlc_state_t state )
98 {
99     return libvlc_state_to_media_state[state];
100 }
101
102 /******************************************************************************
103  * LibVLC Event Callback
104  */
105 static void HandleMediaMetaChanged(const libvlc_event_t * event, void * self)
106 {
107     if( event->u.media_meta_changed.meta_type == libvlc_meta_Publisher ||
108         event->u.media_meta_changed.meta_type == libvlc_meta_NowPlaying )
109     {
110         /* Skip those meta. We don't really care about them for now.
111          * And they occure a lot */
112         return;
113     }
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]];
118     [pool release];
119 }
120
121 static void HandleMediaDurationChanged(const libvlc_event_t * event, void * self)
122 {
123     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
124
125     [[VLCEventManager sharedManager] callOnMainThreadObject:self
126                                                  withMethod:@selector(setLength:)
127                                        withArgumentAsObject:[VLCTime timeWithNumber:
128                                            [NSNumber numberWithLongLong:event->u.media_duration_changed.new_duration]]];
129     [pool release];
130 }
131
132 static void HandleMediaStateChanged(const libvlc_event_t * event, void * self)
133 {
134     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
135
136     [[VLCEventManager sharedManager] callOnMainThreadObject:self
137                                                  withMethod:@selector(setStateAsNumber:)
138                                        withArgumentAsObject:[NSNumber numberWithInt:
139                                             LibVLCStateToMediaState(event->u.media_state_changed.new_state)]];
140     [pool release];
141 }
142
143 static void HandleMediaSubItemAdded(const libvlc_event_t * event, void * self)
144 {
145     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
146     [[VLCEventManager sharedManager] callOnMainThreadObject:self
147                                                  withMethod:@selector(subItemAdded)
148                                        withArgumentAsObject:nil];
149     [pool release];
150 }
151
152 /******************************************************************************
153  * Implementation
154  */
155 @implementation VLCMedia
156 + (id)mediaWithURL:(NSURL *)anURL;
157 {
158     return [[[VLCMedia alloc] initWithURL:anURL] autorelease];
159 }
160
161 + (id)mediaWithPath:(NSString *)aPath;
162 {
163     return [[[VLCMedia alloc] initWithPath:aPath] autorelease];
164 }
165
166 + (id)mediaAsNodeWithName:(NSString *)aName;
167 {
168     return [[[VLCMedia alloc] initAsNodeWithName:aName] autorelease];
169 }
170
171 - (id)initWithPath:(NSString *)aPath
172 {
173     return [self initWithURL:[NSURL fileURLWithPath:aPath isDirectory:NO]];
174 }
175
176 - (id)initWithURL:(NSURL *)anURL
177 {
178     if (self = [super init])
179     {
180         libvlc_exception_t ex;
181         libvlc_exception_init(&ex);
182
183         p_md = libvlc_media_new([VLCLibrary sharedInstance],
184                                            [[anURL absoluteString] UTF8String],
185                                            &ex);
186         catch_exception(&ex);
187
188         delegate = nil;
189         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
190
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?
193         length = nil;
194
195         [self initInternalMediaDescriptor];
196     }
197     return self;
198 }
199
200 - (id)initAsNodeWithName:(NSString *)aName
201 {
202     if (self = [super init])
203     {
204         libvlc_exception_t ex;
205         libvlc_exception_init(&ex);
206
207         p_md = libvlc_media_new_as_node([VLCLibrary sharedInstance],
208                                                    [aName UTF8String],
209                                                    &ex);
210         catch_exception(&ex);
211
212         delegate = nil;
213         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
214
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?
217         length = nil;
218
219         [self initInternalMediaDescriptor];
220     }
221     return self;
222 }
223
224 - (void)release
225 {
226     @synchronized(self)
227     {
228         if([self retainCount] <= 1)
229         {
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);
238         }
239         [super release];
240     }
241 }
242
243 - (void)dealloc
244 {
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.
247     delegate = nil;
248     [length release];
249     [url release];
250     [subitems release];
251     [metaDictionary release];
252
253     libvlc_media_release( p_md );
254
255     [super dealloc];
256 }
257
258 - (NSString *)description
259 {
260     NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
261     return [NSString stringWithFormat:@"<%@ %p> %@", [self className], self, (result ? result : [url absoluteString])];
262 }
263
264 - (NSComparisonResult)compare:(VLCMedia *)media
265 {
266     if (self == media)
267         return NSOrderedSame;
268     if (!media)
269         return NSOrderedDescending;
270     return p_md == [media libVLCMediaDescriptor] ? NSOrderedSame : NSOrderedAscending;
271 }
272
273 @synthesize delegate;
274
275 - (VLCTime *)length
276 {
277     if (!length)
278     {
279         // Try figuring out what the length is
280         long long duration = libvlc_media_get_duration( p_md, NULL );
281         if (duration > -1)
282         {
283             length = [[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]] retain];
284             return [[length retain] autorelease];
285         }
286         return [VLCTime nullTime];
287     }
288     return [[length retain] autorelease];
289 }
290
291 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
292 {
293     static const long long thread_sleep = 10000;
294
295     if (!length)
296     {
297         // Force preparsing of this item.
298         [self length];
299
300         // wait until we are preparsed
301         while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
302         {
303             usleep( thread_sleep );
304         }
305
306         // So we're done waiting, but sometimes we trap the fact that the parsing
307         // was done before the length gets assigned, so lets go ahead and assign
308         // it ourselves.
309         if (!length)
310             return [self length];
311     }
312
313     return [[length retain] autorelease];
314 }
315
316 - (BOOL)isPreparsed
317 {
318     return libvlc_media_is_preparsed( p_md );
319 }
320
321 @synthesize url;
322 @synthesize subitems;
323 @synthesize metaDictionary;
324 @synthesize state;
325
326 @end
327
328 /******************************************************************************
329  * Implementation VLCMedia (LibVLCBridging)
330  */
331 @implementation VLCMedia (LibVLCBridging)
332
333 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
334 {
335     return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
336 }
337
338 - (id)initWithLibVLCMediaDescriptor:(void *)md
339 {
340     if (self = [super init])
341     {
342         libvlc_media_retain( md );
343         p_md = md;
344
345         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
346         [self initInternalMediaDescriptor];
347     }
348     return self;
349 }
350
351 - (void *)libVLCMediaDescriptor
352 {
353     return p_md;
354 }
355
356 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
357 {
358     libvlc_media_t * p_md;
359     p_md = libvlc_media_duplicate( [media libVLCMediaDescriptor] );
360
361     for( NSString * key in [options allKeys] )
362     {
363         libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=#%@", key, [options objectForKey:key]] UTF8String]);
364     }
365     return [VLCMedia mediaWithLibVLCMediaDescriptor:p_md];
366 }
367
368 @end
369
370 /******************************************************************************
371  * Implementation VLCMedia (Private)
372  */
373 @implementation VLCMedia (Private)
374
375 + (libvlc_meta_t)stringToMetaType:(NSString *)string
376 {
377     static NSDictionary * stringToMetaDictionary = nil;
378     // TODO: Thread safe-ize
379     if( !stringToMetaDictionary )
380     {
381 #define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
382         stringToMetaDictionary =
383             [[NSDictionary dictionaryWithObjectsAndKeys:
384                 VLCStringToMeta(Title),
385                 VLCStringToMeta(Artist),
386                 VLCStringToMeta(Genre),
387                 VLCStringToMeta(Copyright),
388                 VLCStringToMeta(Album),
389                 VLCStringToMeta(TrackNumber),
390                 VLCStringToMeta(Description),
391                 VLCStringToMeta(Rating),
392                 VLCStringToMeta(Date),
393                 VLCStringToMeta(Setting),
394                 VLCStringToMeta(URL),
395                 VLCStringToMeta(Language),
396                 VLCStringToMeta(NowPlaying),
397                 VLCStringToMeta(Publisher),
398                 VLCStringToMeta(ArtworkURL),
399                 VLCStringToMeta(TrackID),
400                 nil] retain];
401 #undef VLCStringToMeta
402     }
403     NSNumber * number = [stringToMetaDictionary objectForKey:string];
404     return number ? [number intValue] : -1;
405 }
406
407 + (NSString *)metaTypeToString:(libvlc_meta_t)type
408 {
409 #define VLCMetaToString( name, type )   if (libvlc_meta_##name == type) return VLCMetaInformation##name;
410     VLCMetaToString(Title, type);
411     VLCMetaToString(Artist, type);
412     VLCMetaToString(Genre, type);
413     VLCMetaToString(Copyright, type);
414     VLCMetaToString(Album, type);
415     VLCMetaToString(TrackNumber, type);
416     VLCMetaToString(Description, type);
417     VLCMetaToString(Rating, type);
418     VLCMetaToString(Date, type);
419     VLCMetaToString(Setting, type);
420     VLCMetaToString(URL, type);
421     VLCMetaToString(Language, type);
422     VLCMetaToString(NowPlaying, type);
423     VLCMetaToString(Publisher, type);
424     VLCMetaToString(ArtworkURL, type);
425     VLCMetaToString(TrackID, type);
426 #undef VLCMetaToString
427     return nil;
428 }
429
430 - (void)initInternalMediaDescriptor
431 {
432     char * p_url = libvlc_media_get_mrl( p_md );
433
434     url = [[NSURL URLWithString:[NSString stringWithUTF8String:p_url]] retain];
435     if( !url ) /* Attempt to interpret as a file path then */
436         url = [[NSURL fileURLWithPath:[NSString stringWithUTF8String:p_url]] retain];
437     free( p_url );
438
439     libvlc_media_set_user_data( p_md, (void*)self );
440
441     libvlc_event_manager_t * p_em = libvlc_media_event_manager( p_md );
442     libvlc_event_attach(p_em, libvlc_MediaMetaChanged,     HandleMediaMetaChanged,     self);
443     libvlc_event_attach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
444     libvlc_event_attach(p_em, libvlc_MediaStateChanged,    HandleMediaStateChanged,    self);
445     libvlc_event_attach(p_em, libvlc_MediaSubItemAdded,    HandleMediaSubItemAdded,    self);
446
447     libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
448
449     if (!p_mlist)
450         subitems = nil;
451     else
452     {
453         subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
454         libvlc_media_list_release( p_mlist );
455     }
456
457     state = LibVLCStateToMediaState(libvlc_media_get_state( p_md ));
458 }
459
460 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
461 {
462     char * psz_value = libvlc_media_get_meta( p_md, [VLCMedia stringToMetaType:metaType] );
463     NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
464     NSString * oldValue = [metaDictionary valueForKey:metaType];
465     free(psz_value);
466
467     if ( newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame) )
468     {
469         // Only fetch the art if needed. (ie, create the NSImage, if it was requested before)
470         if (isArtFetched && [metaType isEqualToString:VLCMetaInformationArtworkURL])
471         {
472             [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
473                                          toTarget:self
474                                        withObject:newValue];
475         }
476
477         [metaDictionary setValue:newValue forKeyPath:metaType];
478     }
479 }
480
481 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
482 {
483     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
484     NSImage * art = nil;
485
486     if( anURL )
487     {
488         // Go ahead and load up the art work
489         NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
490
491         // Don't attempt to fetch artwork from remote. Core will do that alone
492         if ([artUrl isFileURL])
493             art  = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease];
494     }
495
496     // If anything was found, lets save it to the meta data dictionary
497     [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
498
499     [pool release];
500 }
501
502 - (void)setArtwork:(NSImage *)art
503 {
504     if (!art)
505     {
506         [metaDictionary removeObjectForKey:@"artwork"];
507         return;
508     }
509
510     [metaDictionary setObject:art forKey:@"artwork"];
511 }
512
513 - (void)metaChanged:(NSString *)metaType
514 {
515     [self fetchMetaInformationFromLibVLCWithType:metaType];
516 }
517
518 - (void)subItemAdded
519 {
520     if( subitems )
521         return; /* Nothing to do */
522
523     libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
524
525     NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
526
527     [self willChangeValueForKey:@"subitems"];
528     subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
529     [self didChangeValueForKey:@"subitems"];
530     libvlc_media_list_release( p_mlist );
531 }
532
533 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
534 {
535     [self setState: [newStateAsNumber intValue]];
536 }
537
538 - (id)valueForKeyPath:(NSString *)keyPath
539 {
540     if( !isArtFetched && [keyPath isEqualToString:@"metaDictionary.artwork"])
541     {
542         isArtFetched = YES;
543         /* Force the retrieval of the artwork now that someone asked for it */
544         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
545     }
546     else if( !areOthersMetaFetched && [keyPath hasPrefix:@"metaDictionary."])
547     {
548         areOthersMetaFetched = YES;
549         /* Force VLCMetaInformationTitle, that will trigger preparsing
550          * And all the other meta will be added through the libvlc event system */
551         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
552
553     }
554     else if( !isArtURLFetched && [keyPath hasPrefix:@"metaDictionary.artworkURL"])
555     {
556         isArtURLFetched = YES;
557         /* Force isArtURLFetched, that will trigger artwork download eventually
558          * And all the other meta will be added through the libvlc event system */
559         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
560     }
561
562     return [super valueForKeyPath:keyPath];
563 }
564 @end
565
566 /******************************************************************************
567  * Implementation VLCMedia (VLCMediaPlayerBridging)
568  */
569
570 @implementation VLCMedia (VLCMediaPlayerBridging)
571
572 - (void)setLength:(VLCTime *)value
573 {
574     if (length && value && [length compare:value] == NSOrderedSame)
575         return;
576
577     [length release];
578     length = value ? [value retain] : nil;
579 }
580
581 @end