瀏覽代碼

playlist: add codec information

On iPad, right swipe on cell, on phone/pod, long touch to display
Felix Paul Kühne 10 年之前
父節點
當前提交
7319b0b428

+ 16 - 3
Resources/VLCFuturePlaylistCollectionViewCell.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="5053" systemVersion="12F45" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="6254" systemVersion="14B25" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none">
     <dependencies>
-        <deployment version="1792" defaultVersion="1552" identifier="iOS"/>
+        <deployment version="1792" identifier="iOS"/>
         <development version="5000" identifier="xcode"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3733"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
     </dependencies>
     <objects>
         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
@@ -56,6 +56,13 @@
                         <color key="highlightedColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                         <size key="shadowOffset" width="0.0" height="0.0"/>
                     </label>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Meta Data" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="lP6-D2-Oef">
+                        <rect key="frame" x="10" y="8" width="321" height="125"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                        <color key="textColor" cocoaTouchSystemColor="lightTextColor"/>
+                        <nil key="highlightedColor"/>
+                    </label>
                 </subviews>
                 <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
             </view>
@@ -64,6 +71,7 @@
                 <outlet property="folderIconView" destination="0uT-W6-UMC" id="coZ-4M-nCp"/>
                 <outlet property="isSelectedView" destination="pl6-iw-2ZI" id="1sX-gh-yiU"/>
                 <outlet property="mediaIsUnreadView" destination="ftR-Og-FOs" id="5aH-vE-E37"/>
+                <outlet property="metaDataLabel" destination="lP6-D2-Oef" id="OBz-fP-Wk1"/>
                 <outlet property="subtitleLabel" destination="jdp-lJ-hIF" id="YUa-G7-CMl"/>
                 <outlet property="thumbnailView" destination="31" id="46"/>
                 <outlet property="titleLabel" destination="4XD-oC-pqG" id="Te1-JA-An0"/>
@@ -75,4 +83,9 @@
         <image name="folderIcon.png" width="221" height="205"/>
         <image name="gradient-cell-ios7-ipad.png" width="2" height="190"/>
     </resources>
+    <simulatedMetricsContainer key="defaultSimulatedMetrics">
+        <simulatedStatusBarMetrics key="statusBar"/>
+        <simulatedOrientationMetrics key="orientation"/>
+        <simulatedScreenMetrics key="destination"/>
+    </simulatedMetricsContainer>
 </document>

+ 22 - 9
Resources/VLCFuturePlaylistTableViewCell.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="5053" systemVersion="12F45" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6254" systemVersion="14B25" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
     <dependencies>
-        <deployment version="1792" defaultVersion="1552" identifier="iOS"/>
+        <deployment version="1792" identifier="iOS"/>
         <development version="5000" identifier="xcode"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3733"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
     </dependencies>
     <objects>
         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
@@ -16,20 +16,20 @@
                 <autoresizingMask key="autoresizingMask"/>
                 <subviews>
                     <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" id="4">
-                        <rect key="frame" x="0.0" y="0.0" width="320" height="90"/>
+                        <rect key="frame" x="0.0" y="0.0" width="320" height="91"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
                     </imageView>
                     <imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="top" image="folderIcon.png" id="5iA-hI-rrr">
-                        <rect key="frame" x="0.0" y="35" width="320" height="55"/>
+                        <rect key="frame" x="0.0" y="35" width="320" height="56"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                     </imageView>
                     <imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="gradient-cell-ios7.png" id="22">
-                        <rect key="frame" x="0.0" y="0.0" width="320" height="90"/>
+                        <rect key="frame" x="0.0" y="0.0" width="320" height="91"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                     </imageView>
                     <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Title" lineBreakMode="wordWrap" baselineAdjustment="none" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" id="6">
-                        <rect key="frame" x="7" y="46" width="305" height="21"/>
+                        <rect key="frame" x="7" y="47" width="305" height="21"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
                         <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="15"/>
                         <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -37,7 +37,7 @@
                         <size key="shadowOffset" width="0.0" height="0.0"/>
                     </label>
                     <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="New" textAlignment="right" lineBreakMode="wordWrap" baselineAdjustment="none" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" id="sYw-l2-Tmv">
