diff --git a/assets/css/misuzu/embed.css b/assets/css/misuzu/embed.css
index 01f8351..0a67b2c 100644
--- a/assets/css/misuzu/embed.css
+++ b/assets/css/misuzu/embed.css
@@ -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);
+}
diff --git a/assets/js/misuzu/aembed.js b/assets/js/misuzu/aembed.js
new file mode 100644
index 0000000..ee0fbd9
--- /dev/null
+++ b/assets/js/misuzu/aembed.js
@@ -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;
+}
diff --git a/assets/js/misuzu/embed.js b/assets/js/misuzu/embed.js
index 88fe365..9af9d83 100644
--- a/assets/js/misuzu/embed.js
+++ b/assets/js/misuzu/embed.js
@@ -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';
}
diff --git a/assets/js/misuzu/vembed.js b/assets/js/misuzu/vembed.js
index 665f58c..59960e4 100644
--- a/assets/js/misuzu/vembed.js
+++ b/assets/js/misuzu/vembed.js
@@ -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 + '?'),
}
],
- }
+ },
],
},
],
diff --git a/src/Parsers/BBCode/Tags/AudioTag.php b/src/Parsers/BBCode/Tags/AudioTag.php
index e96f598..c78a06c 100644
--- a/src/Parsers/BBCode/Tags/AudioTag.php
+++ b/src/Parsers/BBCode/Tags/AudioTag.php
@@ -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(
- // ''
- // . '',
- // $matches[1]
- // );
-
- return "";
+ return sprintf(
+ ''
+ . '',
+ $matches[1]
+ );
},
$text
);