VLCMovieViewController.m 61.6 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCMovieViewController.m
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
6 7 8 9
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Gleb Pinigin <gpinigin # gmail.com>
10 11
 *          Carola Nitz <caro # videolan.org>
 *          Tobias Conradi <videolan # tobias-conradi.de>
12 13 14
 *          Ahmad Harb <harb.dev.leb # gmail.com>
 *          Fabio Ritrovato <sephiroth87 # videolan.org>
 *          Pierre SAGASPE <pierre.sagaspe # me.com>
15 16 17 18
 *          Filipe Cabecinhas <vlc # filcab dot net>
 *          Marc Etcheverry <marc # taplightsoftware dot com>
 *          Christopher Loessl <cloessl # x-berg dot de>
 *          Sylver Bruneau <sylver.bruneau # gmail dot com>
19 20 21
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
Felix Paul Kühne's avatar
Felix Paul Kühne committed
22

23
#import "VLCMovieViewController.h"
24
#import "VLCExternalDisplayController.h"
25 26
#import "VLCTrackSelectorTableViewCell.h"
#import "VLCTrackSelectorHeaderView.h"
27
#import "VLCEqualizerView.h"
28
#import "VLCMultiSelectionMenuView.h"
29 30
#import "VLCPlaybackController.h"
#import "UIDevice+VLC.h"
31
#import "VLCTimeNavigationTitleView.h"
32 33
#import "VLCPlayerDisplayController.h"
#import "VLCAppDelegate.h"
34
#import "VLCStatusLabel.h"
Felix Paul Kühne's avatar
Felix Paul Kühne committed
35

36 37
#define FORWARD_SWIPE_DURATION 30
#define BACKWARD_SWIPE_DURATION 10
38

39 40 41
#define TRACK_SELECTOR_TABLEVIEW_CELL @"track selector table view cell"
#define TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER @"track selector table view section header"

42 43 44 45
#define LOCKCHECK \
if (_interfaceIsLocked) \
return

46 47 48 49 50 51 52
typedef NS_ENUM(NSInteger, VLCPanType) {
  VLCPanTypeNone,
  VLCPanTypeBrightness,
  VLCPanTypeSeek,
  VLCPanTypeVolume,
};