-                        <rect key="frame" x="222" y="61" width="93" height="26"/>
+                        <rect key="frame" x="222" y="62" width="93" height="26"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
                         <fontDescription key="fontDescription" type="system" pointSize="12"/>
                         <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -45,19 +45,27 @@
                         <size key="shadowOffset" width="0.0" height="0.0"/>
                     </label>
                     <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Subtitle — Subtitle" lineBreakMode="wordWrap" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="7">
-                        <rect key="frame" x="7" y="66" width="252" height="15"/>
+                        <rect key="frame" x="7" y="67" width="252" height="15"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
                         <fontDescription key="fontDescription" type="system" pointSize="12"/>
                         <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                         <color key="highlightedColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                         <size key="shadowOffset" width="0.0" height="0.0"/>
                     </label>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Meta Data" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3xi-uj-aAV">
+                        <rect key="frame" x="7" y="8" width="305" height="41.5"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                        <color key="textColor" cocoaTouchSystemColor="lightTextColor"/>
+                        <nil key="highlightedColor"/>
+                    </label>
                 </subviews>
             </tableViewCellContentView>
             <color key="backgroundColor" white="0.12" alpha="1" colorSpace="calibratedWhite"/>
             <connections>
                 <outlet property="folderIconView" destination="5iA-hI-rrr" id="K2w-2u-dEl"/>
                 <outlet property="mediaIsUnreadView" destination="sYw-l2-Tmv" id="us6-sJ-NVp"/>
+                <outlet property="metaDataLabel" destination="3xi-uj-aAV" id="BXL-7W-XC9"/>
                 <outlet property="subtitleLabel" destination="7" id="8"/>
                 <outlet property="thumbnailView" destination="4" id="9"/>
                 <outlet property="titleLabel" destination="6" id="10"/>
@@ -68,4 +76,9 @@
         <image name="folderIcon.png" width="120" height="112"/>
         <image name="gradient-cell-ios7.png" width="2" height="92"/>
     </resources>
+    <simulatedMetricsContainer key="defaultSimulatedMetrics">
+        <simulatedStatusBarMetrics key="statusBar"/>
+        <simulatedOrientationMetrics key="orientation"/>
+        <simulatedScreenMetrics key="destination" type="retina4"/>
+    </simulatedMetricsContainer>
 </document>

+ 4 - 1
Sources/VLCPlaylistCollectionViewCell.h

@@ -2,7 +2,7 @@
  * VLCPlaylistCollectionViewCell.h
  * VLC for iOS
  *****************************************************************************
- * Copyright (c) 2013 VideoLAN. All rights reserved.
+ * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
  * $Id$
  *
  * Authors: Felix Paul Kühne <fkuehne # videolan.org>
@@ -24,13 +24,16 @@
 @property (nonatomic, strong) IBOutlet UIView *mediaIsUnreadView;
 @property (nonatomic, strong) IBOutlet UIImageView *isSelectedView;
 @property (nonatomic, strong) IBOutlet UIImageView *folderIconView;
+@property (nonatomic, strong) IBOutlet UILabel *metaDataLabel;
 
 @property (nonatomic, retain) MLFile *mediaObject;
 
 @property (nonatomic, weak) UICollectionView *collectionView;
+@property (readonly) BOOL showsMetaData;
 
 - (void)setEditing:(BOOL)editing animated:(BOOL)animated;
 - (void)selectionUpdate;
 - (void)shake:(BOOL)shake;
+- (void)showMetadata:(BOOL)showMeta;
 
 @end

+ 151 - 1
Sources/VLCPlaylistCollectionViewCell.m

@@ -2,7 +2,7 @@
  * VLCPlaylistCollectionViewCell.m
  * VLC for iOS
  *****************************************************************************
- * Copyright (c) 2013 VideoLAN. All rights reserved.
+ * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
  * $Id$
  *
  * Authors: Felix Paul Kühne <fkuehne # videolan.org>
