JavaFX + JS〜Youtube playerを作る〜

javaFXとJSでYoutubePlayerを作ります。イメージとしては、ラズパイ の電源を入れたら、YoutubePlayerを起動、画面に表示して再生するというものを作ります。

JavaFXとは

JavaFXはJavaでの画面作成API→Javaで画面を作ります。

そして、今回はJSも使います。つまりは、JavaからJSを起動します。

疑問点

なんでまたJavaFXを使用するのか?
これは、PCで、ブラウザを使用しないで、Youtubeを観れるようにするためです。「いや、ブラウザを使えよ!」と言われそうですが、これは起動した画面とウェブサーバーを連携させたいという意図があります。

実行環境

ラズベリーバイでこのプログラムを起動しようと考えています。

形的には、ラズパイこらJavaFXを起動、サーバーも起動(この部分は後々)

最後に動画を再生するというイメージです。

具体的には

以下のような手順で実行します。

  1. ラズパイでGithubから最新のソースをPULLする
  2. ビルドして、実行ファイル(JAR)を生成
  3. 生成したJARファイルを実行
  4. Youtubeの再生リストを無限ループで再生

このために用意するものは以下のものになります。

必要な資源

【概要】
  1. javaFXを起動する、Javaのプログラム
  2. YoutubePlayerを起動するためのHTML(+JS)プログラム
  3. プログラムで再生リストを作成するためのCSVファイル
  4. ラズパイの実行時に起動する、シェル(プログラム)

JavaFXプログラム

JavaFXでラズパイ上で起動するためのプログラムを作成します。

使用するテクノロジーは上記の通りJavaFXで、WebViewクラスを使用して、HTMLファイルを画面として表示できるようにします。

public class Main extends Application {
    /** プロパティファイル */
    private Properties prop;
    /** プロパティファイルのキー・セット */
    private Setlt;String> keySet;
    /** 最大ウィンドウサイズ(幅) */
    private double windowWidth;
    /** 最大ウィンドウサイズ(高さ) */
    private int windowHeight;
    /** JSに渡すURLの配列文字列 */
    private String videoIds;

    @Override
    public void start(Stage primaryStage) {
        try {
            // レイアウト(土台)になるペインを作成
            BorderPane root = new BorderPane();

            // WebView(ブラウザ)の作成
            WebView browser = new WebView();
            WebEngine engine = browser.getEngine();
            // JSの起動
            engine.executeScript("var data = " + videoIds + ";");
            // イベントハンドラ
            engine.setOnAlert(event -> System.out.println("Data: " + event.getData()));
//          ChangeListener listener = createDomCntlListener();
//          engine.getLoadWorker().stateProperty().addListener();

            // ロードするURI(ファイルを指定するのでURI)
            String htmlURI = getClass().getResource("iframePlayer.html").toExternalForm();
            System.out.println(htmlURI);
            engine.load(htmlURI);

            root.setCenter(browser);
            // 画面を表示する土台(シーン)を作成
            Scene scene = new Scene(root,this.windowWidth,this.windowHeight);
            // JavaFX用のCSSを読み込む
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setOnCloseRequest(event -> Platform.exit());
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }

    /** コンストラクタ */
    public Main() {
        windowWidth = 0;
        windowHeight = 0;
        // プロパティファイルの読み込み
        prop = new Properties();
        loadProperties();
        // ウィンドウ情報の取得(縦横の幅)
        initWindowInfo();

    }

    /** デバック用 */
    private EventHandlerlt;WebEvent<String>> getAlertEvent() {
        return null;
    }
    /*****************************************
     * 必要な処理を行うメソッド群(JUniテストを行う)*
     *****************************************/

    /**
     * プロパティファイルを読み込む。
     * 初期起動時に設定を読み込むための処理
     */
    public void loadProperties() {
        // java.nio.PathでJavaFxのPathではない
        Path propFile = Paths.get("resources/application.csv");
        final StringBuilder build = new StringBuilder();
        build.append("[");
        try {
            // CSVィファイルの読み込み
            BufferedReader reader = Files.newBufferedReader(propFile);
            String line = null;
            reader.readLine();
            while((line = reader.readLine()) != null) {
                String[] datas = line.split(",");
                build.append("[");
                for(String data : datas) {
                    build.append("\"" + data + "\",");
                }
                build.setLength(build.length() - 1);
                build.append("], ");
            }
            build.setLength(build.length() - 1);
            build.append("]");
            System.out.println("Build: " + build.toString());
            videoIds = build.toString();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("System Error: can not read application.properties");
        }
    }

    /**
     * ロードしたプロパティファイルのキーを返却。
     * @return keySet
     */
    public Setlt;String> getKeySet() {
        return keySet;
    }
    /**
     * application.propertiesから値を取得する。
     * 
     * @param key プロパティキー
     * @return プロパティの値
     */
    public String getProperty(String key) {
        return prop.getProperty(key);
    }

    /**
     * 起動しているデバイスの画面サイズを取得する
     */
    private void initWindowInfo() {
        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Rectangle size = env.getMaximumWindowBounds();
        windowHeight = size.height;
        windowWidth = size.width;
    }

    /**
     * @return the windowWidth
     */
    public double getWindowWidth() {
        return windowWidth;
    }

    /**
     * @param windowWidth the windowWidth to set
     */
    public void setWindowWidth(double windowWidth) {
        this.windowWidth = windowWidth;
    }

    /**
     * @return the windowHeight
     */
    public int getWindowHeight() {
        return windowHeight;
    }

    /**
     * @param windowHeight the windowHeight to set
     */
    public void setWindowHeight(int windowHeight) {
        this.windowHeight = windowHeight;
    }
}

Javaコード解説

クラス定義

public class Main extends Application {

JavaFXのフレームワーク、クラス「javafx.application.Application」を継承して実装します。

このApplicationクラスを継承して、startメソッドをオーバーライドします。

@Override
public void start(Stage primaryStage)

こうすることで、画面を起動した時に内容を定義(表示)することができます。

なので実装のメインになるのはpublic start(Stage primaryStage)になります。

フィールド変数

作成したクラスには以下のようなフィールド変数を定義しています。
ちなみに、現在は①、②は使用していません。
以前実装した時の残骸になります。

そして、同様に、以下のメソッドも残骸になります。

