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リポジトリからクローン〜
JavaFX関連ページ
- Eclipse SceneBuilderを追加する
- JavaFX SceneBuilder 〜EclipseとSceneBuilder連携~
- JavaFX SceneBuilder〜ボタンにメソッドを割り当てるワンポイント〜
- Java プロコンゲーム 〜見た目の作成(SceneBuilderの使用)〜