@@ -41,6 +41,7 @@
         _checkboxEmptyImage = [UIImage imageNamed:@"checkbox-legacy-empty"];
         _checkboxImage = [UIImage imageNamed:@"checkbox-legacy"];
     }
+    self.metaDataLabel.hidden = YES;
 }
 
 - (void)setEditing:(BOOL)editing animated:(BOOL)animated
@@ -345,4 +346,153 @@
     }
 }
 
+- (void)showMetadata:(BOOL)showMeta
+{
+    if (showMeta) {
+        NSMutableString *mediaInfo = [[NSMutableString alloc] init];
+
+        MLFile *theFile;
+        if ([self.mediaObject isKindOfClass:[MLFile class]])
+            theFile = self.mediaObject;
+        else if ([self.mediaObject isKindOfClass:[MLShowEpisode class]])
+            theFile = [[(MLShowEpisode *)self.mediaObject files]anyObject];
+        else if ([self.mediaObject isKindOfClass:[MLAlbumTrack class]])
+            theFile = [[(MLAlbumTrack *)self.mediaObject files]anyObject];
+
+        if (!theFile) {
+            self.metaDataLabel.hidden = YES;
+            return;
+        }
+
+        NSMutableArray *videoTracks = [[NSMutableArray alloc] init];
+        NSMutableArray *audioTracks = [[NSMutableArray alloc] init];
+        NSMutableArray *spuTracks = [[NSMutableArray alloc] init];
+        NSArray *tracks = [[theFile tracks] allObjects];
+        NSUInteger trackCount = tracks.count;
+
+        for (NSUInteger x = 0; x < trackCount; x++) {
+            NSManagedObject *track = tracks[x];
+            NSString *trackEntityName = [[track entity] name];
+            if ([trackEntityName isEqualToString:@"VideoTrackInformation"])
+                [videoTracks addObject:track];
+            else if ([trackEntityName isEqualToString:@"AudioTrackInformation"])
+                [audioTracks addObject:track];
+            else if ([trackEntityName isEqualToString:@"SubtitlesTrackInformation"])
+                [spuTracks addObject:track];
+        }
+
+        /* print video info */
+        trackCount = videoTracks.count;
+        if (trackCount != 1)
+            [mediaInfo appendFormat:@"%lu video tracks", (unsigned long)trackCount];
+        else
+            [mediaInfo appendString:@"1 video track"];
+
+        if (trackCount > 0) {
+            [mediaInfo appendString:@" ("];
+            for (NSUInteger x = 0; x < trackCount; x++) {
+                int fourcc = [[videoTracks[x] valueForKey:@"codec"] intValue];
+                if (x != 0)
+                    [mediaInfo appendFormat:@", %4.4s", (char *)&fourcc];
+                else
+                    [mediaInfo appendFormat:@"%4.4s", (char *)&fourcc];
+            }
+            [mediaInfo appendString:@")"];
+        }
+        [mediaInfo appendString:@"\n\n"];
+
+        /* print audio info */
+        trackCount = audioTracks.count;
+        if (trackCount != 1)
+            [mediaInfo appendFormat:@"%lu audio tracks", (unsigned long)trackCount];
+        else
+            [mediaInfo appendString:@"1 audio track"];
+
+        if (trackCount > 0) {
+            [mediaInfo appendString:@":\n"];
+            for (NSUInteger x = 0; x < trackCount; x++) {
+                NSManagedObject *track = audioTracks[x];
+                int fourcc = [[track valueForKey:@"codec"] intValue];
+                if (x != 0)
+                    [mediaInfo appendFormat:@", %4.4s", (char *)&fourcc];
+                else
+                    [mediaInfo appendFormat:@"%4.4s", (char *)&fourcc];
+
+                int channelNumber = [[track valueForKey:@"channelsNumber"] intValue];
+                NSString *language = [track valueForKey:@"language"];
+                int bitrate = [[track valueForKey:@"bitrate"] intValue];
+                if (channelNumber != 0 || language != nil || bitrate > 0) {
+                    [mediaInfo appendString:@" ["];
+
+                    if (bitrate > 0)
+                        [mediaInfo appendFormat:@"%i kbit/s", bitrate / 1024];
+
+                    if (channelNumber > 0) {
+                        if (bitrate > 0)
+                            [mediaInfo appendString:@", "];
+
+                        if (channelNumber == 1)
+                            [mediaInfo appendString:@"MONO"];
+                        else if (channelNumber == 2)
+                            [mediaInfo appendString:@"STEREO"];
+                        else
+                            [mediaInfo appendString:@"MULTI-CHANNEL"];
+                    }
+
+                    if (language != nil) {
+                        if (channelNumber > 0 || bitrate > 0)
+                            [mediaInfo appendString:@", "];
+
+                        [mediaInfo appendString:[language uppercaseString]];
+                    }
+
+                    [mediaInfo appendString:@"]"];
+                }
+            }
+        }
+        [mediaInfo appendString:@"\n\n"];
+
+        /* SPU */
+        trackCount = spuTracks.count;
+        if (trackCount != 1)
+            [mediaInfo appendFormat:@"%lu subtitles tracks", (unsigned long)trackCount];
+        else
+            [mediaInfo appendString:@"1 subtitles track"];
+
+        if (trackCount > 0) {
+            [mediaInfo appendString:@" ("];
+            for (NSUInteger x = 0; x < trackCount; x++) {
+                NSString *language = [spuTracks[x] valueForKey:@"language"];
+
+                if (language) {
+                    if (x != 0)
+                        [mediaInfo appendFormat:@", %@", [language uppercaseString]];
+                    else
+                        [mediaInfo appendString:[language uppercaseString]];
+                }
+            }
+            [mediaInfo appendString:@")"];
+        }
+
+        self.metaDataLabel.text = mediaInfo;
+        videoTracks = audioTracks = spuTracks = nil;
+    }
+
+    void (^animationBlock)() = ^() {
+        self.metaDataLabel.hidden = !showMeta;
+    };
+
+    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
+        self.metaDataLabel.hidden = !showMeta;
+    };
+
+    NSTimeInterval animationDuration = .2;
+    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
+}
+
+- (BOOL)showsMetaData
+{
+    return !self.metaDataLabel.hidden;
+}
+
 @end