   /**
     * ロードしたプロパティファイルのキーを返却。
     * @return keySet
     */
    public Set<String> getKeySet() {
        return keySet;
    }
    /**
     * application.propertiesから値を取得する。
     * 
     * @param key プロパティキー
     * @return プロパティの値
     */
    public String getProperty(String key) {
        return prop.getProperty(key);
    }

他にも使用していないメソッドがあるかもです。。。

駄菓子菓子!肝心なのはこれからです。

このクラスは、初めの実装ではプロパティファイルを読み込んでいましたが、現在ではCSVファイルを読み込んでいます。

プロパティファイル:キーと値をセットにしたファイル

例:

「*.properties」
key1=value1
key2=value2

CSVファイル:カンマ区切りのデータファイル

例:

「*.csv」
value1, value2, , value3, value4, value5....
value1-1, value1-2, , value1-3, value1-4, value1-5....

フィールド変数とは

クラスの中ではどこからでも参照できる変数です。
継承関係を作り、その中で使用することもできます。
細かい部分は後日。。。
とりあえず「クラスの中ならどこからでもアクセスできる」と理解してもらえばオッケ!

/** ①プロパティファイル */
private Properties prop;
/** ②プロパティファイルのキー・セット */
private Set<String> keySet;
/** ③最大ウィンドウサイズ(幅) */
private double windowWidth;
/** ④最大ウィンドウサイズ(高さ) */
private int windowHeight;
/** ⑤JSに渡すURLの配列文字列 */
private String videoIds;


WebViewを使う

早い話が、ブラウザと同じようなことをしてくれるクラスです。これを使用してHTMLファイルを表示します。
そして、HTMLファイルに埋め込んだJSを実行します。

// WebView(ブラウザ)の作成
WebView browser = new WebView();
WebEngine engine = browser.getEngine();

ここで取得しているWebEngineクラスを使用してHTML側のJSを動かします。

// JSの起動
engine.executeScript("var data = " + videoIds + ";");

実施に行なっているのはJSの変数「data」にJava側で取得したvideoIdsを設定している処理です。

ファイル読み込み

上のvideoIdsはフィールド変数で、読み込んだCSVファイルの中身を設定しています。
CSVファイルは下のようなものを使用します。

#Youtube Id, time, target
S0LbTzCrkiw, 5000, 1
uzEK0owHXfI, 10000, 1
2oKiPesbvB0, 15000, 2
US-V_zCHbSY, 30000, 2
6qhJsvpd0ds, 15000, 3
OUO-obP61QI, 30000, 3
bxIXsqOndNo, 15000, 4
jQqyj61P2GU, 30000, 4

そして、Javaで読み込む時には、初めの一行を飛ばします。

/**
 * プロパティファイルを読み込む。
 * 初期起動時に設定を読み込むための処理
 */
public void loadProperties() {
    // java.nio.PathでJavaFxのPathではない
    Path propFile = Paths.get("resources/application.csv");
    final StringBuilder build = new StringBuilder();
    build.append("[");
    try {
        // CSVィファイルの読み込み
        BufferedReader reader = Files.newBufferedReader(propFile);
        String line = null;
        reader.readLine();
        while((line = reader.readLine()) != null) {
            String[] datas = line.split(",");
            build.append("[");
            for(String data : datas) {
                build.append("\"" + data + "\",");
            }
            build.setLength(build.length() - 1);
            build.append("], ");
        }
        build.setLength(build.length() - 1);
        build.append("]");
        System.out.println("Build: " + build.toString());
        videoIds = build.toString();
    } catch (IOException e) {
        e.printStackTrace();
        System.out.println("System Error: can not read application.properties");
    }
}

Pathsクラスで、プロジェクトルートから「resources/application.csv」を読み込みます。
そして読み取ったデータをBufferedReaderで一行ずつ取得し
StringBuilderで文字列としてつなげていきます。

最終的に[["aaa","bbb", "ccc"] ... ] のような2次元配列の文字列を作成します。

HTMLを使う

HTMLでYoutubeの画面と下に、宣伝的な文言を入れます。そして、YoutubeプレーヤーをJSから起動します。

<!DOCTYPE html>
<html>
  <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <style type="text/css">
        <!--
        -->
      </style>
  </head>
  <body height="100%">
    <!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
    <div id="player" height="90%"></div>
    <div id="shopInfo" style="width: 100%; height: 10%; text-align: center;">
        <div style="text-align: center;">
            <span>営業時間:10:00〜20:00</span>
            <span>休日:土・日</span>
        </div>
    </div>

