VLCMedia.m 33.8 KB
Newer Older
1
/*****************************************************************************
2
 * VLCMedia.m: VLCKit.framework VLCMedia implementation
3 4
 *****************************************************************************
 * Copyright (C) 2007 Pierre d'Herbemont
5
 * Copyright (C) 2013 Felix Paul Kühne
6
 * Copyright (C) 2007, 2013 VLC authors and VideoLAN
7 8 9
 * $Id$
 *
 * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
10
 *          Felix Paul Kühne <fkuehne # videolan.org>
11
 *
12 13 14
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
15 16 17 18
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
21
 *
22 23 24
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 26
 *****************************************************************************/

27 28
#import "VLCMedia.h"
#import "VLCMediaList.h"
29 30
#import "VLCEventManager.h"
#import "VLCLibrary.h"
31
#import "VLCLibVLCBridging.h"
32
#import <vlc/libvlc.h>
33
#import <sys/sysctl.h> // for sysctlbyname
34

35
/* Meta Dictionary Keys */
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
NSString *const VLCMetaInformationTitle          = @"title";
NSString *const VLCMetaInformationArtist         = @"artist";
NSString *const VLCMetaInformationGenre          = @"genre";
NSString *const VLCMetaInformationCopyright      = @"copyright";
NSString *const VLCMetaInformationAlbum          = @"album";
NSString *const VLCMetaInformationTrackNumber    = @"trackNumber";
NSString *const VLCMetaInformationDescription    = @"description";
NSString *const VLCMetaInformationRating         = @"rating";
NSString *const VLCMetaInformationDate           = @"date";
NSString *const VLCMetaInformationSetting        = @"setting";
NSString *const VLCMetaInformationURL            = @"url";
NSString *const VLCMetaInformationLanguage       = @"language";
NSString *const VLCMetaInformationNowPlaying     = @"nowPlaying";
NSString *const VLCMetaInformationPublisher      = @"publisher";
NSString *const VLCMetaInformationEncodedBy      = @"encodedBy";
NSString *const VLCMetaInformationArtworkURL     = @"artworkURL";
NSString *const VLCMetaInformationArtwork        = @"artwork";
NSString *const VLCMetaInformationTrackID        = @"trackID";
54 55 56 57 58 59 60 61
NSString *const VLCMetaInformationTrackTotal     = @"trackTotal";
NSString *const VLCMetaInformationDirector       = @"director";
NSString *const VLCMetaInformationSeason         = @"season";
NSString *const VLCMetaInformationEpisode        = @"episode";
NSString *const VLCMetaInformationShowName       = @"showName";
NSString *const VLCMetaInformationActors         = @"actors";
NSString *const VLCMetaInformationAlbumArtist    = @"AlbumArtist";
NSString *const VLCMetaInformationDiscNumber     = @"discNumber";
62

63
/* Notification Messages */
64
NSString *const VLCMediaMetaChanged              = @"VLCMediaMetaChanged";
65

66
/******************************************************************************
67
 * VLCMedia ()
68
 */
69
@interface VLCMedia()
70
{
71 72 73 74 75 76
    void *                  p_md;              //< Internal media descriptor instance
    BOOL                    isArtFetched;      //< Value used to determine of the artwork has been parsed
    BOOL                    areOthersMetaFetched; //< Value used to determine of the other meta has been parsed
    BOOL                    isArtURLFetched;   //< Value used to determine of the other meta has been preparsed
    BOOL                    isParsed;
    NSMutableDictionary     *_metaDictionary;
77
}
78

79 80 81 82
/* Make our properties internally readwrite */
@property (nonatomic, readwrite) VLCMediaState state;
@property (nonatomic, readwrite, strong) VLCMediaList * subitems;

83 84 85 86 87 88 89 90 91
/* Statics */
+ (libvlc_meta_t)stringToMetaType:(NSString *)string;
+ (NSString *)metaTypeToString:(libvlc_meta_t)type;

/* Initializers */
- (void)initInternalMediaDescriptor;

