Browse Source

Implement special gesture recognizer for Siri Remote which can detect the touch location and select short and long presses
Both the SiriRemote recognizer and the pan gesture recognizer work with the same touches.

Possible improvements to think about:
An improvement in user experience might be to cancel the SiriRemote recognizer instead of updating the touch location if the touch moved to much.
But then we have the problem that the hint does not update to the other location until the user lifts the finger and puts it down again.

Tobias Conradi 9 years ago
parent
commit
258b149bc7

+ 94 - 10
VLC for Apple TV/Playback/VLCFullscreenMovieTVViewController.m

@@ -14,6 +14,7 @@
 #import "VLCPlaybackInfoTVAnimators.h"
 #import "VLCIRTVTapGestureRecognizer.h"
 #import "VLCHTTPUploaderController.h"
+#import "VLCSiriRemoteGestureRecognizer.h"
 
 typedef NS_ENUM(NSInteger, VLCPlayerScanState)
 {
@@ -71,6 +72,7 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
     // Panning and Swiping
 
     UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
+    panGestureRecognizer.delegate = self;
     [self.view addGestureRecognizer:panGestureRecognizer];
 
     // Button presses
@@ -78,10 +80,6 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
     playpauseGesture.allowedPressTypes = @[@(UIPressTypePlayPause)];
     [self.view addGestureRecognizer:playpauseGesture];
 
-    UITapGestureRecognizer *selectTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(selectButtonPressed:)];
-    selectTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeSelect)];
-    [self.view addGestureRecognizer:selectTapGestureRecognizer];
-
     UITapGestureRecognizer *menuTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuButtonPressed:)];
     menuTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
     menuTapGestureRecognizer.delegate = self;
@@ -99,6 +97,12 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
     UITapGestureRecognizer *rightArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIRPressRight)];
     rightArrowRecognizer.allowedPressTypes = @[@(UIPressTypeRightArrow)];
     [self.view addGestureRecognizer:rightArrowRecognizer];
+
+    // Siri remote arrow presses
+    VLCSiriRemoteGestureRecognizer *siriArrowRecognizer = [[VLCSiriRemoteGestureRecognizer alloc] initWithTarget:self action:@selector(handleSiriRemote:)];
+    siriArrowRecognizer.delegate = self;
+    [self.view addGestureRecognizer:siriArrowRecognizer];
+
 }
 
 - (void)didReceiveMemoryWarning
@@ -162,7 +166,7 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
 
     VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
     if (self.transportBar.scrubbing) {
-        [self selectButtonPressed:nil];
+        [self selectButtonPressed];
     } else {
         [vpc playPause];
     }
@@ -195,6 +199,7 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
             return;
         }
     }
+
     [self showPlaybackControlsIfNeededForUserInteraction];
     [self setScanState:VLCPlayerScanStateNone];
 
@@ -222,7 +227,7 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
     [self updateTimeLabelsForScrubbingFraction:scrubbingFraction];
 }
 
-- (void)selectButtonPressed:(UITapGestureRecognizer *)recognizer
+- (void)selectButtonPressed
 {
     [self showPlaybackControlsIfNeededForUserInteraction];
     [self setScanState:VLCPlayerScanStateNone];
@@ -286,18 +291,88 @@ typedef NS_ENUM(NSInteger, VLCPlayerScanState)
     }
 }
 