53
@interface VLCMovieViewController () <UIGestureRecognizerDelegate, UITableViewDataSource, UITableViewDelegate, VLCMultiSelectionViewDelegate, VLCEqualizerViewUIDelegate>
54
{
55 56 57 58 59 60 61 62 63
    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    NSTimer *_idleTimer;

64
    BOOL _viewAppeared;
65
    BOOL _displayRemainingTime;
66
    BOOL _positionSet;
67
    BOOL _playerIsSetup;
68
    BOOL _isScrubbing;
69
    BOOL _interfaceIsLocked;
70
    BOOL _switchingTracksNotChapters;
71
    BOOL _audioOnly;
Ahmad Harb's avatar
Ahmad Harb committed
72 73

    BOOL _swipeGesturesEnabled;
74
    BOOL _variableJumpDurationEnabled;
75
    UIPinchGestureRecognizer *_pinchRecognizer;
76
    VLCPanType _currentPanType;
Ahmad Harb's avatar
Ahmad Harb committed
77 78 79
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
80 81
    UISwipeGestureRecognizer *_swipeRecognizerUp;
    UISwipeGestureRecognizer *_swipeRecognizerDown;
82
    UITapGestureRecognizer *_tapRecognizer;
83
    UITapGestureRecognizer *_tapOnVideoRecognizer;
84 85 86

    UIView *_trackSelectorContainer;
    UITableView *_trackSelectorTableView;
87 88

    VLCEqualizerView *_equalizerView;
89
    VLCMultiSelectionMenuView *_multiSelectionView;
90 91 92 93

    UIView *_sleepTimerContainer;
    UIDatePicker *_sleepTimeDatePicker;

94
    NSInteger _mediaDuration;
95 96
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
97 98
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
99 100
@end

101
@implementation VLCMovieViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
102

103 104 105 106 107 108 109
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
110 111
- (void)dealloc
{
112 113
    [[NSNotificationCenter defaultCenter] removeObserver:self];

114
    if (_tapRecognizer)
115
        [self.view removeGestureRecognizer:_tapRecognizer];
Ahmad Harb's avatar
Ahmad Harb committed
116
    if (_swipeRecognizerLeft)
117
        [self.view removeGestureRecognizer:_swipeRecognizerLeft];
Ahmad Harb's avatar
Ahmad Harb committed
118
    if (_swipeRecognizerRight)
119
        [self.view removeGestureRecognizer:_swipeRecognizerRight];
120 121 122 123
    if (_swipeRecognizerUp)
        [self.view removeGestureRecognizer:_swipeRecognizerUp];
    if (_swipeRecognizerDown)
        [self.view removeGestureRecognizer:_swipeRecognizerDown];
Ahmad Harb's avatar
Ahmad Harb committed
124
    if (_panRecognizer)
125
        [self.view removeGestureRecognizer:_panRecognizer];
126 127 128 129
    if (_pinchRecognizer)
        [self.view removeGestureRecognizer:_pinchRecognizer];
    [self.view removeGestureRecognizer:_tapOnVideoRecognizer];

130 131 132
    _tapRecognizer = nil;
    _swipeRecognizerLeft = nil;
    _swipeRecognizerRight = nil;
133 134
    _swipeRecognizerUp = nil;
    _swipeRecognizerDown = nil;
135
    _panRecognizer = nil;
136 137
    _pinchRecognizer = nil;
    _tapOnVideoRecognizer = nil;
138 139
}

140
- (void)viewDidLoad
Felix Paul Kühne's avatar
Felix Paul Kühne committed
141
{
142
    [super viewDidLoad];
143 144
    CGRect rect;

145 146
    self.extendedLayoutIncludesOpaqueBars = YES;
    self.edgesForExtendedLayout = UIRectEdgeAll;
147

148 149
    self.videoFilterView.hidden = YES;
    _videoFiltersHidden = YES;
150
    _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", nil);
151 152
    _hueSlider.accessibilityLabel = _hueLabel.text;
    _hueSlider.isAccessibilityElement = YES;
153
    _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", nil);
154 155
    _contrastSlider.accessibilityLabel = _contrastLabel.text;
    _contrastSlider.isAccessibilityElement = YES;
156
    _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", nil);
157 158
    _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
    _brightnessSlider.isAccessibilityElement = YES;
159
    _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", nil);
160 161
    _saturationSlider.accessibilityLabel = _saturationLabel.text;
    _saturationSlider.isAccessibilityElement = YES;
162
    _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", nil);
163 164
    _gammaSlider.accessibilityLabel = _gammaLabel.text;
    _gammaSlider.isAccessibilityElement = YES;
165
    _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", nil);
166 167
    _playbackSpeedSlider.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedSlider.isAccessibilityElement = YES;
168 169 170 171 172 173
    _audioDelayLabel.text = NSLocalizedString(@"AUDIO_DELAY", nil);
    _audioDelaySlider.accessibilityLabel = _audioDelayLabel.text;
    _audioDelaySlider.isAccessibilityElement = YES;
    _spuDelayLabel.text = NSLocalizedString(@"SPU_DELAY", nil);
    _spuDelaySlider.accessibilityLabel = _spuDelayLabel.text;
    _spuDelaySlider.isAccessibilityElement = YES;
174

175 176 177 178
    _trackSwitcherButton.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
    _trackSwitcherButton.isAccessibilityElement = YES;
    _trackSwitcherButtonLandscape.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
    _trackSwitcherButtonLandscape.isAccessibilityElement = YES;
179 180
    _playbackSpeedButton.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButton.isAccessibilityElement = YES;
181 182
    _playbackSpeedButtonLandscape.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButtonLandscape.isAccessibilityElement = YES;
183
    _videoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
184
    _videoFilterButton.isAccessibilityElement = YES;
185
    _videoFilterButtonLandscape.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
186
    _videoFilterButtonLandscape.isAccessibilityElement = YES;
187
    _resetVideoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER_RESET_BUTTON", nil);
188
    _resetVideoFilterButton.isAccessibilityElement = YES;
189 190
    UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(playPauseLongPress:)];
    [_playPauseButton addGestureRecognizer:longPressRecognizer];
191
    _playPauseButton.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
192
    _playPauseButton.accessibilityHint = NSLocalizedString(@"LONGPRESS_TO_STOP", nil);
193
    _playPauseButton.isAccessibilityElement = YES;
194 195
    UILongPressGestureRecognizer *longPressRecognizerLandscape = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(playPauseLongPress:)];
    [_playPauseButton addGestureRecognizer:longPressRecognizerLandscape];
