VLCMovieViewController.m 63.2 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

    UIView *_sleepTimerContainer;
    UIDatePicker *_sleepTimeDatePicker;
93
    NSTimer *_sleepCountDownTimer;
94

95
    NSInteger _mediaDuration;
96 97
}

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

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

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

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

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

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

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

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

149 150
    self.videoFilterView.hidden = YES;
    _videoFiltersHidden = YES;
151
    _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", nil);
152 153
    _hueSlider.accessibilityLabel = _hueLabel.text;
    _hueSlider.isAccessibilityElement = YES;
154
    _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", nil);
155 156
    _contrastSlider.accessibilityLabel = _contrastLabel.text;
    _contrastSlider.isAccessibilityElement = YES;
157
    _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", nil);
158 159
    _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
    _brightnessSlider.isAccessibilityElement = YES;
160
    _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", nil);
161 162
    _saturationSlider.accessibilityLabel = _saturationLabel.text;
    _saturationSlider.isAccessibilityElement = YES;
163
    _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", nil);
164 165
    _gammaSlider.accessibilityLabel = _gammaLabel.text;
    _gammaSlider.isAccessibilityElement = YES;
166
    _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", nil);
167 168
    _playbackSpeedSlider.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedSlider.isAccessibilityElement = YES;
169 170 171 172 173 174
    _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;
175

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

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

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

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

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

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

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

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

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

252 253 254 255 256 257
    _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];
258 259

    _currentPanType = VLCPanTypeNone;
260 261 262 263 264 265 266 267
    _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;
268 269 270 271 272 273
    _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;
274 275 276

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

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

Felix Paul Kühne's avatar
Felix Paul Kühne committed
293 294 295 296 297 298 299 300 301 302 303
    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;
304

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

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

324
    _playerIsSetup = NO;
325

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

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

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

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

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

    [self.view addSubview:_trackSelectorContainer];
365

366 367 368 369 370
    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.)];
    }
371
    _equalizerView.delegate = self.playbackController;
372
    _equalizerView.UIdelegate = self;
373 374 375
    _equalizerView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
    _equalizerView.hidden = YES;
    [self.view addSubview:_equalizerView];
376

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

Felix Paul Kühne's avatar
Felix Paul Kühne committed
382 383 384 385 386 387 388
    _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];
389
    _sleepTimeDatePicker.frame = CGRectMake(0., 0., 300., 162.);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
    _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]];
        }
409
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
410
    _sleepTimeDatePicker.datePickerMode = UIDatePickerModeCountDownTimer;
411
    _sleepTimeDatePicker.minuteInterval = 1;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
412 413 414 415 416
    _sleepTimeDatePicker.minimumDate = [NSDate date];
    _sleepTimeDatePicker.countDownDuration = 1200.;
    [_sleepTimeDatePicker addTarget:self action:@selector(sleepTimerAction:) forControlEvents:UIControlEventValueChanged];

    [self.view addSubview:_sleepTimerContainer];
417 418

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

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

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

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

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

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

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

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

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

455 456
- (void)viewWillLayoutSubviews
{
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
    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;
490
    }
491 492

    _multiSelectionView.frame = multiSelectionFrame;
493 494
}

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

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

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

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

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

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

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

529 530
#pragma mark - controls visibility

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

535 536 537
    if (!_swipeGesturesEnabled)
        return;

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

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

    return YES;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

683 684 685 686
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
687
    LOCKCHECK;
688 689
    [[VLCPlaybackController sharedInstance] stopPlayback];
}
690

691 692 693 694
- (IBAction)minimizePlayback:(id)sender
{
    LOCKCHECK;
    [[UIApplication sharedApplication] sendAction:@selector(closeFullscreenPlayback) to:nil from:self forEvent:nil];
695
}
696

697 698
- (IBAction)positionSliderAction:(UISlider *)sender
{
699 700
    LOCKCHECK;

701 702 703
    /* 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. */
704
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
705
    if (_mediaDuration > 0) {
706 707
        VLCTime *newPosition = [VLCTime timeWithInt:(int)(sender.value * _mediaDuration)];
        [self.timeNavigationTitleView.timeDisplayButton setTitle:newPosition.stringValue forState:UIControlStateNormal];
708
        [self.timeNavigationTitleView setNeedsLayout];
709
        self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
710 711
        _positionSet = NO;
    }
712
    [self _resetIdleTimer];
713 714
}

715 716 717
- (void)_setPositionForReal
{
    if (!_positionSet) {
718 719
        self.playbackController.mediaPlayer.position = self.timeNavigationTitleView.positionSlider.value;
        [self.playbackController setNeedsMetadataUpdate];
720 721 722 723
        _positionSet = YES;
    }
}

724 725
- (IBAction)positionSliderTouchDown:(id)sender
{
726 727
    LOCKCHECK;

728 729
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
730
    _isScrubbing = YES;
731 732 733 734
}

- (IBAction)positionSliderTouchUp:(id)sender
{
735 736
    LOCKCHECK;

737
    self.scrubIndicatorView.hidden = YES;
738
    _isScrubbing = NO;
739 740 741 742
}

- (void)_updateScrubLabel
{
743
    float speed = self.timeNavigationTitleView.positionSlider.scrubbingSpeed;
744
    if (speed == 1.)
745
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", nil);
746
    else if (speed == .5)
747
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", nil);
748
    else if (speed == .25)
749
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", nil);
750
    else
751
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_FINE", nil);
752 753

    [self _resetIdleTimer];
754 755 756 757
}

- (IBAction)positionSliderDrag:(id)sender
{
758 759
    LOCKCHECK;

760 761 762
    [self _updateScrubLabel];
}

763 764
- (IBAction)volumeSliderAction:(id)sender
{
765 766
    LOCKCHECK;

767 768 769
    [self _resetIdleTimer];
}

770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798
- (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];
}

799 800 801 802 803 804 805 806 807 808 809
- (void)updateTimeDisplayButton
{
    VLCMediaPlayer *mediaPlayer = self.playbackController.mediaPlayer;
    UIButton *timeDisplayButton = self.timeNavigationTitleView.timeDisplayButton;
    if (_displayRemainingTime)
        [timeDisplayButton setTitle:[[mediaPlayer remainingTime] stringValue] forState:UIControlStateNormal];
    else
        [timeDisplayButton setTitle:[[mediaPlayer time] stringValue] forState:UIControlStateNormal];
    [self.timeNavigationTitleView setNeedsLayout];
}