Multithreading example with video play by JavaFx
Tags: java, javafx, multithreading
Published: 2022-05-30 09:52:00

When start research about Java multithread, I saw an example how it should use:

> when downloading a large file (e.g., an image, an audio clip or a video clip) over the Internet, the user may not want to wait until the entire clip downloads before starting the playback.

It just normal thing nowadays, like what Youtube did

![Youtube allow you to downloading and play at the same time
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1kzirnklhjg824dgiz7.png)

So i start implement an Video Play app to get understand about it
### 1.Range HTTP request header
You want to download a portion of video file, your server need to support [Range HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range)

In this example, I store media file on AWS S3 server, which one supported Range header request

Sample file: [Sting+-+Shape+of+My+Heart+(Leon).mp4](https://pdminhdev.s3.ap-southeast-1.amazonaws.com/Sting+-+Shape+of+My+Heart+(Leon).mp4)

Here is sample Download task that extend javafx.concurrent.Task

Input URL and range define in String, output a byte[]
```java
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.How to download and play at the sametime
#### 2.1. Get content-length and calculate chunk size in byte
This simple snippet code use java.net.HttpURLConnection allow me to get file information without download it
```java
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.getContentType();
conn.getContentLengthLong()
```
Depends on content-length, I calculate chunk size = contentLength/10. First download byte[] has range: 0-{chunk size}

Ex: a mp4 file with size = 20MB (20.000.000 Byte)
Chunk size = 20.000.000/10 = 2.000.000 Byte
First Range header: "bytes=0-2000000"
Next Range header: "bytes=2000001-4000000"
Next Range header: "bytes=4000001-6000000"

#### 2.2. Create file from first download byte[]
When first part of file downloaded, I create a file to temp directory of Operation System

One more luxury feature is try to grab thumbnail from video file
There's a library from org.bytedeco support to do it.
![Load button calculate and download first part of file, extract thumbnail and setup javafx.scene.media.MediaPlay ready to play
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3kxpnqyvmv7nckpkqv7q.png)

#### 2.3. When next download happen?
javafx.scene.media.MediaPlay support event listener every second when media file playing
```java
MediaPlayer player = new MediaPlayer(media);
player.currentTimeProperty().addListener(observable -> {
    // Event fire every 1/4 second
    int currentSecond = (int) player.getCurrentTime().toSeconds();
});
```
Base on this, I submit Download task when played amount = x percent of downloaded
x should < 70% of total downloaded to make video play without pause.
This step is quite complex cause it depends on file size, internet speed... And it hard to optimize for large file

When new part downloaded, I just append byte[] to the temp file created on first step

![The next download point](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0lifa5083nnqbpdc2hak.png)
 
### 3.Thread information
I put some log in applications to see how many threads were created and what it is, and here is result
![Three threads created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5egly2b9mxxrkn5ndk9.png)

**Main thread** -> created automatically when our program is started, your Java code execute by this thread
**JavaFX Application Thread** -> Thread to perform GUI tasks
**pool-2-thread-1** -> Thread open to download data 
### 4. Remaining issues of the app
- Can not resume play when progress rearch the end of downloaded data (network error or download point not optimize)
- Seed not support
- MediaPlay not fully support media type, for example I could not play .mkv file. See Supported media type here
Source is available at my [Github repo](https://github.com/docvominh/java/tree/main/java-fx-video-play)