196
    _playPauseButtonLandscape.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
197
    _playPauseButtonLandscape.accessibilityHint = NSLocalizedString(@"LONGPRESS_TO_STOP", nil);
198
    _playPauseButtonLandscape.isAccessibilityElement = YES;
199
    _bwdButton.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
200
    _bwdButton.isAccessibilityElement = YES;
201
    _bwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
202
    _bwdButtonLandscape.isAccessibilityElement = YES;
203
    _fwdButton.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
204
    _fwdButton.isAccessibilityElement = YES;
205
    _fwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
206
    _fwdButtonLandscape.isAccessibilityElement = YES;
207 208 209
    _sleepTimerButton.accessibilityLabel = NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil);
    _sleepTimerButton.isAccessibilityElement = YES;
    [_sleepTimerButton setTitle:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil) forState:UIControlStateNormal];
210

211 212 213 214 215 216
    _multiSelectionView = [[VLCMultiSelectionMenuView alloc] init];
    _multiSelectionView.delegate = self;
    _multiSelectionView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
    _multiSelectionView.hidden = YES;
    [self.view addSubview:_multiSelectionView];

217
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
218

219 220
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
221

222 223 224 225 226
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
227 228
    [center addObserver:self selector:@selector(screenBrightnessChanged:)
                   name:UIScreenBrightnessDidChangeNotification object:nil];
229 230 231 232
    [center addObserver:self
               selector:@selector(appBecameActive:)
                   name:UIApplicationDidBecomeActiveNotification
                 object:nil];
233

234 235
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
236
    if ([[UIDevice currentDevice] hasExternalDisplay])
237
        [self showOnExternalDisplay];
238

239 240
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

241
    _movieView.userInteractionEnabled = NO;
242 243 244
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
245

246 247 248
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
    _swipeGesturesEnabled = [[defaults objectForKey:kVLCSettingPlaybackGestures] boolValue];
249
    _variableJumpDurationEnabled = [[defaults objectForKey:kVLCSettingVariableJumpDuration] boolValue];
250

251 252 253 254 255 256
    _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    _pinchRecognizer.delegate = self;
    [self.view addGestureRecognizer:_pinchRecognizer];

    _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized)];
    [_tapRecognizer setNumberOfTouchesRequired:2];
257 258

    _currentPanType = VLCPanTypeNone;
259 260 261 262 263 264 265 266
    _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panRecognized:)];
    [_panRecognizer setMinimumNumberOfTouches:1];
    [_panRecognizer setMaximumNumberOfTouches:1];

    _swipeRecognizerLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerLeft.direction = UISwipeGestureRecognizerDirectionLeft;
    _swipeRecognizerRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerRight.direction = UISwipeGestureRecognizerDirectionRight;
267 268 269 270 271 272
    _swipeRecognizerUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerUp.direction = UISwipeGestureRecognizerDirectionUp;
    _swipeRecognizerUp.numberOfTouchesRequired = 2;
    _swipeRecognizerDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerDown.direction = UISwipeGestureRecognizerDirectionDown;
    _swipeRecognizerDown.numberOfTouchesRequired = 2;
273 274 275

    [self.view addGestureRecognizer:_swipeRecognizerLeft];
    [self.view addGestureRecognizer:_swipeRecognizerRight];
276 277
    [self.view addGestureRecognizer:_swipeRecognizerUp];
    [self.view addGestureRecognizer:_swipeRecognizerDown];
278 279 280 281
    [self.view addGestureRecognizer:_panRecognizer];
    [self.view addGestureRecognizer:_tapRecognizer];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];