+ 6 - 1
Sources/VLCPlaylistTableViewCell.h

@@ -2,7 +2,7 @@
  * VLCPlaylistTableViewCell.h
  * VLC for iOS
  *****************************************************************************
- * Copyright (c) 2013 VideoLAN. All rights reserved.
+ * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
  * $Id$
  *
  * Authors: Felix Paul Kühne <fkuehne # videolan.org>
@@ -18,14 +18,19 @@
 
 @property (nonatomic, strong) IBOutlet UILabel *titleLabel;
 @property (nonatomic, strong) IBOutlet UILabel *subtitleLabel;
+@property (nonatomic, strong) IBOutlet UILabel *metaDataLabel;
 @property (nonatomic, strong) IBOutlet UIImageView *thumbnailView;
 @property (nonatomic, strong) IBOutlet VLCLinearProgressIndicator *progressIndicator;
 @property (nonatomic, strong) IBOutlet UIView *mediaIsUnreadView;
 @property (nonatomic, strong) IBOutlet UIImageView *folderIconView;
 
+@property (readonly) BOOL isExpanded;
+
 @property (nonatomic, strong) NSManagedObject *mediaObject;
 
 + (VLCPlaylistTableViewCell *)cellWithReuseIdentifier:(NSString *)ident;
 + (CGFloat)heightOfCell;
 
+- (void)collapsWithAnimation:(BOOL)animate;
+
 @end

+ 168 - 4
Sources/VLCPlaylistTableViewCell.m

@@ -2,7 +2,7 @@
  * VLCPlaylistTableViewCell.m
  * VLC for iOS
  *****************************************************************************
