播放暂停按钮不会在曲目跳过时重置
playPause button not resetting upon track skip
所以这是我第一次使用 JavaScript,我遇到了播放器的一些错误。目前除了一些 CSS 问题外,只有两件事不起作用。首先,此 post 的主要问题是播放/暂停按钮在我更改歌曲时未重置。要播放下一首歌曲,我必须双击、暂停播放和播放暂停(然后开始播放歌曲),而且自动播放下一首歌曲的功能也不起作用,我猜这是相关的?
第二个小问题是曲目名称没有改变。您可以看到的轨道的 HTML 行是
<div class="song-title">Track1</div>
Visual of player
const background = document.querySelector('#background');
const thumbnail = document.querySelector('#thumbnail');
const song = document.querySelector('#song');
const songArtist = document.querySelector('.song-artist');
const songTitle = document.querySelector('.song-title');
const progressBar = document.querySelector('#progress-bar');
let pPause = document.querySelector('#play-pause');
songIndex = 0;
songs = ['/music/track1.mp3', '/music/track2.mp3', '/music/track3.mp3', '/music/track4.mp3', '/music/track5.mp3', '/music/track6.mp3', '/music/track7.mp3'];
thumbnails = ['/images/J&G Logo.png', '/images/J&G Logo.png', '/images/J&G Logo.png', '/images/J&G Logo.png', '/images/J&G Logo.png', '/images/J&G Logo.png', '/images/J&G Logo.png', ];
songArtists = ['Jelly & The GOAT', 'Jelly & The GOAT', 'Jelly & The GOAT', 'Jelly & The GOAT', 'Jelly & The GOAT', 'Jelly & The GOAT', 'Jelly & The GOAT',];
songTitles = ["Track1", "Track2", "Track3", "Track4", "Track5", "Track6", "Track7"];
let playing = true;
function playPause() {
if (playing) {
const song = document.querySelector('#song'),
thumbnail = document.querySelector('#thumbnail');
pPause.src = "/images/pause-icon.png"
thumbnail.style.transform = "scale(1.15)";
song.play();
playing = false;
} else {
pPause.src = "/images/play-icon.png"
thumbnail.style.transform = "scale(1)";
song.pause();
playing = true;
}
}
song.addEventListener('ended', function(){
nextSong();
});
function nextSong() {
songIndex++;
if (songIndex === songs.length) {
songIndex = 0;
};
song.src = songs[songIndex];
thumbnail.src = thumbnails[songIndex];
background.src = thumbnails[songIndex];
songArtist.innerHTML = songArtists[songIndex];
songTitle.innerHTML = songTitles[songIndex];
playing = true;
playPause();
}
function previousSong() {
songIndex--;
if (songIndex < 0) {
songIndex = songs.length - 1;
};
song.src = songs[songIndex];
thumbnail.src = thumbnails[songIndex];
background.src = thumbnails[songIndex];
songArtist.innerHTML = songArtists[songIndex];
songTitle.innerHTML = songTitles[songIndex];
playing = true;
playPause();
}
function updateProgressValue() {
progressBar.max = song.duration;
progressBar.value = song.currentTime;
document.querySelector('.currentTime').innerHTML = (formatTime(Math.floor(song.currentTime)));
if (document.querySelector('.durationTime').innerHTML === "NaN:NaN") {
document.querySelector('.durationTime').innerHTML = "0:00";
} else {
document.querySelector('.durationTime').innerHTML = (formatTime(Math.floor(song.duration)));
}
};
function formatTime(seconds) {
let min = Math.floor((seconds / 60));
let sec = Math.floor(seconds - (min * 60));
if (sec < 10){
sec = `0${sec}`;
};
return `${min}:${sec}`;
};
setInterval (updateProgressValue, 500);
function changeProgressBar() {
song.currentTime = progressBar.value;
};
如果您需要 HTML,请告诉我。请注意,图标切换正常。感谢您的帮助,我们将不胜感激 :) !
差异
术语媒体标签是<audio>
和<video>
的通用名称标签.
OP 缺少任何 event listeners or on-event properties so it's safe to assume that there are on-event attributes:
<button onclick='lame()' type='button'>Click Me I'm Lame</button>
不要使用它们,它们很烂。而是使用:
<button type='button'>Click Me</button>
const btn = document.queryselector('button');
btn.addEventListener('click', clickHandler);
/* OR */
btn.onclick = clickHandler;
有一个名为 isPlaying
的布尔值。两个问题:
最初不应该是true
。常识表明 song
在页面加载后不会播放(如果播放,为了用户体验重新考虑)。
不使用标志,而是使用媒体属性 return 一个关于媒体标签状态的布尔值(即 song
):song.paused
,song.ended
, 和 song.playing
.
nextSong()
的第一个if
条件是错误的:
if (songIndex === songs.length) {...
songs.length
是7。somgIndex
范围是0-6。所以应该是songs.length -1
没有包含 <progress>
标签及其处理程序,看起来它被正确复制了……差不多。这看起来好像是后来添加的:
setInterval (updateProgressValue, 500);
function changeProgressBar() {
song.currentTime = progressBar.value;
};
setInterval()
是 "timeupdate"
事件的糟糕替代品。媒体标签在播放时每 250 毫秒触发一次 "timeupedate"
。使用属性 song.currentTime
和 song.duration
来监视播放期间经过的时间。
changeProgressBar()
看起来没用。已经有一个名为 updateProgressValue()
的处理程序。 changeProgressBar()
中唯一的一行是 updateProgressValue()
:
中其中一行的反向版本
song.currentTime = progressBar.value;
progressBar.value = song.currentTime;
建议
术语表单控件是<input>
、<output>
的通用术语、<textarea>
、<button>
、<select>
、<fieldset>
和 <object>
标签。
术语 祖先标签 是指在 DOM 树上共享相同分支的标签的术语高于 targets -- 开发人员打算灌输由事件触发的行为的祖先标签的后代标签的术语。
如果您的布局有多个表单控件,请将所有内容包装在 <form>
标记中。这样做允许您使用 HTMLFormElement interface and HTMLFormControlsCollection API。语法简洁,简化了对表单控件的访问。
<form id='main'>
<fieldset name='set'>
<input>
<button type='button'>
[type='button'] is needed when nested within a <form>...
</button>
<button>
...otherwise it is a [type='submit'] by default.
</button>
</fieldset>
<fieldset name='set'>
<input id='input1'>
<input id='input2'>
</fieldset>
</form>
<script>
/*
- Referencing the <form> by #ID
- Or bracket notation document.forms['main'];
- Or by index document.forms[0];
*/
const main = document.forms.main;
/*
- Once the <form> is referenced -- use the `.elements` property to collect
all of its form controls into a **HTMLCollection** (Use an short and easy
to remember identifier to reference it).
*/
const ui = main.elements;
/*
- <form> tags and form controls can be referenced by [name] attribute as
well. Keep in mind that if there is more than one tag with the same
[name] -- then they will be collected into a **HTMLCollection**.
- Note: There is another fieldset[name=set] -- so in this situation you can
access them both: `sets[0]` and `sets[1]`
*/
const sets = ui.set;
/*
- To reference a form control without a #ID or [name], its index is always
available. There's an archaic way as well: `const input = ui.item(1)`
- There's no need to reference the second <button> because its a
[type='submit'] tag. They have default behavior built-in: *Sends data to
server* and *Resets the <form>*
*/
const input = ui[1];
const button = ui[2];
/*
- Of the many ways to reference a form control -- #ID is the easiest.
*/
const i1 = ui.input1;
const i2 = ui.input2;
</script>
Event Delegation is a programming pattern that leverages Event Bubbling 这样我们...
...不仅可以侦听在未知和无限数量的标签上触发的事件——我们还可以侦听页面加载后动态创建的标签的触发事件。
此外,监听事件只需要一个所有目标标签共有的祖先标签,(window
、document
、body
是始终可用,但仅在没有其他标签更接近所有目标时才使用。
document.forms[0].onclick = controlPlayer;
Event handler function must pass the Event Object so that the .target
属性 可用于确定与用户交互的实际标签(在本例中为点击的内容)。
function controlPlayer(event) {
const clicked = event.target;
...
一旦确定,我们就会进一步缩小可能性。这样做可以让我们通过明确排除不需要处理的标签来排除它们:
if (event.target.matches('[type=button]')) {...
更改标签的外观以指示状态可以通过切换 .classes
来完成,这些标签具有其所代表的状态所特有的样式。通常的做法是使用 pseudo-elements ::before
and ::after
演示
const thumb = document.querySelector('.thumb');
const song = document.querySelector('#song');
const form = document.forms.ui;
const ui = form.elements;
const artist = ui.artist;
const title = ui.title;
const play = ui.playBack;
const prev = ui.prev;
const next = ui.next;
const base = 'https://glpjt.s3.amazonaws.com/so/av';
const songs = ['/mp3/righteous.mp3', '/mp3/jerky.mp3', '/mp3/nosegoblin.mp3', '/mp3/balls.mp3', '/mp3/behave.mp3'];
const thumbs = ['/img/link.gif', '/img/sol.png', '/img/ren.gif', '/img/balls.gif', '/img/austin.gif'];
const artists = ['Samuel L. Jackson', 'Sol Rosenberg', 'Ren', 'Al Pachino', 'Mike Myers'];
const titles = ["Righteous", "Whatnot", "Magic Nose Goblins", "Balls", "Behave"];
let index = 0;
let quantity = songs.length;
const playMP3 = () => {
play.classList.remove('play');
play.classList.add('pause');
song.play();
}
const pauseMP3 = () => {
play.classList.remove('pause');
play.classList.add('play');
song.pause();
}
form.onclick = controlPlayer;
song.onended = function(event) {
pauseMP3();
index++;
if (index > quantity - 1) {
index = 0;
}
song.src = base + songs[index];
thumb.src = base + thumbs[index];
artist.value = artists[index];
title.value = titles[index];
playMP3();
}
function init() {
song.src = base + songs[0];
thumb.src = base + thumbs[0];
artist.value = artists[0];
title.value = titles[0];
song.load();
}
function controlPlayer(event) {
const clicked = event.target;
if (clicked.matches('#playBack')) {
if (song.paused || song.ended) {
playMP3();
} else {
pauseMP3();
}
}
if (clicked.matches('#prev')) {
pauseMP3();
index--;
if (index < 0) {
index = quantity - 1;
}
song.src = base + songs[index];
thumb.src = base + thumbs[index];
artist.value = artists[index];
title.value = titles[index];
playMP3();
}
if (clicked.matches('#next')) {
pauseMP3();
index++;
if (index > quantity - 1) {
index = 0;
}
song.src = base + songs[index];
thumb.src = base + thumbs[index];
artist.value = artists[index];
title.value = titles[index];
playMP3();
}
}
init();
:root,
body {
font: 700 small-caps 2.5vw/1.5 Verdana;
}
b {
display: inline-block;
width: 6ch;
color: white;
}
output {
color: gold;
text-shadow: 0 0 5px red;
}
button {
display: inline-block;
width: 3vw;
height: 11.25vw;
line-height: 11.25vw;
border: 0;
font: inherit;
font-size: 3rem;
vertical-align: middle;
color: lime;
background: none;
cursor: pointer;
}
button:hover {
color: cyan;
}
#playBack.play::before {
content: '[=20=]25b6';
}
#playBack.pause {
height: 5.625vw;
line-height: 5.625vw;
}
#playBack.pause::before {
content: '[=20=]258e\a0[=20=]258e';
height: 5.625vw;
line-height: 5.625vw;
font-size: 2rem;
vertical-align: top;
}
#prev::before {
content: '[=20=]23ee';
height: 5vw;
line-height: 5vw;
font-size: 3.25rem;
vertical-align: top;
}
#next,
#prev {
height: 5vw;
line-height: 5vw;
}
#next::before {
content: '[=20=]23ed';
height: 5vw;
line-height: 5vw;
font-size: 3.25rem;
vertical-align: top;
}
figure {
position: relative;
min-height: 150px;
margin: 0px auto;
}
figcaption {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
height: max-content;
padding: 3px 5px;
font-size: 1.2rem;
}
figcaption label {
background: rgba(0, 0, 0, 0.4);
}
.thumb {
display: block;
max-width: 100%;
height: auto;
margin: 5px auto;
object-fit: contain;
}
.controls {
display: flex;
flex-flow: row nowrap;
justify-content: space-evenly;
align-items: center;
height: 6vw;
}
<main>
<form id='ui'>
<audio id='song'></audio>
<fieldset>
<figure>
<img class='thumb'>
<figcaption>
<label>
<b>Artist:</b> <output id='artist'></output>
</label>
<label><br>
<b>Title:</b> <output id='title'></output>
</label>
</figcaption>
</figure>
<fieldset class='controls'>
<button id='prev' type='button'></button>
<button id='playBack' class='play' type='button'></button>
<button id='next' type='button'></button>
</fieldset>
</fieldset>
</form>
</main>
所以这是我第一次使用 JavaScript,我遇到了播放器的一些错误。目前除了一些 CSS 问题外,只有两件事不起作用。首先,此 post 的主要问题是播放/暂停按钮在我更改歌曲时未重置。要播放下一首歌曲,我必须双击、暂停播放和播放暂停(然后开始播放歌曲),而且自动播放下一首歌曲的功能也不起作用,我猜这是相关的?
第二个小问题是曲目名称没有改变。您可以看到的轨道的 HTML 行是
<div class="song-title">Track1</div>
Visual of player
const background = document.querySelector('#background');
const thumbnail = document.querySelector('#thumbnail');
const song = document.querySelector('#song');
const songArtist = document.querySelector('.song-artist');
const songTitle = document.querySelector('.song-title');
const progressBar = document.querySelector('#progress-bar');
let pPause = document.querySelector('#play-pause');
songIndex = 0;
songs = ['/music/track1.mp3', '/music/track2.mp3', '/music/track3.mp3', '/music/track4.mp3', '/music/track5.mp3', '/music/track6.mp3', '/music/track7.mp3'];
thumbnails = ['/images/J&G Logo.png', '/images/J&G Logo.png', '/images/J&G Logo.png', '/images/J&G Logo.png', '/images/J&G Logo.png', '/images/J&G Logo.png', '/images/J&G Logo.png', ];
songArtists = ['Jelly & The GOAT', 'Jelly & The GOAT', 'Jelly & The GOAT', 'Jelly & The GOAT', 'Jelly & The GOAT', 'Jelly & The GOAT', 'Jelly & The GOAT',];
songTitles = ["Track1", "Track2", "Track3", "Track4", "Track5", "Track6", "Track7"];
let playing = true;
function playPause() {
if (playing) {
const song = document.querySelector('#song'),
thumbnail = document.querySelector('#thumbnail');
pPause.src = "/images/pause-icon.png"
thumbnail.style.transform = "scale(1.15)";
song.play();
playing = false;
} else {
pPause.src = "/images/play-icon.png"
thumbnail.style.transform = "scale(1)";
song.pause();
playing = true;
}
}
song.addEventListener('ended', function(){
nextSong();
});
function nextSong() {
songIndex++;
if (songIndex === songs.length) {
songIndex = 0;
};
song.src = songs[songIndex];
thumbnail.src = thumbnails[songIndex];
background.src = thumbnails[songIndex];
songArtist.innerHTML = songArtists[songIndex];
songTitle.innerHTML = songTitles[songIndex];
playing = true;
playPause();
}
function previousSong() {
songIndex--;
if (songIndex < 0) {
songIndex = songs.length - 1;
};
song.src = songs[songIndex];
thumbnail.src = thumbnails[songIndex];
background.src = thumbnails[songIndex];
songArtist.innerHTML = songArtists[songIndex];
songTitle.innerHTML = songTitles[songIndex];
playing = true;
playPause();
}
function updateProgressValue() {
progressBar.max = song.duration;
progressBar.value = song.currentTime;
document.querySelector('.currentTime').innerHTML = (formatTime(Math.floor(song.currentTime)));
if (document.querySelector('.durationTime').innerHTML === "NaN:NaN") {
document.querySelector('.durationTime').innerHTML = "0:00";
} else {
document.querySelector('.durationTime').innerHTML = (formatTime(Math.floor(song.duration)));
}
};
function formatTime(seconds) {
let min = Math.floor((seconds / 60));
let sec = Math.floor(seconds - (min * 60));
if (sec < 10){
sec = `0${sec}`;
};
return `${min}:${sec}`;
};
setInterval (updateProgressValue, 500);
function changeProgressBar() {
song.currentTime = progressBar.value;
};
如果您需要 HTML,请告诉我。请注意,图标切换正常。感谢您的帮助,我们将不胜感激 :) !
差异
术语媒体标签是<audio>
和<video>
的通用名称标签.
OP 缺少任何 event listeners or on-event properties so it's safe to assume that there are on-event attributes:
<button onclick='lame()' type='button'>Click Me I'm Lame</button>
不要使用它们,它们很烂。而是使用:
<button type='button'>Click Me</button> const btn = document.queryselector('button'); btn.addEventListener('click', clickHandler); /* OR */ btn.onclick = clickHandler;
有一个名为
isPlaying
的布尔值。两个问题:最初不应该是
true
。常识表明song
在页面加载后不会播放(如果播放,为了用户体验重新考虑)。不使用标志,而是使用媒体属性 return 一个关于媒体标签状态的布尔值(即
song
):song.paused
,song.ended
, 和song.playing
.
nextSong()
的第一个if
条件是错误的:if (songIndex === songs.length) {...
songs.length
是7。somgIndex
范围是0-6。所以应该是songs.length -1
没有包含
<progress>
标签及其处理程序,看起来它被正确复制了……差不多。这看起来好像是后来添加的:setInterval (updateProgressValue, 500); function changeProgressBar() { song.currentTime = progressBar.value; };
setInterval()
是"timeupdate"
事件的糟糕替代品。媒体标签在播放时每 250 毫秒触发一次"timeupedate"
。使用属性song.currentTime
和song.duration
来监视播放期间经过的时间。
中其中一行的反向版本changeProgressBar()
看起来没用。已经有一个名为updateProgressValue()
的处理程序。changeProgressBar()
中唯一的一行是updateProgressValue()
:song.currentTime = progressBar.value; progressBar.value = song.currentTime;
建议
术语表单控件是<input>
、<output>
的通用术语、<textarea>
、<button>
、<select>
、<fieldset>
和 <object>
标签。
术语 祖先标签 是指在 DOM 树上共享相同分支的标签的术语高于 targets -- 开发人员打算灌输由事件触发的行为的祖先标签的后代标签的术语。
如果您的布局有多个表单控件,请将所有内容包装在
<form>
标记中。这样做允许您使用 HTMLFormElement interface and HTMLFormControlsCollection API。语法简洁,简化了对表单控件的访问。<form id='main'> <fieldset name='set'> <input> <button type='button'> [type='button'] is needed when nested within a <form>... </button> <button> ...otherwise it is a [type='submit'] by default. </button> </fieldset> <fieldset name='set'> <input id='input1'> <input id='input2'> </fieldset> </form> <script> /* - Referencing the <form> by #ID - Or bracket notation document.forms['main']; - Or by index document.forms[0]; */ const main = document.forms.main; /* - Once the <form> is referenced -- use the `.elements` property to collect all of its form controls into a **HTMLCollection** (Use an short and easy to remember identifier to reference it). */ const ui = main.elements; /* - <form> tags and form controls can be referenced by [name] attribute as well. Keep in mind that if there is more than one tag with the same [name] -- then they will be collected into a **HTMLCollection**. - Note: There is another fieldset[name=set] -- so in this situation you can access them both: `sets[0]` and `sets[1]` */ const sets = ui.set; /* - To reference a form control without a #ID or [name], its index is always available. There's an archaic way as well: `const input = ui.item(1)` - There's no need to reference the second <button> because its a [type='submit'] tag. They have default behavior built-in: *Sends data to server* and *Resets the <form>* */ const input = ui[1]; const button = ui[2]; /* - Of the many ways to reference a form control -- #ID is the easiest. */ const i1 = ui.input1; const i2 = ui.input2; </script>
Event Delegation is a programming pattern that leverages Event Bubbling 这样我们...
...不仅可以侦听在未知和无限数量的标签上触发的事件——我们还可以侦听页面加载后动态创建的标签的触发事件。
此外,监听事件只需要一个所有目标标签共有的祖先标签,(
window
、document
、body
是始终可用,但仅在没有其他标签更接近所有目标时才使用。document.forms[0].onclick = controlPlayer;
Event handler function must pass the Event Object so that the
.target
属性 可用于确定与用户交互的实际标签(在本例中为点击的内容)。function controlPlayer(event) { const clicked = event.target; ...
一旦确定,我们就会进一步缩小可能性。这样做可以让我们通过明确排除不需要处理的标签来排除它们:
if (event.target.matches('[type=button]')) {...
更改标签的外观以指示状态可以通过切换
.classes
来完成,这些标签具有其所代表的状态所特有的样式。通常的做法是使用 pseudo-elements::before
and::after
演示
const thumb = document.querySelector('.thumb');
const song = document.querySelector('#song');
const form = document.forms.ui;
const ui = form.elements;
const artist = ui.artist;
const title = ui.title;
const play = ui.playBack;
const prev = ui.prev;
const next = ui.next;
const base = 'https://glpjt.s3.amazonaws.com/so/av';
const songs = ['/mp3/righteous.mp3', '/mp3/jerky.mp3', '/mp3/nosegoblin.mp3', '/mp3/balls.mp3', '/mp3/behave.mp3'];
const thumbs = ['/img/link.gif', '/img/sol.png', '/img/ren.gif', '/img/balls.gif', '/img/austin.gif'];
const artists = ['Samuel L. Jackson', 'Sol Rosenberg', 'Ren', 'Al Pachino', 'Mike Myers'];
const titles = ["Righteous", "Whatnot", "Magic Nose Goblins", "Balls", "Behave"];
let index = 0;
let quantity = songs.length;
const playMP3 = () => {
play.classList.remove('play');
play.classList.add('pause');
song.play();
}
const pauseMP3 = () => {
play.classList.remove('pause');
play.classList.add('play');
song.pause();
}
form.onclick = controlPlayer;
song.onended = function(event) {
pauseMP3();
index++;
if (index > quantity - 1) {
index = 0;
}
song.src = base + songs[index];
thumb.src = base + thumbs[index];
artist.value = artists[index];
title.value = titles[index];
playMP3();
}
function init() {
song.src = base + songs[0];
thumb.src = base + thumbs[0];
artist.value = artists[0];
title.value = titles[0];
song.load();
}
function controlPlayer(event) {
const clicked = event.target;
if (clicked.matches('#playBack')) {
if (song.paused || song.ended) {
playMP3();
} else {
pauseMP3();
}
}
if (clicked.matches('#prev')) {
pauseMP3();
index--;
if (index < 0) {
index = quantity - 1;
}
song.src = base + songs[index];
thumb.src = base + thumbs[index];
artist.value = artists[index];
title.value = titles[index];
playMP3();
}
if (clicked.matches('#next')) {
pauseMP3();
index++;
if (index > quantity - 1) {
index = 0;
}
song.src = base + songs[index];
thumb.src = base + thumbs[index];
artist.value = artists[index];
title.value = titles[index];
playMP3();
}
}
init();
:root,
body {
font: 700 small-caps 2.5vw/1.5 Verdana;
}
b {
display: inline-block;
width: 6ch;
color: white;
}
output {
color: gold;
text-shadow: 0 0 5px red;
}
button {
display: inline-block;
width: 3vw;
height: 11.25vw;
line-height: 11.25vw;
border: 0;
font: inherit;
font-size: 3rem;
vertical-align: middle;
color: lime;
background: none;
cursor: pointer;
}
button:hover {
color: cyan;
}
#playBack.play::before {
content: '[=20=]25b6';
}
#playBack.pause {
height: 5.625vw;
line-height: 5.625vw;
}
#playBack.pause::before {
content: '[=20=]258e\a0[=20=]258e';
height: 5.625vw;
line-height: 5.625vw;
font-size: 2rem;
vertical-align: top;
}
#prev::before {
content: '[=20=]23ee';
height: 5vw;
line-height: 5vw;
font-size: 3.25rem;
vertical-align: top;
}
#next,
#prev {
height: 5vw;
line-height: 5vw;
}
#next::before {
content: '[=20=]23ed';
height: 5vw;
line-height: 5vw;
font-size: 3.25rem;
vertical-align: top;
}
figure {
position: relative;
min-height: 150px;
margin: 0px auto;
}
figcaption {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
height: max-content;
padding: 3px 5px;
font-size: 1.2rem;
}
figcaption label {
background: rgba(0, 0, 0, 0.4);
}
.thumb {
display: block;
max-width: 100%;
height: auto;
margin: 5px auto;
object-fit: contain;
}
.controls {
display: flex;
flex-flow: row nowrap;
justify-content: space-evenly;
align-items: center;
height: 6vw;
}
<main>
<form id='ui'>
<audio id='song'></audio>
<fieldset>
<figure>
<img class='thumb'>
<figcaption>
<label>
<b>Artist:</b> <output id='artist'></output>
</label>
<label><br>
<b>Title:</b> <output id='title'></output>
</label>
</figcaption>
</figure>
<fieldset class='controls'>
<button id='prev' type='button'></button>
<button id='playBack' class='play' type='button'></button>
<button id='next' type='button'></button>
</fieldset>
</fieldset>
</form>
</main>