+- (void)handleSiriRemote:(VLCSiriRemoteGestureRecognizer *)recognizer
+{
+    [self showPlaybackControlsIfNeededForUserInteraction];
+    VLCTransportBarHint hint = self.transportBar.hint;
+    switch (recognizer.state) {
+        case UIGestureRecognizerStateBegan:
+        case UIGestureRecognizerStateChanged:
+            if (recognizer.isLongPress) {
+                if (recognizer.touchLocation == VLCSiriRemoteTouchLocationRight) {
+                    [self setScanState:VLCPlayerScanStateForward2];
+                    return;
+                }
+            } else {
+                switch (recognizer.touchLocation) {
+                    case VLCSiriRemoteTouchLocationLeft:
+                        hint = VLCTransportBarHintJumpBackward10;
+                        break;
+                    case VLCSiriRemoteTouchLocationRight:
+                        hint = VLCTransportBarHintJumpForward10;
+                        break;
+                    default:
+                        hint = VLCTransportBarHintNone;
+                        break;
+                }
+            }
+            break;
+        case UIGestureRecognizerStateEnded:
+            if (recognizer.isClick && !recognizer.isLongPress) {
+                [self handleSiriPressUpAtLocation:recognizer.touchLocation];
+            }
+            [self setScanState:VLCPlayerScanStateNone];
+            break;
+        case UIGestureRecognizerStateCancelled:
+            hint = VLCTransportBarHintNone;
+            [self setScanState:VLCPlayerScanStateNone];
+            break;
+        default:
+            break;
+    }
+    self.transportBar.hint = hint;
+}
+
+- (void)handleSiriPressUpAtLocation:(VLCSiriRemoteTouchLocation)location
+{
+    switch (location) {
+        case VLCSiriRemoteTouchLocationLeft:
+            [self jumpBackward];
+            break;
+        case VLCSiriRemoteTouchLocationRight:
+            [self jumpForward];
+        default:
+            [self selectButtonPressed];
+            break;
+    }
+}
+
 #pragma mark -
 static const NSInteger VLCJumpInterval = 10000; // 10 seconds
 - (void)jumpForward
 {
-    [self jumpInterval:VLCJumpInterval];
+    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
+    VLCMediaPlayer *player = vpc.mediaPlayer;
+
+    if (player.isPlaying) {
+        [player jumpForward:VLCJumpInterval];
+    } else {
+        [self scrubbingJumpInterval:VLCJumpInterval];
+    }
 }
 - (void)jumpBackward
 {
-    [self jumpInterval:-VLCJumpInterval];
+    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
+    VLCMediaPlayer *player = vpc.mediaPlayer;
+
+    if (player.isPlaying) {
+        [player jumpBackward:VLCJumpInterval];
+    } else {
+        [self scrubbingJumpInterval:-VLCJumpInterval];
+    }
 }
 