282 283
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerUp];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerDown];
284 285 286 287

    _panRecognizer.delegate = self;
    _swipeRecognizerRight.delegate = self;
    _swipeRecognizerLeft.delegate = self;
288 289
    _swipeRecognizerUp.delegate = self;
    _swipeRecognizerDown.delegate = self;
290
    _tapRecognizer.delegate = self;
291

Felix Paul Kühne's avatar
Felix Paul Kühne committed
292 293 294 295 296 297 298 299 300 301 302
    self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
    self.toolbar.tintColor = [UIColor whiteColor];
    self.toolbar.barStyle = UIBarStyleBlack;

    rect = self.resetVideoFilterButton.frame;
    rect.origin.y = rect.origin.y + 5.;
    self.resetVideoFilterButton.frame = rect;
    rect = self.toolbar.frame;
    rect.size.height = rect.size.height + rect.origin.y;
    rect.origin.y = 0;
    self.toolbar.frame = rect;
303

304
    /* FIXME: there is a saner iOS 6+ API for this! */
305
    /* this looks a bit weird, but we need to support iOS 5 and should show the same appearance */
306 307 308 309 310 311 312
    void (^initVolumeSlider)(MPVolumeView *) = ^(MPVolumeView *volumeView){
        UISlider *volumeSlider = nil;
        for (id aView in volumeView.subviews){
            if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
                volumeSlider = (UISlider *)aView;
                break;
            }
313
        }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
314
        [volumeView setVolumeThumbImage:[UIImage imageNamed:@"modernSliderKnob"] forState:UIControlStateNormal];
315 316 317 318 319 320 321
        [volumeSlider addTarget:self
                         action:@selector(volumeSliderAction:)
               forControlEvents:UIControlEventValueChanged];
    };

    initVolumeSlider(self.volumeView);
    initVolumeSlider(self.volumeViewLandscape);
322

323
    _playerIsSetup = NO;
324

325 326
    [self.movieView setAccessibilityLabel:NSLocalizedString(@"VO_VIDEOPLAYER_TITLE", nil)];
    [self.movieView setAccessibilityHint:NSLocalizedString(@"VO_VIDEOPLAYER_DOUBLETAP", nil)];
327 328

    rect = self.view.frame;
329 330 331 332 333 334
    CGFloat width;
    CGFloat height;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        width = 300.;
        height = 320.;
    } else {
335
        width = 420.;
336 337
        height = 470.;
    }
338

339
    _trackSelectorTableView = [[UITableView alloc] initWithFrame:CGRectMake(0., 0., width, height) style:UITableViewStylePlain];
340 341
    _trackSelectorTableView.delegate = self;
    _trackSelectorTableView.dataSource = self;
342
    _trackSelectorTableView.separatorColor = [UIColor clearColor];
343
    _trackSelectorTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
344 345 346
    _trackSelectorTableView.rowHeight = 44.;
    _trackSelectorTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    _trackSelectorTableView.sectionHeaderHeight = 28.;
347 348
    [_trackSelectorTableView registerClass:[VLCTrackSelectorTableViewCell class] forCellReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL];
    [_trackSelectorTableView registerClass:[VLCTrackSelectorHeaderView class] forHeaderFooterViewReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
349
    _trackSelectorTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
350

351
    _trackSelectorContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake((rect.size.width - width) / 2., (rect.size.height - height) / 2., width, height)];
352
    [_trackSelectorContainer addSubview:_trackSelectorTableView];
353
    _trackSelectorContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight;
354 355 356 357 358 359
    _trackSelectorContainer.hidden = YES;

    if ([[UIDevice currentDevice] speedCategory] >= 3) {
        _trackSelectorTableView.opaque = NO;
        _trackSelectorTableView.backgroundColor = [UIColor clearColor];
    } else
360
        _trackSelectorTableView.backgroundColor = [UIColor blackColor];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
361
    _trackSelectorTableView.allowsMultipleSelection = YES;
362 363

    [self.view addSubview:_trackSelectorContainer];
364