- * Copyright (c) 2013 VideoLAN. All rights reserved.
+ * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
  * $Id$
  *
  * Authors: Felix Paul Kühne <fkuehne # videolan.org>
@@ -42,10 +42,17 @@
     NSAssert([[nibContentArray lastObject] isKindOfClass:[VLCPlaylistTableViewCell class]], @"meh meh");
     VLCPlaylistTableViewCell *cell = (VLCPlaylistTableViewCell *)[nibContentArray lastObject];
     cell.multipleSelectionBackgroundView = [[UIView alloc] initWithFrame:cell.frame];
+    cell.metaDataLabel.hidden = YES;
 
     return cell;
 }
 
+- (void)setNeedsDisplay
+{
+    [self collapsWithAnimation:NO];
+    [super setNeedsDisplay];
+}
+
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
 {
     [self _updatedDisplayedInformationForKeyPath:keyPath];
@@ -125,6 +132,7 @@
         [self _addObserver];
     }
 
+    [self collapsWithAnimation:NO];
     [self _updatedDisplayedInformationForKeyPath:nil];
 }
 
@@ -330,18 +338,174 @@
 
 - (void)longTouchGestureAction:(UIGestureRecognizer *)recognizer
 {
+    _isExpanded = YES;
     CGRect frame = self.frame;
-    if (frame.size.height > 90.)
+    frame.size.height = 180.;
+    BOOL metaHidden = NO;
+
+    MLFile *theFile;
+    if ([self.mediaObject isKindOfClass:[MLFile class]])
+        theFile = (MLFile *)self.mediaObject;
+    else if ([self.mediaObject isKindOfClass:[MLShowEpisode class]])
+        theFile = [[(MLShowEpisode *)self.mediaObject files]anyObject];
+    else if ([self.mediaObject isKindOfClass:[MLAlbumTrack class]])
+        theFile = [[(MLAlbumTrack *)self.mediaObject files]anyObject];
+
+    NSMutableString *mediaInfo = [[NSMutableString alloc] init];
+
+    if (theFile) {
+        NSMutableArray *videoTracks = [[NSMutableArray alloc] init];
+        NSMutableArray *audioTracks = [[NSMutableArray alloc] init];
+        NSMutableArray *spuTracks = [[NSMutableArray alloc] init];
+        NSArray *tracks = [[theFile tracks] allObjects];
+        NSUInteger trackCount = tracks.count;
+
+        for (NSUInteger x = 0; x < trackCount; x++) {
+            NSManagedObject *track = tracks[x];
+            NSString *trackEntityName = [[track entity] name];
+            if ([trackEntityName isEqualToString:@"VideoTrackInformation"])
+                [videoTracks addObject:track];
+            else if ([trackEntityName isEqualToString:@"AudioTrackInformation"])
+                [audioTracks addObject:track];
+            else if ([trackEntityName isEqualToString:@"SubtitlesTrackInformation"])
+                [spuTracks addObject:track];
+        }
+
+        /* print video info */
+        trackCount = videoTracks.count;
+        if (trackCount != 1)
+            [mediaInfo appendFormat:@"%lu video tracks", (unsigned long)trackCount];
+        else
+            [mediaInfo appendString:@"1 video track"];
+
+        if (trackCount > 0) {
+            [mediaInfo appendString:@" ("];
+            for (NSUInteger x = 0; x < trackCount; x++) {
+                int fourcc = [[videoTracks[x] valueForKey:@"codec"] intValue];
+                if (x != 0)
+                    [mediaInfo appendFormat:@", %4.4s", (char *)&fourcc];
+                else
+                    [mediaInfo appendFormat:@"%4.4s", (char *)&fourcc];
+            }
+            [mediaInfo appendString:@")"];
+        }
+        [mediaInfo appendString:@"\n\n"];
+
+        /* print audio info */
+        trackCount = audioTracks.count;
+        if (trackCount != 1)
+            [mediaInfo appendFormat:@"%lu audio tracks", (unsigned long)trackCount];
+        else
+            [mediaInfo appendString:@"1 audio track"];
+
+        if (trackCount > 0) {
+            [mediaInfo appendString:@":\n"];
+            for (NSUInteger x = 0; x < trackCount; x++) {
+                NSManagedObject *track = audioTracks[x];
+                int fourcc = [[track valueForKey:@"codec"] intValue];
+                if (x != 0)
+                    [mediaInfo appendFormat:@", %4.4s", (char *)&fourcc];
+                else
+                    [mediaInfo appendFormat:@"%4.4s", (char *)&fourcc];
+
+                int channelNumber = [[track valueForKey:@"channelsNumber"] intValue];
+                NSString *language = [track valueForKey:@"language"];
+                int bitrate = [[track valueForKey:@"bitrate"] intValue];
+                if (channelNumber != 0 || language != nil || bitrate > 0) {
+                    [mediaInfo appendString:@" ["];
+
+                    if (bitrate > 0)
+                        [mediaInfo appendFormat:@"%i kbit/s", bitrate / 1024];
+
+                    if (channelNumber > 0) {
+                        if (bitrate > 0)
+                            [mediaInfo appendString:@", "];
+
+                        if (channelNumber == 1)
+                            [mediaInfo appendString:@"MONO"];
+                        else if (channelNumber == 2)
+                            [mediaInfo appendString:@"STEREO"];
+                        else
+                            [mediaInfo appendString:@"MULTI-CHANNEL"];
+                    }
+
+                    if (language != nil) {
+                        if (channelNumber > 0 || bitrate > 0)
+                            [mediaInfo appendString:@", "];
+
+                        [mediaInfo appendString:[language uppercaseString]];
+                    }
+
+                    [mediaInfo appendString:@"]"];
+                }
+            }
+        }
+        [mediaInfo appendString:@"\n\n"];
+
+        /* SPU */
+        trackCount = spuTracks.count;
+        if (trackCount != 1)
+            [mediaInfo appendFormat:@"%lu subtitles tracks", (unsigned long)trackCount];
+        else
+            [mediaInfo appendString:@"1 subtitles track"];
+
+        if (trackCount > 0) {
+            [mediaInfo appendString:@" ("];
+            for (NSUInteger x = 0; x < trackCount; x++) {
+                NSString *language = [spuTracks[x] valueForKey:@"language"];
+
+                if (language) {
+                    if (x != 0)
+                        [mediaInfo appendFormat:@", %@", [language uppercaseString]];
+                    else
+                        [mediaInfo appendString:[language uppercaseString]];
+                }
+            }
+            [mediaInfo appendString:@")"];
+        }
+
+        self.metaDataLabel.text = mediaInfo;
+        videoTracks = audioTracks = spuTracks = nil;
+    } else
+        self.metaDataLabel.text = @"";
+
+    void (^animationBlock)() = ^() {
+        self.frame = frame;
+        self.metaDataLabel.hidden = metaHidden;
+    };
+
+    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
+        self.frame = frame;
+        self.metaDataLabel.hidden = metaHidden;
+    };
+
+    NSTimeInterval animationDuration = .2;
+    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
+}
+
+- (void)collapsWithAnimation:(BOOL)animate
+{
+    _isExpanded = NO;
+    if (!animate) {
+        self.metaDataLabel.hidden = YES;
+        CGRect frame = self.frame;
         frame.size.height = 90.;
-    else if (recognizer.state == UIGestureRecognizerStateBegan)
-        frame.size.height = 180;
+        self.frame = frame;
+        return;
+    }
+
+    CGRect frame = self.frame;
+    frame.size.height = 90.;
+    BOOL metaHidden = YES;
 
     void (^animationBlock)() = ^() {
         self.frame = frame;
+        self.metaDataLabel.hidden = metaHidden;
     };
 
     void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
         self.frame = frame;
+        self.metaDataLabel.hidden = metaHidden;
     };
 
     NSTimeInterval animationDuration = .2;

