]> git.sesse.net Git - vlc/blob - projects/macosx/framework/Sources/VLCMedia.m
6295611433eb624e2266de7c780ffc8ce7aa15c7
[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 drain];
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 drain];
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 drain];
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 drain];
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)setValue:(NSString *)value forMeta:(NSString *)meta
215 {
216     libvlc_meta_t metaName = [VLCMedia stringToMetaType:meta];
217     NSAssert(metaName >= 0, @"Invalid meta");
218     libvlc_media_set_meta(p_md, metaName, [value UTF8String]);
219 }
220
221 - (void)release
222 {
223     @synchronized(self)
224     {
225         if([self retainCount] <= 1)
226         {
227             /* We must make sure we won't receive new event after an upcoming dealloc
228              * We also may receive a -retain in some event callback that may occcur
229              * Before libvlc_event_detach. So this can't happen in dealloc */
230             libvlc_event_manager_t * p_em = libvlc_media_event_manager(p_md);
231             libvlc_event_detach(p_em, libvlc_MediaMetaChanged,     HandleMediaMetaChanged,     self);
232             libvlc_event_detach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
233             libvlc_event_detach(p_em, libvlc_MediaStateChanged,    HandleMediaStateChanged,    self);
234             libvlc_event_detach(p_em, libvlc_MediaSubItemAdded,    HandleMediaSubItemAdded,    self);
235         }
236         [super release];
237     }
238 }
239
240 - (void)dealloc
241 {
242     // Testing to see if the pointer exists is not required, if the pointer is null
243     // then the release message is not sent to it.
244     delegate = nil;
245     [length release];
246     [url release];
247     [subitems release];
248     [metaDictionary release];
249
250     libvlc_media_release( p_md );
251
252     [super dealloc];
253 }
254
255 - (NSString *)description
256 {
257     NSString * result = [metaDictionary objectForKey:VLCMetaInformationTitle];
258     return [NSString stringWithFormat:@"<%@ %p> %@", [self className], self, (result ? result : [url absoluteString])];
259 }
260
261 - (NSComparisonResult)compare:(VLCMedia *)media
262 {
263     if (self == media)
264         return NSOrderedSame;
265     if (!media)
266         return NSOrderedDescending;
267     return p_md == [media libVLCMediaDescriptor] ? NSOrderedSame : NSOrderedAscending;
268 }
269
270 @synthesize delegate;
271
272 - (VLCTime *)length
273 {
274     if (!length)
275     {
276         // Try figuring out what the length is
277         long long duration = libvlc_media_get_duration( p_md );
278         if (duration > -1)
279         {
280             length = [[VLCTime timeWithNumber:[NSNumber numberWithLongLong:duration]] retain];
281             return [[length retain] autorelease];
282         }
283         return [VLCTime nullTime];
284     }
285     return [[length retain] autorelease];
286 }
287
288 - (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
289 {
290     static const long long thread_sleep = 10000;
291
292     if (!length)
293     {
294         // Force preparsing of this item.
295         [self length];
296
297         // wait until we are preparsed
298         while (!length && ![self isParsed] && [aDate timeIntervalSinceNow] > 0)
299         {
300             usleep( thread_sleep );
301         }
302
303         // So we're done waiting, but sometimes we trap the fact that the parsing
304         // was done before the length gets assigned, so lets go ahead and assign
305         // it ourselves.
306         if (!length)
307             return [self length];
308     }
309
310     return [[length retain] autorelease];
311 }
312
313 - (BOOL)isParsed
314 {
315     return libvlc_media_is_parsed( p_md );
316 }
317
318 @synthesize url;
319 @synthesize subitems;
320 @synthesize metaDictionary;
321 @synthesize state;
322
323 @end
324
325 /******************************************************************************
326  * Implementation VLCMedia (LibVLCBridging)
327  */
328 @implementation VLCMedia (LibVLCBridging)
329
330 + (id)mediaWithLibVLCMediaDescriptor:(void *)md
331 {
332     return [[[VLCMedia alloc] initWithLibVLCMediaDescriptor:md] autorelease];
333 }
334
335 - (id)initWithLibVLCMediaDescriptor:(void *)md
336 {
337     if (self = [super init])
338     {
339         libvlc_media_retain( md );
340         p_md = md;
341
342         metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
343         [self initInternalMediaDescriptor];
344     }
345     return self;
346 }
347
348 - (void *)libVLCMediaDescriptor
349 {
350     return p_md;
351 }
352
353 + (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
354 {
355     libvlc_media_t * p_md;
356     p_md = libvlc_media_duplicate( [media libVLCMediaDescriptor] );
357
358     for( NSString * key in [options allKeys] )
359     {
360         if ( [options objectForKey:key] != [NSNull null] )
361             libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=%@", key, [options objectForKey:key]] UTF8String]);
362         else
363             libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@", key] UTF8String]);
364
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     char * p_url = libvlc_media_get_mrl( p_md );
434
435     url = [[NSURL URLWithString:[NSString stringWithUTF8String:p_url]] retain];
436     if( !url ) /* Attempt to interpret as a file path then */
437         url = [[NSURL fileURLWithPath:[NSString stringWithUTF8String:p_url]] retain];
438     free( p_url );
439
440     libvlc_media_set_user_data( p_md, (void*)self );
441
442     libvlc_event_manager_t * p_em = libvlc_media_event_manager( p_md );
443     libvlc_event_attach(p_em, libvlc_MediaMetaChanged,     HandleMediaMetaChanged,     self);
444     libvlc_event_attach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, self);
445     libvlc_event_attach(p_em, libvlc_MediaStateChanged,    HandleMediaStateChanged,    self);
446     libvlc_event_attach(p_em, libvlc_MediaSubItemAdded,    HandleMediaSubItemAdded,    self);
447
448     libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
449
450     if (!p_mlist)
451         subitems = nil;
452     else
453     {
454         subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
455         libvlc_media_list_release( p_mlist );
456     }
457
458     state = LibVLCStateToMediaState(libvlc_media_get_state( p_md ));
459 }
460
461 - (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
462 {
463     char * psz_value = libvlc_media_get_meta( p_md, [VLCMedia stringToMetaType:metaType] );
464     NSString * newValue = psz_value ? [NSString stringWithUTF8String: psz_value] : nil;
465     NSString * oldValue = [metaDictionary valueForKey:metaType];
466     free(psz_value);
467
468     if ( newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame) )
469     {
470         // Only fetch the art if needed. (ie, create the NSImage, if it was requested before)
471         if (isArtFetched && [metaType isEqualToString:VLCMetaInformationArtworkURL])
472         {
473             [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
474                                          toTarget:self
475                                        withObject:newValue];
476         }
477
478         [metaDictionary setValue:newValue forKeyPath:metaType];
479     }
480 }
481
482 - (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
483 {
484     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
485     NSImage * art = nil;
486
487     if( anURL )
488     {
489         // Go ahead and load up the art work
490         NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
491
492         // Don't attempt to fetch artwork from remote. Core will do that alone
493         if ([artUrl isFileURL])
494             art  = [[[NSImage alloc] initWithContentsOfURL:artUrl] autorelease];
495     }
496
497     // If anything was found, lets save it to the meta data dictionary
498     [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
499
500     [pool release];
501 }
502
503 - (void)setArtwork:(NSImage *)art
504 {
505     if (!art)
506     {
507         [metaDictionary removeObjectForKey:@"artwork"];
508         return;
509     }
510
511     [metaDictionary setObject:art forKey:@"artwork"];
512 }
513
514 - (void)metaChanged:(NSString *)metaType
515 {
516     [self fetchMetaInformationFromLibVLCWithType:metaType];
517 }
518
519 - (void)subItemAdded
520 {
521     if( subitems )
522         return; /* Nothing to do */
523
524     libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
525
526     NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");
527
528     [self willChangeValueForKey:@"subitems"];
529     subitems = [[VLCMediaList mediaListWithLibVLCMediaList:p_mlist] retain];
530     [self didChangeValueForKey:@"subitems"];
531     libvlc_media_list_release( p_mlist );
532 }
533
534 - (void)setStateAsNumber:(NSNumber *)newStateAsNumber
535 {
536     [self setState: [newStateAsNumber intValue]];
537 }
538
539 - (id)valueForKeyPath:(NSString *)keyPath
540 {
541     if( !isArtFetched && [keyPath isEqualToString:@"metaDictionary.artwork"])
542     {
543         isArtFetched = YES;
544         /* Force the retrieval of the artwork now that someone asked for it */
545         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
546     }
547     else if( !areOthersMetaFetched && [keyPath hasPrefix:@"metaDictionary."])
548     {
549         areOthersMetaFetched = YES;
550         /* Force VLCMetaInformationTitle, that will trigger preparsing
551          * And all the other meta will be added through the libvlc event system */
552         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
553
554     }
555     else if( !isArtURLFetched && [keyPath hasPrefix:@"metaDictionary.artworkURL"])
556     {
557         isArtURLFetched = YES;
558         /* Force isArtURLFetched, that will trigger artwork download eventually
559          * And all the other meta will be added through the libvlc event system */
560         [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
561     }
562
563     return [super valueForKeyPath:keyPath];
564 }
565 @end
566
567 /******************************************************************************
568  * Implementation VLCMedia (VLCMediaPlayerBridging)
569  */
570
571 @implementation VLCMedia (VLCMediaPlayerBridging)
572
573 - (void)setLength:(VLCTime *)value
574 {
575     if (length && value && [length compare:value] == NSOrderedSame)
576         return;
577
578     [length release];
579     length = value ? [value retain] : nil;
580 }
581
582 @end