JavaFX in RPi 〜RPiでビルドをかける〜

JavaFXで作成したアプリケーションをRPi(ラズパイ)にインストールしたいと思います。

今までやってきたのは下にリンクをつけて置きます。

  1. メディアプレーヤ作成開始
  2. ラズパイでJavaFXを使うには
  3. [JavaFXとプロパティファイルを読み込み](Java FX in RPi 〜プロパティファイルの読み込み JavaFX実装〜)
  4. JavaFXで画面サイズ取得
  5. Youtubeにアクセス
  6. JavaFXでHTMLをロードする
  7. IFrame YoutubePlayerを使う

RPiでビルド準備

単純にビルドをかけるためにはMavenを使います。

Mavenとは

世間では「ビルドツール」としてよく目にしますが、「ビルドツールって何?」とか「用途は?」と疑問に思うことがあります。
早い話が、「ビルド!」ってやると、以下のようなことができます。

  1. 実行ファイルを作る
  2. 自動でテスト
  3. デプロイ
  4. 他の人のプロジェクトをダウンロード(git pull)

大雑把に上記のようなことができます。

Mavenのセットアップ in RPi

早速、RPiにアクセスしようと思います。SSH接続の詳細に関してはこちらに記載しています
補足としてメモもあります

とりあえずはSSHで接続します。

上のようにMavenがインストールされているか?
mvn -version
Javaがインストールされているか?を確認します。
java --version

しかし、上記の通りに何もインストールしていないのでここからインストールします。

Javaのインストール

Oracleからダウンロードしてインストールするのが一番良いと思います。(コマンドでインストールするとどこに何が?となる。。。)
調べてみたところ、ラズパイのサイトから見つけた方法が良いかと思いました。
これを実行します。

ラズパイにJDK

ラズパイのへのJDKのインストールに関しては、オラクルのサイトから手順を見ながら行いました。

bellsoft-jdk13-linux-arm32-vfp-hflt.debをインストールして、ラズパイでのJavaFXを起動できるようにします。

詳細に関しては、以下の通りです。

  1. 通常のJDK、Windowsなどの通常のPCで使用しているJDKではなく、ARM32用のJDKをインストール
  2. 「update-alternatives」コマンドで、使用するJDKを変更する
  3. Mavenをインストールする

この部分に関しては、下の記事に詳細を記載しています。
RPi JavaFX execution ~ラズパイでJavaFX起動~

しかし、自分がこの設定を行い実行したときは、リモートデスクトップ接続した状態だったので動きませんでした。
エラー詳細に関してはこちらに記載しています、とりあえずはリモートデスクトップ接続では動かなかったというところです。

あとは、ラズパイでの実行確認を行うだけなのですが、時間がなく。。。

中途半端な状態です。

でわでわ。。。

関連記事

Failed to write to /sys/class/input/mice/uevent

実行した環境

ラズパイでリモートデスクトップの設定を行い、WindowsのPCからリモートデスクトップ接続にてJavaFXを起動したときのエラーメッセージです。

ラズパイでのリモートデスクトップ設定に関してはリンク先の記事に書きました。

Failed to write to /sys/class/input/mice/uevent

ラズパイで作成したコード、JavaFXを起動するアプリケーションのコードをGithubから取得して、コンパイル、JARファイルを生成、実行したときのエラーです。

dev: Failed to write to /sys/class/input/mice/uevent
      Check that you have permission to access input devices
java.io.FileNotFoundException: /sys/class/input/mice/uevent (許可がありません)
    at java.io.FileOutputStream.open0(Native Method)
    at java.io.FileOutputStream.open(FileOutputStream.java:270)
    at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
    at java.io.FileOutputStream.<init>(FileOutputStream.java:101)
    at com.sun.glass.ui.monocle.SysFS.write(SysFS.java:121)
    at com.sun.glass.ui.monocle.SysFS.triggerUdevNotification(SysFS.java:108)
    at com.sun.glass.ui.monocle.LinuxInputDeviceRegistry.<init>(LinuxInputDeviceRegistry.java:74)
    at com.sun.glass.ui.monocle.LinuxPlatform.createInputDeviceRegistry(LinuxPlatform.java:37)
    at com.sun.glass.ui.monocle.NativePlatform.getInputDeviceRegistry(NativePlatform.java:67)
    at com.sun.glass.ui.monocle.MonocleApplication.<init>(MonocleApplication.java:71)
    at com.sun.glass.ui.monocle.MonoclePlatformFactory.createApplication(MonoclePlatformFactory.java:42)
    at com.sun.glass.ui.Application.run(Application.java:146)
    at com.sun.javafx.tk.quantum.QuantumToolkit.startup(QuantumToolkit.java:259)
    at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:211)
    at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:675)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:337)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)