/* Operations */
- (void)fetchMetaInformationFromLibVLCWithType:(NSString*)metaType;
Pierre's avatar
Pierre committed
92
#if !TARGET_OS_IPHONE
93
- (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL;
94
- (void)setArtwork:(NSImage *)art;
Pierre's avatar
Pierre committed
95 96 97
#endif

- (void)parseIfNeeded;
98 99

/* Callback Methods */
Pierre's avatar
Pierre committed
100
- (void)parsedChanged:(NSNumber *)isParsedAsNumber;
101
- (void)metaChanged:(NSString *)metaType;
102
- (void)subItemAdded;
103
- (void)setStateAsNumber:(NSNumber *)newStateAsNumber;
104

105 106
@end

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
static VLCMediaState libvlc_state_to_media_state[] =
{
    [libvlc_NothingSpecial] = VLCMediaStateNothingSpecial,
    [libvlc_Stopped]        = VLCMediaStateNothingSpecial,
    [libvlc_Opening]        = VLCMediaStateNothingSpecial,
    [libvlc_Buffering]      = VLCMediaStateBuffering,
    [libvlc_Ended]          = VLCMediaStateNothingSpecial,
    [libvlc_Error]          = VLCMediaStateError,
    [libvlc_Playing]        = VLCMediaStatePlaying,
    [libvlc_Paused]         = VLCMediaStatePlaying,
};

static inline VLCMediaState LibVLCStateToMediaState( libvlc_state_t state )
{
    return libvlc_state_to_media_state[state];
}
123 124 125 126

/******************************************************************************
 * LibVLC Event Callback
 */
127
static void HandleMediaMetaChanged(const libvlc_event_t * event, void * self)
128
{
129 130 131 132 133
    @autoreleasepool {
        [[VLCEventManager sharedManager] callOnMainThreadObject:(__bridge id)(self)
                                                     withMethod:@selector(metaChanged:)
                                           withArgumentAsObject:[VLCMedia metaTypeToString:event->u.media_meta_changed.meta_type]];
    }
134 135
}

136 137
static void HandleMediaDurationChanged(const libvlc_event_t * event, void * self)
{
138 139 140 141 142 143
    @autoreleasepool {
        [[VLCEventManager sharedManager] callOnMainThreadObject:(__bridge id)(self)
                                                     withMethod:@selector(setLength:)
                                           withArgumentAsObject:[VLCTime timeWithNumber:
                                               @(event->u.media_duration_changed.new_duration)]];
    }
144
}
145

146
static void HandleMediaStateChanged(const libvlc_event_t * event, void * self)
147
{
148 149 150 151 152
    @autoreleasepool {
        [[VLCEventManager sharedManager] callOnMainThreadObject:(__bridge id)(self)
                                                     withMethod:@selector(setStateAsNumber:)
                                           withArgumentAsObject:@(LibVLCStateToMediaState(event->u.media_state_changed.new_state))];
    }
153 154
}

155
static void HandleMediaSubItemAdded(const libvlc_event_t * event, void * self)
156
{
157 158 159 160 161
    @autoreleasepool {
        [[VLCEventManager sharedManager] callOnMainThreadObject:(__bridge id)(self)
                                                     withMethod:@selector(subItemAdded)
                                           withArgumentAsObject:nil];
    }
162 163
}

Pierre's avatar
Pierre committed
164 165
static void HandleMediaParsedChanged(const libvlc_event_t * event, void * self)
{
166 167 168 169 170
    @autoreleasepool {
        [[VLCEventManager sharedManager] callOnMainThreadObject:(__bridge id)(self)
                                                     withMethod:@selector(parsedChanged:)
                                           withArgumentAsObject:@((BOOL)event->u.media_parsed_changed.new_status)];
    }
Pierre's avatar
Pierre committed
171 172 173
}


174 175 176
/******************************************************************************
 * Implementation
 */
177
@implementation VLCMedia
178

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
+ (NSString *)codecNameForFourCC:(uint32_t)fourcc trackType:(NSString *)trackType
{
    libvlc_track_type_t track_type = libvlc_track_unknown;

    if ([trackType isEqualToString:VLCMediaTracksInformationTypeAudio])
        track_type = libvlc_track_audio;
    else if ([trackType isEqualToString:VLCMediaTracksInformationTypeVideo])
        track_type = libvlc_track_video;
    else if ([trackType isEqualToString:VLCMediaTracksInformationTypeText])
        track_type = libvlc_track_text;

    const char *ret = libvlc_media_get_codec_description(track_type, fourcc);
    if (ret)
        return [NSString stringWithUTF8String:ret];

    return @"";
}

James Dumay's avatar
James Dumay committed
197
+ (instancetype)mediaWithURL:(NSURL *)anURL;
198
{
199
    return [[VLCMedia alloc] initWithURL:anURL];
200 201
}

James Dumay's avatar
James Dumay committed
202
+ (instancetype)mediaWithPath:(NSString *)aPath;
203
{
204
    return [[VLCMedia alloc] initWithPath:aPath];
205 206
}

James Dumay's avatar
James Dumay committed
207
+ (instancetype)mediaAsNodeWithName:(NSString *)aName;
208
{
209
    return [[VLCMedia alloc] initAsNodeWithName:aName];
210 211
}

James Dumay's avatar
James Dumay committed
212
- (instancetype)initWithPath:(NSString *)aPath
213
{
214
    return [self initWithURL:[NSURL fileURLWithPath:aPath isDirectory:NO]];
215 216
}

James Dumay's avatar
James Dumay committed
217
- (instancetype)initWithURL:(NSURL *)anURL
218
{
219
    if (self = [super init]) {
220 221 222 223
        VLCLibrary *library = [VLCLibrary sharedLibrary];
        NSAssert(library.instance, @"no library instance when creating media");

        p_md = libvlc_media_new_location(library.instance, [[anURL absoluteString] UTF8String]);
224

225
        _metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
226 227 228 229

        [self initInternalMediaDescriptor];
    }
    return self;
230 231
}

James Dumay's avatar
James Dumay committed
232
- (instancetype)initAsNodeWithName:(NSString *)aName
233
{
234
    if (self = [super init]) {
235
        p_md = libvlc_media_new_as_node([VLCLibrary sharedInstance], [aName UTF8String]);
236

237
        _metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
238

239
        [self initInternalMediaDescriptor];
240 241 242 243
    }
    return self;
}

244
- (void)dealloc
245
{
Pierre's avatar
Pierre committed
246
    libvlc_event_manager_t * p_em = libvlc_media_event_manager(p_md);
247 248 249 250 251 252 253
    if (p_em) {
        libvlc_event_detach(p_em, libvlc_MediaMetaChanged,     HandleMediaMetaChanged,     (__bridge void *)(self));
        libvlc_event_detach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, (__bridge void *)(self));
        libvlc_event_detach(p_em, libvlc_MediaStateChanged,    HandleMediaStateChanged,    (__bridge void *)(self));
        libvlc_event_detach(p_em, libvlc_MediaSubItemAdded,    HandleMediaSubItemAdded,    (__bridge void *)(self));
        libvlc_event_detach(p_em, libvlc_MediaParsedChanged,   HandleMediaParsedChanged,   (__bridge void *)(self));
    }
Pierre's avatar
Pierre committed
254

255
    [[VLCEventManager sharedManager] cancelCallToObject:self];
256

257
    libvlc_media_release( p_md );
258 259
}

