-
-
Save monaka/ee2e262ba008713d49717b8cfd7c35b2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
</head> | |
<body> | |
<!-- オーバーレイ / Overlay --> | |
<div id="overlay"> | |
<p><span class="far"></span>now loading...</p> | |
</div> | |
<!-- ヘッダ / Header --> | |
<div id="header"> | |
<!-- 再生コントロール / Playback control --> | |
<div id="control" class="far"> | |
<a href="#" id="play" class="disabled"></a> | |
<a href="#" id="stop" class="disabled"></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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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