-- (void)jumpInterval:(NSInteger)interval
+- (void)scrubbingJumpInterval:(NSInteger)interval
 {
     NSInteger duration = [VLCPlaybackController sharedInstance].mediaDuration;
     if (duration==0) {
@@ -351,6 +426,10 @@ static const NSInteger VLCJumpInterval = 10000; // 10 seconds
 
 - (void)setScanState:(VLCPlayerScanState)scanState
 {
+    if (_scanState == scanState) {
+        return;
+    }
+
     if (_scanState == VLCPlayerScanStateNone) {
         self.scanSavedPlaybackRate = @([VLCPlaybackController sharedInstance].playbackRate);
     }
@@ -489,6 +568,8 @@ static const NSInteger VLCJumpInterval = 10000; // 10 seconds
     return _infoViewController;
 }
 
+
+
 #pragma mark - playback controller delegation
 
 - (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
@@ -555,7 +636,10 @@ currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
     }
     return YES;
 }
-
+- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
+{
+    return YES;
+}
 @end
 
 

+ 4 - 1
VLC for Apple TV/Playback/VLCIRTVTapGestureRecognizer.m

@@ -22,7 +22,10 @@
 
 - (BOOL)vlc_isSynthetic
 {
-    /*Attention we are using private API here
+    /*
+     * !!! Attention: We are using private API !!!
+     * !!!  Might break in any future release  !!!
+     *
      * For internal name changes the press might wrongly detected as non-synthetic.
      * Since we us it to filter for only non-synthetic presses arrow taps
      * on the Siri remote might be additionally be detected.

+ 27 - 0
VLC for Apple TV/Playback/VLCSiriRemoteGestureRecognizer.h

@@ -0,0 +1,27 @@
+/*****************************************************************************
+ * VLC for iOS
+ *****************************************************************************
+ * Copyright (c) 2015 VideoLAN. All rights reserved.
+ * $Id$
+ *
+ * Authors: Tobias Conradi <videolan # tobias-conradi.de>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+typedef NS_ENUM(NSInteger, VLCSiriRemoteTouchLocation){
+    VLCSiriRemoteTouchLocationUnknown,
+    VLCSiriRemoteTouchLocationLeft,
+    VLCSiriRemoteTouchLocationRight,
+};
+
+@interface VLCSiriRemoteGestureRecognizer : UIGestureRecognizer
+@property (nonatomic) NSTimeInterval minLongPressDuration; // default = 0.5
+@property (nonatomic, readonly, getter=isLongPress) BOOL longPress;
+@property (nonatomic, readonly, getter=isClick) BOOL click;
+@property (nonatomic, readonly) VLCSiriRemoteTouchLocation touchLocation;
+
+@end
+

+ 143 - 0
VLC for Apple TV/Playback/VLCSiriRemoteGestureRecognizer.m

@@ -0,0 +1,143 @@
+/*****************************************************************************
+ * VLC for iOS
+ *****************************************************************************
+ * Copyright (c) 2015 VideoLAN. All rights reserved.
+ * $Id$
+ *
+ * Authors: Tobias Conradi <videolan # tobias-conradi.de>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+#import "VLCSiriRemoteGestureRecognizer.h"
+#import <UIKit/UIGestureRecognizerSubclass.h>
+
+@interface UIEvent (VLCDigitizerLocation)
+- (CGPoint)vlc_digitizerLocation;
+@end
+
+@interface VLCSiriRemoteGestureRecognizer ()
+{
+	NSTimer *_longPressTimer;
+}
+@end
+
+@implementation VLCSiriRemoteGestureRecognizer
+@dynamic delegate;
+
+- (instancetype)initWithTarget:(id)target action:(SEL)action
+{
+    self = [super initWithTarget:target action:action];
+    if (self) {
+        self.allowedTouchTypes = @[@(UITouchTypeIndirect)];
+        self.allowedPressTypes = @[@(UIPressTypeSelect)];
+		self.minLongPressDuration = 0.5;
+        self.cancelsTouchesInView = NO;
+    }
+    return self;
+}
+- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
+{
+    self.state = UIGestureRecognizerStateBegan;
+    [self updateTouchLocationWithEvent:event];
+}
+- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
+{
+    [self updateTouchLocationWithEvent:event];
+}
+- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
+{
+    [self updateTouchLocation:VLCSiriRemoteTouchLocationUnknown];
+}
+- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
+{
+    [self updateTouchLocation:VLCSiriRemoteTouchLocationUnknown];
+}
+
+- (void)updateTouchLocationWithEvent:(UIEvent *)event
+{
+    CGPoint digitizerLocation = [event vlc_digitizerLocation];
+    VLCSiriRemoteTouchLocation location = VLCSiriRemoteTouchLocationUnknown;
+    if (digitizerLocation.x <= 0.2) {
+        location = VLCSiriRemoteTouchLocationLeft;
+    } else if (0.8 <= digitizerLocation.x) {
+        location = VLCSiriRemoteTouchLocationRight;
+    }
+    [self updateTouchLocation:location];
+}
+
+- (void)updateTouchLocation:(VLCSiriRemoteTouchLocation)location
+{
+	if (_touchLocation == location) {
+		return;
+	}
+
+	_touchLocation = location;
+
+    self.state = UIGestureRecognizerStateChanged;
+}
+
+- (void)reset
+{
+	_click = NO;
+	_touchLocation = VLCSiriRemoteTouchLocationUnknown;
+	_longPress = NO;
+	[_longPressTimer invalidate];
+	_longPressTimer = nil;
+    [super reset];
+}
+
+- (void)longPressTimerFired
+{
+	if (_click && (self.state == UIGestureRecognizerStateBegan || self.state == UIGestureRecognizerStateChanged)) {
+		_longPress = YES;
+        self.state = UIGestureRecognizerStateChanged;
+    }
+}
+
+- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
+{
+	if ([self.allowedPressTypes containsObject:@(presses.anyObject.type)]) {
+		_click = YES;
+		_longPressTimer = [NSTimer scheduledTimerWithTimeInterval:self.minLongPressDuration target:self selector:@selector(longPressTimerFired) userInfo:nil repeats:NO];
+		self.state = UIGestureRecognizerStateChanged;
+	}
+}
+- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
+{
+	self.state = UIGestureRecognizerStateChanged;
+}
+- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
+{
+	self.state = UIGestureRecognizerStateCancelled;
+}
+- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
+{
+	if (_click) {
+		self.state = UIGestureRecognizerStateEnded;
+	}
+}
+@end
+
+
+@implementation UIEvent (VLCDigitizerLocation)
+
+- (CGPoint)vlc_digitizerLocation
+{
+    /*
+     * !!! Attention: We are using private API !!!
+     * !!!  Might break in any future release  !!!
+     *
+     * The digitizer location is the absolut location of the touch on the touch pad.
+     * The location is in a 0,0 (top left) to 1,1 (bottom right) coordinate system.
+     */
+    NSString *key = [@"digitiz" stringByAppendingString:@"erLocation"];
+    NSNumber *value = [self valueForKey:key];
+    if ([value isKindOfClass:[NSValue class]]) {
+        return [value CGPointValue];
+    }
+    // default to center position as undefined position
+    return CGPointMake(0.5,0.5);
+}
+
+@end

+ 1 - 1
VLC for Apple TV/Playback/VLCTransportBar.h

@@ -30,7 +30,7 @@ IB_DESIGNABLE @interface VLCTransportBar : UIView
 @property (nonatomic, readonly) UILabel *markerTimeLabel;
 @property (nonatomic, readonly) UILabel *remainingTimeLabel;
 
--(void)setHint:(VLCTransportBarHint)hint;
+@property (nonatomic) VLCTransportBarHint hint;
 @end
 
 NS_ASSUME_NONNULL_END

+ 1 - 0
VLC for Apple TV/Playback/VLCTransportBar.m

@@ -147,6 +147,7 @@ static inline void sharedSetup(VLCTransportBar *self) {
 }
 - (void)setHint:(VLCTransportBarHint)hint
 {
+    _hint = hint;
     UIImage *leftImage = nil;
     UIImage *rightImage = nil;
 	switch (hint) {

+ 6 - 0
VLC for iOS.xcodeproj/project.pbxproj

@@ -312,6 +312,7 @@
 		DD3EFF5D1BDEBCE500B68579 /* VLCLocalServerDiscoveryController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3EFF2B1BDEBCE500B68579 /* VLCLocalServerDiscoveryController.m */; };
 		DD3EFF5E1BDEBCE500B68579 /* VLCLocalServerDiscoveryController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3EFF2B1BDEBCE500B68579 /* VLCLocalServerDiscoveryController.m */; };
 		DD490B171BE6BA580010F335 /* VLCIRTVTapGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = DD490B161BE6BA580010F335 /* VLCIRTVTapGestureRecognizer.m */; };
