<div id="overlay">
<p><span class="far"></span>now loading...</p>
</div>
<div id="header">
<div id="control" class="far">
<a href="#" id="play" class="disabled"></a>
<a href="#" id="stop" class="disabled"></a>
</div>
<div id="meta">
<div id="artist">artist: <span>-</span></div>
<div id="song">song: <span>-</span></div>
</div>
</div>
<div id="media"></div>
<div id="lyrics">
<div id="text"></div>
<div id="bar"></div>
</div>
<div id="seekbar">
<div></div>
</div>
body {
background: #c33c68;
background-image: linear-gradient(0deg, #eed475 0, #60a8a9 50%, #d7809e 100%);
background-attachment: fixed;
background-size: 100vw 100vh;
}
#overlay {
user-select: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
background: #0006;
color: #fffc;
z-index: 5;
}
#overlay.disabled {
display: none;
}
#overlay > p {
width: 100vw;
font-size: 40px;
text-align: center;
}
#overlay > p > span {
display: inline-block;
padding-right: 20px;
margin-right: 20px;
border-right: 1px solid #fff6;
}
#header,
#media {
position: fixed;
left: 0;
background: #000c;
color: #fff;
z-index: 1;
}
#header {
top: 20px;
padding: 10px 16px;
font-size: 10.5px;
font-weight: bold;
display: flex;
align-items: center;
}
#control {
font-size: 21px;
padding-right: 10px;
border-right: 1px solid #fff9;
}
#control.disabled {
display: none;
}
#control a {
color: rgb(99 208 226);
text-decoration: none;
}
#control a.disabled {
opacity: 0.3;
}
#control a:hover {
color: rgb(255 148 56);
}
#meta {
padding-left: 10px;
}
#meta span {
font-weight: normal;
}
#media {
bottom: 10px;
}
#media.disabled > .textalive-media-wrapper {
width: 0;
height: 0;
}
@keyframes activateBeatBar {
0% {
opacity: 0;
}
100% {
width: 100%;
opacity: 1;
}
}
@keyframes showBeatBar {
0% {
width: 0;
opacity: 1;
}
50% {
width: 100%;
opacity: 1;
}
100% {
width: 100%;
opacity: 0;
}
}
@keyframes showLyrics {
0% {
transform: translate3d(0, 100%, 0);
opacity: 0;
}
100% {
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
#bar {
opacity: 0;
height: 3px;
background: rgb(255 222 193);
}
#bar.active {
animation: activateBeatBar 0.3s;
}
#bar.beat {
animation: showBeatBar 0.5s;
}
#lyrics {
z-index: 0;
padding: 3em 0 5em 0;
line-height: 2em;
font-size: 36px;
font-family: "Shippori Mincho B1", serif;
color: #e2f8fc;
text-shadow: 2px 2px 3px #e4215a;
user-select: none;
cursor: pointer;
transform: rotateX(10deg) rotateY(-10deg);
}
#text > div {
display: inline-block;
}
#text > div > div {
animation: showLyrics 0.5s;
}
#text .noun {
color: #ddf9ff;
font-size: 40px;
}
#text .lastChar {
margin-right: 40px;
}
#text .firstCharInEnglishWord {
margin-left: 20px;
}
#text .lastCharInEnglishWord {
margin-right: 20px;
}
#text .lastCharInEnglishWord + .firstCharInEnglishWord {
margin-left: 0;
}
#seekbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 10px;
background: rgba(255 255 255 / 40%);
}
#seekbar > div {
width: 0;
height: 100%;
background: rgba(255 255 255 / 80%);
}
const { Player, stringToDataUrl } = TextAliveApp;
const player = new Player({
app: { token: "test" },
mediaElement: document.querySelector("#media"),
mediaBannerPosition: "bottom right"
});
const overlay = document.querySelector("#overlay");
const bar = document.querySelector("#bar");
const textContainer = document.querySelector("#text");
const seekbar = document.querySelector("#seekbar");
const paintedSeekbar = seekbar.querySelector("div");
let b, c;
player.addListener({
onAppReady(app) {
if (app.managed) {
document.querySelector("#control").className = "disabled";
}
if (!app.songUrl) {
document.querySelector("#media").className = "disabled";
player.createFromSongUrl("https://piapro.jp/t/hZ35/20240130103028", {
video: {
beatId: 4592293,
chordId: 2727635,
repetitiveSegmentId: 2824326,
lyricId: 59415,
lyricDiffId: 13962
}
});
}
},
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) {
paintedSeekbar.style.width = `${
parseInt((position * 1000) / player.video.duration) / 10
}%`;
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();
}
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;
});
seekbar.addEventListener("click", (e) => {
e.preventDefault();
if (player) {
player.requestMediaSeek(
(player.video.duration * e.offsetX) / seekbar.clientWidth
);
}
return false;
});
function newChar(current) {
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");
}
if (current.parent.language === "en") {
if (current.parent.lastChar === current) {
classes.push("lastCharInEnglishWord");
} else if (current.parent.firstChar === current) {
classes.push("firstCharInEnglishWord");
}
}
const div = document.createElement("div");
div.appendChild(document.createTextNode(current.text));
const container = document.createElement("div");
container.className = classes.join(" ");
container.appendChild(div);
container.addEventListener("click", () => {
player.requestMediaSeek(current.startTime);
});
textContainer.appendChild(container);
}
function resetChars() {
c = null;
while (textContainer.firstChild)
textContainer.removeChild(textContainer.firstChild);
}