365 366 367 368 369
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        _equalizerView = [[VLCEqualizerView alloc] initWithFrame:CGRectMake((rect.size.width - 450.) / 2., self.controllerPanel.frame.origin.y - 200., 450., 240.)];
    } else {
        _equalizerView = [[VLCEqualizerView alloc] initWithFrame:CGRectMake((rect.size.width - 450.) / 2., self.controllerPanel.frame.origin.y - 240., 450., 240.)];
    }
370
    _equalizerView.delegate = self.playbackController;
371
    _equalizerView.UIdelegate = self;
372 373 374
    _equalizerView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
    _equalizerView.hidden = YES;
    [self.view addSubview:_equalizerView];
375

Felix Paul Kühne's avatar
Felix Paul Kühne committed
376 377 378 379
    /* add sleep timer UI */
    _sleepTimerContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake(0., 0., 300., 162.)];
    _sleepTimerContainer.center = self.view.center;
    _sleepTimerContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;
380

Felix Paul Kühne's avatar
Felix Paul Kühne committed
381 382 383 384 385 386 387
    _sleepTimeDatePicker = [[UIDatePicker alloc] init];
    if ([[UIDevice currentDevice] speedCategory] >= 3) {
        _sleepTimeDatePicker.opaque = NO;
        _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
    } else
        _sleepTimeDatePicker.backgroundColor = [UIColor blackColor];
    _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
388
    _sleepTimeDatePicker.frame = CGRectMake(0., 0., 300., 162.);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
    _sleepTimeDatePicker.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;
    [_sleepTimerContainer addSubview:_sleepTimeDatePicker];

    /* adapt the date picker style to suit our needs */
    [_sleepTimeDatePicker setValue:[UIColor whiteColor] forKeyPath:@"textColor"];
    SEL selector = NSSelectorFromString(@"setHighlightsToday:");
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDatePicker instanceMethodSignatureForSelector:selector]];
    BOOL no = NO;
    [invocation setSelector:selector];
    [invocation setArgument:&no atIndex:2];
    [invocation invokeWithTarget:_sleepTimeDatePicker];

    if (_sleepTimerContainer.subviews.count > 0) {
        NSArray *subviewsOfSubview = [_sleepTimeDatePicker.subviews[0] subviews];
        NSUInteger subviewCount = subviewsOfSubview.count;
        for (NSUInteger x = 0; x < subviewCount; x++) {
            if ([subviewsOfSubview[x] isKindOfClass:[UILabel class]])
                [subviewsOfSubview[x] setTextColor:[UIColor VLCLightTextColor]];
        }
408
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
409 410 411 412 413 414 415
    _sleepTimeDatePicker.datePickerMode = UIDatePickerModeCountDownTimer;
    _sleepTimeDatePicker.minuteInterval = 5;
    _sleepTimeDatePicker.minimumDate = [NSDate date];
    _sleepTimeDatePicker.countDownDuration = 1200.;
    [_sleepTimeDatePicker addTarget:self action:@selector(sleepTimerAction:) forControlEvents:UIControlEventValueChanged];

    [self.view addSubview:_sleepTimerContainer];
416 417

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackDidStop:) name:VLCPlaybackControllerPlaybackDidStop object:nil];
418
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
419

420 421
- (void)viewWillAppear:(BOOL)animated
{
422
    [super viewWillAppear:animated];
423

424 425 426 427 428 429
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

430
    _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];
431
    _variableJumpDurationEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingVariableJumpDuration] boolValue];
432

433
    [self.navigationController setNavigationBarHidden:YES animated:YES];
434

435
    VLCPlaybackController *vpc = self.playbackController;
436
    vpc.delegate = self;
437
    [vpc recoverPlaybackState];
438

439
    [self screenBrightnessChanged:nil];
440
    [self setControlsHidden:NO animated:YES];
441 442 443 444 445
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
446
    _viewAppeared = YES;
447

448
    VLCPlaybackController *vpc = self.playbackController;
449
    [vpc recoverDisplayedMetadata];
450 451
    vpc.videoOutputView = nil;
    vpc.videoOutputView = self.movieView;
452 453
}