+		DD490B1F1BE95B5C0010F335 /* VLCSiriRemoteGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = DD490B1E1BE95B5C0010F335 /* VLCSiriRemoteGestureRecognizer.m */; };
 		DD510B701B14E564003BA71C /* VLCPlayerDisplayController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD510B6F1B14E564003BA71C /* VLCPlayerDisplayController.m */; };
 		DD7110F01AF38B2B00854776 /* MLMediaLibrary+playlist.m in Sources */ = {isa = PBXBuildFile; fileRef = DD7110EF1AF38B2B00854776 /* MLMediaLibrary+playlist.m */; };
 		DD7BA2631B680C8E002D9F54 /* MediaLibraryKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD7BA2601B680C1B002D9F54 /* MediaLibraryKit.framework */; };
@@ -931,6 +932,8 @@
 		DD3EFF2C1BDEBCE500B68579 /* VLCNetworkServerBrowser-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "VLCNetworkServerBrowser-Protocol.h"; sourceTree = "<group>"; };
 		DD490B151BE6BA580010F335 /* VLCIRTVTapGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCIRTVTapGestureRecognizer.h; sourceTree = "<group>"; };
 		DD490B161BE6BA580010F335 /* VLCIRTVTapGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCIRTVTapGestureRecognizer.m; sourceTree = "<group>"; };
+		DD490B1D1BE95B5C0010F335 /* VLCSiriRemoteGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCSiriRemoteGestureRecognizer.h; sourceTree = "<group>"; };
+		DD490B1E1BE95B5C0010F335 /* VLCSiriRemoteGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCSiriRemoteGestureRecognizer.m; sourceTree = "<group>"; };
 		DD510B6E1B14E564003BA71C /* VLCPlayerDisplayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCPlayerDisplayController.h; path = Sources/VLCPlayerDisplayController.h; sourceTree = SOURCE_ROOT; };
 		DD510B6F1B14E564003BA71C /* VLCPlayerDisplayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCPlayerDisplayController.m; path = Sources/VLCPlayerDisplayController.m; sourceTree = SOURCE_ROOT; };
 		DD7110EE1AF38B2B00854776 /* MLMediaLibrary+playlist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MLMediaLibrary+playlist.h"; sourceTree = "<group>"; };
@@ -1737,6 +1740,8 @@
 				7DEC8BDC1BD67899006E1093 /* VLCFullscreenMovieTVViewController.m */,
 				DD490B151BE6BA580010F335 /* VLCIRTVTapGestureRecognizer.h */,
 				DD490B161BE6BA580010F335 /* VLCIRTVTapGestureRecognizer.m */,
+				DD490B1D1BE95B5C0010F335 /* VLCSiriRemoteGestureRecognizer.h */,
+				DD490B1E1BE95B5C0010F335 /* VLCSiriRemoteGestureRecognizer.m */,
 				DD8095FA1BE628800065D8E1 /* Playback Info */,
 			);
 			path = Playback;
@@ -2624,6 +2629,7 @@
 				7D0EDE061BE774BF00363AA1 /* WhiteRaccoon.m in Sources */,
 				7D60696B1BD93AC800AB765C /* VLCDropboxTableViewController.m in Sources */,
 				7D405ED01BEA11C1006ED886 /* VLCHTTPConnection.m in Sources */,
+				DD490B1F1BE95B5C0010F335 /* VLCSiriRemoteGestureRecognizer.m in Sources */,
 				DD3EFF3C1BDEBCE500B68579 /* VLCNetworkServerBrowserVLCMedia.m in Sources */,
 				7D1334801BE132F10012E919 /* VLCNetworkServerBrowserUPnP.m in Sources */,
 				DDEAECC71BDEC79D00756C83 /* VLCLocalNetworkServiceBrowserSAP.m in Sources */,