実行手順

ラズパイを起動して、以下のようなコマンドを叩きました。

cd repos # リポジトリのあるディレクトリに移動
git pull # Gitからソースを取得する

そして、Javaコマンドで作成したJARファイルを実行

これは、コマンドの実行時に、「sudo」をつけることで解決しました。

つまりは以下のようなコマンドを実行すればよいわけです。エラーの原因はファイルにアクセスする権限がなかったということです。

なので「sudo」コマンドでスーパーユーザーの権限でjavaコマンドを実行したら解決したというわけです。

sudo java -jar target/RPiMediaPlayer-0.0.1-SNAPSHOT-jar-with-dependencies.jar

次のエラーメッセージ

出力されたメッセージは下のようなものです。

Exception in Application constructor
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Unable to construct Application instance: class application.Main
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:907)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$1(LauncherImpl.java:182)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$7(LauncherImpl.java:819)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$7(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$5(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$6(PlatformImpl.java:294)
    at com.sun.glass.ui.monocle.RunnableProcessor.runLoop(RunnableProcessor.java:93)
    at com.sun.glass.ui.monocle.RunnableProcessor.run(RunnableProcessor.java:52)
    ... 1 more
Caused by: java.awt.AWTError: Can't connect to X11 window server using ':10.0' as the value of the DISPLAY variable.
    at sun.awt.X11GraphicsEnvironment.initDisplay(Native Method)
    at sun.awt.X11GraphicsEnvironment.access$200(X11GraphicsEnvironment.java:65)
    at sun.awt.X11GraphicsEnvironment$1.run(X11GraphicsEnvironment.java:115)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.awt.X11GraphicsEnvironment.<clinit>(X11GraphicsEnvironment.java:74)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at java.awt.GraphicsEnvironment.createGE(GraphicsEnvironment.java:103)
    at java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(GraphicsEnvironment.java:82)
    at application.Main.initWindowInfo(Main.java:154)
    at application.Main.<init>(Main.java:88)
    ... 12 more
Exception running application application.Main

キーポイント

ここで忘れてはいけないのが「リモートデスクトップ」での実行であったというところ

そして、以下の部分がキーポイントになりました。

Can't connect to X11 window server using ':10.0' as the value of the DISPLAY variable.

しかし、この部分の解決に至らず。。。

もう少し調査をすることにしました。

そして、調べていくうちにSSHなどのように外部からアクセスして実行するケースで上のようなエラーメッセージが表示されるという記事を見つけました。

つまりは、リモートデスクトップでのアプリケーションの起動は、エラーが出るということでした。

次は、ラズパイ本体で実行してみることにします。

でわでわ。。。

java.lang.UnsatisfiedLinkError: Can’t load library

Can't load library

表題のようなエラーが出ました。

java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
        at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$1(LauncherImpl.java:182)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.UnsatisfiedLinkError: Can't load library: jdk1.8.0_265\jre\bin\jfxwebkit.dll
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1820)
        at java.lang.Runtime.load0(Runtime.java:810)
        at java.lang.System.load(System.java:1088)
        at com.sun.glass.utils.NativeLibLoader.loadLibraryFullPath(NativeLibLoader.java:201)
        at com.sun.glass.utils.NativeLibLoader.loadLibraryInternal(NativeLibLoader.java:94)
        at com.sun.glass.utils.NativeLibLoader.loadLibrary(NativeLibLoader.java:39)
        at com.sun.webkit.WebPage.lambda$static$0(WebPage.java:133)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.webkit.WebPage.<clinit>(WebPage.java:132)
        at javafx.scene.web.WebEngine.<init>(WebEngine.java:881)
        at javafx.scene.web.WebEngine.<init>(WebEngine.java:868)
        at javafx.scene.web.WebView.<init>(WebView.java:273)
        at application.Main.start(Main.java:49)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$8(LauncherImpl.java:863)
        at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$7(PlatformImpl.java:326)
        at com.sun.javafx.application.PlatformImpl.lambda$null$5(PlatformImpl.java:295)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$6(PlatformImpl.java:294)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
        at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
        at com.sun.glass.ui.win.WinApplication.lambda$null$3(WinApplication.java:177)
        ... 1 more
Exception running application application.Main
Caused by: java.lang.UnsatisfiedLinkError: Can't load library: D:\Apps\jdk1.8.0_265\jre\bin\jfxwebkit.dll
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1820)
        at java.lang.Runtime.load0(Runtime.java:810)
        at java.lang.System.load(System.java:1088)
        at com.sun.glass.utils.NativeLibLoader.loadLibraryFullPath(NativeLibLoader.java:201)
        at com.sun.glass.utils.NativeLibLoader.loadLibraryInternal(NativeLibLoader.java:94)
        at com.sun.glass.utils.NativeLibLoader.loadLibrary(NativeLibLoader.java:39)
        at com.sun.webkit.WebPage.lambda$static$0(WebPage.java:133)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.webkit.WebPage.<clinit>(WebPage.java:132)
        at javafx.scene.web.WebEngine.<init>(WebEngine.java:881)
        at javafx.scene.web.WebEngine.<init>(WebEngine.java:868)
        at javafx.scene.web.WebView.<init>(WebView.java:273)
        at application.Main.start(Main.java:49)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$8(LauncherImpl.java:863)
        at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$7(PlatformImpl.java:326)
        at com.sun.javafx.application.PlatformImpl.lambda$null$5(PlatformImpl.java:295)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$6(PlatformImpl.java:294)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
        at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
        at com.sun.glass.ui.win.WinApplication.lambda$null$3(WinApplication.java:177)
        ... 1 more

