Skip to content

Beginner Tutorial

LucQuebec edited this page Jul 4, 2026 · 3 revisions

Beginner Tutorial — Build a Download App from Scratch

This tutorial walks you through building a simple Android app that can download videos using yt-dlp-android. No prior experience with the library is required — we'll explain every step.

What you'll build: a single-screen app with a URL field, a Download button, a progress bar, and a status message.


Prerequisites

  • Android Studio installed
  • A basic Android project (File → New → New Project → Empty Views Activity)
  • Java (this tutorial uses Java; the library also works with Kotlin)
  • Android API 24+ target (Android 7.0)

Step 1 — Install the library

1a. Add JitPack to settings.gradle

Open settings.gradle (the one at the root of your project, not the one inside app/) and add the JitPack repository:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }  // ← add this line
    }
}

1b. Add the dependency to app/build.gradle

dependencies {
    implementation 'com.github.ffmpegkit-maintained:yt-dlp-android:1.0.1'
    // ... other dependencies
}

Click Sync Now when Android Studio prompts you.

1c. Add the INTERNET permission to AndroidManifest.xml

<manifest ...>
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifest>

yt-dlp needs internet access to download videos. Without this line, every download will silently fail.


Step 2 — Create the layout

Open res/layout/activity_main.xml and replace its content with:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp">

    <EditText
        android:id="@+id/urlInput"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Paste a video URL here"
        android:inputType="textUri"
        android:singleLine="true" />

    <Button
        android:id="@+id/downloadButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Download" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:max="100"
        android:visibility="gone" />

    <TextView
        android:id="@+id/statusText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textSize="14sp" />

</LinearLayout>

Step 3 — Understand initialization

Before you can download anything, you must call YtDlp.init(context) once when the app starts. This sets up the yt-dlp runtime.

The best place for this is your Application class. If you don't have one, you can also do it in MainActivity.onCreate() — just make sure it runs before any download.

Why does it need to be initialized? The library sets up internal file paths and unpacks its binary on first launch. If you try to download without calling init() first, you'll get a YtDlpException: YtDlp not initialized error.


Step 4 — Write the Activity

Open MainActivity.java and replace its content with this complete working example:

package com.example.mydownloader;  // change this to your actual package name

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import dev.ffmpegkit_maintained.ytdlp.YtDlp;
import dev.ffmpegkit_maintained.ytdlp.YtDlpException;
import dev.ffmpegkit_maintained.ytdlp.YtDlpRequest;
import dev.ffmpegkit_maintained.ytdlp.YtDlpResponse;

import java.io.File;
import java.util.concurrent.Future;

public class MainActivity extends AppCompatActivity {

    private EditText urlInput;
    private Button downloadButton;
    private ProgressBar progressBar;
    private TextView statusText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // --- Initialize yt-dlp ---
        // This must run once before any download.
        // Do it here if you don't have an Application class.
        try {
            YtDlp.init(this);
        } catch (YtDlpException e) {
            // Initialization failed — this is unusual.
            // Log the error and disable the download button.
            e.printStackTrace();
        }

        // --- Wire up the views ---
        urlInput       = findViewById(R.id.urlInput);
        downloadButton = findViewById(R.id.downloadButton);
        progressBar    = findViewById(R.id.progressBar);
        statusText     = findViewById(R.id.statusText);

