|
|
|
@@ -78,12 +78,26 @@
|
|
|
|
let volumeBar = $state();
|
|
|
|
let volumeBar = $state();
|
|
|
|
let innerWidth = $state();
|
|
|
|
let innerWidth = $state();
|
|
|
|
let innerHeight = $state();
|
|
|
|
let innerHeight = $state();
|
|
|
|
|
|
|
|
let isSafari = $state(false);
|
|
|
|
|
|
|
|
|
|
|
|
onMount(async () => {
|
|
|
|
onMount(async () => {
|
|
|
|
|
|
|
|
// work around coreaudio bug
|
|
|
|
|
|
|
|
// see https://git.webbieweb.org/apt-get/strimserve/issues/1
|
|
|
|
|
|
|
|
const ua = navigator.userAgent;
|
|
|
|
|
|
|
|
const isIOS = /iPad|iPhone|iPod/.test(ua);
|
|
|
|
|
|
|
|
const isDesktopSafari = /Safari/.test(ua) && !/Chrome/.test(ua) && !/Chromium/.test(ua);
|
|
|
|
|
|
|
|
isSafari = isIOS || isDesktopSafari;
|
|
|
|
|
|
|
|
|
|
|
|
// default volume
|
|
|
|
// default volume
|
|
|
|
const volumeData = localStorage.getItem('volume');
|
|
|
|
const volumeData = localStorage.getItem('volume');
|
|
|
|
volume = volumeData ? parseFloat(volumeData) : 0.67;
|
|
|
|
volume = volumeData ? parseFloat(volumeData) : 0.67;
|
|
|
|
setVolume(volume);
|
|
|
|
setVolume(volume);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// set actions for previous/next track media buttons
|
|
|
|
|
|
|
|
if ('mediaSession' in navigator) {
|
|
|
|
|
|
|
|
navigator.mediaSession.setActionHandler('previoustrack', previousTrack);
|
|
|
|
|
|
|
|
navigator.mediaSession.setActionHandler('nexttrack', nextTrack);
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// update browser metadata on track changes
|
|
|
|
// update browser metadata on track changes
|
|
|
|
@@ -93,8 +107,28 @@
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function previousTrack() {
|
|
|
|
|
|
|
|
if (!ctx.current || ctx.songIndex == null) return;
|
|
|
|
|
|
|
|
const tracks = ctx.current.tracks;
|
|
|
|
|
|
|
|
const trackStart = tracks[ctx.songIndex][0];
|
|
|
|
|
|
|
|
// if more than 3s into the track, restart it; otherwise go to previous
|
|
|
|
|
|
|
|
if (audio.currentTime - trackStart > 3 || ctx.songIndex === 0) {
|
|
|
|
|
|
|
|
audio.currentTime = currentTime = trackStart;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
audio.currentTime = currentTime = tracks[ctx.songIndex - 1][0];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function nextTrack() {
|
|
|
|
|
|
|
|
if (!ctx.current || ctx.songIndex == null) return;
|
|
|
|
|
|
|
|
const tracks = ctx.current.tracks;
|
|
|
|
|
|
|
|
if (ctx.songIndex < tracks.length - 1) {
|
|
|
|
|
|
|
|
audio.currentTime = currentTime = tracks[ctx.songIndex + 1][0];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function seek(event, bounds) {
|
|
|
|
function seek(event, bounds) {
|
|
|
|
let x = event.pageX - bounds.left;
|
|
|
|
let x = event.clientX - bounds.left;
|
|
|
|
return Math.min(Math.max(x / bounds.width, 0), 1);
|
|
|
|
return Math.min(Math.max(x / bounds.width, 0), 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -136,11 +170,6 @@
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function seekAudio(event) {
|
|
|
|
|
|
|
|
if (!songBar) return;
|
|
|
|
|
|
|
|
audio.currentTime = seek(event, songBar.getBoundingClientRect()) * duration;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateSeekVisual(event) {
|
|
|
|
function updateSeekVisual(event) {
|
|
|
|
if (!songBar) return;
|
|
|
|
if (!songBar) return;
|
|
|
|
pendingSeekTime = seek(event, songBar.getBoundingClientRect()) * duration;
|
|
|
|
pendingSeekTime = seek(event, songBar.getBoundingClientRect()) * duration;
|
|
|
|
@@ -154,7 +183,7 @@
|
|
|
|
muted = false;
|
|
|
|
muted = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatSeconds(totalSeconds) {
|
|
|
|
function formatSeconds(totalSeconds, forceHours = false) {
|
|
|
|
if (isNaN(totalSeconds)) return 'No Data';
|
|
|
|
if (isNaN(totalSeconds)) return 'No Data';
|
|
|
|
totalSeconds = parseInt(totalSeconds, 10);
|
|
|
|
totalSeconds = parseInt(totalSeconds, 10);
|
|
|
|
var hours = Math.floor(totalSeconds / 3600);
|
|
|
|
var hours = Math.floor(totalSeconds / 3600);
|
|
|
|
@@ -163,18 +192,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
return [hours, minutes, seconds]
|
|
|
|
return [hours, minutes, seconds]
|
|
|
|
.map((v) => (v < 10 ? '0' + v : v))
|
|
|
|
.map((v) => (v < 10 ? '0' + v : v))
|
|
|
|
.filter((v, i) => v !== '00' || i > 0)
|
|
|
|
.filter((v, i) => v !== '00' || i > 0 || forceHours)
|
|
|
|
.join(':');
|
|
|
|
.join(':');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function seekTooltip(event) {
|
|
|
|
function seekTooltip(event) {
|
|
|
|
if (!inlineTooltip) {
|
|
|
|
if (!inlineTooltip) {
|
|
|
|
let tooltipBounds = tooltip.getBoundingClientRect();
|
|
|
|
let tooltipBounds = tooltip.getBoundingClientRect();
|
|
|
|
tooltipX = Math.min(event.pageX + 10, innerWidth - tooltipBounds.width);
|
|
|
|
tooltipX = Math.min(event.clientX + 10, innerWidth - tooltipBounds.width);
|
|
|
|
tooltipY = Math.min(songBar.offsetTop - 30, innerHeight - tooltipBounds.height);
|
|
|
|
tooltipY = Math.min(songBar.offsetTop - 30, innerHeight - tooltipBounds.height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let bounds = songBar.getBoundingClientRect();
|
|
|
|
let bounds = songBar.getBoundingClientRect();
|
|
|
|
let seekValue = ((event.pageX - bounds.left) * duration) / bounds.width;
|
|
|
|
let seekValue = ((event.clientX - bounds.left) * duration) / bounds.width;
|
|
|
|
|
|
|
|
updateTooltipText(seekValue);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateTooltipText(seekValue: number) {
|
|
|
|
if (!ctx.current) return;
|
|
|
|
if (!ctx.current) return;
|
|
|
|
let trackArray = ctx.current.tracks[ctx.getSongAtTime(seekValue)];
|
|
|
|
let trackArray = ctx.current.tracks[ctx.getSongAtTime(seekValue)];
|
|
|
|
seekTrack = trackArray[1] + ' - ' + trackArray[2];
|
|
|
|
seekTrack = trackArray[1] + ' - ' + trackArray[2];
|
|
|
|
@@ -220,6 +253,8 @@
|
|
|
|
onmouseup={() => {
|
|
|
|
onmouseup={() => {
|
|
|
|
if (seeking && pendingSeekTime != null) {
|
|
|
|
if (seeking && pendingSeekTime != null) {
|
|
|
|
audio.currentTime = pendingSeekTime;
|
|
|
|
audio.currentTime = pendingSeekTime;
|
|
|
|
|
|
|
|
currentTime = pendingSeekTime;
|
|
|
|
|
|
|
|
updateTooltipText(pendingSeekTime);
|
|
|
|
pendingSeekTime = null;
|
|
|
|
pendingSeekTime = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
seeking = volumeSeeking = false;
|
|
|
|
seeking = volumeSeeking = false;
|
|
|
|
@@ -244,14 +279,13 @@
|
|
|
|
bind:this={songBar}
|
|
|
|
bind:this={songBar}
|
|
|
|
value={seeking && pendingSeekTime != null ? pendingSeekTime : (currentTime || 0)}
|
|
|
|
value={seeking && pendingSeekTime != null ? pendingSeekTime : (currentTime || 0)}
|
|
|
|
max={duration}
|
|
|
|
max={duration}
|
|
|
|
onmousedown={() => (seeking = true)}
|
|
|
|
onmousedown={(e) => { seeking = true; updateSeekVisual(e); }}
|
|
|
|
onmouseenter={() => (showTooltip = true)}
|
|
|
|
onmouseenter={() => (showTooltip = true)}
|
|
|
|
onmouseleave={() => (showTooltip = false)}
|
|
|
|
onmouseleave={() => (showTooltip = false)}
|
|
|
|
onclick={seekAudio}
|
|
|
|
|
|
|
|
style="--primary-color:{barPrimaryColor}; --secondary-color:{barSecondaryColor}"
|
|
|
|
style="--primary-color:{barPrimaryColor}; --secondary-color:{barSecondaryColor}"
|
|
|
|
class="song-progress"
|
|
|
|
class="song-progress"
|
|
|
|
></progress>
|
|
|
|
></progress>
|
|
|
|
<div class="control-times">{formatSeconds(currentTime)}/{formatSeconds(duration)}</div>
|
|
|
|
<div class="control-times">{formatSeconds(currentTime, duration >= 3600)}/{formatSeconds(duration)}</div>
|
|
|
|
<button
|
|
|
|
<button
|
|
|
|
style="--icon-color:{iconColor}"
|
|
|
|
style="--icon-color:{iconColor}"
|
|
|
|
class="material-icons"
|
|
|
|
class="material-icons"
|
|
|
|
@@ -311,8 +345,13 @@
|
|
|
|
onended={bubble('ended')}
|
|
|
|
onended={bubble('ended')}
|
|
|
|
{preload}
|
|
|
|
{preload}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<source {src} type="audio/ogg;codecs=opus" />
|
|
|
|
{#if isSafari}
|
|
|
|
<source src="{src}.mp3" type="audio/mpeg" />
|
|
|
|
<source src="{src}.mp3" type="audio/mpeg" />
|
|
|
|
|
|
|
|
<source {src} type="audio/ogg;codecs=opus" />
|
|
|
|
|
|
|
|
{:else}
|
|
|
|
|
|
|
|
<source {src} type="audio/ogg;codecs=opus" />
|
|
|
|
|
|
|
|
<source src="{src}.mp3" type="audio/mpeg" />
|
|
|
|
|
|
|
|
{/if}
|
|
|
|
</audio>
|
|
|
|
</audio>
|
|
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
<style>
|
|
|
|
@@ -337,6 +376,9 @@
|
|
|
|
.control-times {
|
|
|
|
.control-times {
|
|
|
|
margin: auto;
|
|
|
|
margin: auto;
|
|
|
|
margin-right: 5px;
|
|
|
|
margin-right: 5px;
|
|
|
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
min-width: max-content;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tooltip {
|
|
|
|
.tooltip {
|
|
|
|
|