    <script>
      // 1. set datas(JavaFXでこの部分を出力します。)
//      var data = ["S0LbTzCrkiw"
//          , "uzEK0owHXfI"
//          , "2oKiPesbvB0"];

      var counter = 0;
      var timerCounter = 0;
      // 2. This code loads the IFrame Player API code asynchronously.
      let tag = document.createElement('script');

      tag.src = "https://www.youtube.com/iframe_api";
      let firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

      // 3. This function creates an <iframe> (and YouTube player)
      //    after the API code downloads.
      var player;
      function onYouTubeIframeAPIReady() {
        player = new YT.Player('player', {
          height: window.innerHeight * 0.9,
          width: '100%',
          videoId: data[counter][0],
          events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
          }
        });
      }
      //counter++;
      // 4. The API will call this function when the video player is ready.
      function onPlayerReady(event) {
        alert(counter);
        event.target.playVideo();
        counter++;
      }

      // 5. The API calls this function when the player's state changes.
      //    The function indicates that when playing a video (state=1),
      //    the player should play for six seconds and then stop.
      var done = false;
      function onPlayerStateChange(event) {

        if (event.data == YT.PlayerState.PLAYING && !done) {
          setTimeout(nextVideo, data[timerCounter][1]);
          event.target.playVideo();
          timerCounter++;
          // done = true;
        } else if (event.data == YT.PlayerState.ENDED) {
//          event.target.loadVideoById(data[counter][0]);
        }
      }
      // next video
      function nextVideo() {
        if (counter == data.length) {
          counter = 0;
        }
        player.loadVideoById(data[counter][0]);
        counter++;
      }

      function stopVideo() {
        player.stopVideo();
      }

      function initFooter() {
        document.getElementById("shopInfo");
      }
    </script>
  </body>
</html>


画面を表示する

<!DOCTYPE html>
<html>
  <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <style type="text/css">
        <!--
        -->
      </style>
  </head>
  <body height="100%">
    <!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
    <div id="player" height="90%"></div>
    <div id="shopInfo" style="width: 100%; height: 10%; text-align: center;">
        <div style="text-align: center;">
            <span>営業時間:10:00〜20:00</span>
            <span>休日:土・日</span>
        </div>
</div>

画面を表示するためのHTMLコードはこの部分だけです。
他は全てJSを実装しています。

Youtbe用の画面に高さを90%、下の部分に高さを10%指定しています。

YoutubePlayer

    <script>
      // 1. set datas(JavaFXでこの部分を出力します。)
//      var data = ["S0LbTzCrkiw"
//          , "uzEK0owHXfI"
//          , "2oKiPesbvB0"];

      var counter = 0;
      var timerCounter = 0;
      // 2. This code loads the IFrame Player API code asynchronously.
      let tag = document.createElement('script');

      tag.src = "https://www.youtube.com/iframe_api";
      let firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

      // 3. This function creates an <iframe> (and YouTube player)
      //    after the API code downloads.
      var player;
      function onYouTubeIframeAPIReady() {
        player = new YT.Player('player', {
          height: window.innerHeight * 0.9,
          width: '100%',
          videoId: data[counter][0],
          events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
          }
        });
      }
      //counter++;
      // 4. The API will call this function when the video player is ready.
      function onPlayerReady(event) {
        alert(counter);
        event.target.playVideo();
        counter++;
      }

      // 5. The API calls this function when the player's state changes.
      //    The function indicates that when playing a video (state=1),
      //    the player should play for six seconds and then stop.
      var done = false;
      function onPlayerStateChange(event) {

        if (event.data == YT.PlayerState.PLAYING && !done) {
          setTimeout(nextVideo, data[timerCounter][1]);
          event.target.playVideo();
          timerCounter++;
          // done = true;
        } else if (event.data == YT.PlayerState.ENDED) {
//          event.target.loadVideoById(data[counter][0]);
        }
      }
      // next video
      function nextVideo() {
        if (counter == data.length) {
          counter = 0;
        }
        player.loadVideoById(data[counter][0]);
        counter++;
      }

      function stopVideo() {
        player.stopVideo();
      }

      function initFooter() {
        document.getElementById("shopInfo");
      }
    </script>

YoubetubePlayer参照

以下の部分で参照、プレイヤーを取得しています。

// 2. This code loads the IFrame Player API code asynchronously.
let tag = document.createElement('script');

tag.src = "https://www.youtube.com/iframe_api";
let firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);


scriptタグを作成して、そのタグにhttps://www.youtube.com/iframe_apiへの参照を追加します。
そして、このタグの親ノード、下のような構成であればbbbの親はaaaのタグが親ノードになります。

