]> git.sesse.net Git - vlc/blob - projects/macosx/framework/Sources/VLCMedia.m
e4cbef933876fe34e2cc0f19448bc2400343be27
[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         p_md = libvlc_media_new_location([VLCLibrary sharedInstance],
181                                            [[anURL absoluteString] UTF8String]);
182
183         delegate = nil;
184         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
185
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?
188         length = nil;
189
190         [self initInternalMediaDescriptor];
191     }
192     return self;
193 }
194
195 - (id)initAsNodeWithName:(NSString *)aName
196 {
197     if (self = [super init])
198     {
199         p_md = libvlc_media_new_as_node([VLCLibrary sharedInstance],
200                                                    [aName UTF8String]);
201
202         delegate = nil;
203         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
204
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?
207         length = nil;
208
209         [self initInternalMediaDescriptor];
210     }
211     return self;
212 }
213
214 - (void)release
215 {
216     @synchronized(self)
217     {
218         if([self retainCount] <= 1)
219         {
220             /* We must make sure we won't receive new event after an upcoming dealloc
221              * We also may receive a -retain in some event callback that may occcur
222              * Before libvlc_event_detach. So this can't happen in dealloc */
223             libvlc_event_manager_t * p_em = libvlc_media_event_manager(p_md);
224             libvlc_event_detach(p_em, libvlc_MediaMetaChanged,     HandleMediaMetaChanged,     self);
225             libvlc_event_detach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
226             libvlc_event_detach(p_em, libvlc_MediaStateChanged,    HandleMediaStateChanged,    self);
227             libvlc_event_detach(p_em, libvlc_MediaSubItemAdded,    HandleMediaSubItemAdded,    self);
228         }
229         [super release];
230     }
231 }
232
233 - (void)dealloc
234 {
235     // Testing to see if the pointer exists is not required, if the pointer is null
236     // then the release message is not sent to it.
237     delegate = nil;
238     [length release];
239     [url release];
240     [subitems release];
241     [metaDictionary release];
242
243     libvlc_media_release( p_md );
244
245     [super dealloc];
246 }
247
248 - (NSString *)description
249 {
250     NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
251     return [NSString stringWithFormat:@"<%@ %p> %@", [self className], self, (result ? result : [url absoluteString])];
252 }
253
254 - (NSComparisonResult)compare:(VLCMedia *)media
255 {
256     if (self == media)
257         return NSOrderedSame;
258     if (!media)
259         return NSOrderedDescending;
260     return p_md == [media libVLCMediaDescriptor] ? NSOrderedSame : NSOrderedAscending;
261 }
262
263 @synthesize delegate;
264
265 - (VLCTime *)length
266 {
267     if (!length)
268     {
269         // Try figuring out what the length is
270         long long duration = libvlc_media_get_duration( p_md );
271         if (duration > -1)
272         {
273             length = [[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]] retain];
274             return [[length retain] autorelease];
275         }
276         return [VLCTime nullTime];
277     }
278     return [[length retain] autorelease];
279 }
280
281 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
282 {
283     static const long long thread_sleep = 10000;
284
285     if (!length)
286     {
287         // Force preparsing of this item.
288         [self length];
289
290         // wait until we are preparsed
291         while (!length && ![self isPreparsed] && [aDate timeIntervalSinceNow] > 0)
292         {
293             usleep( thread_sleep );
294         }
295
296         // So we're done waiting, but sometimes we trap the fact that the parsing
297         // was done before the length gets assigned, so lets go ahead and assign
298         // it ourselves.
299         if (!length)
300             return [self length];
301     }
302
303     return [[length retain] autorelease];
304 }
305
306 - (BOOL)isPreparsed
307 {
308     return libvlc_media_is_preparsed( p_md );
309 }
310
311 @synthesize url;
312 @synthesize subitems;
313 @synthesize metaDictionary;
314 @synthesize state;
315
316 @end
317
318 /******************************************************************************
319  * Implementation VLCMedia (LibVLCBridging)
320  */
321 @implementation VLCMedia (LibVLCBridging)
322
323 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
324 {
325     return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
326 }
327
328 - (id)initWithLibVLCMediaDescriptor:(void *)md
329 {
330     if (self = [super init])
331     {
332         libvlc_media_retain( md );
333         p_md = md;
334
335         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
336         [self initInternalMediaDescriptor];
337     }
338     return self;
339 }
340
341 - (void *)libVLCMediaDescriptor
342 {
343     return p_md;
344 }
345
346 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
347 {
348     libvlc_media_t * p_md;
349     p_md = libvlc_media_duplicate( [media libVLCMediaDescriptor] );
350
351     for( NSString * key in [options allKeys] )
352     {
353         libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=#%@", key, [options objectForKey:key]] UTF8String]);
354     }
355     return [VLCMedia mediaWithLibVLCMediaDescriptor:p_md];
356 }
357
358 @end
359
360 /******************************************************************************
361  * Implementation VLCMedia (Private)
362  */
363 @implementation VLCMedia (Private)
364
365 + (libvlc_meta_t)stringToMetaType:(NSString *)string
366 {
367     static NSDictionary * stringToMetaDictionary = nil;
368     // TODO: Thread safe-ize
369     if( !stringToMetaDictionary )
370     {
371 #define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
372         stringToMetaDictionary =
373             [[NSDictionary dictionaryWithObjectsAndKeys:
374                 VLCStringToMeta(Title),
375                 VLCStringToMeta(Artist),
376                 VLCStringToMeta(Genre),
377                 VLCStringToMeta(Copyright),
378                 VLCStringToMeta(Album),
379                 VLCStringToMeta(TrackNumber),
380                 VLCStringToMeta(Description),
381                 VLCStringToMeta(Rating),
382                 VLCStringToMeta(Date),
383                 VLCStringToMeta(Setting),
384                 VLCStringToMeta(URL),
385                 VLCStringToMeta(Language),
386                 VLCStringToMeta(NowPlaying),
387                 VLCStringToMeta(Publisher),
388                 VLCStringToMeta(ArtworkURL),
389                 VLCStringToMeta(TrackID),
390                 nil] retain];
391 #undef VLCStringToMeta
392     }
393     NSNumber * number = [stringToMetaDictionary objectForKey:string];
394     return number ? [number intValue] : -1;
395 }
396
397 + (NSString *)metaTypeToString:(libvlc_meta_t)type
398 {
399 #define VLCMetaToString( name, type )   if (libvlc_meta_##name == type) return VLCMetaInformation##name;
400     VLCMetaToString(Title, type);
401     VLCMetaToString(Artist, type);
402     VLCMetaToString(Genre, type);
403     VLCMetaToString(Copyright, type);
404     VLCMetaToString(Album, type);
405     VLCMetaToString(TrackNumber, type);
406     VLCMetaToString(Description, type);
407     VLCMetaToString(Rating, type);
408     VLCMetaToString(Date, type);
409     VLCMetaToString(Setting, type);
410     VLCMetaToString(URL, type);
411     VLCMetaToString(Language, type);
412     VLCMetaToString(NowPlaying, type);
413     VLCMetaToString(Publisher, type);
414     VLCMetaToString(ArtworkURL, type);
415     VLCMetaToString(TrackID, type);
416 #undef VLCMetaToString
417     return nil;
418 }
419
420 - (void)initInternalMediaDescriptor
421 {
422     char * p_url = libvlc_media_get_mrl( p_md );
423
424     url = [[NSURL URLWithString:[NSString stringWithUTF8String:p_url]] retain];
425     if( !url ) /* Attempt to interpret as a file path then */
426         url = [[NSURL fileURLWithPath:[NSString stringWithUTF8String:p_url]] retain];
427     free( p_url );
428
429     libvlc_media_set_user_data( p_md, (void*)self );
430
431     libvlc_event_manager_t * p_em = libvlc_media_event_manager( p_md );
432     libvlc_event_attach(p_em, libvlc_MediaMetaChanged,     HandleMediaMetaChanged,     self);
433     libvlc_event_attach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
434     libvlc_event_attach(p_em, libvlc_MediaStateChanged,    HandleMediaStateChanged,    self);
435     libvlc_event_attach(p_em, libvlc_MediaSubItemAdded,    HandleMediaSubItemAdded,    self);
436
437     libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
438
439     if (!p_mlist)
440         subitems = nil;
441     else
442     {
443         subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
444         libvlc_media_list_release( p_mlist );
445     }
446
447     state = LibVLCStateToMediaState(libvlc_media_get_state( p_md ));
448 }
449
450 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
451 {
452     char * psz_value = libvlc_media_get_meta( p_md, [VLCMedia stringToMetaType:metaType] );
453     NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
454     NSString * oldValue = [metaDictionary valueForKey:metaType];
455     free(psz_value);
456
457     if ( newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame) )
458     {
459         // Only fetch the art if needed. (ie, create the NSImage, if it was requested before)
460         if (isArtFetched && [metaType isEqualToString:VLCMetaInformationArtworkURL])
461         {
462             [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
463                                          toTarget:self
464                                        withObject:newValue];
465         }
466
467         [metaDictionary setValue:newValue forKeyPath:metaType];
468     }
469 }
470
471 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
472 {
473     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
474     NSImage * art = nil;
475
476     if( anURL )
477     {
478         // Go ahead and load up the art work
479         NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
480
481         // Don't attempt to fetch artwork from remote. Core will do that alone
482         if ([artUrl isFileURL])
483             art  = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease];
484     }
485
486     // If anything was found, lets save it to the meta data dictionary
487     [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
488
489     [pool release];
490 }
491
492 - (void)setArtwork:(NSImage *)art
493 {
494     if (!art)
495     {
496         [metaDictionary removeObjectForKey:@"artwork"];
497         return;
498     }
499
500     [metaDictionary setObject:art forKey:@"artwork"];
501 }
502
503 - (void)metaChanged:(NSString *)metaType
504 {
505     [self fetchMetaInformationFromLibVLCWithType:metaType];
506 }
507
508 - (void)subItemAdded
509 {
510     if( subitems )
511         return; /* Nothing to do */
512
513     libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
514
515     NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
516
517     [self willChangeValueForKey:@"subitems"];
518     subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
519     [self didChangeValueForKey:@"subitems"];
520     libvlc_media_list_release( p_mlist );
521 }
522
523 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
524 {
525     [self setState: [newStateAsNumber intValue]];
526 }
527
528 - (id)valueForKeyPath:(NSString *)keyPath
529 {
530     if( !isArtFetched && [keyPath isEqualToString:@"metaDictionary.artwork"])
531     {
532         isArtFetched = YES;
533         /* Force the retrieval of the artwork now that someone asked for it */
534         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
535     }
536     else if( !areOthersMetaFetched && [keyPath hasPrefix:@"metaDictionary."])
537     {
538         areOthersMetaFetched = YES;
539         /* Force VLCMetaInformationTitle, that will trigger preparsing
540          * And all the other meta will be added through the libvlc event system */
541         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
542
543     }
544     else if( !isArtURLFetched && [keyPath hasPrefix:@"metaDictionary.artworkURL"])
545     {
546         isArtURLFetched = YES;
547         /* Force isArtURLFetched, that will trigger artwork download eventually
548          * And all the other meta will be added through the libvlc event system */
549         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
550     }
551
552     return [super valueForKeyPath:keyPath];
553 }
554 @end
555
556 /******************************************************************************
557  * Implementation VLCMedia (VLCMediaPlayerBridging)
558  */
559
560 @implementation VLCMedia (VLCMediaPlayerBridging)
561
562 - (void)setLength:(VLCTime *)value
563 {
564     if (length && value && [length compare:value] == NSOrderedSame)
565         return;
566
567     [length release];
568     length = value ? [value retain] : nil;
569 }
570
571 @end