JavaFX JS〜Youtube playerを作る〜

javaFXとJSでYoutubePlayerを作りました。

JavaFXに関して

疑問点

なんでまたJavaFXを使用するのか?
これは、PCで、ブラウザを使用しないで、Youtubeを観れるようにするためです。

実行環境

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

具体的には

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

  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++環境〜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関連ページ

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