260 261
- (NSString *)description
{
262 263
    NSString * result = _metaDictionary[VLCMetaInformationTitle];
    return [NSString stringWithFormat:@"<%@ %p> %@", [self class], self, (result ? result : [_url absoluteString])];
264 265 266 267
}

- (NSComparisonResult)compare:(VLCMedia *)media
{
268
    if (self == media)
269
        return NSOrderedSame;
270
    if (!media)
271
        return NSOrderedDescending;
272
    return p_md == [media libVLCMediaDescriptor] ? NSOrderedSame : NSOrderedAscending;
273 274 275
}

- (VLCTime *)length
276
{
277
    if (!_length) {
278
        // Try figuring out what the length is
279
        long long duration = libvlc_media_get_duration( p_md );
280 281 282
        if (duration < 0)
            return [VLCTime nullTime];
         _length = [VLCTime timeWithNumber:@(duration)];
283
    }
284
    return _length;
285 286
}

287 288
- (VLCTime *)lengthWaitUntilDate:(NSDate *)aDate
{
289
    static const long long thread_sleep = 10000;
290

291
    if (!_length) {
Pierre's avatar
Pierre committed
292 293
        // Force parsing of this item.
        [self parseIfNeeded];
294 295

        // wait until we are preparsed
296
        while (!_length && !libvlc_media_is_parsed(p_md) && [aDate timeIntervalSinceNow] > 0)
297
            usleep( thread_sleep );
298

299 300 301
        // So we're done waiting, but sometimes we trap the fact that the parsing
        // was done before the length gets assigned, so lets go ahead and assign
        // it ourselves.
302
        if (!_length)
303 304
            return [self length];
    }
305

306
    return _length;
307
}
308

Pierre's avatar
Pierre committed
309
- (BOOL)isParsed
310
{
Pierre's avatar
Pierre committed
311 312 313 314 315
    return isParsed;
}

- (void)parse
{
316 317
    if (p_md)
        libvlc_media_parse_async(p_md);
Pierre's avatar
Pierre committed
318 319
}

320 321 322 323 324 325
- (void)synchronousParse
{
    if (p_md)
        libvlc_media_parse(p_md);
}

326 327
- (void)addOptions:(NSDictionary*)options
{
328
    if (p_md) {
329 330 331
        [options enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
            if (![obj isKindOfClass:[NSNull class]])
                libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=%@", key, obj] UTF8String]);
332
            else
333 334
                libvlc_media_add_option(p_md, [key UTF8String]);
        }];
335 336 337
    }
}