454 455
- (void)viewWillLayoutSubviews
{
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
    CGRect multiSelectionFrame;
    CGRect controllerPanelFrame;

    if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPhone) {
        controllerPanelFrame = _controllerPanel.frame;
        multiSelectionFrame = (CGRect){CGPointMake(0., 0.), [_multiSelectionView proposedDisplaySize]};
        multiSelectionFrame.origin.x = controllerPanelFrame.size.width - multiSelectionFrame.size.width;
        multiSelectionFrame.origin.y = controllerPanelFrame.origin.y - multiSelectionFrame.size.height;
        _multiSelectionView.frame = multiSelectionFrame;
        _multiSelectionView.showsEqualizer = YES;
        return;
    }

    CGSize viewSize = self.view.frame.size;

    if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
        [_controllerPanel removeFromSuperview];
        controllerPanelFrame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanelLandscape.frame.size.height), CGSizeMake(viewSize.width, _controllerPanelLandscape.frame.size.height)};
        _controllerPanelLandscape.frame = controllerPanelFrame;
        [self.view addSubview:_controllerPanelLandscape];
        _multiSelectionView.showsEqualizer = YES;
        multiSelectionFrame = (CGRect){CGPointMake(0., 0.), [_multiSelectionView proposedDisplaySize]};
        multiSelectionFrame.origin.x = controllerPanelFrame.size.width - multiSelectionFrame.size.width;
        multiSelectionFrame.origin.y = controllerPanelFrame.origin.y - multiSelectionFrame.size.height;
    } else {
        [_controllerPanelLandscape removeFromSuperview];
        controllerPanelFrame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanel.frame.size.height), CGSizeMake(viewSize.width, _controllerPanel.frame.size.height)};
        _controllerPanel.frame = controllerPanelFrame;
        [self.view addSubview:_controllerPanel];
        _multiSelectionView.showsEqualizer = NO;
        multiSelectionFrame = (CGRect){CGPointMake(0., 0.), [_multiSelectionView proposedDisplaySize]};
        multiSelectionFrame.origin.x = controllerPanelFrame.size.width - multiSelectionFrame.size.width;
        multiSelectionFrame.origin.y = controllerPanelFrame.origin.y - multiSelectionFrame.size.height;
489
    }
490 491

    _multiSelectionView.frame = multiSelectionFrame;
492 493
}

494
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
495
{
496
    VLCPlaybackController *vpc = self.playbackController;
497 498 499
    if (vpc.videoOutputView == self.movieView) {
        vpc.videoOutputView = nil;
    }
500

501
    _viewAppeared = NO;
502
    if (_idleTimer) {
503
        [_idleTimer invalidate];
504 505
        _idleTimer = nil;
    }
506
    [self.navigationController setNavigationBarHidden:NO animated:YES];
507
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
508
    [super viewWillDisappear:animated];
509

Felix Paul Kühne's avatar
Felix Paul Kühne committed
510 511 512 513
    // hide filter UI for next run
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

514 515 516
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
517 518
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;
519 520

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
521 522 523 524 525 526 527
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

528 529
#pragma mark - controls visibility

530 531
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
532 533
    LOCKCHECK;

534 535 536
    if (!_swipeGesturesEnabled)
        return;

537 538 539 540
    if (recognizer.velocity < 0.)
        [self closePlayback:nil];
}

541 542
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
543
    if (touch.view != self.view)
544 545 546 547 548
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
549 550 551 552 553
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

