Commit 6bad00fc authored by Felix Paul Kühne's avatar Felix Paul Kühne

ATV: re-write audio playback appearance from scratch

parent 278de66d
......@@ -31,6 +31,4 @@ pod 'box-ios-sdk-v2', :git => 'git://github.com/fkuehne/box-ios-sdk-v2.git' #has
pod 'upnpx', '~>1.3.6'
pod 'CocoaHTTPServer', :git => 'git://github.com/fkuehne/CocoaHTTPServer.git' # has our fixes
pod 'MetaDataFetcherKit', '~>0.1.5'
pod 'MSWeakTimer', :git => 'git://github.com/fkuehne/MSWeakTimer.git' #has tvOS support added
pod 'JSAnimatedImagesView', :git => 'git://github.com/fkuehne/JSAnimatedImagesView.git' #has tvOS support added
end
......@@ -20,10 +20,8 @@ PODS:
- CocoaHTTPServer (2.3)
- HockeySDK (3.6.4)
- InAppSettingsKit (2.2.2)
- JSAnimatedImagesView (1.0.1)
- MetaDataFetcherKit (0.1.5):
- AFNetworking (= 3.0.0-beta.1)
- MSWeakTimer (1.1.1)
- OBSlider (1.1.0)
- RESideMenu (4.0.7)
- SSKeychain (1.2.3)
......@@ -35,9 +33,7 @@ DEPENDENCIES:
- HockeySDK (~> 3.6.4)
- InAppSettingsKit (from `git://github.com/fkuehne/InAppSettingsKit.git`, commit
`415ea6bb`)
- JSAnimatedImagesView (from `git://github.com/fkuehne/JSAnimatedImagesView.git`)
- MetaDataFetcherKit (~> 0.1.5)
- MSWeakTimer (from `git://github.com/fkuehne/MSWeakTimer.git`)
- OBSlider (= 1.1.0)
- RESideMenu (~> 4.0.7)
- SSKeychain (from `git://github.com/fkuehne/sskeychain.git`)
......@@ -51,10 +47,6 @@ EXTERNAL SOURCES:
InAppSettingsKit:
:commit: 415ea6bb
:git: git://github.com/fkuehne/InAppSettingsKit.git
JSAnimatedImagesView:
:git: git://github.com/fkuehne/JSAnimatedImagesView.git
MSWeakTimer:
:git: git://github.com/fkuehne/MSWeakTimer.git
SSKeychain:
:git: git://github.com/fkuehne/sskeychain.git
......@@ -68,12 +60,6 @@ CHECKOUT OPTIONS:
InAppSettingsKit:
:commit: 415ea6bb
:git: git://github.com/fkuehne/InAppSettingsKit.git
JSAnimatedImagesView:
:commit: fd203bc32fedccc4589afb72974b7b1a9edf79f3
:git: git://github.com/fkuehne/JSAnimatedImagesView.git
MSWeakTimer:
:commit: 12a962bf2feaf219a044ccfa06b380f5b3ab26ee
:git: git://github.com/fkuehne/MSWeakTimer.git
SSKeychain:
:commit: a8e9b21f96adf1ec296e51778ef137f0ea3bd078
:git: git://github.com/fkuehne/sskeychain.git
......@@ -84,9 +70,7 @@ SPEC CHECKSUMS:
CocoaHTTPServer: 07df8b05a8bde406fe367d22c90a24a2fd4ca49f
HockeySDK: c07cdd580296737edcd0963e292c19885a53f563
InAppSettingsKit: 76d5cfbaa3e3f8aa53fe3628516da7eb1aa6a5cb
JSAnimatedImagesView: ad2d21873490d3e675cbe8e49954dfd9495bdcec
MetaDataFetcherKit: be17957a9498740bc34329a7e8295e930800884a
MSWeakTimer: 08188e0ae3f8eafcc31b95639565543c3c03cda6
OBSlider: 490f108007bfdd5414a38650b211fe403a95b8a0
RESideMenu: f24c508404b49c667344c54aba7e590883533958
SSKeychain: 3f42991739c6c60a9cf1bbd4dff6c0d3694bcf3d
......
......@@ -18,5 +18,6 @@
@property (nonatomic) NSURLSessionDataTask *downloadTask;
- (void)setImageWithURL:(NSURL *)url;
- (void)cancelLoading;
@property (nonatomic, readwrite) BOOL animateImageSetting;
@end
......@@ -59,7 +59,12 @@ static NSCache *sharedImageCache = nil;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if ([strongSelf.downloadTask.originalRequest.URL isEqual:url]) {
strongSelf.image = image;
if (strongSelf.animateImageSetting) {
[UIView animateWithDuration:.3 animations:^{
strongSelf.image = image;
}];
} else
strongSelf.image = image;
strongSelf.downloadTask = nil;
}
}];
......
......@@ -14,6 +14,8 @@
#import "VLCPlaybackController.h"
#import "VLCTransportBar.h"
@class VLCNetworkImageView;
@interface VLCFullscreenMovieTVViewController : UIViewController <VLCPlaybackControllerDelegate>
@property (readwrite, nonatomic, weak) IBOutlet UIView *movieView;
......@@ -25,6 +27,15 @@
@property (readwrite, nonatomic, weak) IBOutlet UIActivityIndicatorView *activityIndicator;
@property (readwrite, nonatomic, weak) IBOutlet UIView *dimmingView;
@property (readwrite, nonatomic, weak) IBOutlet UIView *audioView;
@property (readwrite, nonatomic, weak) IBOutlet VLCNetworkImageView *audioLargeBackgroundImageView;
@property (readwrite, nonatomic, weak) IBOutlet UIVisualEffectView *audioVisualEffectView;
@property (readwrite, nonatomic, weak) IBOutlet VLCNetworkImageView *audioArtworkImageView;
@property (readwrite, nonatomic, weak) IBOutlet UILabel *audioTitleLabel;
@property (readwrite, nonatomic, weak) IBOutlet UILabel *audioArtistLabel;
@property (readwrite, nonatomic, weak) IBOutlet UILabel *audioAlbumNameLabel;
@property (readwrite, nonatomic, weak) IBOutlet UITextView *audioDescriptionTextView;
+ (instancetype) fullscreenMovieTVViewController;
@end
......@@ -15,8 +15,8 @@
#import "VLCIRTVTapGestureRecognizer.h"
#import "VLCHTTPUploaderController.h"
#import "VLCSiriRemoteGestureRecognizer.h"
#import "JSAnimatedImagesView.h"
#import "MetaDataFetcherKit.h"
#import "VLCNetworkImageView.h"
typedef NS_ENUM(NSInteger, VLCPlayerScanState)
{
......@@ -28,16 +28,16 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
@interface VLCFullscreenMovieTVViewController (UIViewControllerTransitioningDelegate) <UIViewControllerTransitioningDelegate, UIGestureRecognizerDelegate>
@end
@interface VLCFullscreenMovieTVViewController () <JSAnimatedImagesViewDataSource, MDFHatchetFetcherDataRecipient>
@interface VLCFullscreenMovieTVViewController () <MDFHatchetFetcherDataRecipient>
@property (nonatomic) CADisplayLink *displayLink;
@property (nonatomic) NSTimer *audioDescriptionScrollTimer;
@property (nonatomic) NSTimer *hidePlaybackControlsViewAfterDeleayTimer;
@property (nonatomic) VLCPlaybackInfoTVViewController *infoViewController;
@property (nonatomic) NSNumber *scanSavedPlaybackRate;
@property (nonatomic) VLCPlayerScanState scanState;
@property (nonatomic) JSAnimatedImagesView *animatedImageView;
@property (nonatomic) MDFHatchetFetcher *audioMetaDataFetcher;
@property (nonatomic) NSString *lastArtist;
@property (nonatomic) NSMutableArray *audioImagesArray;
@end
......@@ -75,7 +75,6 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
self.bufferingLabel.text = NSLocalizedString(@"PLEASE_WAIT", nil);
// Panning and Swiping
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
......@@ -110,11 +109,9 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
siriArrowRecognizer.delegate = self;
[self.view addGestureRecognizer:siriArrowRecognizer];
JSAnimatedImagesView *animatedImageView = [[JSAnimatedImagesView alloc] initWithFrame:self.view.frame];
animatedImageView.dataSource = self;
animatedImageView.backgroundColor = [UIColor blackColor];
animatedImageView.timePerImage = 20;
self.animatedImageView = animatedImageView;
self.audioView.hidden = YES;
self.audioArtworkImageView.animateImageSetting = YES;
self.audioLargeBackgroundImageView.animateImageSetting = YES;
}
- (void)didReceiveMemoryWarning
......@@ -155,6 +152,8 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
[vpc stopPlayback];
[self stopAudioDescriptionAnimation];
[super viewWillDisappear:animated];
/* clean caches in case remote playback was used
......@@ -614,6 +613,11 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
[UIView animateWithDuration:.3 animations:^{
self.bufferingLabel.hidden = YES;
}];
if (controller.audioOnlyPlaybackSession) {
[UIView animateWithDuration:.3 animations:^{
self.audioView.hidden = NO;
}];
}
}
}
......@@ -626,10 +630,10 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
{
self.titleLabel.text = title;
JSAnimatedImagesView *animatedImageView = self.animatedImageView;
if (audioOnly) {
if (!self.audioImagesArray)
self.audioImagesArray = [NSMutableArray array];
self.audioArtworkImageView.image = nil;
self.audioDescriptionTextView.text = nil;
[self stopAudioDescriptionAnimation];
if (!self.audioMetaDataFetcher) {
self.audioMetaDataFetcher = [[MDFHatchetFetcher alloc] init];
......@@ -639,24 +643,39 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
[self.audioMetaDataFetcher cancelAllRequests];
if (artist != nil && album != nil) {
[UIView animateWithDuration:.3 animations:^{
self.audioArtistLabel.text = artist;
self.audioAlbumNameLabel.text = album;
}];
APLog(@"Audio-only track meta changed, tracing artist '%@' and album '%@'", artist, album);
} else if (artist != nil) {
[UIView animateWithDuration:.3 animations:^{
self.audioArtistLabel.text = artist;
self.audioAlbumNameLabel.text = nil;
}];
APLog(@"Audio-only track meta changed, tracing artist '%@'", artist);
} else if (title != nil) {
NSRange deviderRange = [title rangeOfString:@" - "];
if (deviderRange.length != 0) { // for radio stations, all we have is "ARTIST - TITLE"
title = [title substringToIndex:deviderRange.location];
artist = [title substringToIndex:deviderRange.location];
title = [title substringFromIndex:deviderRange.location + deviderRange.length];
}
APLog(@"Audio-only track meta changed, tracing artist '%@'", title);
artist = title;
APLog(@"Audio-only track meta changed, tracing artist '%@'", artist);
[UIView animateWithDuration:.3 animations:^{
self.audioArtistLabel.text = artist;
self.audioTitleLabel.text = nil;
self.audioAlbumNameLabel.text = nil;
}];
}
if (![self.lastArtist isEqualToString:artist]) {
@synchronized(self.audioImagesArray) {
[self.animatedImageView stopAnimating];
[self.audioImagesArray removeAllObjects];
}
UIImage *dummyImage = [UIImage imageNamed:@"about-app-icon"];
[UIView animateWithDuration:.3 animations:^{
self.audioArtworkImageView.image = dummyImage;
self.audioLargeBackgroundImageView.image = dummyImage;
}];
}
self.lastArtist = artist;
self.audioTitleLabel.text = title;
if (artist != nil) {
if (album != nil) {
......@@ -664,15 +683,11 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
} else
[self.audioMetaDataFetcher searchForArtist:artist];
}
[self performSelectorOnMainThread:@selector(showAnimatedImagesView) withObject:nil waitUntilDone:NO];
} else if (animatedImageView.superview != nil) {
} else if (!self.audioView.hidden) {
[self.audioMetaDataFetcher cancelAllRequests];
[animatedImageView stopAnimating];
[animatedImageView removeFromSuperview];
if (self.audioImagesArray) {
[self.audioImagesArray removeAllObjects];
}
self.audioView.hidden = YES;
self.audioArtworkImageView.image = nil;
[self.audioLargeBackgroundImageView stopAnimating];
}
}
......@@ -705,90 +720,49 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
return YES;
}
#pragma mark - slide show view data source
- (NSUInteger)animatedImagesNumberOfImages:(JSAnimatedImagesView *)animatedImagesView
{
NSUInteger retValue;
@synchronized(self.audioImagesArray) {
retValue = self.audioImagesArray.count;
}
return retValue;
}
- (UIImage *)animatedImagesView:(JSAnimatedImagesView *)animatedImagesView imageAtIndex:(NSUInteger)index
{
UIImage *retImage;
@synchronized(self.audioImagesArray) {
if (index < self.audioImagesArray.count) {
retImage = self.audioImagesArray[index];
}
}
return retImage;
}
- (void)showAnimatedImagesView
{
NSUInteger imageCount;
JSAnimatedImagesView *animatedImageView = self.animatedImageView;
@synchronized(self.audioImagesArray) {
imageCount = self.audioImagesArray.count;
}
if (animatedImageView.superview == nil && imageCount > 1) {
[animatedImageView reloadData];
animatedImageView.frame = self.view.frame;
[self.view addSubview:animatedImageView];
} else if (imageCount > 1) {
[animatedImageView reloadData];
[animatedImageView startAnimating];
}
}
#pragma mark - meta data recipient
- (void)MDFHatchetFetcher:(MDFHatchetFetcher *)aFetcher didFindAlbum:(MDFMusicAlbum *)album forArtistName:(NSString *)artistName
- (void)MDFHatchetFetcher:(MDFHatchetFetcher * _Nonnull)aFetcher
didFindAlbum:(MDFMusicAlbum * _Nonnull)album
byArtist:(MDFArtist * _Nullable)artist
forSearchRequest:(NSString *)searchRequest
{
NSString *artworkImageURLString = album.artworkImage;
if (artworkImageURLString != nil)
[self fetchAudioImage:[NSURL URLWithString:artworkImageURLString]];
NSArray *imageURLStrings = album.largeSizedArtistImages;
NSUInteger imageCount = imageURLStrings.count;
NSUInteger totalImageCount;
@synchronized(self.audioImagesArray) {
totalImageCount = self.audioImagesArray.count;
/* we have no match */
if (!artist) {
[self _simplifyMetaDataSearchString:searchRequest];
return;
}
self.audioArtistLabel.text = artist.name;
if (artist.biography) {
[UIView animateWithDuration:.3 animations:^{
self.audioDescriptionTextView.text = artist.biography;
}];
[self startAudioDescriptionAnimation];
} else
[self stopAudioDescriptionAnimation];
/* reasonably limit the number of images we fetch */
if (imageCount > 10)
imageCount = 10;
totalImageCount += imageCount;
for (NSUInteger x = 0; x < imageCount; x++)
[self fetchAudioImage:[NSURL URLWithString:imageURLStrings[x]]];
NSString *imageURLString = album.artworkImage;
if (!imageURLString) {
NSArray *imageURLStrings = album.largeSizedArtistImages;
/* when we only have 1 HD image, duplicate it */
if (imageCount == 1) {
[self fetchAudioImage:[NSURL URLWithString:[imageURLStrings firstObject]]];
if (imageURLStrings.count > 0) {
imageURLString = imageURLStrings.firstObject;
} else {
imageURLStrings = artist.mediumSizedImages;
if (imageURLStrings.count > 0) {
imageURLString = imageURLStrings.firstObject;
}
}
}
/* if we have too few HD pictures, try to add some medium quality ones */
if (imageCount < 4 && totalImageCount < 10) {
imageURLStrings = album.mediumSizedArtistImages;
imageCount = imageURLStrings.count;
if (imageCount > 10)
imageCount = 10;
totalImageCount += imageCount;
for (NSUInteger x = 0; x < imageCount; x++)
[self fetchAudioImage:[NSURL URLWithString:imageURLStrings[x]]];
/* oh crap, well better than a black screen */
if (imageCount == 1)
[self fetchAudioImage:[NSURL URLWithString:[imageURLStrings firstObject]]];
}
if (imageURLString) {
[self.audioArtworkImageView setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?height=500&width=500", imageURLString]]];
[self.audioLargeBackgroundImageView setImageWithURL:[NSURL URLWithString:imageURLString]];
} else {
UIImage *dummyImage = [UIImage imageNamed:@"about-app-icon"];
self.audioArtworkImageView.image = dummyImage;
self.audioLargeBackgroundImageView.image = dummyImage;
/* we have too few images, let's simplify our search request */
if (totalImageCount < 4) {
[self _simplifyMetaDataSearchString:artistName];
[self _simplifyMetaDataSearchString:searchRequest];
}
}
......@@ -799,43 +773,36 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
- (void)MDFHatchetFetcher:(MDFHatchetFetcher *)aFetcher didFindArtist:(MDFArtist *)artist forSearchRequest:(NSString *)searchRequest
{
NSArray *imageURLStrings = artist.largeSizedImages;
NSUInteger imageCount = imageURLStrings.count;
NSUInteger totalImageCount;
@synchronized(self.audioImagesArray) {
totalImageCount = self.audioImagesArray.count;
/* we have no match */
if (!artist) {
[self _simplifyMetaDataSearchString:searchRequest];
return;
}
self.audioArtistLabel.text = artist.name;
if (artist.biography) {
[UIView animateWithDuration:.3 animations:^{
self.audioDescriptionTextView.text = artist.biography;
}];
[self startAudioDescriptionAnimation];
} else
[self stopAudioDescriptionAnimation];
/* reasonably limit the number of images we fetch */
if (imageCount > 10)
imageCount = 10;
totalImageCount += imageCount;
for (NSUInteger x = 0; x < imageCount; x++)
[self fetchAudioImage:[NSURL URLWithString:imageURLStrings[x]]];
/* when we only have 1 HD image, duplicate it */
if (imageCount == 1) {
[self fetchAudioImage:[NSURL URLWithString:[imageURLStrings firstObject]]];
}
NSArray *imageURLStrings = artist.largeSizedImages;
NSString *imageURLString;
/* if we have too few HD pictures, try to add some medium quality ones */
if (imageCount < 4 && totalImageCount < 10) {
if (imageURLStrings.count > 0) {
imageURLString = imageURLStrings.firstObject;
} else {
imageURLStrings = artist.mediumSizedImages;
imageCount = imageURLStrings.count;
if (imageCount > 10)
imageCount = 10;
totalImageCount += imageCount;
for (NSUInteger x = 0; x < imageCount; x++)
[self fetchAudioImage:[NSURL URLWithString:imageURLStrings[x]]];
/* oh crap, well better than a black screen */
if (imageCount == 1)
[self fetchAudioImage:[NSURL URLWithString:[imageURLStrings firstObject]]];
if (imageURLStrings.count > 0) {
imageURLString = imageURLStrings.firstObject;
}
}
/* we have too few images, let's simplify our search request */
if (totalImageCount < 4) {
if (imageURLString) {
[self.audioArtworkImageView setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?height=500&width=500",imageURLString]]];
[self.audioLargeBackgroundImageView setImageWithURL:[NSURL URLWithString:imageURLString]];
} else {
[self _simplifyMetaDataSearchString:searchRequest];
}
}
......@@ -852,21 +819,53 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
APLog(@"%s: %@", __PRETTY_FUNCTION__, searchRequest);
}
- (void)fetchAudioImage:(NSURL *)url
- (void)scrollAudioDescriptionAnimationToTop
{
__weak typeof(self) weakSelf = self;
NSURLSession *sharedSession = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [sharedSession dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!data) {
return;
}
UIImage *image = [UIImage imageWithData:data];
@synchronized(weakSelf.audioImagesArray) {
[weakSelf.audioImagesArray addObject:image];
}
[weakSelf performSelectorOnMainThread:@selector(showAnimatedImagesView) withObject:nil waitUntilDone:NO];
}];
[task resume];
[self stopAudioDescriptionAnimation];
[self.audioDescriptionTextView setContentOffset:CGPointZero animated:YES];
[self startAudioDescriptionAnimation];
}
- (void)startAudioDescriptionAnimation
{
[self.audioDescriptionScrollTimer invalidate];
self.audioDescriptionScrollTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(animateAudioDescription)
userInfo:nil repeats:NO];
}
- (void)stopAudioDescriptionAnimation
{
[self.audioDescriptionScrollTimer invalidate];
self.audioDescriptionScrollTimer = nil;
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)animateAudioDescription
{
[self.displayLink invalidate];
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTriggered:)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)displayLinkTriggered:(CADisplayLink*)link
{
UIScrollView *scrollView = self.audioDescriptionTextView;
CGFloat viewHeight = CGRectGetHeight(scrollView.frame);
CGFloat maxOffsetY = scrollView.contentSize.height - viewHeight;
CFTimeInterval secondsPerPage = 4.0;
CGFloat offset = link.duration/secondsPerPage * viewHeight;
CGFloat newYOffset = scrollView.contentOffset.y + offset;
if (newYOffset > maxOffsetY+viewHeight) {
scrollView.contentOffset = CGPointMake(0, -viewHeight);
} else {
scrollView.contentOffset = CGPointMake(0, newYOffset);
}
}
@end
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder.AppleTV.XIB" version="3.0" toolsVersion="9059" systemVersion="15B42" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder.AppleTV.XIB" version="3.0" toolsVersion="9527.1" systemVersion="15B42" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9049"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9525.1"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="VLCFullscreenMovieTVViewController">
<connections>
<outlet property="activityIndicator" destination="UcH-Yc-Sd0" id="9fo-IU-bbs"/>
<outlet property="audioAlbumNameLabel" destination="AX0-qH-hQt" id="3oT-RW-4am"/>
<outlet property="audioArtistLabel" destination="uF6-s0-ZW1" id="Mgp-XN-P8r"/>
<outlet property="audioArtworkImageView" destination="J7W-id-hCi" id="Zvs-aZ-ggK"/>
<outlet property="audioDescriptionTextView" destination="91t-Cy-Ney" id="Acz-Ql-RHD"/>
<outlet property="audioLargeBackgroundImageView" destination="GYU-SB-lrM" id="SuQ-il-5aj"/>
<outlet property="audioTitleLabel" destination="yY5-l1-Z6Q" id="As3-vK-BBo"/>
<outlet property="audioView" destination="9Pa-FT-05g" id="j1O-RP-Bo9"/>
<outlet property="audioVisualEffectView" destination="Fgv-ZO-u0W" id="p6v-kE-e25"/>
<outlet property="bottomOverlayView" destination="SzB-KN-vRr" id="HdG-qX-fha"/>
<outlet property="bufferingLabel" destination="yaX-qU-D4t" id="pDV-iQ-eaa"/>
<outlet property="dimmingView" destination="VBa-d2-15H" id="5jE-Qy-meO"/>
......@@ -24,31 +32,97 @@
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SpU-aa-czI">
<rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9Pa-FT-05g" userLabel="Audio View">
<rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="GYU-SB-lrM" customClass="VLCNetworkImageView">
<rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
</imageView>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Fgv-ZO-u0W">
<rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="YW4-FT-0On">
<rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<blurEffect style="light"/>
</visualEffectView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Ludovico Einaudi" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uF6-s0-ZW1">
<rect key="frame" x="1142" y="312" width="368" height="58"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Part 2 Live" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yY5-l1-Z6Q">
<rect key="frame" x="1142" y="392" width="133" height="35"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Royal Albert Hall Concert" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AX0-qH-hQt">
<rect key="frame" x="1142" y="449" width="319" height="35"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" bounces="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="91t-Cy-Ney">
<rect key="frame" x="1118" y="506" width="548" height="262"/>