338 339
- (NSDictionary*) stats
{
340
    if (!p_md)
341
        return nil;
342 343 344 345

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
    return @{
        @"demuxBitrate" : @(p_stats.f_demux_bitrate),
        @"inputBitrate" : @(p_stats.f_input_bitrate),
        @"sendBitrate" : @(p_stats.f_send_bitrate),
        @"decodedAudio" : @(p_stats.i_decoded_audio),
        @"decodedVideo" : @(p_stats.i_decoded_video),
        @"demuxCorrupted" : @(p_stats.i_demux_corrupted),
        @"demuxDiscontinuity" : @(p_stats.i_demux_discontinuity),
        @"demuxReadBytes" : @(p_stats.i_demux_read_bytes),
        @"displayedPictures" : @(p_stats.i_displayed_pictures),
        @"lostAbuffers" : @(p_stats.i_lost_abuffers),
        @"lostPictures" : @(p_stats.i_lost_pictures),
        @"playedAbuffers" : @(p_stats.i_played_abuffers),
        @"readBytes" : @(p_stats.i_read_bytes),
        @"sentBytes" : @(p_stats.i_sent_bytes),
        @"sentPackets" : @(p_stats.i_sent_packets)
    };
363 364
}

365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
- (NSInteger)numberOfReadBytesOnInput
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_read_bytes;
}

- (float)inputBitrate
{
    if (!p_md)
        return .0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.f_input_bitrate;
}

- (NSInteger)numberOfReadBytesOnDemux
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_demux_read_bytes;
}

- (float)demuxBitrate
{
    if (!p_md)
        return .0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.f_demux_bitrate;
}

- (NSInteger)numberOfDecodedVideoBlocks
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_decoded_video;
}

- (NSInteger)numberOfDecodedAudioBlocks
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_decoded_audio;
}

- (NSInteger)numberOfDisplayedPictures
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_displayed_pictures;
}

- (NSInteger)numberOfLostPictures
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_lost_pictures;
}

- (NSInteger)numberOfPlayedAudioBuffers
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_played_abuffers;
}

- (NSInteger)numberOfLostAudioBuffers
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_lost_abuffers;
}

- (NSInteger)numberOfSentPackets
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_sent_packets;
}

- (NSInteger)numberOfSentBytes
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_sent_bytes;
}

- (float)streamOutputBitrate
{
    if (!p_md)
        return .0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.f_send_bitrate;
}

- (NSInteger)numberOfCorruptedDataPackets
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_demux_corrupted;
}

- (NSInteger)numberOfDiscontinuties
{
    if (!p_md)
        return 0;

    libvlc_media_stats_t p_stats;
    libvlc_media_get_stats(p_md, &p_stats);

    return p_stats.i_demux_discontinuity;
}

530 531 532
NSString *const VLCMediaTracksInformationCodec = @"codec"; // NSNumber
NSString *const VLCMediaTracksInformationId    = @"id";    // NSNumber
NSString *const VLCMediaTracksInformationType  = @"type";  // NSString
Pierre's avatar
Pierre committed
533

534 535
NSString *const VLCMediaTracksInformationCodecProfile  = @"profile"; // NSNumber
NSString *const VLCMediaTracksInformationCodecLevel    = @"level";   // NSNumber
536

537 538 539 540
NSString *const VLCMediaTracksInformationTypeAudio    = @"audio";
NSString *const VLCMediaTracksInformationTypeVideo    = @"video";
NSString *const VLCMediaTracksInformationTypeText     = @"text";
NSString *const VLCMediaTracksInformationTypeUnknown  = @"unknown";
Pierre's avatar
Pierre committed
541

542 543 544
NSString *const VLCMediaTracksInformationBitrate      = @"bitrate"; // NSNumber
NSString *const VLCMediaTracksInformationLanguage     = @"language"; // NSString
NSString *const VLCMediaTracksInformationDescription  = @"description"; // NSString
Pierre's avatar
Pierre committed
545

546 547
NSString *const VLCMediaTracksInformationAudioChannelsNumber = @"channelsNumber"; // NSNumber
NSString *const VLCMediaTracksInformationAudioRate           = @"rate";           // NSNumber
Pierre's avatar
Pierre committed
548

549 550
NSString *const VLCMediaTracksInformationVideoHeight = @"height"; // NSNumber
NSString *const VLCMediaTracksInformationVideoWidth  = @"width";  // NSNumber
Pierre's avatar
Pierre committed
551

552 553
NSString *const VLCMediaTracksInformationSourceAspectRatio        = @"sar_num"; // NSNumber
NSString *const VLCMediaTracksInformationSourceAspectDenominator  = @"sar_den";  // NSNumber
554

555 556
NSString *const VLCMediaTracksInformationFrameRate             = @"frame_rate_num"; // NSNumber
NSString *const VLCMediaTracksInformationFrameRateDenominator  = @"frame_rate_den";  // NSNumber
557

558
NSString *const VLCMediaTracksInformationTextEncoding = @"encoding"; // NSString
559