<div>aaa
    <div>bbb</div>
</div>

YoutbePlayerの生成

var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
  height: window.innerHeight * 0.9,
  width: '100%',
  videoId: data[counter][0],
  events: {
    'onReady': onPlayerReady,
    'onStateChange': onPlayerStateChange
  }
});
}

このメソッドでYoutubePlayerが生成されます。
なので、一番初めに呼ばれます。コンストラクタとしては下のようになっていて、プロパティの部分に高さ、幅、表示するVideoID、イベントハンドラを設定します。
new YT.Player('使用するタグのID', プロパティ);
プロパティの実装部分は以下のようなものです。

  events: {
    'onReady': onPlayerReady,
    'onStateChange': onPlayerStateChange
  }

下の方にonPlayerReady(プレーヤー起動時)とonPlayerStateChange(プレーヤーの状態変化時)のメソッドを定義します。

でわでわ。。。

関連ページ一覧

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseにCDTをインストール〜
  3. Setup OpenGL with Java〜JOGLを使う準備 for Eclipse〜
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder 〜EclipseとSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 〜GitからソースをPullしよう〜
  3. IntelliJ IDEA Git〜Gitリポジトリからクローン〜

JavaFX関連ページ

  1. Eclipse SceneBuilderを追加する
  2. JavaFX SceneBuilder 〜EclipseとSceneBuilder連携~
  3. JavaFX SceneBuilder〜ボタンにメソッドを割り当てるワンポイント〜
  4. Java プロコンゲーム 〜見た目の作成(SceneBuilderの使用)〜

JavaFX ワンポイント 〜Fontを指定する〜

TextFIeldやTextAreaを使用するときにFontの設定をしてやると大きさなど変更できます。

設定方法は以下のようなコードです。

newWindow = new Stage();
StackPane stack = new StackPane();
textArea = new TextArea();
textArea.setFont(Font.font("MS UI Gothic", FontWeight.BLACK, 19));
textArea.setOnKeyPressed(keyEvent -> {
    KeyCode code = keyEvent.getCode();
    if (keyEvent.isShiftDown() && KeyCode.SPACE.equals(code)) {
        newWindow.close();
        isTextIn = false;
    }
});
textArea.setOnKeyReleased(keyEvent -> {
    KeyCode code = keyEvent.getCode();
    if (keyEvent.isShiftDown() && KeyCode.ENTER.equals(code)) {
        textArea.setText("");
        textArea.positionCaret(0);
    }
});
stack.getChildren().add(textArea);
Scene scene = new Scene(stack, 450, 100);
newWindow.setScene(scene);
newWindow.initModality(Modality.WINDOW_MODAL);
newWindow.initOwner(primary);
newWindow.setX(primary.getWidth() * 0.4);
newWindow.setY(primary.getHeight() * 0.8);
newWindow.show();

return newWindow;

TextAreaで実装しましたが、これはTextFieldにも使用できます。

でわでわ。。。



関連ページ一覧

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜

JavaFX ワンポイント 〜新しいWindowを立ち上げる〜

JavaFXを起動するときにすでにウィンドウを1つ起動していますが、さらにウィンドウを使いたいとき。。。

今回はそんなところを「ワンポイントレッスン」的に記載したいと思います。

ちなみに、JavaFXはこんな感じのものです。余計な音が入っています。そして、文言を表示する部分でnewWindowを使用しています。

ウィンドウを新しく作る

WindowはStageです。なので、下のように実装したらできました。
参考にしたサイトはこちらです。

private Stage createNewWindow(Stage primary) {
    newWindow = new Stage();
    StackPane stack = new StackPane();
    textArea = new TextArea();
    textArea.setOnKeyPressed(keyEvent -> {
        KeyCode code = keyEvent.getCode();
        if (keyEvent.isShiftDown() && KeyCode.SPACE.equals(code)) {
            newWindow.close();
            isTextIn = false;
        }
    });
    textArea.setOnKeyReleased(keyEvent -> {
        KeyCode code = keyEvent.getCode();
        if (keyEvent.isShiftDown() && KeyCode.ENTER.equals(code)) {
            textArea.setText("");
            textArea.positionCaret(0);
        }
    });
    stack.getChildren().add(textArea);
    Scene scene = new Scene(stack, 250, 50);
    newWindow.setScene(scene);
    newWindow.initModality(Modality.WINDOW_MODAL);
    newWindow.initOwner(primary);
    newWindow.setX(primary.getWidth() * 0.4);
    newWindow.setY(primary.getHeight() * 0.8);
    newWindow.show();

    return newWindow;
}

作成したウィンドウにキーイベントを追加して見ました。

ちなみに、
簡単ですが、こんな感じで。。。

でわでわ。。。



関連ページ一覧

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜

Java OpenGL JOGLを使う 〜3Dモデルを読み込むために1〜

今まで、プロコン用ゲームを作成するために、仕様の作成を行なっていましたが、ちょいと仕様作成につまづいて(調査することが多いため)ゲームで使用する(使用したい)3Dモデルの読み込みアプリを作成しようと考えています。

