]> git.sesse.net Git - vlc/blob - projects/macosx/framework/Sources/VLCMedia.m
macosx/framework: handle duration changed events.
[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, 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);
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     [self setLength:nil];
249
250     [url release];
251     [subitems release];
252     [metaDictionary release];
253
254     libvlc_media_release( p_md );
255
256     [super dealloc];
257 }
258
259 - (NSString *)description
260 {
261     NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
262     return [NSString stringWithFormat:@"<%@ %p> %@", [self className], self, (result ? result : [url absoluteString])];
263 }
264
265 - (NSComparisonResult)compare:(VLCMedia *)media
266 {
267     if (self == media)
268         return NSOrderedSame;
269     if (!media)
270         return NSOrderedDescending;
271     return p_md == [media libVLCMediaDescriptor] ? NSOrderedSame : NSOrderedAscending;
272 }
273
274 @synthesize delegate;
275
276 - (VLCTime *)length
277 {
278     if (!length) 
279     {
280         // Try figuring out what the length is
281         long long duration = libvlc_media_get_duration( p_md, NULL );
282         if (duration > -1) 
283         {
284             [self setLength:[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]]];
285             return [[length retain] autorelease];
286         }
287         return [VLCTime nullTime];
288     }
289     return [[length retain] autorelease];
290 }
291
292 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
293 {
294     static const long long thread_sleep = 10000;
295
296     if (!length)
297     {
298         // Force preparsing of this item.
299         [self length];
300
301         // wait until we are preparsed
302         while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
303         {
304             usleep( thread_sleep );
305         }
306
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
309         // it ourselves.
310         if (!length)
311             return [self length];
312     }
313
314     return [[length retain] autorelease];
315 }
316
317 - (BOOL)isPreparsed
318 {
319     return libvlc_media_is_preparsed( p_md );
320 }
321
322 @synthesize url;
323 @synthesize subitems;
324 @synthesize metaDictionary;
325 @synthesize state;
326
327 @end
328
329 /******************************************************************************
330  * Implementation VLCMedia (LibVLCBridging)
331  */
332 @implementation VLCMedia (LibVLCBridging)
333
334 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
335 {
336     return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
337 }
338
339 - (id)initWithLibVLCMediaDescriptor:(void *)md
340 {
341     if (self = [super init])
342     {
343         libvlc_media_retain( md );
344         p_md = md;
345         
346         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
347         [self initInternalMediaDescriptor];
348     }
349     return self;
350 }
351
352 - (void *)libVLCMediaDescriptor 
353 {
354     return p_md;
355 }
356
357 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
358 {
359     libvlc_media_t * p_md;
360     p_md = libvlc_media_duplicate( [media libVLCMediaDescriptor] );
361
362     for( NSString * key in [options allKeys] )
363     {
364         libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=#%@", key, [options objectForKey:key]] UTF8String]);
365     }
366     return [VLCMedia mediaWithLibVLCMediaDescriptor:p_md];
367 }
368
369 @end
370
371 /******************************************************************************
372  * Implementation VLCMedia (Private)
373  */
374 @implementation VLCMedia (Private)
375
376 + (libvlc_meta_t)stringToMetaType:(NSString *)string
377 {
378     static NSDictionary * stringToMetaDictionary = nil;
379     // TODO: Thread safe-ize
380     if( !stringToMetaDictionary )
381     {
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),
401                 nil] retain];
402 #undef VLCStringToMeta
403     }
404     NSNumber * number = [stringToMetaDictionary objectForKey:string];
405     return number ? [number intValue] : -1;
406 }
407
408 + (NSString *)metaTypeToString:(libvlc_meta_t)type
409 {
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
428     return nil;
429 }
430
431 - (void)initInternalMediaDescriptor
432 {
433     libvlc_exception_t ex;
434     libvlc_exception_init( &ex );
435
436     char * p_url = libvlc_media_get_mrl( p_md );
437
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];
441     free( p_url );
442
443     libvlc_media_set_user_data( p_md, (void*)self );
444
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);
450
451     libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
452
453     if (!p_mlist)
454         subitems = nil;
455     else
456     {
457         subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
458         libvlc_media_list_release( p_mlist );
459     }
460
461     state = LibVLCStateToMediaState(libvlc_media_get_state( p_md ));
462 }
463
464 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
465 {
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];
469     free(psz_value);
470
471     if ( newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame) )
472     {
473         // Only fetch the art if needed. (ie, create the NSImage, if it was requested before)
474         if (isArtFetched && [metaType isEqualToString:VLCMetaInformationArtworkURL])
475         {
476             [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:) 
477                                          toTarget:self
478                                        withObject:newValue];
479         }
480
481         [metaDictionary setValue:newValue forKeyPath:metaType];
482     }
483 }
484
485 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
486 {
487     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
488     NSImage * art = nil;
489
490     if( anURL )
491     {
492         // Go ahead and load up the art work
493         NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
494
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]; 
498     }
499
500     // If anything was found, lets save it to the meta data dictionary
501     [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
502
503     [pool release];
504 }
505
506 - (void)setArtwork:(NSImage *)art
507 {
508     if (!art)
509     {
510         [metaDictionary removeObjectForKey:@"artwork"];
511         return;
512     }
513
514     [metaDictionary setObject:art forKey:@"artwork"];
515 }
516
517 - (void)metaChanged:(NSString *)metaType
518 {
519     [self fetchMetaInformationFromLibVLCWithType:metaType];
520 }
521
522 - (void)subItemAdded
523 {
524     if( subitems )
525         return; /* Nothing to do */
526
527     libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
528
529     NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
530
531     [self willChangeValueForKey:@"subitems"];
532     subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
533     [self didChangeValueForKey:@"subitems"];
534     libvlc_media_list_release( p_mlist );
535 }
536
537 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
538 {
539     [self setState: [newStateAsNumber intValue]];
540 }
541
542 - (id)valueForKeyPath:(NSString *)keyPath
543 {
544     if( !isArtFetched && [keyPath isEqualToString:@"metaDictionary.artwork"])
545     {
546         isArtFetched = YES;
547         /* Force the retrieval of the artwork now that someone asked for it */
548         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
549     }
550     else if( !areOthersMetaFetched && [keyPath hasPrefix:@"metaDictionary."])
551     {
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];
556     }
557     else if( !isArtURLFetched && [keyPath hasPrefix:@"metaDictionary.artworkURL"])
558     {
559         isArtURLFetched = YES;
560         /* Force isArtURLFetched, that will trigger artwork download eventually
561          * And all the other meta will be added through the libvlc event system */
562         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
563     }
564     
565     return [super valueForKeyPath:keyPath];
566 }
567 @end
568
569 /******************************************************************************
570  * Implementation VLCMedia (VLCMediaPlayerBridging)
571  */
572
573 @implementation VLCMedia (VLCMediaPlayerBridging)
574
575 - (void)setLength:(VLCTime *)value
576 {
577     if (length && value && [length compare:value] == NSOrderedSame)
578         return;
579         
580     [length release];       
581     length = value ? [value retain] : nil;
582 }
583
584 @end