Multithreading example with video play by JavaFx
Pham Duc Minh
Posted on May 30, 2022
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
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
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
Here is sample Download task that extend javafx.concurrent.Task
Input URL and range define in String, output a byte[]
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
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.
2.3. When next download happen?
javafx.scene.media.MediaPlay support event listener every second when media file playing
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
3.Thread information
I put some log in applications to see how many threads were created and what it is, and here is result
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
Posted on May 30, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.