+ 38 - 3
Sources/VLCPlaylistViewController.m

@@ -2,7 +2,7 @@
  * VLCPlaylistViewController.m
  * VLC for iOS
  *****************************************************************************
- * Copyright (c) 2013-2014 VideoLAN. All rights reserved.
+ * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
  * $Id$
  *
  * Authors: Felix Paul Kühne <fkuehne # videolan.org>
@@ -571,8 +571,10 @@ static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutoria
     VLCPlaylistTableViewCell *cell = (VLCPlaylistTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
     if (cell == nil)
         cell = [VLCPlaylistTableViewCell cellWithReuseIdentifier:CellIdentifier];
+    else
+        [cell collapsWithAnimation:NO];
 
-    UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightGestureAction:)];
+    UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightOnTableViewCellGestureAction:)];
     [swipeRight setDirection:(UISwipeGestureRecognizerDirectionRight)];
     [cell addGestureRecognizer:swipeRight];
 
@@ -627,6 +629,18 @@ static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutoria
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
 {
+    if ([(VLCPlaylistTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] isExpanded]) {
+        [(VLCPlaylistTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] collapsWithAnimation:YES];
+        return;
+    }
+
+    NSArray *visibleCells = [tableView visibleCells];
+    NSUInteger cellCount = visibleCells.count;
+    for (NSUInteger x = 0; x < cellCount; x++) {
+        if ([visibleCells[x] isExpanded])
+            [visibleCells[x] collapsWithAnimation:NO];
+    }
+
     if (tableView.isEditing) {
         if (_libraryMode == VLCLibraryModeCreateFolder) {
             _folderObject = _foundMedia[indexPath.row];
@@ -690,7 +704,7 @@ static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutoria
 }
 
 #pragma mark - Gesture Action
-- (void)swipeRightGestureAction:(UIGestureRecognizer *)recognizer
+- (void)swipeRightOnTableViewCellGestureAction:(UIGestureRecognizer *)recognizer
 {
     if ([[self.editButtonItem title] isEqualToString:NSLocalizedString(@"BUTTON_CANCEL", nil)])
         [self setEditing:NO animated:YES];
@@ -704,6 +718,14 @@ static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutoria
     }
 }
 
+- (void)swipeRightOnCollectionViewCellGestureAction:(UIGestureRecognizer *)recognizer
+{
+    NSLog(@"swipeRightOnCollectionViewCellGestureAction");
+    NSIndexPath *path = [self.collectionView indexPathForItemAtPoint:[recognizer locationInView:self.collectionView]];
+    VLCPlaylistCollectionViewCell *cell = (VLCPlaylistCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:path];
+    [cell showMetadata:!cell.showsMetaData];
+}
+
 - (void)tapTwiceGestureAction:(UIGestureRecognizer *)recognizer
 {
     _searchBar.hidden = !_searchBar.hidden;
@@ -734,6 +756,10 @@ static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutoria
 
     [cell setEditing:self.editing animated:NO];
 
+    UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightOnCollectionViewCellGestureAction:)];
+    [swipeRight setDirection:(UISwipeGestureRecognizerDirectionRight)];
+    [cell addGestureRecognizer:swipeRight];
+
     return cell;
 }
 
@@ -772,6 +798,9 @@ static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutoria
 
 - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
 {
+    NSArray *visibleCells = [collectionView visibleCells];
+    NSUInteger cellCount = visibleCells.count;
+
     if (self.editing) {
         if (_libraryMode == VLCLibraryModeCreateFolder) {
             _folderObject = _foundMedia[indexPath.item];
@@ -784,6 +813,12 @@ static NSString *kDisplayedFirstSteps = @"Did we display the first steps tutoria
         return;
     }
 
+    for (NSUInteger x = 0; x < cellCount; x++) {
+        VLCPlaylistCollectionViewCell *cell = visibleCells[x];
+        if ([cell showsMetaData])
+            [cell showMetadata:NO];
+    }
+
     NSManagedObject *selectedObject = _foundMedia[indexPath.row];
     if ([selectedObject isKindOfClass:[MLAlbumTrack class]]) {
         VLCMediaList *list;