実行環境

Eclipseで作成したJavaFXアプリケーションをJarファイルに出力して実行したときのエラーです。
ライブラリが参照できていないようです。

参照できていないライブラリは以下のようなものでした。

jdk1.8.0_265\jre\bin\
- jfxwebkit.dll
- fxplugins.dll
- glib-lite.dll
- gstreamer-lite.dll
- jfxmedia.dll
- jfxwebkit.dll

これは、JDKにJavaFXが梱包されているJDK corret(Amazon)なら問題ないようです。

そんなにてこずらずに解決できました。

でわでわ。。。

JavaFX SceneBuilder 使い方~インストールからCSS指定方法など~

イントロダクション

現在(2020/12/20)作成中のゲーム「プロコンRPG」改め「プロコン・クエスト」の実装で画面作成を行うのにSceneBuilderを使用しました。

仕組みとしては、UIで作成した画面でFXMLファイルを出力して、そのFXMLをプログラムからロードして起動、ボタンアクションなどを実装してやる、というような形で実装できるので、作業が楽になります。

そして、JavaFX用のCSSも使えるのでsの使用方法に関しても記載しました。

JavaFX with SceneBuilder

参考サイトはこちらオラクル・ドキュメントです。

そして、参考になるページをリストアップしておきます。

  1. JavaFX CSSリファレンスガイド
  2. CSSによるUIコントロールのスタイル設定
  3. CSSを使用したレイアウト・ペインのスタイル設定
  4. JavaFX UIコントロールの使用(ボタンなどのリファレンス)
  5. SceneBuilder User Guide(英語)

手順としては以下の通りです。

  1. SceneBuilderで画面の見た目を作成。
  2. 出力したFXMLをJavaプログラムで読み込み画面を表示する
@Override
    public void start(Stage primaryStage) throws Exception {
    // 枠なしの画面を作成する
        primaryStage.initStyle(StageStyle.TRANSPARENT);
    // FXMLのロード
        FXMLLoader loader = new FXMLLoader(ClassLoader.getSystemResource("TestingCv.fxml"));
        BorderPane root = (BorderPane) loader.load();
    // シーンに登録、サイズ指定など。。。
        Scene scene = new Scene(root, 800, 600);
        scene.setFill(null);
    // JavaFX CSSの適用
        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
  }

JavaFX CSS

定義したスタイルを適用するのに、ID・クラスを使用します。ここはHTMLで使用するCSSと同じです。
<CSS(ID)>

#welcome-text {
   -fx-font-size: 32px;
   -fx-font-family: "Arial Black";
   -fx-fill: #818181;
   -fx-effect: innershadow( three-pass-box , rgba(0,0,0,0.7) , 6, 0.0 , 0 , 2 );
}
#actiontarget {
  -fx-fill: FIREBRICK;
  -fx-font-weight: bold;
  -fx-effect: dropshadow( gaussian , rgba(255,255,255,0.5) , 0,0,0,1 );  
}

<CSS(クラス)>

.button { ... }
.check-box { ... }
.scroll-bar { ... }

.check-box .label { ... }
.check-box .box { ... }
.radio-button .dot { ... }

.check-box:focused {
    -fx-color: -fx-focused-base;
}

<Java>

Text scenetitle = new Text("Welcome");
scenetitle.setId("welcome-text");
// IDの割り当て
grid.add(actiontarget, 1, 6);
actiontarget.setId("actiontarget");

// クラスの割り当て
Button buttonAccept = new Button("Accept");
buttonAccept.getStyleClass().add("button1");

// Javaコード内での割り当て
Button buttonColor = new Button("Color");
buttonColor.setStyle("-fx-background-color: slateblue; -fx-text-fill: white;");

背景イメージの追加

背景イメージは.rootスタイルに適用されますが、これはSceneインスタンスのルート・ノードに適用されることを意味します。スタイル定義は、プロパティの名前(-fx-background-image)とプロパティの値(url("background.jpg"))で構成されます。

