Я пытаюсь воспроизвести мультимедиа в javafx, используя MediaView, MediaPlayer и Media. Класс Media, похоже, принимает только источник URL-адреса для создания мультимедиа (который используется для передачи в MediaPlayer). Я хотел бы передать кэшированный массив байтов для создания объекта Media. Это поддерживается?
Мне удалось воспроизвести мультимедиа, используя URL-адрес и файл, но не массив байтов.
Из медиа-документа «Поддерживаются только URI HTTP, HTTPS, FILE и JAR». Итак, вам нужно передать свои байты от провайдера по одному из этих протоколов.
Вы можете запустить встроенный http-сервер и обслуживать его байты по протоколу http.
Или вы можете использовать файловую систему в памяти, обслуживающую байты с использованием файловых или jar-протоколов.
Пример встроенного http-сервера, обслуживающего контент из байтов в памяти.
В примере используются типы изображений mime, но обслуживание других типов контента для мультимедиа будет работать, если входной поток корректен (например, представляет правильно закодированные данные mp3 или mp4).
Просто интересно, как будет выглядеть решение, использующее файловую систему в памяти?
Самый простой вариант — записать байты в файл, а затем передать URI file
конструктору Media
.
public Media toMediaFile(byte[] bytes, String extension) throws IOException {
Objects.requireNonNull(bytes);
if (extension.isBlank())
throw new IllegalArgumentException("blank extension");
var file = Files.createTempFile("jfxmedia", "." + extension);
Files.write(file, bytes, StandardOpenOption.WRITE);
return new Media(file.toUri().toString());
}
Однако, если вы хотите хранить байты мультимедиа в памяти, то я думаю, что самым простым решением будет одно из предложений в ответе Jewelsea : создать встроенный сервер для обслуживания мультимедиа через HTTP. Для этого вы можете использовать модуль jdk.httpserver . Пример использования этого модуля приведен в другом ответе Jewelsea (ссылка на который содержится в их ответе на этот вопрос). Но вот еще один пример, более специфичный для вашего варианта использования.
Обратите внимание, что этот пример сервера не реализован для обработки HTTP-заголовка Range
. Я считаю, что реализация мультимедиа JavaFX будет использовать этот заголовок, по крайней мере, в некоторых случаях. Возможно, вы захотите реализовать эту функциональность на сервере.
Кроме того, возможно, будет проще найти более многофункциональную библиотеку HTTP-сервера и добавить ее в свой проект в качестве зависимости, чем использовать модуль jdk.httpserver
. Хотя последнего должно быть достаточно для простого сервера, который существует только для обслуживания массивов byte[]
через HTTP на локальном хосте.
InMemoryMedia.java
package com.example;
public record InMemoryMedia(byte[] bytes, String contentType) {
public int contentLength() {
return bytes.length;
}
}
InMemoryMediaServer.java
package com.example;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
public class InMemoryMediaServer implements AutoCloseable {
private final HttpServer httpServer;
public InMemoryMediaServer(int port) throws IOException {
var address = new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
httpServer = HttpServer.create(address, 1);
httpServer.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
httpServer.start();
}
public RegistrationKey registerMedia(String path, InMemoryMedia media) {
Objects.requireNonNull(path);
Objects.requireNonNull(media);
var context = httpServer.createContext(path, new InMemoryMediaHandler(media));
return new RegistrationKey(context);
}
@Override
public void close() {
httpServer.stop(0);
}
public class RegistrationKey {
private final AtomicBoolean unregistered = new AtomicBoolean();
private final HttpContext context;
RegistrationKey(HttpContext context) {
this.context = context;
}
public URI uri() {
var addr = httpServer.getAddress();
var scheme = "http";
var host = addr.getAddress().getHostAddress();
int port = addr.getPort();
var path = path();
try {
return new URI(scheme, null, host, port, path, null, null);
} catch (URISyntaxException ex) {
throw new IllegalArgumentException(ex);
}
}
public String path() {
return context.getPath();
}
public void unregister() {
if (unregistered.compareAndSet(false, true)) {
httpServer.removeContext(context);
}
}
}
}
InMemoryMediaHandler.java
package com.example;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
final class InMemoryMediaHandler implements HttpHandler {
private final InMemoryMedia media;
InMemoryMediaHandler(InMemoryMedia media) {
this.media = media;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
switch (exchange.getRequestMethod()) {
case "HEAD" -> {
setResponseHeaders(exchange.getResponseHeaders());
exchange.sendResponseHeaders(200, -1);
}
case "GET" -> {
setResponseHeaders(exchange.getResponseHeaders());
exchange.sendResponseHeaders(200, media.contentLength());
try (var out = exchange.getResponseBody()) {
out.write(media.bytes());
}
}
default -> exchange.sendResponseHeaders(405, -1);
}
}
private void setResponseHeaders(Headers headers) {
headers.set("Accept-Ranges", "none");
headers.set("Content-Type", media.contentType());
headers.set("Content-Length", Integer.toString(media.contentLength()));
}
}
Некоторый код, показывающий, как использовать класс InMemoryMediaServer
.
Main.java
package com.example;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;
public class Main extends Application {
private InMemoryMediaServer server;
private String mediaUri;
@Override
public void init() throws Exception {
server = new InMemoryMediaServer(8888);
var key = server.registerMedia("/test", getInMemoryMedia());
mediaUri = key.uri().toString();
}
private InMemoryMedia getInMemoryMedia() {
throw new UnsupportedOperationException("not implemented"); // TODO: Implement
}
@Override
public void start(Stage primaryStage) {
var media = new Media(mediaUri);
media.setOnError(() -> media.getError().printStackTrace());
var player = new MediaPlayer(media);
player.setAutoPlay(true);
primaryStage.setScene(new Scene(new StackPane(new MediaView(player))));
primaryStage.show();
}
@Override
public void stop() {
server.close();
}
}
Спасибо за интересный материал. Я попробую файловую систему в памяти.