Locked History Actions

vlcj/JavaFx2

= JavaFXでの利用2 ==

これも同じく

を参考にし、簡単化、かつ拡張した。ウインドウの大きさに応じてスクリーンも拡縮する。プレイバック位置を示し操作できるスライダを追加。

import java.nio.*;

import com.google.common.eventbus.*;
import com.sun.jna.*;

import javafx.animation.*;
import javafx.application.*;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.*;
import javafx.util.*;
import uk.co.caprica.vlcj.component.*;
import uk.co.caprica.vlcj.discovery.*;
import uk.co.caprica.vlcj.player.direct.*;
import uk.co.caprica.vlcj.player.direct.format.*;

public class Resizable extends Application {

  // "c:/users/admin/desktop/video.mp4"ではだめ、native(Windows)に渡されるため
  private static final String VIDEO_FILE = "c:\\users\\admin\\desktop\\video.mp4";

  // mediaPlayerComponentはGUI階層に加えられず、機能が利用されるだけ。ここで保持しておく必要がある。
  private DirectMediaPlayerComponent mediaPlayerComponent;

  private DirectMediaPlayer mediaPlayer;

  @Override
  public void start(Stage stage) throws Exception {

    // 動画スクリーンを作成する
    final MediaScreen mediaScreen = new MediaScreen();

    // プレイバック位置スライダ
    PositionSlider positionSlider = new PositionSlider();
    positionSlider.eventBus.register(new Object() {
      @Subscribe
      public void accept(Long value) {
        mediaPlayer.setTime(value);
      }
    });

    // 定期的な処理を行うためのタイマー
    TimeInterpolator interpolator = new TimeInterpolator();
    Timeline timer = new Timeline(new KeyFrame(new Duration(200), new EventHandler<ActionEvent>() {
      @Override
      public void handle(ActionEvent event) {
        long time = mediaPlayer.getTime();
        time = interpolator.getCurrentTime(time);
        positionSlider.setTime(time);
      }
    }));

    // メディアプレイヤーコンポーネント
    mediaPlayerComponent = new CanvasPlayerComponent(mediaScreen.getPixelWriter(), new MediaCallback() {
      public void mediaCallback(int width, int height) {
        long length = mediaPlayer.getLength();
        positionSlider.setLength(length);
        mediaScreen.setMediaSize(mediaPlayer, width, height);
      }
    });
    mediaPlayer = mediaPlayerComponent.getMediaPlayer();

    // レイアウト
    BorderPane borderPane = new BorderPane();
    borderPane.setCenter(mediaScreen.getNode());
    positionSlider.setHeight(30);
    borderPane.setBottom(positionSlider.getNode());
    Scene scene = new Scene(borderPane);
    stage.setScene(scene);
    mediaPlayerComponent.getMediaPlayer().prepareMedia(VIDEO_FILE);
    mediaPlayerComponent.getMediaPlayer().start();
    stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
      @Override
      public void handle(WindowEvent event) {
        Platform.exit();
        System.exit(0);
      }
    });
    stage.show();

    // タイマーの開始
    timer.setCycleCount(Timeline.INDEFINITE);
    timer.play();
  }

  /** 動画表示スクリーン */
  public static class MediaScreen {

    Pane pane;
    ImageView imageView;
    WritableImage writableImage;
    private FloatProperty videoSourceRatioProperty;

    public MediaScreen() {
      this.pane = new Pane();

      pane.widthProperty().addListener((observable, oldValue, newValue) -> {
        fitImageViewSize(newValue.floatValue(), (float) pane.getHeight());
      });
      pane.heightProperty().addListener((observable, oldValue, newValue) -> {
        fitImageViewSize((float) pane.getWidth(), newValue.floatValue());
      });

      Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
      int screenWidth = (int) visualBounds.getWidth();
      int screenHeight = (int) visualBounds.getHeight();
      writableImage = new WritableImage(screenWidth, screenHeight);

      imageView = new ImageView(writableImage);
      pane.setPrefSize(screenWidth / 2, screenHeight / 2);

      pane.getChildren().add(imageView);
      videoSourceRatioProperty = new SimpleFloatProperty(0.4f);
      videoSourceRatioProperty.addListener((observable, oldValue, newValue) -> {
        fitImageViewSize((float) pane.getWidth(), (float) pane.getHeight());
      });
    }

    public Node getNode() {
      return pane;
    }

    public PixelWriter getPixelWriter() {
      return writableImage.getPixelWriter();
    }

    public void setMediaSize(DirectMediaPlayer mp, int width, int height) {
      videoSourceRatioProperty.set((float) height / (float) width);
    }

    private void fitImageViewSize(float width, float height) {
      float fitHeight = videoSourceRatioProperty.get() * width;
      if (fitHeight > height) {
        imageView.setFitHeight(height);
        double fitWidth = height / videoSourceRatioProperty.get();
        imageView.setFitWidth(fitWidth);
        imageView.setX((width - fitWidth) / 2);
        imageView.setY(0);
      } else {
        imageView.setFitWidth(width);
        imageView.setFitHeight(fitHeight);
        imageView.setY((height - fitHeight) / 2);
        imageView.setX(0);
      }
    }
  }

  /** プレイバック位置スライダ */
  public static class PositionSlider {
    Slider slider;
    EventBus eventBus = new EventBus();
    boolean setting = false;

    PositionSlider() {
      slider = new Slider();
      slider.valueProperty().addListener((ObservableValue<? extends Number> ov, Number oldValue, Number newValue) -> {
        if (setting)
          return;
        eventBus.post(newValue.longValue());
      });
    }

    public Node getNode() {
      return slider;
    }

    public void setHeight(int height) {
      slider.setPrefHeight(30);
      slider.setMaxHeight(30);
    }

    public void setLength(long length) {
      setting = true;
      try {
        slider.setMax(length);
      } finally {
        setting = false;
      }
    }

    public void setTime(long time) {
      setting = true;
      try {
        slider.setValue(time);
      } finally {
        setting = false;
      }
    }
  }

  /** メディアプレーヤーコンポーネント */
  static private class CanvasPlayerComponent extends DirectMediaPlayerComponent {

    private PixelWriter pixelWriter;
    PixelFormat<ByteBuffer> pixelFormat;

    public CanvasPlayerComponent(PixelWriter pixelWriter, final MediaCallback mediaCallback) {
      super(new BufferFormatCallback() {
        @Override
        public BufferFormat getBufferFormat(int sourceWidth, int sourceHeight) {
          Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
          Platform.runLater(() -> {
            mediaCallback.mediaCallback(sourceWidth, sourceHeight);
          });
          return new RV32BufferFormat((int) visualBounds.getWidth(), (int) visualBounds.getHeight());
        }
      });
      this.pixelWriter = pixelWriter;
      pixelFormat = PixelFormat.getByteBgraPreInstance();
    }

    @Override
    public void display(DirectMediaPlayer mediaPlayer, Memory[] nativeBuffers, BufferFormat bufferFormat) {
      Platform.runLater(() -> {
        Memory nativeBuffer = mediaPlayer.lock()[0];
        try {
          ByteBuffer byteBuffer = nativeBuffer.getByteBuffer(0, nativeBuffer.size());
          pixelWriter.setPixels(0, 0, bufferFormat.getWidth(), bufferFormat.getHeight(), pixelFormat, byteBuffer,
              bufferFormat.getPitches()[0]);
        } finally {
          mediaPlayer.unlock();
        }
      });
    }
  }

  public interface MediaCallback {
    public void mediaCallback(int with, int height);
  }

  /**
   * MediaPlayerの返す「時刻」は荒い値なので補完する。
   * 
   * @see https://github.com/caprica/vlcj/issues/74
   */
  public static class TimeInterpolator {
    private long lastPlayTime = 0;
    private long lastPlayTimeGlobal = 0;

    public long getCurrentTime(long currentTime) {
      if (lastPlayTime == currentTime && lastPlayTime != 0) {
        currentTime += System.currentTimeMillis() - lastPlayTimeGlobal;
      } else {
        lastPlayTime = currentTime;
        lastPlayTimeGlobal = System.currentTimeMillis();
      }
      return currentTime;
    }
  }

  public static void main(String[] args) {
    new NativeDiscovery().discover();
    Application.launch(Resizable.class);
  }

}