参考サイトにあるように、下のようなCSSを記述すると背景として画像を指定できるようです。
ファイルのパス指定の時にURLなので「file:」を忘れないようにしましょう。

.root {
     -fx-background-image: url("file:C:/documents/background.jpg");
}

SceneBuilderでのイメージ設定

ImageViewを選択し、プロパティを表示し、指定の画像を設定します。

イメージの確認

イメージの確認には、「Preview」を使用します。

プレビュー結果

ラベルのスタイル設定

同様にラベルの指定方法です。

.label {
    -fx-font-size: 12px;
    -fx-font-weight: bold;
    -fx-text-fill: #333333;
    -fx-effect: dropshadow( gaussian , rgba(255,255,255,0.5) , 0,0,0,1 );
}

この例では、フォント・サイズおよびフォントの太さを増し、灰色(#333333)のドロップ・シャドウを適用します。ドロップ・シャドウの目的は、濃い灰色のテキストと薄い灰色の背景の間にコントラストを追加することです。ドロップ・シャドウ・プロパティのパラメータの詳細は、JavaFX CSSリファレンス・ガイドの効果に関する項を参照してください。

テキストのスタイル設定

フォントサイズ、フォントファミリ、文字の色を指定した例です。

.playerText {
   -fx-font-size: 16px;
   -fx-font-family: "Arial Black";
     -fx-text-fill: rgb(255, 255, 255);
}

シーンのスキニング

.rootスタイル・クラスをカスタマイズすることにより、UIの外観を簡単に変更できます。スタイル・シートのサンプルでは両方とも、フォントのサイズとファミリ、他の色の導出元となる基本色、およびシーンの背景色が設定されています。例37-3に、controlStyle2.cssの.rootスタイルを示します。

  • フォントファミリとは
    font-familyとは? 通常のフォントや太字、イタリック体、斜体など、デザインを統一した複数のフォントをまとめたものを「フォントファミリー」といいます。
.root{
    -fx-font-size: 16pt;
    -fx-font-family: "Courier New";
    -fx-base: rgb(132, 145, 47);
    -fx-background: rgb(225, 228, 203);
    -fx-background-image: url(/com/sun/javafx/scene/control/skin/Paste.png);
}

でわでわ。。。

関連ページ一覧

[Eclipse セットアップ](http://zenryokuservice.com/wp/2020/09/01/%e9%96%8b%e7%99%ba%e7%92%b0%e5%a2%83%e6%a7%8b%e7%af%89%ef%bd%9ewindows%e7%89%88eclipse%e3%81%ae%e8%a8%ad%e5%ae%9a%ef%bd%9e/)

  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の使用)〜

ステップアップ関連ページ一覧

  1. Java 初めてでも大丈夫〜ステップアッププログラミングのススメ〜
  2. ステップアッププログラミング〜Java FxでHelloWorld解説〜
  3. Java StepUpPrograming〜JavaFX で四則計算〜
  4. Java StepUpPrograming〜JavaFXで画面切り替えを作る1〜
  5. Java StepUpPrograming〜JavaFXで画面切り替え2ボタン作成〜
  6. Java StepUpPrograming〜JavaFXで画面切り替え3アクション〜
  7. Java StepUpPrograming〜JavaFXで画面切り替え4Pane切り替え〜
  8. Java StepUpPrograming〜JavaFXで画面切り替え5WebEngine

JavaFX + ND4Jで機械学習準備

  1. JavaFX + ND4J〜数学への挑戦1:ND4Jのインストール〜
  2. JavaFX + ND4J〜数学への挑戦2: 行列の計算〜
  3. Java + ND4J 〜数学への挑戦3: ベクトル(配列)の作成方法〜

オブジェクト指向関連ページ

  1. [オブジェクト指向の概念1〜OracleDocのチュートリアル1〜](https://zenryokuservice.com/wp/2019/10/301. /%e3%82%aa%e3%83%96%e3%82%b8%e3%82%a7%e3%82%af%e3%83%88%e6%8c%87%e5%90%91%e3%81%ae%e6%a6%82%e5%bf%b5-%e3%80%9coracledoc%e3%81%ae%e3%83%81%e3%83%a5%e3%83%bc%e3%83%88%e3%83%aa%e3%82%a2%e3%83%ab%ef%bc%91/)
  2. オブジェクト指向の概念2〜クラスとは〜

Java Discord

  1. IntelliJ IDEA Discord Botを作る〜Gradle環境のセットアップ〜
  2. Java Discord セットアップ〜Hello Discord〜
  3. Java Discord ピンポン〜Discordプログラム〜
  4. Java Discord Listener実装〜コマンドを好きなだけ追加しよう〜

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の使用)〜