554
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
555
{
556
    _controlsHidden = hidden;
557
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
558

559 560
    if (!_controlsHidden) {
        _controllerPanel.alpha = 0.0f;
561
        _controllerPanel.hidden = !_videoFiltersHidden;
562 563
        _controllerPanelLandscape.alpha = 0.0f;
        _controllerPanelLandscape.hidden = !_videoFiltersHidden;
564 565
        _toolbar.alpha = 0.0f;
        _toolbar.hidden = NO;
566 567
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
568 569
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
570 571
        _trackSelectorContainer.alpha = 0.0f;
        _trackSelectorContainer.hidden = YES;
572 573
        _equalizerView.alpha = 0.0f;
        _equalizerView.hidden = YES;
574 575 576 577
        if (_sleepTimerContainer) {
            _sleepTimerContainer.alpha = 0.0f;
            _sleepTimerContainer.hidden = YES;
        }
578 579
        _multiSelectionView.alpha = 0.0f;
        _multiSelectionView.hidden = YES;
580 581 582 583

        _artistNameLabel.hidden = NO;
        _albumNameLabel.hidden = NO;
        _trackNameLabel.hidden = NO;
584
    }
585

586 587
    void (^animationBlock)() = ^() {
        _controllerPanel.alpha = alpha;
588
        _controllerPanelLandscape.alpha = alpha;
589
        _toolbar.alpha = alpha;
590
        _videoFilterView.alpha = alpha;
591
        _playbackSpeedView.alpha = alpha;
592
        _trackSelectorContainer.alpha = alpha;
593
        _equalizerView.alpha = alpha;
594
        _multiSelectionView.alpha = alpha;
595 596
        if (_sleepTimerContainer)
            _sleepTimerContainer.alpha = alpha;
597 598 599 600 601

        CGFloat metaInfoAlpha = _audioOnly ? 1.0f : alpha;
        _artistNameLabel.alpha = metaInfoAlpha;
        _albumNameLabel.alpha = metaInfoAlpha;
        _trackNameLabel.alpha = metaInfoAlpha;
602
    };
603

604
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
605 606
        _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
        _controllerPanelLandscape.hidden = _videoFiltersHidden ? _controlsHidden : NO;
607
        _toolbar.hidden = _controlsHidden;
608
        _videoFilterView.hidden = _videoFiltersHidden;
609
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
610
        _trackSelectorContainer.hidden = YES;
611
        _equalizerView.hidden = YES;
612 613
        if (_sleepTimerContainer)
            _sleepTimerContainer.hidden = YES;
614
        _multiSelectionView.hidden = YES;
615 616 617 618

        _artistNameLabel.hidden = _audioOnly ? NO : _controlsHidden;
        _albumNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
        _trackNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
619
    };
620

621 622
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
623 624

    [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
625
    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
626

627
    _volumeView.hidden = _volumeViewLandscape.hidden = _controllerPanel.hidden;
628
}
629

630
- (void)toggleControlsVisible
631
{
632 633 634 635 636
    if (!_trackSelectorContainer.hidden) {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:YES];
    }

637 638 639
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

640
    [self setControlsHidden:!_controlsHidden animated:YES];
641 642
}

643
- (void)_resetIdleTimer
644 645
{
    if (!_idleTimer)
646
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
647 648 649 650 651
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
652 653
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
654 655 656 657 658 659 660
    }
}

- (void)idleTimerExceeded
{
    _idleTimer = nil;
    if (!_controlsHidden)
661
        [self toggleControlsVisible];
662

663 664 665
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

666 667 668
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

669 670 671
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
672 673
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
674 675
}

676 677
- (UIResponder *)nextResponder
{
678
    [self _resetIdleTimer];
679 680 681
    return [super nextResponder];
}

682 683 684 685
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
686
    LOCKCHECK;
687
    [[UIApplication sharedApplication] sendAction:@selector(closeFullscreenPlayback) to:nil from:self forEvent:nil];
688

689
}
690 691
- (IBAction)positionSliderAction:(UISlider *)sender
{
692 693
    LOCKCHECK;

694 695 696
    /* we need to limit the number of events sent by the slider, since otherwise, the user
     * wouldn't see the I-frames when seeking on current mobile devices. This isn't a problem
     * within the Simulator, but especially on older ARMv7 devices, it's clearly noticeable. */
697
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
698
    if (_mediaDuration > 0) {
699 700
        VLCTime *newPosition = [VLCTime timeWithInt:(int)(sender.value * _mediaDuration)];
        [self.timeNavigationTitleView.timeDisplayButton setTitle:newPosition.stringValue forState:UIControlStateNormal];
701
        [self.timeNavigationTitleView setNeedsLayout];
702
        self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
703 704
        _positionSet = NO;
    }
705
    [self _resetIdleTimer];
706 707
}

