diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt
index 6d949c89a656febc667b1ad38050caf0fe22b166..1b86fb0a777e13ea0ca9057a247dcb1961f389df 100644
--- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt
+++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt
@@ -67,7 +67,7 @@ class TutorialFragment : MainFragment() {
 
             addView(createHeader("Initial setup"))
             initialSetupVideos.forEach {
-                addView(createTutorialPill(R.drawable.ic_movie, it.name).apply {
+                addView(createTutorialPill(R.drawable.ic_movie, it.name, it.description).apply {
                     onClick.subscribe {
                         fragment.navigate<VideoDetailFragment>(it)
                     }
@@ -76,7 +76,7 @@ class TutorialFragment : MainFragment() {
 
             addView(createHeader("Features"))
             featuresVideos.forEach {
-                addView(createTutorialPill(R.drawable.ic_movie, it.name).apply {
+                addView(createTutorialPill(R.drawable.ic_movie, it.name, it.description).apply {
                     onClick.subscribe {
                         fragment.navigate<VideoDetailFragment>(it)
                     }
@@ -95,10 +95,11 @@ class TutorialFragment : MainFragment() {
             }
         }
 
-        private fun createTutorialPill(iconPrefix: Int, t: String): WidePillButton {
+        private fun createTutorialPill(iconPrefix: Int, t: String, d: String): WidePillButton {
             return WidePillButton(context).apply {
                 setIconPrefix(iconPrefix)
                 setText(t)
+                setDescription(d)
                 setIconSuffix(R.drawable.ic_play_notif)
                 layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
                     setMargins(15.dp(resources), 0, 15.dp(resources), 12.dp(resources))
@@ -163,7 +164,7 @@ class TutorialFragment : MainFragment() {
             TutorialVideo(
                 uuid = "3b99ebfe-2640-4643-bfe0-a0cf04261fc5",
                 name = "Getting started",
-                description = "Learn how to get started with Grayjay.",
+                description = "Learn how to get started with Grayjay. How do you install plugins?",
                 thumbnailUrl = "https://releases.grayjay.app/tutorials/getting-started.jpg",
                 videoUrl = "https://releases.grayjay.app/tutorials/getting-started.mp4",
                 duration = 50
@@ -171,7 +172,7 @@ class TutorialFragment : MainFragment() {
             TutorialVideo(
                 uuid = "793aa009-516c-4581-b82f-a8efdfef4c27",
                 name = "Is Grayjay free?",
-                description = "Learn how Grayjay is monetized.",
+                description = "Learn how Grayjay is monetized. How do we make money?",
                 thumbnailUrl = "https://releases.grayjay.app/tutorials/pay.jpg",
                 videoUrl = "https://releases.grayjay.app/tutorials/pay.mp4",
                 duration = 52
@@ -182,7 +183,7 @@ class TutorialFragment : MainFragment() {
             TutorialVideo(
                 uuid = "d2238d88-4252-4a91-a12d-b90c049bb7cf",
                 name = "Searching",
-                description = "Learn about searching in Grayjay.",
+                description = "Learn about searching in Grayjay. How can I find channels, videos or playlists?",
                 thumbnailUrl = "https://releases.grayjay.app/tutorials/search.jpg",
                 videoUrl = "https://releases.grayjay.app/tutorials/search.mp4",
                 duration = 39
@@ -198,10 +199,18 @@ class TutorialFragment : MainFragment() {
             TutorialVideo(
                 uuid = "94d36959-e3fc-4c24-a988-89147067a179",
                 name = "Casting",
-                description = "Learn about casting in Grayjay.",
+                description = "Learn about casting in Grayjay. How do I show video on my TV?",
                 thumbnailUrl = "https://releases.grayjay.app/tutorials/how-to-cast.jpg",
                 videoUrl = "https://releases.grayjay.app/tutorials/how-to-cast.mp4",
                 duration = 79
+            ),
+            TutorialVideo(
+                uuid = "5128c2e3-852b-4281-869b-efea2ec82a0e",
+                name = "Monetization",
+                description = "How can I monetize as a creator?",
+                thumbnailUrl = "https://releases.grayjay.app/tutorials/monetization.jpg",
+                videoUrl = "https://releases.grayjay.app/tutorials/monetization.mp4",
+                duration = 47
             )
         )
     }
diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt
index 2fa89c8eef2fdb974c8b83b7031e1582f4a2d878..4f8b4abdbf90838c5965a02d46a2ff9b61972e28 100644
--- a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt
+++ b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt
@@ -11,6 +11,7 @@ import android.util.AttributeSet
 import android.view.GestureDetector
 import android.view.LayoutInflater
 import android.view.MotionEvent
+import android.view.ScaleGestureDetector
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
@@ -24,6 +25,7 @@ import com.futo.platformplayer.R
 import com.futo.platformplayer.Settings
 import com.futo.platformplayer.constructs.Event0
 import com.futo.platformplayer.constructs.Event1
+import com.futo.platformplayer.constructs.Event2
 import com.futo.platformplayer.logging.Logger
 import com.futo.platformplayer.views.others.CircularProgressBar
 import kotlinx.coroutines.CancellationException
@@ -74,10 +76,19 @@ class GestureControlView : LinearLayout {
     private var _fullScreenFactorUp = 1.0f;
     private var _fullScreenFactorDown = 1.0f;
 
+    private var _scaleGestureDetector: ScaleGestureDetector
+    private var _scaleFactor = 1.0f
+    private var _translationX = 0.0f
+    private var _translationY = 0.0f
+    private val _layoutControlsZoom: FrameLayout
+    private val _textZoom: TextView
+
     private val _gestureController: GestureDetectorCompat;
 
     val onSeek = Event1<Long>();
     val onBrightnessAdjusted = Event1<Float>();
+    val onPan = Event2<Float, Float>();
+    val onZoom = Event1<Float>();
     val onSoundAdjusted = Event1<Float>();
     val onToggleFullscreen = Event0();
 
@@ -95,9 +106,27 @@ class GestureControlView : LinearLayout {
         _layoutControlsSound = findViewById(R.id.layout_controls_sound);
         _progressSound = findViewById(R.id.progress_sound);
         _layoutControlsBrightness = findViewById(R.id.layout_controls_brightness);
+        _layoutControlsZoom = findViewById(R.id.layout_controls_zoom)
+        _textZoom = findViewById(R.id.text_zoom)
         _progressBrightness = findViewById(R.id.progress_brightness);
         _layoutControlsFullscreen = findViewById(R.id.layout_controls_fullscreen);
 
+        _scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
+            override fun onScale(detector: ScaleGestureDetector): Boolean {
+                Logger.i(TAG, "onScale _scaleFactor $_scaleFactor sf " + detector.scaleFactor.toString())
+                _scaleFactor = (_scaleFactor + detector.scaleFactor - 1.0f).coerceAtLeast(1.0f).coerceAtMost(5.0f)
+                onZoom.emit(_scaleFactor)
+
+                _translationX = _translationX.coerceAtLeast(-height / _scaleFactor)
+                _translationY = _translationY.coerceAtLeast(-width / _scaleFactor)
+                onPan.emit(_translationX, _translationY)
+
+                _layoutControlsZoom.visibility = View.VISIBLE
+                _textZoom.text = "${String.format("%.1f", _scaleFactor)}x"
+                return true
+            }
+        })
+
         _gestureController = GestureDetectorCompat(context, object : GestureDetector.OnGestureListener {
             override fun onDown(p0: MotionEvent): Boolean { return false; }
             override fun onShowPress(p0: MotionEvent) = Unit;
@@ -107,41 +136,53 @@ class GestureControlView : LinearLayout {
                 if(p0 == null)
                     return false;
 
-                val minDistance = Math.min(width, height)
-                if (_isFullScreen && _adjustingBrightness) {
-                    val adjustAmount = (distanceY * 2) / minDistance;
-                    _brightnessFactor = (_brightnessFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
-                    _progressBrightness.progress = _brightnessFactor;
-                    onBrightnessAdjusted.emit(_brightnessFactor);
-                } else if (_isFullScreen && _adjustingSound) {
-                    val adjustAmount = (distanceY * 2) / minDistance;
-                    _soundFactor = (_soundFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
-                    _progressSound.progress = _soundFactor;
-                    onSoundAdjusted.emit(_soundFactor);
-                } else if (_adjustingFullscreenUp) {
-                    val adjustAmount = (distanceY * 2) / minDistance;
-                    _fullScreenFactorUp = (_fullScreenFactorUp + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
-                    _layoutControlsFullscreen.alpha = _fullScreenFactorUp;
-                } else if (_adjustingFullscreenDown) {
-                    val adjustAmount = (-distanceY * 2) / minDistance;
-                    _fullScreenFactorDown = (_fullScreenFactorDown + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
-                    _layoutControlsFullscreen.alpha = _fullScreenFactorDown;
-                } else {
-                    val rx = (p0.x + p1.x) / (2 * width);
-                    val ry = (p0.y + p1.y) / (2 * height);
-                    if (ry > 0.1 && ry < 0.9) {
-                        if (Settings.instance.gestureControls.brightnessSlider && _isFullScreen && rx < 0.2) {
-                            startAdjustingBrightness();
-                        } else if (Settings.instance.gestureControls.volumeSlider && _isFullScreen && rx > 0.8) {
-                            startAdjustingSound();
-                        } else if (Settings.instance.gestureControls.toggleFullscreen && fullScreenGestureEnabled && rx in 0.3..0.7) {
-                            if (_isFullScreen) {
-                                startAdjustingFullscreenDown();
-                            } else {
-                                startAdjustingFullscreenUp();
+                Logger.i(TAG, "p0.pointerCount: " + p0.pointerCount)
+
+                if (p1.pointerCount == 1) {
+                    val minDistance = Math.min(width, height)
+                    if (_isFullScreen && _adjustingBrightness) {
+                        val adjustAmount = (distanceY * 2) / minDistance;
+                        _brightnessFactor = (_brightnessFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
+                        _progressBrightness.progress = _brightnessFactor;
+                        onBrightnessAdjusted.emit(_brightnessFactor);
+                    } else if (_isFullScreen && _adjustingSound) {
+                        val adjustAmount = (distanceY * 2) / minDistance;
+                        _soundFactor = (_soundFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
+                        _progressSound.progress = _soundFactor;
+                        onSoundAdjusted.emit(_soundFactor);
+                    } else if (_adjustingFullscreenUp) {
+                        val adjustAmount = (distanceY * 2) / minDistance;
+                        _fullScreenFactorUp = (_fullScreenFactorUp + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
+                        _layoutControlsFullscreen.alpha = _fullScreenFactorUp;
+                    } else if (_adjustingFullscreenDown) {
+                        val adjustAmount = (-distanceY * 2) / minDistance;
+                        _fullScreenFactorDown = (_fullScreenFactorDown + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
+                        _layoutControlsFullscreen.alpha = _fullScreenFactorDown;
+                    } else if (p0.pointerCount == 1) {
+                        val rx = (p0.x + p1.x) / (2 * width);
+                        val ry = (p0.y + p1.y) / (2 * height);
+                        if (ry > 0.1 && ry < 0.9) {
+                            if (Settings.instance.gestureControls.brightnessSlider && _isFullScreen && rx < 0.2) {
+                                startAdjustingBrightness();
+                            } else if (Settings.instance.gestureControls.volumeSlider && _isFullScreen && rx > 0.8) {
+                                startAdjustingSound();
+                            } else if (Settings.instance.gestureControls.toggleFullscreen && fullScreenGestureEnabled && rx in 0.3..0.7) {
+                                if (_isFullScreen) {
+                                    startAdjustingFullscreenDown();
+                                } else {
+                                    startAdjustingFullscreenUp();
+                                }
                             }
                         }
                     }
+                } else if (_isFullScreen) {
+                    stopAllGestures()
+
+                    _translationX = (_translationX - distanceX).coerceAtLeast(-height / _scaleFactor)
+                    _translationY = (_translationY - distanceY).coerceAtLeast(-width / _scaleFactor)
+
+                    Logger.i(TAG, "onPan " + _translationX.toString() + ", " + _translationY.toString())
+                    onPan.emit(_translationX, _translationY)
                 }
 
                 return true;
@@ -227,9 +268,14 @@ class GestureControlView : LinearLayout {
             stopAdjustingFullscreenDown();
         }
 
+        if (_layoutControlsZoom.visibility == View.VISIBLE && ev.action == MotionEvent.ACTION_UP) {
+            _layoutControlsZoom.visibility = View.GONE
+        }
+
         startHideJobIfNecessary();
 
         _gestureController.onTouchEvent(ev)
+        _scaleGestureDetector.onTouchEvent(ev)
         return true;
     }
 
@@ -562,6 +608,12 @@ class GestureControlView : LinearLayout {
     }
 
     fun setFullscreen(isFullScreen: Boolean) {
+        _scaleFactor = 1.0f
+        onZoom.emit(_scaleFactor)
+        _translationX = 0f
+        _translationY = 0f
+        onPan.emit(_translationX, _translationY)
+
         if (isFullScreen) {
             val c = context
             if (c is Activity && Settings.instance.gestureControls.useSystemBrightness) {
diff --git a/app/src/main/java/com/futo/platformplayer/views/pills/WidePillButton.kt b/app/src/main/java/com/futo/platformplayer/views/pills/WidePillButton.kt
index 3be38af64c0ec3fc6e2d441e248ec28de96e2718..7dd73a685526e0ffa0bf9372f92049115eb3cea0 100644
--- a/app/src/main/java/com/futo/platformplayer/views/pills/WidePillButton.kt
+++ b/app/src/main/java/com/futo/platformplayer/views/pills/WidePillButton.kt
@@ -14,6 +14,7 @@ class WidePillButton : LinearLayout {
     private val _iconPrefix: ImageView
     private val _iconSuffix: ImageView
     private val _text: TextView
+    private val _textDescription: TextView
     val onClick = Event0()
 
     constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
@@ -21,11 +22,13 @@ class WidePillButton : LinearLayout {
         _iconPrefix = findViewById(R.id.image_prefix)
         _iconSuffix = findViewById(R.id.image_suffix)
         _text = findViewById(R.id.text)
+        _textDescription = findViewById(R.id.text_description)
 
         val attrArr = context.obtainStyledAttributes(attrs, R.styleable.WidePillButton, 0, 0)
         setIconPrefix(attrArr.getResourceId(R.styleable.WidePillButton_widePillIconPrefix, -1))
         setIconSuffix(attrArr.getResourceId(R.styleable.WidePillButton_widePillIconSuffix, -1))
-        setText(attrArr.getText(R.styleable.PillButton_pillText) ?: "")
+        setText(attrArr.getText(R.styleable.WidePillButton_widePillText) ?: "")
+        setDescription(attrArr.getText(R.styleable.WidePillButton_widePillDescription))
         attrArr.recycle()
 
         findViewById<LinearLayout>(R.id.root).setOnClickListener {
@@ -54,4 +57,13 @@ class WidePillButton : LinearLayout {
     fun setText(t: CharSequence) {
         _text.text = t
     }
+
+    fun setDescription(t: CharSequence?) {
+        if (!t.isNullOrEmpty()) {
+            _textDescription.visibility = View.VISIBLE
+            _textDescription.text = t
+        } else {
+            _textDescription.visibility= View.GONE
+        }
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt
index 2c7020260af5a8debfef3b2694ff657d9afd24f0..b93af720f95ed913d8a70449c2e0acd2f51ad628 100644
--- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt
+++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt
@@ -263,6 +263,14 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
                 }
             }
         };
+        gestureControl.onPan.subscribe { x, y ->
+            _videoView.translationX = x
+            _videoView.translationY = y
+        }
+        gestureControl.onZoom.subscribe {
+            _videoView.scaleX = it
+            _videoView.scaleY = it
+        }
 
         if(!isInEditMode) {
             _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
diff --git a/app/src/main/res/drawable/background_pill_description.xml b/app/src/main/res/drawable/background_pill_description.xml
new file mode 100644
index 0000000000000000000000000000000000000000..325dc2e91335e4f1a282bb861e4843455521799c
--- /dev/null
+++ b/app/src/main/res/drawable/background_pill_description.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#2A2A2A" />
+    <corners android:radius="25dp" />
+    <size android:height="20dp" />
+    <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_gesture_controls.xml b/app/src/main/res/layout/view_gesture_controls.xml
index e1e33b51124f99adaeaf713ff01ebaee6caf579d..a358a60aec4013c0b73a5d3f5a566cc0e2accdec 100644
--- a/app/src/main/res/layout/view_gesture_controls.xml
+++ b/app/src/main/res/layout/view_gesture_controls.xml
@@ -152,4 +152,29 @@
             android:textSize="16dp"/>
     </FrameLayout>
 
+    <FrameLayout
+        android:id="@+id/layout_controls_zoom"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:background="@drawable/background_gesture_controls"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp"
+        android:visibility="gone">
+
+        <TextView
+            android:id="@+id/text_zoom"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@font/inter_regular"
+            tools:text="@string/zoom"
+            android:textColor="@color/white"
+            android:textSize="16dp"/>
+    </FrameLayout>
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_wide_pill_button.xml b/app/src/main/res/layout/view_wide_pill_button.xml
index 93d3791ae2932e24309e6c197a7a1b44d2eb4254..75552d2c88c02534123f1f1a6fc16e86fb9556fe 100644
--- a/app/src/main/res/layout/view_wide_pill_button.xml
+++ b/app/src/main/res/layout/view_wide_pill_button.xml
@@ -3,7 +3,7 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="50dp"
+    android:layout_height="wrap_content"
     android:paddingTop="6dp"
     android:paddingBottom="7dp"
     android:paddingStart="7dp"
@@ -21,19 +21,36 @@
         android:scaleType="fitCenter"
         app:srcCompat="@drawable/ic_thumb_up" />
 
-    <TextView
-        android:id="@+id/text"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:textColor="@color/white"
-        android:textSize="16sp"
-        android:gravity="center_vertical"
-        android:fontFamily="@font/inter_light"
-        tools:text="500K" />
-
-    <Space android:layout_height="match_parent"
+    <LinearLayout
         android:layout_width="0dp"
-        android:layout_weight="1" />
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColor="@color/white"
+            android:textSize="16sp"
+            android:maxLines="1"
+            android:ellipsize="end"
+            android:gravity="center_vertical"
+            android:fontFamily="@font/inter_light"
+            tools:text="500K" />
+
+        <TextView
+            android:id="@+id/text_description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColor="#848484"
+            android:textSize="16sp"
+            android:maxLines="2"
+            android:ellipsize="end"
+            android:gravity="center_vertical"
+            android:fontFamily="@font/inter_light"
+            tools:text="500K" />
+    </LinearLayout>
 
     <ImageView
         android:id="@+id/image_suffix"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 64923346fac5b4e3e3e27174478029f928ce8e05..312fb33a031402dc3175f805d029bea5b97b7ba5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -739,6 +739,7 @@
     <string name="do_you_want_to_see_the_tutorials_you_can_find_them_at_any_time_through_the_more_button">Do you want to see the tutorials? You can find them at any time through the more button.</string>
     <string name="add_creator">Add Creators</string>
     <string name="select">Select</string>
+    <string name="zoom">Zoom</string>
     <string-array name="home_screen_array">
         <item>Recommendations</item>
         <item>Subscriptions</item>
diff --git a/app/src/main/res/values/wide_pill_button_attrs.xml b/app/src/main/res/values/wide_pill_button_attrs.xml
index 785a45939634d88c17432f174a7b54a726b24f37..a118a18b80bce966134c3248458e6766bd7b7da2 100644
--- a/app/src/main/res/values/wide_pill_button_attrs.xml
+++ b/app/src/main/res/values/wide_pill_button_attrs.xml
@@ -2,7 +2,8 @@
 <resources>
     <declare-styleable name="WidePillButton">
         <attr name="widePillIconPrefix" format="reference" />
-        <attr name="widePilllText" format="string" />
+        <attr name="widePillText" format="string" />
+        <attr name="widePillDescription" format="string" />
         <attr name="widePillIconSuffix" format="reference" />
     </declare-styleable>
 </resources>
\ No newline at end of file