Pierre's avatar
Pierre committed
560 561
- (NSArray *)tracksInformation
{
562
    [self synchronousParse];
Pierre's avatar
Pierre committed
563

564 565
    libvlc_media_track_t **tracksInfo;
    unsigned int count = libvlc_media_tracks_get(p_md, &tracksInfo);
Pierre's avatar
Pierre committed
566
    NSMutableArray *array = [NSMutableArray array];
567
    for (NSUInteger i = 0; i < count; i++) {
Pierre's avatar
Pierre committed
568
        NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
569
                                           @(tracksInfo[i]->i_codec),
570
                                           VLCMediaTracksInformationCodec,
571
                                           @(tracksInfo[i]->i_id),
572
                                           VLCMediaTracksInformationId,
573
                                           @(tracksInfo[i]->i_profile),
574
                                           VLCMediaTracksInformationCodecProfile,
575
                                           @(tracksInfo[i]->i_level),
576
                                           VLCMediaTracksInformationCodecLevel,
577
                                           @(tracksInfo[i]->i_bitrate),
578
                                           VLCMediaTracksInformationBitrate,
Pierre's avatar
Pierre committed
579
                                           nil];
580
        if (tracksInfo[i]->psz_language)
581
            dictionary[VLCMediaTracksInformationLanguage] = [NSString stringWithUTF8String:tracksInfo[i]->psz_language];
582 583

        if (tracksInfo[i]->psz_description)
584
            dictionary[VLCMediaTracksInformationDescription] = [NSString stringWithUTF8String:tracksInfo[i]->psz_description];
Pierre's avatar
Pierre committed
585 586

        NSString *type;
587
        switch (tracksInfo[i]->i_type) {
Pierre's avatar
Pierre committed
588 589
            case libvlc_track_audio:
                type = VLCMediaTracksInformationTypeAudio;
590 591
                dictionary[VLCMediaTracksInformationAudioChannelsNumber] = @(tracksInfo[i]->audio->i_channels);
                dictionary[VLCMediaTracksInformationAudioRate] = @(tracksInfo[i]->audio->i_rate);
Pierre's avatar
Pierre committed
592 593 594
                break;
            case libvlc_track_video:
                type = VLCMediaTracksInformationTypeVideo;
595 596 597 598 599 600
                dictionary[VLCMediaTracksInformationVideoWidth] = @(tracksInfo[i]->video->i_width);
                dictionary[VLCMediaTracksInformationVideoHeight] = @(tracksInfo[i]->video->i_height);
                dictionary[VLCMediaTracksInformationSourceAspectRatio] = @(tracksInfo[i]->video->i_sar_num);
                dictionary[VLCMediaTracksInformationSourceAspectDenominator] = @(tracksInfo[i]->video->i_sar_den);
                dictionary[VLCMediaTracksInformationFrameRate] = @(tracksInfo[i]->video->i_frame_rate_num);
                dictionary[VLCMediaTracksInformationFrameRateDenominator] = @(tracksInfo[i]->video->i_frame_rate_den);
Pierre's avatar
Pierre committed
601 602 603
                break;
            case libvlc_track_text:
                type = VLCMediaTracksInformationTypeText;
604
                if (tracksInfo[i]->subtitle->psz_encoding)
605
                    dictionary[VLCMediaTracksInformationTextEncoding] = [NSString stringWithUTF8String: tracksInfo[i]->subtitle->psz_encoding];
Pierre's avatar
Pierre committed
606 607 608 609 610 611 612 613 614 615
                break;
            case libvlc_track_unknown:
            default:
                type = VLCMediaTracksInformationTypeUnknown;
                break;
        }
        [dictionary setValue:type forKey:VLCMediaTracksInformationType];

        [array addObject:dictionary];
    }
616
    libvlc_media_tracks_release(tracksInfo, count);
Pierre's avatar
Pierre committed
617
    return array;
618 619
}

620 621 622 623
- (BOOL)isMediaSizeSuitableForDevice
{
#if TARGET_OS_IPHONE
    // Trigger parsing if needed
624 625
    if (![self isParsed])
        [self synchronousParse];
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650

    NSUInteger biggestWidth = 0;
    NSUInteger biggestHeight = 0;
    libvlc_media_track_t **tracksInfo;
    unsigned int count = libvlc_media_tracks_get(p_md, &tracksInfo);
    for (NSUInteger i = 0; i < count; i++) {
        switch (tracksInfo[i]->i_type) {
            case libvlc_track_video:
                if (tracksInfo[i]->video->i_width > biggestWidth)
                    biggestWidth = tracksInfo[i]->video->i_width;
                if (tracksInfo[i]->video->i_height > biggestHeight)
                    biggestHeight = tracksInfo[i]->video->i_height;
                break;
            default:
                break;
        }
    }

    if (biggestHeight > 0 && biggestWidth > 0) {
        size_t size;
        sysctlbyname("hw.machine", NULL, &size, NULL, 0);

        char *answer = malloc(size);
        sysctlbyname("hw.machine", answer, &size, NULL, 0);

651
        NSString *currentMachine = @(answer);
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
        free(answer);

        NSUInteger totalNumberOfPixels = biggestWidth * biggestHeight;

        if ([currentMachine hasPrefix:@"iPhone2"] || [currentMachine hasPrefix:@"iPhone3"] || [currentMachine hasPrefix:@"iPad1"] || [currentMachine hasPrefix:@"iPod3"] || [currentMachine hasPrefix:@"iPod4"]) {
            // iPhone 3GS, iPhone 4, first gen. iPad, 3rd and 4th generation iPod touch
            return (totalNumberOfPixels < 600000); // between 480p and 720p
        } else if ([currentMachine hasPrefix:@"iPhone4"] || [currentMachine hasPrefix:@"iPad3,1"] || [currentMachine hasPrefix:@"iPad3,2"] || [currentMachine hasPrefix:@"iPad3,3"] || [currentMachine hasPrefix:@"iPod4"] || [currentMachine hasPrefix:@"iPad2"] || [currentMachine hasPrefix:@"iPod5"]) {
            // iPhone 4S, iPad 2 and 3, iPod 4 and 5
            return (totalNumberOfPixels < 922000); // 720p
        } else {
            // iPhone 5, iPad 4
            return (totalNumberOfPixels < 2074000); // 1080p
        }
    }
#endif

    return YES;
}