708 709 710
- (void)_setPositionForReal
{
    if (!_positionSet) {
711 712
        self.playbackController.mediaPlayer.position = self.timeNavigationTitleView.positionSlider.value;
        [self.playbackController setNeedsMetadataUpdate];
713 714 715 716
        _positionSet = YES;
    }
}

717 718
- (IBAction)positionSliderTouchDown:(id)sender
{
719 720
    LOCKCHECK;

721 722
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
723
    _isScrubbing = YES;
724 725 726 727
}

- (IBAction)positionSliderTouchUp:(id)sender
{
728 729
    LOCKCHECK;

730
    self.scrubIndicatorView.hidden = YES;
731
    _isScrubbing = NO;
732 733 734 735
}

- (void)_updateScrubLabel
{
736
    float speed = self.timeNavigationTitleView.positionSlider.scrubbingSpeed;
737
    if (speed == 1.)
738
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", nil);
739
    else if (speed == .5)
740
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", nil);
741
    else if (speed == .25)
742
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", nil);
743
    else
744
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_FINE", nil);
745 746

    [self _resetIdleTimer];
747 748 749 750
}

- (IBAction)positionSliderDrag:(id)sender
{
751 752
    LOCKCHECK;

753 754 755
    [self _updateScrubLabel];
}

756 757
- (IBAction)volumeSliderAction:(id)sender
{
758 759
    LOCKCHECK;

760 761 762
    [self _resetIdleTimer];
}

763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791
- (void)playPauseLongPress:(UILongPressGestureRecognizer *)recognizer
{
    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan:
        {   UIImage *image = [UIImage imageNamed:@"stopIcon"];
            [_playPauseButton setImage:image forState:UIControlStateNormal];
            [_playPauseButtonLandscape setImage:image forState:UIControlStateNormal];
        }
            break;
        case UIGestureRecognizerStateEnded:
            [self.playbackController stopPlayback];
            break;
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateFailed:
            [self updatePlayPauseButton];
            break;
        default:
            break;
    }
}

- (void)updatePlayPauseButton
{
    const BOOL isPlaying = self.playbackController.isPlaying;
    UIImage *playPauseImage = isPlaying ? [UIImage imageNamed:@"pauseIcon"] : [UIImage imageNamed:@"playIcon"];
    [_playPauseButton setImage:playPauseImage forState:UIControlStateNormal];
    [_playPauseButtonLandscape setImage:playPauseImage forState:UIControlStateNormal];
}

792 793
#pragma mark - playback controller delegation

794 795 796 797 798 799 800 801
- (VLCPlaybackController *)playbackController
{
    if (!_playbackController) {
        _playbackController = [VLCPlaybackController sharedInstance];
    }
    return _playbackController;
}

802
- (void)playbackPositionUpdated:(VLCPlaybackController *)controller
803
{
804
    VLCMediaPlayer *mediaPlayer = controller.mediaPlayer;
805
    if (!_isScrubbing) {
806
        self.timeNavigationTitleView.positionSlider.value = [mediaPlayer position];
807 808
    }

809
    if (_displayRemainingTime)
810
        [self.timeNavigationTitleView.timeDisplayButton setTitle:[[mediaPlayer remainingTime] stringValue] forState:UIControlStateNormal];
811
    else
812
        [self.timeNavigationTitleView.timeDisplayButton setTitle:[[mediaPlayer time] stringValue] forState:UIControlStateNormal];
813
    [self.timeNavigationTitleView setNeedsLayout];
814 815
}

816
- (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
817
{
818
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
819 820 821
    self.timeNavigationTitleView.positionSlider.value = 0.;
    [self.timeNavigationTitleView.timeDisplayButton setTitle:@"" forState:UIControlStateNormal];
    self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = @"";
822 823 824 825 826
    if (![[UIDevice currentDevice] hasExternalDisplay])
        self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
    [_equalizerView reloadData];

    float playbackRate = controller.playbackRate;
827 828
    self.playbackSpeedSlider.value = log2(playbackRate);
    self.playbackSpeedIndicator.text = [NSString stringWithFormat:@"%.2fx", playbackRate];
829

830 831
    float audioDelay = controller.audioDelay;
    self.audioDelaySlider.value = audioDelay;