以前、LWJGLを使用して実装したのですが、ライセンスなどが、面倒なのとインストール(セットアップ)が面倒なのでシンプルにJOGLを使用してみようと考えています。

JOGL= OpenGL

Java版のOpenGLということらしいです。LWJGLでも同じようなメソッドとかクラスとか使用していました。
なので、LWJGLよりも使いやすいと思ったのです。

早速写経

参考サイトはこちらです
写経したものを、実行してみます。クラス名は自分の作成したクラス名になっています。そして、オーバーライドするメソッドが増えていたので、それも追加しました。

public class ModelUtil implements GLEventListener {
    @Override
    public void display(GLAutoDrawable drawable) {
        final GL2 gl = drawable.getGL().getGL2();

        gl.glBegin (GL2.GL_LINES);//static field
        gl.glVertex3f(0.50f,-0.50f,0);
        gl.glVertex3f(-0.50f,0.50f,0);
        gl.glEnd();

     }

    @Override
    public void reshape(GLAutoDrawable arg0, int arg1, int arg2, int arg3, int arg4) {
    }

     @Override
     public void dispose(GLAutoDrawable arg0) {
     }

     @Override
     public void init(GLAutoDrawable arg0) {
     }

     public static void main(String[] args) {

        final GLProfile profile = GLProfile.get(GLProfile.GL2);
        GLCapabilities capabilities = new GLCapabilities(profile);

        final GLCanvas glcanvas = new GLCanvas(capabilities);
        Line l = new Line();
        glcanvas.addGLEventListener((GLEventListener) l);
        glcanvas.setSize(400, 400);

        final JFrame frame = new JFrame ("straight Line");

        frame.getContentPane().add(glcanvas);

        frame.setSize(frame.getContentPane().getPreferredSize());
        frame.setVisible(true);
    }
}

描画するのに使用するのはJavaFXではなく、「Swing」のようです。
サンプルはこちらのサイトにありました。

写経してみて

動画にありますが、写経後に色々と気がつくことが多いです。
とりあえずは、今回作成したコードからして、画面に何かしらを描画するためには下のクラスを使用する必要があるということがわかりました。

  1. GLProfile
  2. GLCapabilities
  3. GLCanvas
  4. GLEventListener

コードを眺める

今回作成した「ModelUtil」クラスはGLEventListenerの実装クラスです。
このインターフェースクラスを実装(implements)すると必ず、下のメソッドをオーバーライドしなくてはいけません。
Javaはそういうルールをコンパイラで作っています。→このようにしないとビルドエラーになるということです。

  1. display()
  2. reshape()
  3. dispose()
  4. init()

ちなみにこれらのメソッドはから実装です(笑)。下のような形です。

@Override
public void reshape(GLAutoDrawable arg0, int arg1, int arg2, int arg3, int arg4) {
}

 @Override
 public void dispose(GLAutoDrawable arg0) {
 }

 @Override
 public void init(GLAutoDrawable arg0) {
 }

それでも、Mainメソッドで動かしているので問題なく表示できました。

とりあえず、写経して線を引いただけですが、ここからVoxcelやBlenderなどで作成した#Dモデルを表示することを目指します。

でわでわ。。。

関連ページ

  1.  Chapter1[外枠の表示のみ]
  2. Chapter2-1〜クラスの構成〜
  3. Chapter2-2〜インターフェースの使い方と詳細〜
  4. Chapter2-3〜GameEngineクラス(サンプルクラス)〜/li>
  5. Chapter2-4〜Windowクラス(サンプルクラス)〜
  6. Chapter3〜描画処理を読む〜
  7. Chapter4〜シェーダについて〜
  8. Chapter5-1〜レンダリングについて〜
  9. Chapter5-2〜レンダリング詳細〜
  10. Chapter6〜Projection(投影)〜
  11. Chapter7-1〜Cubeを作る〜
  12. Chapter7-2〜Texture〜

@Override
public void reshape(GLAutoDrawable arg0, int arg1, int arg2, int arg3, int arg4) {
}

@Override
public void dispose(GLAutoDrawable arg0) {
}

@Override
public void init(GLAutoDrawable arg0) {
}

JavaFX in RPi 〜IFrame Youtube Player自動再生〜

今回は、以前作成したプロパティファイルを読み込む処理を連携して、以下のような処理を作成したいと思います。

  1. プロパティファイルを読み込む
  2. 取得したURLを15秒おきに再生

前回JavaFXでHTML+JSで作成した Youtube Playerを表示しました。

ポイント

  1. JavaFXでプロパティファイルを読み込みます
  2. 読み込んだデータをJSに出力します。
    engine.executeScript("var data = " + videoIds + ";");
  3. あとはHTML(+JS)でVideoIDの数だけ動画をループして表示します。

HTMLに実装したJSはこのページをコピって作成しました

作成したコードはGithubにアップしてあります。

  1. Main.java
  2. iframePlayer.html

とりあえず、JSでYoutubeの動画を15秒ずつ切り替えるところが大変でした。

動画の指定方法として、URLではなくVideoIDを使用するところがキーポイントでした。

でわでわ。。。