672 673 674
- (NSString *)metadataForKey:(NSString *)key
{
    if (!p_md)
675
        return nil;
676 677 678 679 680 681 682

    if (![self isParsed])
        [self synchronousParse];

    char *returnValue = libvlc_media_get_meta(p_md, [VLCMedia stringToMetaType:key]);

    if (!returnValue)
683
        return nil;
684

James Dumay's avatar
James Dumay committed
685
    NSString *actualReturnValue = @(returnValue);
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
    free(returnValue);

    return actualReturnValue;
}

- (void)setMetadata:(NSString *)data forKey:(NSString *)key
{
    if (!p_md)
        return;

    libvlc_media_set_meta(p_md, [VLCMedia stringToMetaType:key], [data UTF8String]);
}

- (BOOL)saveMetadata
{
    if (p_md)
702
        return libvlc_media_save_meta(p_md) != 0;
703 704 705 706

    return NO;
}

707
/******************************************************************************
708
 * Implementation VLCMedia ()
709
 */
710

711 712
+ (libvlc_meta_t)stringToMetaType:(NSString *)string
{
713 714
    static NSDictionary * stringToMetaDictionary = nil;
    // TODO: Thread safe-ize
715
    if (!stringToMetaDictionary) {
716 717
#define VLCStringToMeta( name ) [NSNumber numberWithInt: libvlc_meta_##name], VLCMetaInformation##name
        stringToMetaDictionary =
718
            [NSDictionary dictionaryWithObjectsAndKeys:
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734
                VLCStringToMeta(Title),
                VLCStringToMeta(Artist),
                VLCStringToMeta(Genre),
                VLCStringToMeta(Copyright),
                VLCStringToMeta(Album),
                VLCStringToMeta(TrackNumber),
                VLCStringToMeta(Description),
                VLCStringToMeta(Rating),
                VLCStringToMeta(Date),
                VLCStringToMeta(Setting),
                VLCStringToMeta(URL),
                VLCStringToMeta(Language),
                VLCStringToMeta(NowPlaying),
                VLCStringToMeta(Publisher),
                VLCStringToMeta(ArtworkURL),
                VLCStringToMeta(TrackID),
735 736 737 738 739 740 741 742
                VLCStringToMeta(TrackTotal),
                VLCStringToMeta(Director),
                VLCStringToMeta(Season),
                VLCStringToMeta(Episode),
                VLCStringToMeta(ShowName),
                VLCStringToMeta(Actors),
                VLCStringToMeta(AlbumArtist),
                VLCStringToMeta(DiscNumber),
743
                nil];
744
#undef VLCStringToMeta
745
    }
746
    NSNumber * number = stringToMetaDictionary[string];
747
    return (libvlc_meta_t) (number ? [number intValue] : -1);
748 749 750 751
}

+ (NSString *)metaTypeToString:(libvlc_meta_t)type
{
752
#define VLCMetaToString( name, type )   if (libvlc_meta_##name == type) return VLCMetaInformation##name;
753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768
    VLCMetaToString(Title, type);
    VLCMetaToString(Artist, type);
    VLCMetaToString(Genre, type);
    VLCMetaToString(Copyright, type);
    VLCMetaToString(Album, type);
    VLCMetaToString(TrackNumber, type);
    VLCMetaToString(Description, type);
    VLCMetaToString(Rating, type);
    VLCMetaToString(Date, type);
    VLCMetaToString(Setting, type);
    VLCMetaToString(URL, type);
    VLCMetaToString(Language, type);
    VLCMetaToString(NowPlaying, type);
    VLCMetaToString(Publisher, type);
    VLCMetaToString(ArtworkURL, type);
    VLCMetaToString(TrackID, type);
769 770 771 772 773 774 775 776
    VLCMetaToString(TrackTotal, type);
    VLCMetaToString(Director, type);
    VLCMetaToString(Season, type);
    VLCMetaToString(Episode, type);
    VLCMetaToString(ShowName, type);
    VLCMetaToString(Actors, type);
    VLCMetaToString(AlbumArtist, type);
    VLCMetaToString(DiscNumber, type);
777
#undef VLCMetaToString
778 779 780 781
    return nil;
}

