After a long while of headscratching, I managed to put together a video decoder for format MP4, however I am quite far off of what it's supposed to look like.
The bright blue image is what it's currently looking like, and the regular one is what it's supposed to look like. I don't know where it goes wrong in terms of decoding, but I am 90% sure it has to do with how I decode the video.
public class VideoPlayer extends Screen {
private final File videoFile;
private final BlockingQueue<NativeImage> frameQueue = new LinkedBlockingQueue<>(100);
private DynamicTexture dynamicTexture;
private ResourceLocation textureLocation;
public VideoPlayer(ResourceLocation resourceLocation) {
super(Component.literal("Video player"));
this.videoFile = this.convertToFile(resourceLocation);
new Thread(this::decodeFrames).start();
}
@Override
public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
NativeImage frame;
while ((frame = this.frameQueue.poll()) != null) {
if (this.dynamicTexture == null) {
this.dynamicTexture = new DynamicTexture(frame);
this.textureLocation = Minecraft.getInstance().getTextureManager().register("video_player_texture", this.dynamicTexture);
} else {
this.dynamicTexture.setPixels(frame);
this.dynamicTexture.upload();
}
frame.close();
}
if (this.dynamicTexture != null && this.textureLocation != null) {
NativeImage pixels = this.dynamicTexture.getPixels();
if (pixels != null) {
guiGraphics.blit(this.textureLocation, 0, 0, this.width, this.height, 0, 0, pixels.getWidth(), pixels.getHeight(), pixels.getWidth(), pixels.getHeight());
}
}
}
@Override
public void onClose() {
if (this.dynamicTexture != null) {
this.dynamicTexture.close();
this.dynamicTexture = null;
}
this.frameQueue.forEach(NativeImage::close);
this.frameQueue.clear();
super.onClose();
}
private void decodeFrames() {
try {
FrameGrab frameGrab = FrameGrab.createFrameGrab(NIOUtils.readableChannel(this.videoFile));
Picture picture;
while ((picture = frameGrab.getNativeFrame()) != null) {
int width = picture.getWidth();
int height = picture.getHeight();
NativeImage nativeImage = new NativeImage(width, height, false);
byte[] rPlane = picture.getPlaneData(0);
byte[] gPlane = picture.getPlaneData(1);
byte[] bPlane = picture.getPlaneData(2);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int idx = y * width + x;
int r = rPlane[idx] & 0xFF;
int g = gPlane[idx] & 0xFF;
int b = bPlane[idx] & 0xFF;
int argb = (255 << 24) | (r << 16) | (g << 8) | b;
nativeImage.setPixelRGBA(x, y, argb);
}
}
this.frameQueue.put(nativeImage);
}
} catch (JCodecException | IOException | InterruptedException ignored) {
throw new RuntimeException("There was an error playing the video!");
}
}
}