https://github.com/ZenryokuService/RPiMediaPlayer/blob/master/RPiMediaPlayer/src/application/iframePlayer.html

JavaFX in RPi 〜IFrame Youtube Player を作る〜

今回はHTML(とJS)でYoutubePlayerを作成します。
目的と実行手順は以下の通りです。

参考サイトはこちらのGoogle Developperページです。

IFrame Youtube Player

  1. HTMLでYoutubePlayerを作成
  2. JavaFXで作成したHTMLをロード
  3. RPiで起動するように設定
  4. TVなどで再生してみれるようにする

サンプルコードをコピペで動かしてみました。

<!DOCTYPE html>
<html>
  <body>
    <!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
    <div id="player"></div>

    <script>
      // 2. This code loads the IFrame Player API code asynchronously.
      var tag = document.createElement('script');

      tag.src = "https://www.youtube.com/iframe_api";
      var firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

      // 3. This function creates an <iframe> (and YouTube player)
      //    after the API code downloads.
      var player;
      function onYouTubeIframeAPIReady() {
        player = new YT.Player('player', {
          height: '360', // window.innerHeight * 0.9などで値を変更する
          width: '640',
          videoId: 'M7lc1UVf-VE',
          events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
          }
        });
      }

      // 4. The API will call this function when the video player is ready.
      function onPlayerReady(event) {
        event.target.playVideo();
      }

      // 5. The API calls this function when the player's state changes.
      //    The function indicates that when playing a video (state=1),
      //    the player should play for six seconds and then stop.
      var done = false;
      function onPlayerStateChange(event) {
        if (event.data == YT.PlayerState.PLAYING && !done) {
          setTimeout(stopVideo, 6000);
          done = true;
        }
      }
      function stopVideo() {
        player.stopVideo();
      }
    </script>
  </body>
</html>

しかし、このままだと全画面表示でないのでカスタムしてやる必要があります。

カスタムする

ここで、変更したいのはボタンなどをつけて「チャンネルなどを変えることができる」ということをユーザーに気づいてもらえるようにすることです。
具体的には下のようなイメージです。。。
しかし、仮の状態です。

「▶︎」などをつけてみました。
とりあえずは、見た目のみです。

バージョンについて

今回作成するのは、特定のYoutubeの動画IDを15秒おきに再生するものを作成するので、正直のところは「▶︎」ボタンなどはいらないのです。。。
しかし、バージョンわけで表示できるような工夫をしてみたいと思います。
<Version1>
Youtubeを15置きに動画IDを変更して連続的に再生する、アプリが止まるまで続ける自動再生プレーヤ

<Version2>
Youtubeだけでなく、インターネットTVなど表示するテレビ局などを変更、チャンネルの変更などができるようにする。
当然、RPi(ラズパイ)をテレビに接続して表示できるようにする。(試行錯誤中ですが・・・・)

以上のように考えております。

でわでわ。。。



JavaFX in RPi 〜HTMLをロードする〜

今回は作成したHTMLファイルをJavaFXでロードします。
前回URLをロードしてみた結果、表示したくない部分も画面に出てきたのでそれをなくします。

問題と解決策

早い話が、HTMLにiframeを使用して動画部分のみを表示しようというところです。
下の画像がJavaFXでロードした結果です。

HTMLをロードする

JavaFXでHTMLをロードするのはとても簡単でした。
<ロードした結果>

<ロードしたHTML>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
    <iframe width="560" height="315"
         src="https://www.youtube.com/embed/S0LbTzCrkiw"
         frameborder="0"
         allow="accelerometer;
         autoplay;
         encrypted-media;
         gyroscope;
         picture-in-picture"
         allowfullscreen>
    </iframe>
</body>

</html>

<Javaコード>

public void start(Stage primaryStage) {
    try {
        // レイアウト(土台)になるペインを作成
        BorderPane root = new BorderPane();

        // WebView(ブラウザ)の作成
        WebView browser = new WebView();
        WebEngine engine = browser.getEngine();
        String aaa = getClass().getResource("iframe.html").toExternalForm();
        System.out.println(aaa);
        engine.load(aaa);

        root.setCenter(browser);
        // 画面を表示する土台(シーン)を作成
        Scene scene = new Scene(root,this.windowWidth,this.windowHeight);
        // JavaFX用のCSSを読み込む
        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.setOnCloseRequest(event -> Platform.exit());
        primaryStage.show();
    } catch(Exception e) {
        e.printStackTrace();
    }
}

今回の課題

ロードしたHTMLファイルのプロパティ(画面サイズや、そのほかのコントロール)を操作したい

上の要件を満たすために、通常のHTML + JSで実装するのであれば、シンプルにHTMLファイルに全部記述してしまうのが手っ取り早く、メンテナンス性も優れています。

というわけで、Youtubeを単純に全画面表示する場合は下のようなHTMLファイルを作成してやればOKな感じでした。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body width="100%" height="100%">
    <iframe style="border: 0; position:fixed; top:0; left:0; right:0; bottom:0; width:100%; height:100%"
         src="https://www.youtube.com/embed/S0LbTzCrkiw"
         frameborder="0"
         allow="accelerometer;
         autoplay;
         encrypted-media;
         gyroscope;
         picture-in-picture"
         allowfullscreen>
    </iframe>