- (void)initInternalMediaDescriptor
782
{
783
    char * p_url = libvlc_media_get_mrl( p_md );
784 785
    if (!p_url)
        return;
786

787 788 789
    _url = [NSURL URLWithString:@(p_url)];
    if (!_url) /* Attempt to interpret as a file path then */
        _url = [NSURL fileURLWithPath:@(p_url)];
790
    free(p_url);
791

792
    libvlc_media_set_user_data(p_md, (__bridge void*)self);
793

794
    libvlc_event_manager_t * p_em = libvlc_media_event_manager( p_md );
795 796 797 798 799 800 801
    if (p_em) {
        libvlc_event_attach(p_em, libvlc_MediaMetaChanged,     HandleMediaMetaChanged,     (__bridge void *)(self));
        libvlc_event_attach(p_em, libvlc_MediaDurationChanged, HandleMediaDurationChanged, (__bridge void *)(self));
        libvlc_event_attach(p_em, libvlc_MediaStateChanged,    HandleMediaStateChanged,    (__bridge void *)(self));
        libvlc_event_attach(p_em, libvlc_MediaSubItemAdded,    HandleMediaSubItemAdded,    (__bridge void *)(self));
        libvlc_event_attach(p_em, libvlc_MediaParsedChanged,   HandleMediaParsedChanged,   (__bridge void *)(self));
    }
802

803
    libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
804

805 806
    if (p_mlist) {
        self.subitems = [VLCMediaList mediaListWithLibVLCMediaList:p_mlist];
807
        libvlc_media_list_release( p_mlist );
808
    }
809

810 811
    isParsed = libvlc_media_is_parsed(p_md) != 0;
    self.state = LibVLCStateToMediaState(libvlc_media_get_state( p_md ));
812
}
813

814
- (void)fetchMetaInformationFromLibVLCWithType:(NSString *)metaType
815
{
816
    char * psz_value = libvlc_media_get_meta( p_md, [VLCMedia stringToMetaType:metaType] );
817
    NSString * newValue = psz_value ? @(psz_value) : nil;
818
    NSString * oldValue = [_metaDictionary valueForKey:metaType];
819 820
    free(psz_value);

821
    if (newValue != oldValue && !(oldValue && newValue && [oldValue compare:newValue] == NSOrderedSame)) {
822
        // Only fetch the art if needed. (ie, create the NSImage, if it was requested before)
823
        if (isArtFetched && [metaType isEqualToString:VLCMetaInformationArtworkURL]) {
824
            [NSThread detachNewThreadSelector:@selector(fetchMetaInformationForArtWorkWithURL:)
825
                                         toTarget:self
826 827
                                       withObject:newValue];
        }
828

829
        [_metaDictionary setValue:newValue forKeyPath:metaType];
830 831 832
    }
}

