diff --git a/src/containers/audio-selector.jsx b/src/containers/audio-selector.jsx
index 30ef003a96b1edbd9e25cca848e8ad2b5985b25c..7f2b1e2b28581f726d24cbe153e82446a9055fef 100644
--- a/src/containers/audio-selector.jsx
+++ b/src/containers/audio-selector.jsx
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
 import bindAll from 'lodash.bindall';
 import AudioSelectorComponent from '../components/audio-trimmer/audio-selector.jsx';
 import {getEventXY} from '../lib/touch-utils';
+import DragRecognizer from '../lib/drag-recognizer';
 
 const MIN_LENGTH = 0.01;
 const MIN_DURATION = 500;
@@ -27,6 +28,19 @@ class AudioSelector extends React.Component {
         };
 
         this.clickStartTime = 0;
+
+        this.trimStartDragRecognizer = new DragRecognizer({
+            onDrag: this.handleTrimStartMouseMove,
+            onDragEnd: this.handleTrimStartMouseUp,
+            touchDragAngle: 90,
+            distanceThreshold: 0
+        });
+        this.trimEndDragRecognizer = new DragRecognizer({
+            onDrag: this.handleTrimEndMouseMove,
+            onDragEnd: this.handleTrimEndMouseUp,
+            touchDragAngle: 90,
+            distanceThreshold: 0
+        });
     }
     componentWillReceiveProps (newProps) {
         if (newProps.trimStart === this.props.trimStart) return;
@@ -39,22 +53,18 @@ class AudioSelector extends React.Component {
         this.props.onSetTrim(null, null);
     }
     handleNewSelectionMouseDown (e) {
-        this.initialX = getEventXY(e).x;
         const {width, left} = this.containerElement.getBoundingClientRect();
-        this.initialTrimEnd = (this.initialX - left) / width;
+        this.initialTrimEnd = (getEventXY(e).x - left) / width;
         this.initialTrimStart = this.initialTrimEnd;
         this.props.onSetTrim(this.initialTrimStart, this.initialTrimEnd);
 
         this.clickStartTime = Date.now();
 
-        window.addEventListener('mousemove', this.handleTrimEndMouseMove);
-        window.addEventListener('mouseup', this.handleTrimEndMouseUp);
-        window.addEventListener('touchmove', this.handleTrimEndMouseMove);
-        window.addEventListener('touchend', this.handleTrimEndMouseUp);
+        this.containerSize = width;
+        this.trimEndDragRecognizer.start(e);
     }
-    handleTrimStartMouseMove (e) {
-        const containerSize = this.containerElement.getBoundingClientRect().width;
-        const dx = (getEventXY(e).x - this.initialX) / containerSize;
+    handleTrimStartMouseMove (currentOffset, initialOffset) {
+        const dx = (currentOffset.x - initialOffset.x) / this.containerSize;
         const newTrim = Math.max(0, Math.min(1, this.initialTrimStart + dx));
         if (newTrim > this.initialTrimEnd) {
             this.setState({
@@ -66,11 +76,9 @@ class AudioSelector extends React.Component {
                 trimStart: newTrim
             });
         }
-        e.preventDefault();
     }
-    handleTrimEndMouseMove (e) {
-        const containerSize = this.containerElement.getBoundingClientRect().width;
-        const dx = (getEventXY(e).x - this.initialX) / containerSize;
+    handleTrimEndMouseMove (currentOffset, initialOffset) {
+        const dx = (currentOffset.x - initialOffset.x) / this.containerSize;
         const newTrim = Math.min(1, Math.max(0, this.initialTrimEnd + dx));
         if (newTrim < this.initialTrimStart) {
             this.setState({
@@ -82,20 +90,11 @@ class AudioSelector extends React.Component {
                 trimEnd: newTrim
             });
         }
-        e.preventDefault();
     }
     handleTrimStartMouseUp () {
-        window.removeEventListener('mousemove', this.handleTrimStartMouseMove);
-        window.removeEventListener('mouseup', this.handleTrimStartMouseUp);
-        window.removeEventListener('touchmove', this.handleTrimStartMouseMove);
-        window.removeEventListener('touchend', this.handleTrimStartMouseUp);
         this.props.onSetTrim(this.state.trimStart, this.state.trimEnd);
     }
     handleTrimEndMouseUp () {
-        window.removeEventListener('mousemove', this.handleTrimEndMouseMove);
-        window.removeEventListener('mouseup', this.handleTrimEndMouseUp);
-        window.removeEventListener('touchmove', this.handleTrimEndMouseMove);
-        window.removeEventListener('touchend', this.handleTrimEndMouseUp);
         // If the selection was made quickly (tooFast) and is small (tooShort),
         // deselect instead. This allows click-to-deselect even if you drag
         // a little bit by accident. It also allows very quickly making a
@@ -109,23 +108,17 @@ class AudioSelector extends React.Component {
         }
     }
     handleTrimStartMouseDown (e) {
-        this.initialX = getEventXY(e).x;
+        this.containerSize = this.containerElement.getBoundingClientRect().width;
+        this.trimStartDragRecognizer.start(e);
         this.initialTrimStart = this.props.trimStart;
         this.initialTrimEnd = this.props.trimEnd;
-        window.addEventListener('mousemove', this.handleTrimStartMouseMove);
-        window.addEventListener('mouseup', this.handleTrimStartMouseUp);
-        window.addEventListener('touchmove', this.handleTrimStartMouseMove);
-        window.addEventListener('touchend', this.handleTrimStartMouseUp);
         e.stopPropagation();
     }
     handleTrimEndMouseDown (e) {
-        this.initialX = getEventXY(e).x;
+        this.containerSize = this.containerElement.getBoundingClientRect().width;
+        this.trimEndDragRecognizer.start(e);
         this.initialTrimEnd = this.props.trimEnd;
         this.initialTrimStart = this.props.trimStart;
-        window.addEventListener('mousemove', this.handleTrimEndMouseMove);
-        window.addEventListener('mouseup', this.handleTrimEndMouseUp);
-        window.addEventListener('touchmove', this.handleTrimEndMouseMove);
-        window.addEventListener('touchend', this.handleTrimEndMouseUp);
         e.stopPropagation();
     }
     storeRef (el) {