        downloadButton.setOnClickListener(v -> startDownload());
    }

    private void startDownload() {
        String url = urlInput.getText().toString().trim();

        // Basic validation
        if (url.isEmpty()) {
            statusText.setText("Please paste a URL first.");
            return;
        }

        // --- Prepare the UI ---
        downloadButton.setEnabled(false);   // prevent double-taps
        progressBar.setVisibility(View.VISIBLE);
        progressBar.setProgress(0);
        statusText.setText("Starting download…");

        // --- Choose where to save the file ---
        // getExternalFilesDir() returns a folder in the app's private external storage.
        // No special permission is needed for Android 10+ to write here.
        File outputDir = getExternalFilesDir(null);

        // %(title)s and %(ext)s are yt-dlp placeholders — they are replaced
        // automatically with the video's title and file extension.
        String outputTemplate = outputDir.getAbsolutePath() + "/%(title)s.%(ext)s";

        // --- Build the request ---
        YtDlpRequest request = new YtDlpRequest(url)
                .setOutputTemplate(outputTemplate)
                .addOption("-f", "best[height<=720]");  // download at most 720p

        // --- Start the download ---
        // executeAsync() starts the download on a background thread and
        // returns a Future. The progress callback is called during the download.
        Future<YtDlpResponse> future = YtDlp.executeAsync(request, (progress, etaSeconds, line) -> {
            // ⚠️ This callback runs on a BACKGROUND thread.
            // You must use runOnUiThread() to touch any UI element.
            runOnUiThread(() -> {
                progressBar.setProgress((int) progress);
                statusText.setText(String.format("%.0f%%  •  ETA: %ds", progress, etaSeconds));
            });
        });

        // --- Wait for the download to finish ---
        // future.get() blocks until the download is done, so we run it
        // on yet another background thread so the UI stays responsive.
        new Thread(() -> {
            try {
                YtDlpResponse response = future.get();  // waits here until done

                runOnUiThread(() -> {
                    downloadButton.setEnabled(true);
                    progressBar.setVisibility(View.GONE);

                    if (response.isSuccess()) {
                        statusText.setText("Download complete!\nSaved to: " + outputDir.getAbsolutePath());
                    } else {
                        statusText.setText("Download failed (exit code: " + response.getExitCode() + ").\n"
                                + "Check the URL and try again.");
                    }
                });

            } catch (Exception e) {
                runOnUiThread(() -> {
                    downloadButton.setEnabled(true);
                    progressBar.setVisibility(View.GONE);
                    statusText.setText("Error: " + e.getMessage());
                });
            }
        }).start();
    }
}

Step 5 — Run the app

  1. Connect a physical device or start an emulator (API 24+)
  2. Click Run in Android Studio
  3. Paste any supported video URL into the field (see the 1000+ supported sites)
  4. Tap Download

The file will be saved in /Android/data/com.example.mydownloader/files/ on the device's external storage. You can open it with any file manager.


Going further

Download audio only (MP3)

YtDlpRequest request = new YtDlpRequest(url)
        .setOutputTemplate(outputTemplate)
        .addOption("-f", "bestaudio")
        .addOption("--extract-audio")
        .addOption("--audio-format", "mp3");

Choose a specific quality

// Best available quality
request.addOption("-f", "best");

// Up to 1080p (video + audio merged)
request.addOption("-f", "best[height<=1080]");

// List available formats first (dry run, no download)
request.addOption("-F");
YtDlpResponse formats = YtDlp.execute(request, null);
Log.d("YtDlp", formats.getOutput());  // shows the format table in Logcat

Show a safer filename (avoid special characters)

Some video titles contain characters that are not valid in file paths. Add this option to replace them automatically:

request.addOption("--restrict-filenames");

Or use the video ID instead of the title:

String outputTemplate = outputDir + "/%(id)s.%(ext)s";

Download subtitles

request.addOption("--write-subs")
       .addOption("--sub-lang", "en");  // English subtitles

Common beginner mistakes

❌ Calling YtDlp.execute() from the main thread

// DON'T do this — it will freeze the UI and crash with a NetworkOnMainThreadException
YtDlpResponse response = YtDlp.execute(request, null);  // blocks the UI thread
// DO this instead — runs in the background
Future<YtDlpResponse> future = YtDlp.executeAsync(request, callback);

❌ Updating the UI from inside the progress callback

// DON'T — the callback is on a background thread, this will crash
progressBar.setProgress((int) progress);  // ← crash: only main thread can update UI
// DO — wrap any UI update with runOnUiThread()
runOnUiThread(() -> progressBar.setProgress((int) progress));

❌ Forgetting to call YtDlp.init()

You'll get: YtDlpException: YtDlp not initialized. Call YtDlp.init(context) first.

Call YtDlp.init(context) once before any download, typically in onCreate() or your Application class.

❌ Using a hardcoded path like /sdcard/Movies/

// DON'T — requires WRITE_EXTERNAL_STORAGE permission and may not work on all devices
String path = "/sdcard/Movies/video.mp4";
// DO — use the app's private external directory, no permission needed on Android 10+
String path = getExternalFilesDir(null).getAbsolutePath() + "/%(title)s.%(ext)s";

What's next?

Clone this wiki locally