Pierre's avatar
Pierre committed
833
#if !TARGET_OS_IPHONE
834 835
- (void)fetchMetaInformationForArtWorkWithURL:(NSString *)anURL
{
836 837 838 839 840 841 842 843 844 845
    @autoreleasepool {
        NSImage * art = nil;

        if (anURL) {
            // Go ahead and load up the art work
            NSURL * artUrl = [NSURL URLWithString:[anURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
            // Don't attempt to fetch artwork from remote. Core will do that alone
            if ([artUrl isFileURL])
                art  = [[NSImage alloc] initWithContentsOfURL:artUrl];
        }
846

847 848 849
        // If anything was found, lets save it to the meta data dictionary
        [self performSelectorOnMainThread:@selector(setArtwork:) withObject:art waitUntilDone:NO];
    }
850 851
}

852 853
- (void)setArtwork:(NSImage *)art
{
854 855 856
    if (!art)
        [(NSMutableDictionary *)_metaDictionary removeObjectForKey:@"artwork"];
    else
James Dumay's avatar
James Dumay committed
857
        ((NSMutableDictionary *)_metaDictionary)[@"artwork"] = art;
858
}
Pierre's avatar
Pierre committed
859 860 861 862 863 864 865
#endif

- (void)parseIfNeeded
{
    if (![self isParsed])
        [self parse];
}
866

867
- (void)metaChanged:(NSString *)metaType
868
{
869
    [self fetchMetaInformationFromLibVLCWithType:metaType];
870

871 872
    if ([_delegate respondsToSelector:@selector(mediaMetaDataDidChange:)])
        [_delegate mediaMetaDataDidChange:self];
873
}
874 875 876

- (void)subItemAdded
{
877
    if (_subitems)
878 879
        return; /* Nothing to do */

880
    libvlc_media_list_t * p_mlist = libvlc_media_subitems( p_md );
881 882 883

    NSAssert( p_mlist, @"The mlist shouldn't be nil, we are receiving a subItemAdded");

884 885
    self.subitems = [VLCMediaList mediaListWithLibVLCMediaList:p_mlist];

886 887
    libvlc_media_list_release( p_mlist );
}
888

Pierre's avatar
Pierre committed
889 890 891 892 893 894 895
- (void)parsedChanged:(NSNumber *)isParsedAsNumber
{
    [self willChangeValueForKey:@"parsed"];
    isParsed = [isParsedAsNumber boolValue];
    [self didChangeValueForKey:@"parsed"];

    // FIXME: Probably don't even call this if there is no delegate.
896
    if (!_delegate || !isParsed)
Pierre's avatar
Pierre committed
897 898
        return;

899 900
    if ([_delegate respondsToSelector:@selector(mediaDidFinishParsing:)])
        [_delegate mediaDidFinishParsing:self];
Pierre's avatar
Pierre committed
901 902
}

903 904 905 906
- (void)setStateAsNumber:(NSNumber *)newStateAsNumber
{
    [self setState: [newStateAsNumber intValue]];
}
907

Pierre's avatar
Pierre committed
908 909 910 911 912 913
#if TARGET_OS_IPHONE
- (NSDictionary *)metaDictionary
{
    if (!areOthersMetaFetched) {
        areOthersMetaFetched = YES;

914 915 916 917 918 919
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtist];
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationAlbum];
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationDate];
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationGenre];
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTrackNumber];
920
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationNowPlaying];
Pierre's avatar
Pierre committed
921
    }
922
    if (!isArtURLFetched) {
Pierre's avatar
Pierre committed
923 924 925 926 927
        isArtURLFetched = YES;
        /* Force isArtURLFetched, that will trigger artwork download eventually
         * And all the other meta will be added through the libvlc event system */
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
    }
928
    return [NSDictionary dictionaryWithDictionary:_metaDictionary];
Pierre's avatar
Pierre committed
929 930 931 932
}

#else

933 934 935 936 937
- (NSDictionary *)metaDictionary
{
    return [NSDictionary dictionaryWithDictionary:_metaDictionary];
}

938 939
- (id)valueForKeyPath:(NSString *)keyPath
{
940
    if (!isArtFetched && [keyPath isEqualToString:@"metaDictionary.artwork"]) {
941
        isArtFetched = YES;
942 943
        /* Force the retrieval of the artwork now that someone asked for it */
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
944
    } else if (!areOthersMetaFetched && [keyPath hasPrefix:@"metaDictionary."]) {
945 946 947 948
        areOthersMetaFetched = YES;
        /* Force VLCMetaInformationTitle, that will trigger preparsing
         * And all the other meta will be added through the libvlc event system */
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationTitle];
949
    } else if (!isArtURLFetched && [keyPath hasPrefix:@"metaDictionary.artworkURL"]) {
950 951 952 953 954
        isArtURLFetched = YES;
        /* Force isArtURLFetched, that will trigger artwork download eventually
         * And all the other meta will be added through the libvlc event system */
        [self fetchMetaInformationFromLibVLCWithType: VLCMetaInformationArtworkURL];
    }
955 956
    return [super valueForKeyPath:keyPath];
}
Pierre's avatar
Pierre committed
957
#endif
958
@end
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989

/******************************************************************************
 * Implementation VLCMedia (LibVLCBridging)
 */
@implementation VLCMedia (LibVLCBridging)

+ (id)mediaWithLibVLCMediaDescriptor:(void *)md
{
    return [[VLCMedia alloc] initWithLibVLCMediaDescriptor:md];
}

+ (id)mediaWithMedia:(VLCMedia *)media andLibVLCOptions:(NSDictionary *)options
{
    libvlc_media_t * p_md;
    p_md = libvlc_media_duplicate([media libVLCMediaDescriptor]);

    for (NSString * key in [options allKeys]) {
        if (options[key] != [NSNull null])
            libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@=%@", key, options[key]] UTF8String]);
        else
            libvlc_media_add_option(p_md, [[NSString stringWithFormat:@"%@", key] UTF8String]);
    }
    return [VLCMedia mediaWithLibVLCMediaDescriptor:p_md];
}

- (id)initWithLibVLCMediaDescriptor:(void *)md
{
    if (self = [super init]) {
        libvlc_media_retain(md);
        p_md = md;

990
        _metaDictionary = [[NSMutableDictionary alloc] initWithCapacity:3];
991 992 993 994 995 996 997 998 999 1000 1001 1002 1003

        [self initInternalMediaDescriptor];
    }
    return self;
}

- (void *)libVLCMediaDescriptor
{
    return p_md;
}


@end