javaFXとJSでYoutubePlayerを作ります。イメージとしては、ラズパイ の電源を入れたら、YoutubePlayerを起動、画面に表示して再生するというものを作ります。
JavaFXとは
JavaFXはJavaでの画面作成API→Javaで画面を作ります。
そして、今回はJSも使います。つまりは、JavaからJSを起動します。
疑問点
なんでまたJavaFXを使用するのか?
これは、PCで、ブラウザを使用しないで、Youtubeを観れるようにするためです。「いや、ブラウザを使えよ!」と言われそうですが、これは起動した画面とウェブサーバーを連携させたいという意図があります。
実行環境
ラズベリーバイでこのプログラムを起動しようと考えています。
形的には、ラズパイこらJavaFXを起動、サーバーも起動(この部分は後々)
最後に動画を再生するというイメージです。
具体的には
以下のような手順で実行します。
- ラズパイでGithubから最新のソースをPULLする
- ビルドして、実行ファイル(JAR)を生成
- 生成したJARファイルを実行
- Youtubeの再生リストを無限ループで再生
このために用意するものは以下のものになります。
必要な資源
【概要】
- javaFXを起動する、Javaのプログラム
- YoutubePlayerを起動するためのHTML(+JS)プログラム
- プログラムで再生リストを作成するためのCSVファイル
- ラズパイの実行時に起動する、シェル(プログラム)
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 セットアップ
- Java Install Eclipse〜開発ツールのインストール〜
- TensorFlow C++環境〜EclipseにCDTをインストール〜
- Setup OpenGL with Java〜JOGLを使う準備 for Eclipse〜
- Eclipse Meven 開発手順〜プロジェクトの作成〜
- Java OpenCV 環境セットアップ(on Mac)
- Eclipse SceneBuilderを追加する
- JavaFX SceneBuilder 〜EclipseとSceneBuilder連携~
Java Basic一覧
- Java Basic Level 1 〜Hello Java〜
- Java Basic Level2 〜Arithmetic Calculate〜
- Java Basic Level3 〜About String class〜
- Java Basic Level 4〜Boolean〜
- Java Basic Level 5〜If Statement〜
- Java Basic Summary from Level1 to 5
- Java Basic Level 6 〜Traning of If statement〜
- Java Basic Level8 〜How to use for statement〜
- Java Basic Level 8.5 〜Array〜
- Java Basic Level 9〜Training of for statement〜
- Java Basic Level 10 〜While statement 〜
- Java Basic Swing〜オブジェクト指向〜
- Java Basic Swing Level 2〜オブジェクト指向2〜
- サンプル実装〜コンソールゲーム〜
- Java Basic インターフェース・抽象クラスの作り方
- Java Basic クラスとは〜Step2_1〜
- Java Basic JUnit 〜テストスイートの作り方〜
Git関連
- Java Git clone in Eclipse 〜サンプルの取得〜
- Eclipse Gitリポジトリの取得 〜GitからソースをPullしよう〜
- IntelliJ IDEA Git〜Gitリポジトリからクローン〜