Пример многопоточности с воспроизведением видео с помощью JavaFx

Когда я начал изучать многопоточность Java, я увидел пример того, как она должна использоваться:

при загрузке большого файла (например, изображения, аудиоклипа или видеоклипа) через Интернет, пользователь может не захотеть ждать, пока загрузится весь клип, прежде чем начать воспроизведение.

Это обычная вещь в наше время, как это сделал Youtube.

Поэтому я начинаю реализовывать приложение для воспроизведения видео, чтобы понять его суть.

1.Заголовок HTTP-запроса

Если вы хотите загрузить часть видеофайла, ваш сервер должен поддерживать HTTP-заголовок Range.

В этом примере я храню медиафайл на сервере AWS S3, который поддерживает запрос с заголовком Range.

Образец файла: Sting+-+Shape+of+My+Heart+(Leon).mp4

Вот пример задачи Download, которая расширяет javafx.concurrent.Task

Ввод URL и определение диапазона в String, вывод байта[]

package com.vominh.example.thread.fx.task;

import javafx.concurrent.Task;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class Download extends Task {

    private String url;
    private String range;

    public Download(String url, String range) {
        this.url = url;
        this.range = range;
    }

    @Override
    public byte[] call() throws Exception {
        URL publicUrl = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) publicUrl.openConnection();
        conn.setRequestProperty("Range", "bytes=" + range);
        conn.connect();
        byte[] data;
        try (InputStream inputStream = conn.getInputStream()) {
            data = inputStream.readAllBytes();
        }

        return data;
    }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

2.Как скачивать и играть одновременно

2.1. Получение длины содержимого и вычисление размера фрагмента в байтах

Этот простой фрагмент кода, использующий java.net.HttpURLConnection, позволяет мне получить информацию о файле, не скачивая его.

URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.getContentType();
conn.getContentLengthLong()
Вход в полноэкранный режим Выйти из полноэкранного режима

В зависимости от длины содержимого, я вычисляю размер куска = contentLength/10. Первый загруженный байт[] имеет диапазон: 0-{размер куска}

Например: файл mp4 размером = 20MB (20.000.000 байт).
Размер куска = 20.000.000/10 = 2.000.000 байт.
Первый заголовок диапазона: «bytes=0-2000000»
Заголовок следующего диапазона: «bytes=2000001-4000000»
Заголовок следующего диапазона: «bytes=4000001-6000000»

2.2. Создание файла из первой скачанной части файла byte[]

Когда первая часть файла скачана, я создаю файл в каталоге temp операционной системы.

Еще одна роскошная функция — попытка захвата миниатюры из видеофайла.
Для этого есть библиотека из org.bytedeco.

2.3. Когда произойдет следующая загрузка?

javafx.scene.media.MediaPlay поддерживает слушателя событий каждую секунду, когда медиа файл играет

MediaPlayer player = new MediaPlayer(media);
player.currentTimeProperty().addListener(observable -> {
    // Event fire every 1/4 second
    int currentSecond = (int) player.getCurrentTime().toSeconds();
});
Вход в полноэкранный режим Выход из полноэкранного режима

Основываясь на этом, я отправляю задачу Download, когда количество воспроизведенных файлов = x процентов от загруженных.
x должно < 70% от общего количества загруженных файлов, чтобы видео проигрывалось без паузы.
Этот шаг довольно сложный, потому что он зависит от размера файла, скорости интернета… И его трудно оптимизировать для больших файлов.

Когда новая часть скачана, я просто добавляю byte[] в временный файл, созданный на первом шаге.

3.Информация о потоке

Я поместил некоторые логи в приложения, чтобы увидеть, сколько потоков было создано и что это такое, и вот результат

Главный поток -> создается автоматически при запуске нашей программы, ваш Java код выполняется этим потоком
JavaFX Application Thread -> поток для выполнения задач GUI
pool-2-thread-1 -> поток открыт для загрузки данных

4. Остающиеся проблемы приложения

  • Невозможно возобновить воспроизведение, когда прогресс достигает конца загруженных данных (ошибка сети или точка загрузки не оптимизирована)
  • Seed не поддерживается
  • MediaPlay не полностью поддерживает тип медиа, например, я не смог воспроизвести файл .mkv. См. Поддерживаемые типы медиа здесьИсточник доступен в моем репозитории Github

Оцените статью
Procodings.ru
Добавить комментарий