</body>

</html>

しかし、自動再生が走らないのでちょいといじります。
iframeタグのsrcにautoplay=1を、allow属性にallow="accelerometer"を追加しました。HTMLを直接開いた場合は自動再生が始まらなかったのですが、JavaFXでロードしたら自動再生が始まりました。

ちょっと腑に落ちませんが、まぁよしとします(笑)

作成したコードはこちらにアップしてあります。

でわでわ。。。



JavaFX in RPi 〜URLでYoutubeにアクセス〜

JavaFXでのWeb画面表示をやろうと思います。
・参考にするのはORACLEのJavaドキュメント(日本語)です、このページはWebViewクラスの使い方が記載してありました。

・もう1つ使うものがあります。BorderPaneクラスです。
のクラスの使い方はこちらのページにありました

WebView & BorderPane

実行することは以下のような内容らしいです。

  1. ローカルおよびリモートURLからのHTMLコンテンツのレンダリング
  2. Web履歴の取得
  3. JavaScriptコマンドの実行
  4. JavaScriptからJavaFXへのアップコールの実行
  5. Webポップアップ・ウィンドウの管理
  6. 埋込みブラウザへの効果の適用

基本的な使い方は下のようなコードでやるようです。

WebView browser = new WebView();
WebEngine webEngine = browser.getEngine();
webEngine.load("http://mySite.com");

URLでYoutubeへ。。。

それではシンプルに実装してみます。以前やったプロパティファイルの読み込みと画面サイズの取得はそれぞれ以下のメソッドに実装しています。
・initWindowInfo(画面サイズの取得)
・loadProperties(プロパティファイルの読み込み)

ちなみにコードはGithubにアップしています。

上にあるWebViewの使い方を真似してURLを以下のものを指定して表示します。
https://www.youtube.com/watch?v=6qhJsvpd0ds

ちなみに、動画は下のようなものです。

しかし、実行結果は下のような見た目です。

コードは以下に。。。
ちなみにコードはGithubにアップしています。

public class Main extends Application {
    /** プロパティファイル */
    private Properties prop;
    /** プロパティファイルのキー・セット */
    private Set keySet;
    /** 最大ウィンドウサイズ(幅) */
    private double windowWidth;
    /** 最大ウィンドウサイズ(高さ) */
    private int windowHeight;

