Skip to content

Instantly share code, notes, and snippets.

@monaka
Created October 31, 2020 02:03
Show Gist options
  • Save monaka/ee2e262ba008713d49717b8cfd7c35b2 to your computer and use it in GitHub Desktop.
Save monaka/ee2e262ba008713d49717b8cfd7c35b2 to your computer and use it in GitHub Desktop.
<html>
<head>
</head>
<body>
<!-- オーバーレイ / Overlay -->
<div id="overlay">
<p><span class="far">&#xf254;</span>now loading...</p>
</div>
<!-- ヘッダ / Header -->
<div id="header">
<!-- 再生コントロール / Playback control -->
<div id="control" class="far">
<a href="#" id="play" class="disabled">&#xf144;</a>
<a href="#" id="stop" class="disabled">&#xf28d;</a>
</div>
<!-- アーティストと楽曲の情報 / Artist and song info -->
<div id="meta">
<div id="artist">artist: <span>-</span></div>
<div id="song">song: <span>-</span></div>
</div>
</div>
<!-- 音源 / Audio souce -->
<div id="media"></div>
<!-- 歌詞 / Lyrics text -->
<div id="lyrics">
<!-- 文字 / Text -->
<div id="text"></div>
<!-- ビートバー / Beat bar -->
<div id="bar"></div>
</div>
<span id="text"></span>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/textalive-app-api/dist/index.js"></script>
<script src="./sample.js"></script>
</body>
</html>
const { Player } = TextAliveApp;
// TextAlive Player を初期化
const player = new Player({
app: true,
mediaElement: document.querySelector("#media")
// オプション一覧
// https://developer.textalive.jp/packages/textalive-app-api/interfaces/playeroptions.html
});
const overlay = document.querySelector("#overlay");
const bar = document.querySelector("#bar");
const textContainer = document.querySelector("#text");
let b, c;
player.addListener({
/* APIの準備ができたら呼ばれる */
onAppReady(app) {
if (app.managed) {
document.querySelector("#control").className = "disabled";
}
if (!app.songUrl) {
player.createFromSongUrl("http://www.youtube.com/watch?v=ygY2qObZv24");
}
},
/* 楽曲が変わったら呼ばれる */
onAppMediaChange() {
// 画面表示をリセット
overlay.className = "";
bar.className = "";
resetChars();
},
/* 楽曲情報が取れたら呼ばれる */
onVideoReady(video) {
// 楽曲情報を表示
document.querySelector("#artist span").textContent =
player.data.song.artist.name;
document.querySelector("#song span").textContent = player.data.song.name;
// 最後に表示した文字の情報をリセット
c = null;
},
/* 再生コントロールができるようになったら呼ばれる */
onTimerReady() {
overlay.className = "disabled";
document.querySelector("#control > a#play").className = "";
document.querySelector("#control > a#stop").className = "";
},
/* 再生位置の情報が更新されたら呼ばれる */
onTimeUpdate(position) {
// 現在のビート情報を取得
let beat = player.findBeat(position);
if (b !== beat) {
if (beat) {
requestAnimationFrame(() => {
bar.className = "active";
requestAnimationFrame(() => {
bar.className = "active beat";
});
});
}
b = beat;
}
// 歌詞情報がなければこれで処理を終わる
if (!player.video.firstChar) {
return;
}
// 巻き戻っていたら歌詞表示をリセットする
if (c && c.startTime > position + 1000) {
resetChars();
}
// 500ms先に発声される文字を取得
let current = c || player.video.firstChar;
while (current && current.startTime < position + 500) {
// 新しい文字が発声されようとしている
if (c !== current) {
newChar(current);
c = current;
}
current = current.next;
}
},
/* 楽曲の再生が始まったら呼ばれる */
onPlay() {
const a = document.querySelector("#control > a#play");
while (a.firstChild) a.removeChild(a.firstChild);
a.appendChild(document.createTextNode("\uf28b"));
},
/* 楽曲の再生が止まったら呼ばれる */
onPause() {
const a = document.querySelector("#control > a#play");
while (a.firstChild) a.removeChild(a.firstChild);
a.appendChild(document.createTextNode("\uf144"));
}
});
/* 再生・一時停止ボタン */
document.querySelector("#control > a#play").addEventListener("click", (e) => {
e.preventDefault();
if (player) {
if (player.isPlaying) {
player.requestPause();
} else {
player.requestPlay();
}
}
return false;
});
/* 停止ボタン */
document.querySelector("#control > a#stop").addEventListener("click", (e) => {
e.preventDefault();
if (player) {
player.requestStop();
// 再生を停止したら画面表示をリセットする
bar.className = "";
resetChars();
}
return false;
});
/**
* 新しい文字の発声時に呼ばれる
* Called when a new character is being vocalized
*/
function newChar(current) {
// 品詞 (part-of-speech)
// https://developer.textalive.jp/packages/textalive-app-api/interfaces/iword.html#pos
const classes = [];
if (
current.parent.pos === "N" ||
current.parent.pos === "PN" ||
current.parent.pos === "X"
) {
classes.push("noun");
}
// フレーズの最後の文字か否か
if (current.parent.parent.lastChar === current) {
classes.push("lastChar");
}
// noun, lastChar クラスを必要に応じて追加
const div = document.createElement("div");
div.className = classes.join(" ");
div.appendChild(document.createTextNode(current.text));
// 文字を画面上に追加
const container = document.createElement("div");
container.appendChild(div);
container.addEventListener("click", () => {
player.requestMediaSeek(current.startTime);
});
textContainer.appendChild(container);
}
/**
* 歌詞表示をリセットする
* Reset lyrics view
*/
function resetChars() {
c = null;
while (textContainer.firstChild)
textContainer.removeChild(textContainer.firstChild);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment