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"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences" autocorrectionType="no" spellCheckingType="no"/>
</textView>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="J7W-id-hCi" customClass="VLCNetworkImageView">
<rect key="frame" x="349" y="290" width="500" height="500"/>
<constraints>
<constraint firstAttribute="width" constant="500" id="9tA-3A-CHz"/>
<constraint firstAttribute="height" constant="500" id="Y5x-aL-fep"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="GYU-SB-lrM" firstAttribute="centerX" secondItem="9Pa-FT-05g" secondAttribute="centerX" id="53l-bt-9W3"/>
<constraint firstItem="Fgv-ZO-u0W" firstAttribute="centerY" secondItem="9Pa-FT-05g" secondAttribute="centerY" id="9QV-ec-M71"/>
<constraint firstItem="J7W-id-hCi" firstAttribute="centerY" secondItem="9Pa-FT-05g" secondAttribute="centerY" id="BkJ-I8-bKO"/>
<constraint firstItem="91t-Cy-Ney" firstAttribute="leading" secondItem="AX0-qH-hQt" secondAttribute="leading" constant="-24" id="DIJ-cM-X7E"/>
<constraint firstItem="91t-Cy-Ney" firstAttribute="width" secondItem="J7W-id-hCi" secondAttribute="width" priority="250" constant="48" id="Fzd-p3-Y0o"/>
<constraint firstItem="Fgv-ZO-u0W" firstAttribute="width" secondItem="9Pa-FT-05g" secondAttribute="width" id="Gig-2L-fpR"/>
<constraint firstItem="uF6-s0-ZW1" firstAttribute="top" secondItem="J7W-id-hCi" secondAttribute="top" constant="22" id="Kgc-aR-bwd"/>
<constraint firstItem="91t-Cy-Ney" firstAttribute="top" secondItem="AX0-qH-hQt" secondAttribute="bottom" constant="22" id="L53-Di-vxN"/>
<constraint firstItem="AX0-qH-hQt" firstAttribute="top" secondItem="yY5-l1-Z6Q" secondAttribute="bottom" constant="22" id="LOG-Oo-cRF"/>
<constraint firstItem="uF6-s0-ZW1" firstAttribute="leading" secondItem="J7W-id-hCi" secondAttribute="trailing" constant="293" id="SmU-22-m4O"/>
<constraint firstItem="GYU-SB-lrM" firstAttribute="width" secondItem="9Pa-FT-05g" secondAttribute="width" id="ZuZ-16-aUy"/>
<constraint firstItem="Fgv-ZO-u0W" firstAttribute="height" secondItem="9Pa-FT-05g" secondAttribute="height" id="cLJ-77-GBW"/>
<constraint firstItem="GYU-SB-lrM" firstAttribute="height" secondItem="9Pa-FT-05g" secondAttribute="height" id="fJW-hf-0Ea"/>
<constraint firstItem="yY5-l1-Z6Q" firstAttribute="leading" secondItem="uF6-s0-ZW1" secondAttribute="leading" id="lQT-Sg-lwM"/>
<constraint firstItem="AX0-qH-hQt" firstAttribute="leading" secondItem="yY5-l1-Z6Q" secondAttribute="leading" id="q3d-SR-aRv"/>
<constraint firstItem="yY5-l1-Z6Q" firstAttribute="top" secondItem="uF6-s0-ZW1" secondAttribute="bottom" constant="22" id="sKK-Jf-OK2"/>
<constraint firstItem="91t-Cy-Ney" firstAttribute="bottom" secondItem="J7W-id-hCi" secondAttribute="bottom" constant="-22" id="suM-P7-RHE"/>
<constraint firstItem="J7W-id-hCi" firstAttribute="leading" secondItem="9Pa-FT-05g" secondAttribute="leading" constant="349" id="ulU-A9-10q"/>
<constraint firstItem="GYU-SB-lrM" firstAttribute="centerY" secondItem="9Pa-FT-05g" secondAttribute="centerY" id="vqq-ve-Gmf"/>
<constraint firstItem="Fgv-ZO-u0W" firstAttribute="centerX" secondItem="9Pa-FT-05g" secondAttribute="centerX" id="x6U-7r-SuP"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="VBa-d2-15H" userLabel="DimmingView">
<rect key="frame" x="0.0" y="0.0" width="1920" height="886"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.20000000000000001" colorSpace="calibratedWhite"/>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Please wait" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yaX-qU-D4t">
<rect key="frame" x="815" y="506" width="289" height="69"/>
<animations/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
<color key="textColor" red="1" green="1" blue="1" alpha="0.59999999999999998" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="UcH-Yc-Sd0">
<rect key="frame" x="928" y="583" width="64" height="64"/>
<animations/>
</activityIndicatorView>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SzB-KN-vRr" customClass="VLCFrostedGlasView">
<rect key="frame" x="0.0" y="886" width="1920" height="194"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="epY-iV-5FH">
<rect key="frame" x="960" y="10" width="0.0" height="69"/>
<animations/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="69" id="Cum-M9-lbH"/>
</constraints>
......@@ -58,14 +132,12 @@
</label>
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="CO6-ze-V9M" customClass="VLCTransportBar">
<rect key="frame" x="90" y="94" width="1740" height="10"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="N7R-Ku-vGb"/>
</constraints>
</view>
</subviews>
<animations/>
<constraints>
<constraint firstItem="CO6-ze-V9M" firstAttribute="top" secondItem="epY-iV-5FH" secondAttribute="bottom" constant="15" id="3KL-US-KZa"/>
<constraint firstItem="CO6-ze-V9M" firstAttribute="leading" secondItem="SzB-KN-vRr" secondAttribute="leading" constant="90" id="6fm-9L-qPq"/>
......@@ -76,12 +148,13 @@
</constraints>
</view>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="SzB-KN-vRr" secondAttribute="trailing" id="3Bb-JP-76x"/>
<constraint firstAttribute="bottom" secondItem="SzB-KN-vRr" secondAttribute="bottom" id="542-su-jDp"/>
<constraint firstAttribute="trailing" secondItem="VBa-d2-15H" secondAttribute="trailing" id="5Pl-VN-MrE"/>
<constraint firstItem="9Pa-FT-05g" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="AEe-is-9I8"/>
<constraint firstItem="9Pa-FT-05g" firstAttribute="width" secondItem="iN0-l3-epB" secondAttribute="width" id="AfV-Qp-j69"/>
<constraint firstItem="SzB-KN-vRr" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="DAq-5Q-AKJ"/>
<constraint firstItem="VBa-d2-15H" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="IZO-1L-Mhz"/>
<constraint firstAttribute="bottom" secondItem="SpU-aa-czI" secondAttribute="bottom" id="J9T-OC-2Sd"/>
......@@ -91,6 +164,8 @@
<constraint firstItem="SpU-aa-czI" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="XAU-E6-19A"/>
<constraint firstItem="SzB-KN-vRr" firstAttribute="top" secondItem="VBa-d2-15H" secondAttribute="bottom" id="dwd-A2-fZz"/>
<constraint firstItem="yaX-qU-D4t" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="ecl-IU-ALU"/>
<constraint firstItem="9Pa-FT-05g" firstAttribute="height" secondItem="iN0-l3-epB" secondAttribute="height" id="jLL-ib-tkX"/>