    @Override
    public void start(Stage primaryStage) {
        try {
            // レイアウト(土台)になるペインを作成
            BorderPane root = new BorderPane();

            // WebView(ブラウザ)の作成
            WebView browser = new WebView();
            WebEngine engine = browser.getEngine();
            engine.load("https://www.youtube.com/watch?v=6qhJsvpd0ds");

            root.setCenter(browser);
            // 画面を表示する土台(シーン)を作成
            Scene scene = new Scene(root,this.windowWidth,this.windowHeight);
            // JavaFX用のCSSを読み込む
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }

    /** コンストラクタ */
    public Main() {
        windowWidth = 0;
        windowHeight = 0;
        // プロパティファイルの読み込み
        prop = new Properties();
        loadProperties();
        // ウィンドウ情報の取得(縦横の幅)
        initWindowInfo();
    }
      ・
      ・
      ・

問題

表示したいのは動画のみでページ全体ではない

これをなんとかするのに頭を一捻りする必要があります。。。

頭から煙が出てきたのでそろそろと失礼します。

でわでわ。。。



Java FX in RPi 〜画面サイズの取得〜

今回は、起動しているPCの画面サイズを取得する実装を行います。なかなかJavaFXならではの実装にたどり着きませんが、必要な実装ですのでやります。

画面サイズの取得

画面サイズを取得する前に、予備知識として記載します。
Javaの画面関連のパッケージはjava.awtというものですが、OSを起動した時に画面を表示するのに使用している(グラフィック担当)サーバーがあり、それをXサーバー(X-Server)と言います。X-Window Systemのことです、常駐アプリで「表示してください」というリクエストに応えるので「サーバー」です。

java.awtはX-Serverに依存

以前、CUIでラズパイを起動してからJavaFXで画面を表示しようと試みたところJavaFXはGUIモードでないと起動できないことを知りました。。。
つまりX-Serverが動かないとJavaFXも動かない・・・ということです。

画面サイズを取得する

画面サイズを取得するのに「java.awt」を使用します。
そして、実際に使用するクラスはGraphicsEnvironmentです。

テスト駆動なので。。。

まずは、テストケースから入ります。
シンプルな内容なのでテストもシンプルです。
<仕様>
画面のサイズを取得しMainクラスのフィールドにセットする
このメソッドを使用します。

これだけです。
なので作成するテストケースは下のようになります。

/** 画面サイズの取得テスト */
@Test
public void testInitWindowInfo() {
    // privateメソッドの呼び出し
    try {
        // メソッドの取得
        Method mes = target.getClass().getDeclaredMethod("initWindowInfo");
        // メソッドのアクセス範囲をpublicに変更
        mes.setAccessible(true);
        // メソッドの実行(引数も返り値もない
        mes.invoke(target);

    } catch (NoSuchMethodException | SecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    // テストの起動確認
    assertFalse(target.getWindowHeight() == 0);
    assertFalse(target.getWindowWidth() == 0);
}

そして、テストケースを作成する最中に実装も終わっていました。

/** コンストラクタ */
public Main() {
    prop = new Properties();
    windowWidth = 0;
    windowHeight = 0;
}

こんな感じです。

次回は、JavaFXの画面作成に着手したいと思います。

でわでわ。。。



Java FX in RPi 〜プロパティファイルの読み込み JavaFX実装〜

今回は、プロパティファイルの読み込みを実装します。
以前、コンソールアプリ作成でやったものもあるのでそちらもどうぞ。

ちなみに、前回は大まかにどんなものを作るのか?ということと、実装環境に関して記載しました。

プロパティファイルを読む

目的は、アプリケーションの起動時に初めに設定を読み込み表示するYoutubeのURLを設定、?秒おきに表示を切り替えるものです。

ちなみに作成したプロパティファイルは下のようなものです。

# Properties for this application

#########################################################
# URL List for load Youtube (rule "url" + number        #
#########################################################
url1=https://www.youtube.com/watch?v=6qhJsvpd0ds
url2=https://www.youtube.com/watch?v=w9BubZIEGdg
url3=https://www.youtube.com/watch?v=YPuaUUvCdMg
url4=https://www.youtube.com/watch?v=w9iiCnX0STw
url5=https://www.youtube.com/watch?v=XW7FgoR5pzQ&t=63s
url6=https://www.youtube.com/watch?v=DZEWd0Viiuk&t=1s
url7=https://www.youtube.com/watch?v=45xcPvhGhDo
url8=https://www.youtube.com/watch?v=w9BubZIEGdg

プロパティファイルの読み込み

使用するクラスはPropertiesクラスです。

早速プログラムを作成しました。Githubにアップしてあります。
そして、テスト駆動開発ですので、テストケースから作成しています。

テストケースを作成する

JUnitでのテストを作るのに、以下のような手順で作成します。

  1. 作成するクラスはどのように動くか決める
  2. 作成したものが、想定通りに動く確認する処理をかきます
  3. 実行して緑色になることを確認(下の図を参照ください)

作成する時にJUnitでやる時のポイントも記載しておきます。
Junitをビルドパスニ追加しておく必要があります。そして、Mavenでdependencyを追加したときはすでにビルドパスに追加されています。。。

アノテーションで設定

「@Test」: 実行するテストを使用します。
「@Before」: テストを実行する前に起動する。主にテストの実行準備などを行う

具体的に。。。

public class MainTest {
    /** テスト対象クラス */
    private Main target;

    /**
     * テストを行うための準備処理
     */
    @Before
    public void initTest() {
        // テスト対象クラスのインスタンスを生成
        target = new Main();
    }

/** loadProperties()のテスト */
    @Test
    public void testLoadProperties() {
        target.loadProperties();
        Set keySet = target.getKeySet();
        keySet.forEach(System.out::println);
    }
}

上のコードを実行したものが、下のキャプチャです。

そして、@Beforeのついているメソッドは。。。

/**
 * テストを行うための準備処理
 */
@Before
public void initTest() {
    // テスト対象クラスのインスタンスを生成
    target = new Main();
}

のようになっています。単純にテスト対象クラスをインスタンス化してフィールド変数「target」に代入しています。

そして、テストケース(メソッド)「testLoadProperties()」

/** loadProperties()のテスト */
@Test
public void testLoadProperties() {
    target.loadProperties();
    Set keySet = target.getKeySet();
    keySet.forEach(System.out::println);
}

テストとして実行するメソッド(テストケース)は「@Test」をつけてやります。実行結果に関しては上のキャプチャに記載しています。
ちなみに実行したコードは下のものです。

/** loadProperties()のテスト */
@Test
public void testLoadProperties() {
    target.loadProperties();
    Set keySet = target.getKeySet();
    assertNull(keySet);
    keySet.forEach(System.out::println);
}

これは失敗したコードです。そしてテストとしては失敗しなくてはいけないコードです。

どーゆーことか?

コードに色付けができなかったのですが、下のようなコードを追加しました。
assertNull(keySet);このコードは取得したキーセットがNullにならないとAssertErrorを出力するメソッドです。
本当であれば、assertNotNull()を使わないといけません。このような部分はテスト仕様なのでどのような処理を行うかちゃんと設計しなくてはいけません。。。

そして、下のコードが直したものです。

/** loadProperties()のテスト */
@Test
public void testLoadProperties() {
    target.loadProperties();
    Set keySet = target.getKeySet();
    assertNotNull(keySet);
    keySet.forEach(System.out::println);
}

そして、実行結果は緑色になっています。

こんな感じでプロパティファイルが読めていることを確認しています。
keySet.forEach(System.out::println);
出力結果

*** testLoadProperties ***
url5
url6
url3
url4
url1
url2
url7
url8
*** testConvertKeySet ***
test4
test2
test3
test1

こんな感じで作成しました。

でわでわ。。。