Audio embed previews (no player yet).

This commit is contained in:
flash 2023-01-29 20:29:20 +00:00
parent a4be74cae3
commit e6e5c873f6
5 changed files with 593 additions and 10 deletions

View file

@ -204,3 +204,162 @@
-webkit-backdrop-filter: blur(10px);
transition: opacity .2s, transform .2s;
}
.aembed {
display: inline-block;
overflow: hidden;
text-shadow: initial;
}
.aembedph {
display: inline-block;
overflow: hidden;
cursor: pointer;
color: var(--text-colour);
text-decoration: none;
text-shadow: initial;
max-width: 500px;
min-width: 300px;
height: 70px;
border-radius: 5px;
margin: 5px;
}
.aembedph:hover .aembedph-play,
.aembedph:active .aembedph-play,
.aembedph:focus .aembedph-play,
.aembedph:focus-within .aembedph-play {
opacity: 1;
}
.aembedph-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.aembedph-bg-none {
background: var(--background-pattern);
background-color: var(--aembedph-colour, var(--accent-colour));
background-blend-mode: multiply;
}
.aembedph-bg img {
object-fit: cover;
width: 100%;
height: 100%;
display: inline-block;
transform: scale(1.1);
filter: blur(10px) brightness(80%);
}
.aembedph-fg {
width: 100%;
height: 100%;
}
.aembedph-info {
display: flex;
background-color: var(--background-colour-translucent-5);
align-items: center;
width: 100%;
height: 100%;
padding: 5px;
}
.aembedph-info-cover {
flex: 0 0 auto;
overflow: hidden;
border-radius: 5px;
}
.aembedph-info-cover-none {
background-color: var(--aembedph-colour, var(--accent-colour));
width: 5px;
height: 100%;
}
.aembedph-info-cover img {
max-width: 60px;
max-height: 60px;
display: inline-block;
vertical-align: middle;
}
.aembedph-info-cover-none img {
display: none;
}
.aembedph-info-body {
padding: 0 5px;
}
.aembedph-info-title {
font-size: 1.4em;
font-weight: 400;
line-height: 1.2em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 430px;
}
.aembedph-info-title-artist {
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 700;
}
.aembedph-info-album {
line-height: 1.4em;
word-break: break-word;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 430px;
}
.aembedph-info-site {
font-size: .9em;
line-height: 1.2em;
}
.aembedph-play {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity .2s;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
.aembedph-play-internal {
width: 70px;
height: 70px;
text-align: center;
flex: 0 0 auto;
display: flex;
justify-content: center;
align-items: center;
}
.aembedph-play-external {
flex: 1 1 auto;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 5px;
}
.aembedph-play-external-link {
padding: 2px 5px;
line-height: 1.5em;
text-decoration: none;
color: var(--text-colour);
background-color: var(--background-colour-translucent-6);
border-radius: 5px;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
transition: background-color .2s;
}
.aembedph-play-external-link:hover,
.aembedph-play-external-link:focus {
background-color: var(--background-colour-translucent-8);
}
.aembedph-play-external-link:active {
background-color: var(--background-colour-translucent-5);
}

365
assets/js/misuzu/aembed.js Normal file
View file

@ -0,0 +1,365 @@
const MszAudioEmbedPlayerEvents = function() {
return [
'play', 'pause', 'stop',
'mute', 'volume',
'rate', 'duration', 'time',
];
};
const MszAudioEmbed = function(player) {
const elem = $e({
attrs: {
classList: ['aembed', 'aembed-' + player.getType()],
},
child: player,
});
return {
getElement: function() {
return elem;
},
appendTo: function(target) {
target.appendChild(elem);
},
insertBefore: function(ref) {
$ib(ref, elem);
},
nuke: function() {
$r(elem);
},
replaceElement(target) {
$ib(target, elem);
$r(target);
},
getPlayer: function() {
return player;
},
};
};
const MszAudioEmbedPlayer = function(metadata, options) {
options = options || {};
const shouldAutoplay = options.autoplay === undefined || options.autoplay,
haveNativeControls = options.nativeControls !== undefined && options.nativeControls;
const playerAttrs = {
src: metadata.url,
style: {},
};
if(shouldAutoplay)
playerAttrs.autoplay = 'autoplay';
if(haveNativeControls)
playerAttrs.controls = 'controls';
const watchers = new MszWatcherCollection;
watchers.define(MszAudioEmbedPlayerEvents());
const player = $e({
tag: 'audio',
attrs: playerAttrs,
});
const pub = {
getElement: function() {
return player;
},
appendTo: function(target) {
target.appendChild(player);
},
insertBefore: function(ref) {
$ib(ref, player);
},
nuke: function() {
$r(player);
},
replaceElement(target) {
$ib(target, player);
$r(target);
},
getType: function() { return 'external'; },
};
watchers.proxy(pub);
player.addEventListener('play', function() { watchers.call('play', pub); });
const pPlay = function() { player.play(); };
pub.play = pPlay;
const pPause = function() { player.pause(); };
pub.pause = pPause;
let stopCalled = false;
player.addEventListener('pause', function() {
watchers.call(stopCalled ? 'stop' : 'pause', pub);
stopCalled = false;
});
const pStop = function() {
stopCalled = true;
player.pause();
player.currentTime = 0;
};
pub.stop = pStop;
const pIsPlaying = function() { return !player.paused; };
pub.isPlaying = pIsPlaying;
const pIsMuted = function() { return player.muted; };
pub.isMuted = pIsMuted;
let lastMuteState = player.muted;
player.addEventListener('volumechange', function() {
if(lastMuteState !== player.muted) {
lastMuteState = player.muted;
watchers.call('mute', pub, [lastMuteState]);
} else
watchers.call('volume', pub, [player.volume]);
});
const pSetMuted = function(state) { player.muted = state; };
pub.setMuted = pSetMuted;
const pGetVolume = function() { return player.volume; };
pub.getVolume = pGetVolume;
const pSetVolume = function(volume) { player.volume = volume; };
pub.setVolume = pSetVolume;
const pGetPlaybackRate = function() { return player.playbackRate; };
pub.getPlaybackRate = pGetPlaybackRate;
player.addEventListener('ratechange', function() {
watchers.call('rate', pub, [player.playbackRate]);
});
const pSetPlaybackRate = function(rate) { player.playbackRate = rate; };
pub.setPlaybackRate = pSetPlaybackRate;
window.addEventListener('durationchange', function() {
watchers.call('duration', pub, [player.duration]);
});
const pGetDuration = function() { return player.duration; };
pub.getDuration = pGetDuration;
window.addEventListener('timeupdate', function() {
watchers.call('time', pub, [player.currentTime]);
});
const pGetTime = function() { return player.currentTime; };
pub.getTime = pGetTime;
const pSeek = function(time) { player.currentTime = time; };
pub.seek = pSeek;
return pub;
};
const MszAudioEmbedPlaceholder = function(metadata, options) {
options = options || {};
if(typeof options.player !== 'function' && typeof options.onclick !== 'function')
throw 'Neither a player nor an onclick handler were provided.';
let title = [],
album = undefined;
if(metadata.media !== undefined && metadata.media.tags !== undefined) {
const tags = metadata.media.tags;
if(tags.title !== undefined) {
if(tags.artist !== undefined) {
title.push({
tag: 'span',
attrs: {
className: 'aembedph-info-title-artist',
},
child: tags.artist,
});
title.push(' - ');
}
title.push({
tag: 'span',
attrs: {
className: 'aembedph-info-title-title',
},
child: tags.title,
});
} else {
title.push({
tag: 'span',
attrs: {
className: 'aembedph-info-title-title',
},
child: metadata.title,
});
}
if(tags.album !== undefined && tags.album !== tags.title)
album = tags.album;
}
const infoChildren = [];
infoChildren.push({
tag: 'h1',
attrs: {
className: 'aembedph-info-title',
},
child: title,
});
infoChildren.push({
tags: 'p',
attrs: {
className: 'aembedph-info-album',
},
child: album,
});
infoChildren.push({
tag: 'div',
attrs: {
className: 'aembedph-info-site',
},
child: metadata.site_name,
});
const style = [];
if(typeof metadata.color !== 'undefined')
style.push('--aembedph-colour: ' + metadata.color);
const coverBackground = $e({
attrs: {
className: 'aembedph-bg',
},
child: {
tag: 'img',
attrs: {
alt: '',
src: metadata.image,
onerror: function(ev) {
coverBackground.classList.add('aembedph-bg-none');
},
},
},
});
const coverPreview = $e({
attrs: {
className: 'aembedph-info-cover',
},
child: {
tag: 'img',
attrs: {
alt: '',
src: metadata.image,
onerror: function(ev) {
coverPreview.classList.add('aembedph-info-cover-none');
},
},
},
});
const pub = {};
const elem = $e({
attrs: {
className: ('aembedph aembedph-' + (options.type || 'external')),
style: style.join(';'),
title: metadata.title,
},
child: [
coverBackground,
{
attrs: {
className: 'aembedph-fg',
},
child: [
{
attrs: {
className: 'aembedph-info',
},
child: [
coverPreview,
{
attrs: {
className: 'aembedph-info-body',
},
child: infoChildren,
}
],
},
{
attrs: {
className: 'aembedph-play',
onclick: function(ev) {
if(ev.target.tagName.toLowerCase() === 'a')
return;
if(typeof options.onclick === 'function') {
options.onclick(ev);
return;
}
const player = new options.player(metadata, options);
const embed = new MszAudioEmbed(player);
if(options.autoembed === undefined || options.autoembed)
embed.replaceElement(elem);
if(typeof options.onembed === 'function')
options.onembed(embed);
},
},
child: [
{
attrs: {
className: 'aembedph-play-internal',
},
child: {
tag: 'i',
attrs: {
className: 'fas fa-play fa-3x fa-fw',
},
},
},
{
attrs: {
className: 'aembedph-play-external',
},
child: {
tag: 'a',
attrs: {
className: 'aembedph-play-external-link',
href: metadata.url,
target: '_blank',
rel: 'noopener',
},
child: ('or listen on ' + metadata.site_name + '?')
},
}
],
}
],
},
],
});
pub.getElement = function() { return elem; };
pub.appendTo = function(target) { target.appendChild(elem); };
pub.insertBefore = function(ref) { $ib(ref, elem); };
pub.nuke = function() {
$r(elem);
elem = null;
};
pub.replaceElement = function(target) {
$ib(target, elem);
$r(target);
};
return pub;
}

View file

@ -97,7 +97,10 @@ var MszEmbed = (function() {
options.maxWidth = 640;
options.maxHeight = 360;
} else if(metadata.is_audio) {
phc = MszAudioEmbedPlaceholder;
options.type = 'external';
options.player = MszAudioEmbedPlayer;
options.nativeControls = true;
} else if(metadata.is_image) {
options.type = 'external';
}

View file

@ -81,6 +81,61 @@ const MszVideoEmbed = function(playerOrFrame) {
const MszVideoEmbedFrame = function(player, options) {
options = options || {};
const icoStatePlay = 'fa-play',
icoStatePause = 'fa-pause',
icoStateStop = 'fa-stop';
const icoVolMute = 'fa-volume-mute',
icoVolOff = 'fa-volume-off',
icoVolQuiet = 'fa-volume-down',
icoVolLoud = 'fa-volume-up';
const btnPlayPause = $e({
attrs: {},
child: {
tag: 'i',
attrs: {
classList: ['fas', 'fa-fw', icoStatePlay],
},
}
});
const btnStop = $e({
attrs: {},
child: {
tag: 'i',
attrs: {
classList: ['fas', 'fa-fw', icoStateStop],
},
},
});
const numCurrentTime = $e({
attrs: {},
});
const sldProgress = $e({
attrs: {},
child: [],
});
const numDurationRemaining = $e({
attrs: {},
});
const btnVolMute = $e({
attrs: {},
child: {
tag: 'i',
attrs: {
// isMuted === icoVolMute
// vol < 0.01 === icoVolOff
// vol < 0.5 === icoVolQuiet
// vol < 1.0 === icoVolLoud
classList: ['fas', 'fa-fw', icoVolLoud],
},
},
});
const elem = $e({
attrs: {
className: 'embedvf',
@ -106,7 +161,11 @@ const MszVideoEmbedFrame = function(player, options) {
className: 'embedvf-controls',
},
child: [
'Play/Pause Stop [|---------] 1.00x 100%',
btnPlayPause,
btnStop,
numCurrentTime,
sldProgress,
numDurationRemaining,
],
},
],
@ -728,7 +787,6 @@ const MszVideoEmbedPlaceholder = function(metadata, options) {
const elem = $e({
attrs: {
href: 'javascript:void(0);',
className: ('embedph embedph-' + (options.type || 'external')),
style: style.join(';'),
},
@ -821,7 +879,7 @@ const MszVideoEmbedPlaceholder = function(metadata, options) {
child: ('or watch on ' + metadata.site_name + '?'),
}
],
}
},
],
},
],

View file

@ -13,13 +13,11 @@ final class AudioTag extends BBCodeTag {
if(empty($url['scheme']) || !in_array(mb_strtolower($url['scheme']), ['http', 'https'], true))
return $matches[0];
// return sprintf(
// '<span class="js-msz-embed-media" data-msz-embed-url="%1$s"></span>'
// . '<noscript><a href="%1$s" class="link" rel="noopener noreferrer">%1$s</a></noscript>',
// $matches[1]
// );
return "<audio controls src='{$matches[1]}'></audio>";
return sprintf(
'<span class="js-msz-embed-media" data-msz-embed-url="%1$s"></span>'
. '<noscript><a href="%1$s" class="link" rel="noopener noreferrer">%1$s</a></noscript>',
$matches[1]
);
},
$text
);