inital audio player commits
This commit is contained in:
parent
4b67ed6a9c
commit
b0c320cd0a
12 changed files with 693 additions and 39 deletions
|
@ -1,8 +1,10 @@
|
||||||
|
import {File} from "./file.js";
|
||||||
|
|
||||||
class ImagesDisplay {
|
class ImagesDisplay {
|
||||||
images: string[];
|
files: File[];
|
||||||
index = 0;
|
index = 0;
|
||||||
constructor(srcs: string[], index = 0) {
|
constructor(files: File[], index = 0) {
|
||||||
this.images = srcs;
|
this.files = files;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
weakbg = new WeakRef<HTMLElement>(document.createElement("div"));
|
weakbg = new WeakRef<HTMLElement>(document.createElement("div"));
|
||||||
|
@ -14,19 +16,64 @@ class ImagesDisplay {
|
||||||
}
|
}
|
||||||
makeHTML(): HTMLElement {
|
makeHTML(): HTMLElement {
|
||||||
//TODO this should be able to display more than one image at a time lol
|
//TODO this should be able to display more than one image at a time lol
|
||||||
const image = document.createElement("img");
|
const image = this.files[this.index].getHTML(false, true);
|
||||||
image.src = this.images[this.index];
|
|
||||||
image.classList.add("imgfit", "centeritem");
|
image.classList.add("imgfit", "centeritem");
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
show() {
|
show() {
|
||||||
this.background = document.createElement("div");
|
this.background = document.createElement("div");
|
||||||
this.background.classList.add("background");
|
this.background.classList.add("background");
|
||||||
this.background.appendChild(this.makeHTML());
|
let cur = this.makeHTML();
|
||||||
|
if (this.files.length !== 1) {
|
||||||
|
const right = document.createElement("span");
|
||||||
|
right.classList.add("rightArrow", "svg-intoMenu");
|
||||||
|
right.onclick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
this.index++;
|
||||||
|
this.index %= this.files.length;
|
||||||
|
cur.remove();
|
||||||
|
cur = this.makeHTML();
|
||||||
|
if (this.background) {
|
||||||
|
this.background.appendChild(cur);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const left = document.createElement("span");
|
||||||
|
left.onclick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
this.index += this.files.length - 1;
|
||||||
|
this.index %= this.files.length;
|
||||||
|
cur.remove();
|
||||||
|
cur = this.makeHTML();
|
||||||
|
if (this.background) {
|
||||||
|
this.background.appendChild(cur);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
left.classList.add("leftArrow", "svg-leftArrow");
|
||||||
|
this.background.append(right, left);
|
||||||
|
this.background.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "ArrowRight") {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
right.click();
|
||||||
|
}
|
||||||
|
if (e.key === "ArrowLeft") {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
right.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.background.appendChild(cur);
|
||||||
this.background.onclick = (_) => {
|
this.background.onclick = (_) => {
|
||||||
this.hide();
|
this.hide();
|
||||||
};
|
};
|
||||||
document.body.append(this.background);
|
document.body.append(this.background);
|
||||||
|
this.background.setAttribute("tabindex", "0");
|
||||||
|
this.background.focus();
|
||||||
}
|
}
|
||||||
hide() {
|
hide() {
|
||||||
if (this.background) {
|
if (this.background) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {getapiurls, getInstances} from "./utils/utils.js";
|
||||||
import {Guild} from "./guild.js";
|
import {Guild} from "./guild.js";
|
||||||
import {I18n} from "./i18n.js";
|
import {I18n} from "./i18n.js";
|
||||||
import {ImagesDisplay} from "./disimg.js";
|
import {ImagesDisplay} from "./disimg.js";
|
||||||
|
import {File} from "./file.js";
|
||||||
|
|
||||||
class Embed {
|
class Embed {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -169,7 +170,9 @@ class Embed {
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
img.classList.add("messageimg");
|
img.classList.add("messageimg");
|
||||||
img.onclick = function () {
|
img.onclick = function () {
|
||||||
const full = new ImagesDisplay([img.src]);
|
const full = new ImagesDisplay([
|
||||||
|
new File({id: "", filename: "", url: img.src, size: -1, content_type: "image"}, null),
|
||||||
|
]);
|
||||||
full.show();
|
full.show();
|
||||||
};
|
};
|
||||||
img.src = this.json.thumbnail.proxy_url;
|
img.src = this.json.thumbnail.proxy_url;
|
||||||
|
@ -205,7 +208,9 @@ class Embed {
|
||||||
if (this.json.thumbnail) {
|
if (this.json.thumbnail) {
|
||||||
img.classList.add("embedimg");
|
img.classList.add("embedimg");
|
||||||
img.onclick = function () {
|
img.onclick = function () {
|
||||||
const full = new ImagesDisplay([img.src]);
|
const full = new ImagesDisplay([
|
||||||
|
new File({id: "", filename: "", url: img.src, size: -1, content_type: "image"}, null),
|
||||||
|
]);
|
||||||
full.show();
|
full.show();
|
||||||
};
|
};
|
||||||
img.src = this.json.thumbnail.proxy_url;
|
img.src = this.json.thumbnail.proxy_url;
|
||||||
|
@ -381,7 +386,9 @@ class Embed {
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
img.onclick = async () => {
|
img.onclick = async () => {
|
||||||
const full = new ImagesDisplay([img.src]);
|
const full = new ImagesDisplay([
|
||||||
|
new File({id: "", filename: "", url: img.src, size: -1, content_type: "image"}, null),
|
||||||
|
]);
|
||||||
full.show();
|
full.show();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Message} from "./message.js";
|
import {Message} from "./message.js";
|
||||||
import {filejson} from "./jsontypes.js";
|
import {filejson} from "./jsontypes.js";
|
||||||
import {ImagesDisplay} from "./disimg.js";
|
import {ImagesDisplay} from "./disimg.js";
|
||||||
|
import {makePlayBox, MediaPlayer} from "./media.js";
|
||||||
class File {
|
class File {
|
||||||
owner: Message | null;
|
owner: Message | null;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -24,7 +24,7 @@ class File {
|
||||||
this.content_type = fileJSON.content_type;
|
this.content_type = fileJSON.content_type;
|
||||||
this.size = fileJSON.size;
|
this.size = fileJSON.size;
|
||||||
}
|
}
|
||||||
getHTML(temp: boolean = false): HTMLElement {
|
getHTML(temp: boolean = false, fullScreen = false): HTMLElement {
|
||||||
const src = this.proxy_url || this.url;
|
const src = this.proxy_url || this.url;
|
||||||
if (this.width && this.height) {
|
if (this.width && this.height) {
|
||||||
let scale = 1;
|
let scale = 1;
|
||||||
|
@ -37,19 +37,33 @@ class File {
|
||||||
if (this.content_type.startsWith("image/")) {
|
if (this.content_type.startsWith("image/")) {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
|
if (!fullScreen) {
|
||||||
img.classList.add("messageimg");
|
img.classList.add("messageimg");
|
||||||
div.classList.add("messageimgdiv");
|
div.classList.add("messageimgdiv");
|
||||||
img.onclick = function () {
|
}
|
||||||
const full = new ImagesDisplay([img.src]);
|
img.onclick = () => {
|
||||||
|
if (this.owner) {
|
||||||
|
const full = new ImagesDisplay(
|
||||||
|
this.owner.attachments,
|
||||||
|
this.owner.attachments.indexOf(this),
|
||||||
|
);
|
||||||
full.show();
|
full.show();
|
||||||
|
} else {
|
||||||
|
const full = new ImagesDisplay([this]);
|
||||||
|
full.show();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
img.src = src;
|
img.src = src;
|
||||||
div.append(img);
|
div.append(img);
|
||||||
if (this.width) {
|
if (this.width && !fullScreen) {
|
||||||
div.style.width = this.width + "px";
|
div.style.width = this.width + "px";
|
||||||
div.style.height = this.height + "px";
|
div.style.height = this.height + "px";
|
||||||
}
|
}
|
||||||
|
if (!fullScreen) {
|
||||||
return div;
|
return div;
|
||||||
|
} else {
|
||||||
|
return img;
|
||||||
|
}
|
||||||
} else if (this.content_type.startsWith("video/")) {
|
} else if (this.content_type.startsWith("video/")) {
|
||||||
const video = document.createElement("video");
|
const video = document.createElement("video");
|
||||||
const source = document.createElement("source");
|
const source = document.createElement("source");
|
||||||
|
@ -63,17 +77,15 @@ class File {
|
||||||
}
|
}
|
||||||
return video;
|
return video;
|
||||||
} else if (this.content_type.startsWith("audio/")) {
|
} else if (this.content_type.startsWith("audio/")) {
|
||||||
const audio = document.createElement("audio");
|
return this.getAudioHTML();
|
||||||
const source = document.createElement("source");
|
|
||||||
source.src = src;
|
|
||||||
audio.append(source);
|
|
||||||
source.type = this.content_type;
|
|
||||||
audio.controls = !temp;
|
|
||||||
return audio;
|
|
||||||
} else {
|
} else {
|
||||||
return this.createunknown();
|
return this.createunknown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private getAudioHTML() {
|
||||||
|
const src = this.proxy_url || this.url;
|
||||||
|
return makePlayBox(src, player);
|
||||||
|
}
|
||||||
upHTML(files: Blob[], file: globalThis.File): HTMLElement {
|
upHTML(files: Blob[], file: globalThis.File): HTMLElement {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
const contained = this.getHTML(true);
|
const contained = this.getHTML(true);
|
||||||
|
@ -149,4 +161,6 @@ class File {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const player = new MediaPlayer();
|
||||||
export {File};
|
export {File};
|
||||||
|
|
1
src/webpage/icons/leftArrow.svg
Normal file
1
src/webpage/icons/leftArrow.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path fill="none" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="29" d="M109 15 45 90l64 75"/></svg>
|
After Width: | Height: | Size: 189 B |
1
src/webpage/icons/pause.svg
Normal file
1
src/webpage/icons/pause.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="red" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="20" d="M93 55h98v401H93zm220-1h98v401h-98z"/></svg>
|
After Width: | Height: | Size: 204 B |
1
src/webpage/icons/play.svg
Normal file
1
src/webpage/icons/play.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="red" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="23.9" d="M85 466 84 52l360 207Z"/></svg>
|
After Width: | Height: | Size: 193 B |
|
@ -107,7 +107,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flexttb" id="sideContainDiv">
|
||||||
<div class="flexttb" id="sideDiv"></div>
|
<div class="flexttb" id="sideDiv"></div>
|
||||||
|
<div id="player" class="flexttb"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -357,7 +357,7 @@ type filejson = {
|
||||||
content_type: string;
|
content_type: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
proxy_url: string | undefined;
|
proxy_url?: string;
|
||||||
url: string;
|
url: string;
|
||||||
size: number;
|
size: number;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2326,9 +2326,10 @@ class Localuser {
|
||||||
})
|
})
|
||||||
.filter((_) => _ !== undefined);
|
.filter((_) => _ !== undefined);
|
||||||
const sideDiv = document.getElementById("sideDiv");
|
const sideDiv = document.getElementById("sideDiv");
|
||||||
if (!sideDiv) return;
|
const sideContainDiv = document.getElementById("sideContainDiv");
|
||||||
|
if (!sideDiv || !sideContainDiv) return;
|
||||||
sideDiv.innerHTML = "";
|
sideDiv.innerHTML = "";
|
||||||
sideDiv.classList.add("searchDiv");
|
sideContainDiv.classList.add("searchDiv");
|
||||||
let channel: Channel | undefined = undefined;
|
let channel: Channel | undefined = undefined;
|
||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
if (channel !== message.channel) {
|
if (channel !== message.channel) {
|
||||||
|
|
507
src/webpage/media.ts
Normal file
507
src/webpage/media.ts
Normal file
|
@ -0,0 +1,507 @@
|
||||||
|
import {I18n} from "./i18n.js";
|
||||||
|
|
||||||
|
type mediaEvents =
|
||||||
|
| {
|
||||||
|
type: "play";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "pause";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "playing";
|
||||||
|
time: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "done";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "audio";
|
||||||
|
t: "start" | "stop";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "audio";
|
||||||
|
t: "skip";
|
||||||
|
time: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "end";
|
||||||
|
};
|
||||||
|
|
||||||
|
function makePlayBox(mor: string | media, player: MediaPlayer) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.classList.add("flexltr", "Mplayer");
|
||||||
|
|
||||||
|
const button = document.createElement("img");
|
||||||
|
button.classList.add("svg-mediaButton", "svg-play");
|
||||||
|
|
||||||
|
const vDiv = document.createElement("div");
|
||||||
|
vDiv.classList.add("flexttb");
|
||||||
|
|
||||||
|
const title = document.createElement("span");
|
||||||
|
title.textContent = I18n.media.loading();
|
||||||
|
|
||||||
|
const barDiv = document.createElement("div");
|
||||||
|
barDiv.classList.add("flexltr");
|
||||||
|
|
||||||
|
const bar = document.createElement("input");
|
||||||
|
bar.type = "range";
|
||||||
|
bar.disabled = true;
|
||||||
|
bar.value = "0";
|
||||||
|
bar.min = "0";
|
||||||
|
|
||||||
|
const time = document.createElement("span");
|
||||||
|
time.textContent = "0:00/..:..";
|
||||||
|
|
||||||
|
barDiv.append(bar, time);
|
||||||
|
vDiv.append(title, barDiv);
|
||||||
|
div.append(button, vDiv);
|
||||||
|
MediaPlayer.IdentifyFile(mor).then((thing) => {
|
||||||
|
let audio: HTMLAudioElement | undefined = undefined;
|
||||||
|
|
||||||
|
if (!thing) {
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.textContent = I18n.media.notFound();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.addListener(thing.src, followUpdates, div);
|
||||||
|
let int = setInterval((_) => {}, 1000);
|
||||||
|
if (mor instanceof Object) {
|
||||||
|
const audioo = new Audio(mor.src);
|
||||||
|
audioo.load();
|
||||||
|
audioo.autoplay = true;
|
||||||
|
int = setInterval(() => {
|
||||||
|
if (button.classList.contains("svg-pause")) {
|
||||||
|
player.addUpdate(mor.src, {type: "playing", time: audioo.currentTime * 1000});
|
||||||
|
}
|
||||||
|
}, 100) as unknown as number;
|
||||||
|
audioo.onplay = () => {
|
||||||
|
player.addUpdate(mor.src, {type: "play"});
|
||||||
|
};
|
||||||
|
audioo.onpause = () => {
|
||||||
|
player.addUpdate(thing.src, {type: "pause"});
|
||||||
|
};
|
||||||
|
audioo.onloadeddata = () => {
|
||||||
|
audio = audioo;
|
||||||
|
};
|
||||||
|
audioo.onended = () => {
|
||||||
|
player.addUpdate(mor.src, {type: "end"});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
button.onclick = () => {
|
||||||
|
if (!player.isPlaying(thing.src)) {
|
||||||
|
player.setToTopList(thing);
|
||||||
|
} else {
|
||||||
|
player.addUpdate(thing.src, {
|
||||||
|
type: "audio",
|
||||||
|
t: button.classList.contains("svg-play") ? "start" : "stop",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function followUpdates(cur: mediaEvents) {
|
||||||
|
if (audio && cur.type !== "playing") {
|
||||||
|
console.log(cur);
|
||||||
|
}
|
||||||
|
if (cur.type == "audio" && audio) {
|
||||||
|
if (cur.t == "start") {
|
||||||
|
audio.play();
|
||||||
|
} else if (cur.t == "stop") {
|
||||||
|
audio.pause();
|
||||||
|
} else if (cur.t == "skip" && audio) {
|
||||||
|
audio.currentTime = cur.time / 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cur.type == "playing") {
|
||||||
|
regenTime(cur.time);
|
||||||
|
bar.value = "" + cur.time / 1000;
|
||||||
|
button.classList.add("svg-pause");
|
||||||
|
button.classList.remove("svg-play");
|
||||||
|
} else if (cur.type === "play") {
|
||||||
|
button.classList.add("svg-pause");
|
||||||
|
button.classList.remove("svg-play");
|
||||||
|
} else if (cur.type === "pause") {
|
||||||
|
button.classList.add("svg-play");
|
||||||
|
button.classList.remove("svg-pause");
|
||||||
|
} else if (cur.type === "end") {
|
||||||
|
clearInterval(int);
|
||||||
|
if (audio) {
|
||||||
|
audio.pause();
|
||||||
|
audio.src = "";
|
||||||
|
player.end();
|
||||||
|
}
|
||||||
|
button.classList.add("svg-play");
|
||||||
|
button.classList.remove("svg-pause");
|
||||||
|
regenTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const med = thing;
|
||||||
|
if (med.img) {
|
||||||
|
let img: HTMLImageElement;
|
||||||
|
if (mor instanceof Object) {
|
||||||
|
img = button;
|
||||||
|
} else {
|
||||||
|
img = document.createElement("img");
|
||||||
|
div.append(img);
|
||||||
|
}
|
||||||
|
img.classList.add("media-small");
|
||||||
|
img.src = med.img.url;
|
||||||
|
}
|
||||||
|
function timeToString(time: number) {
|
||||||
|
const minutes = Math.floor(time / 1000 / 60);
|
||||||
|
const seconds = Math.round(time / 1000 - minutes * 60) + "";
|
||||||
|
return `${minutes}:${seconds.padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
bar.oninput = () => {
|
||||||
|
player.addUpdate(thing.src, {
|
||||||
|
type: "audio",
|
||||||
|
t: "skip",
|
||||||
|
time: +bar.value * 1000,
|
||||||
|
});
|
||||||
|
regenTime(+bar.value * 1000);
|
||||||
|
};
|
||||||
|
async function regenTime(curTime: number = 0) {
|
||||||
|
console.log(med.length);
|
||||||
|
const len = await med.length;
|
||||||
|
bar.disabled = false;
|
||||||
|
bar.max = "" + len / 1000;
|
||||||
|
|
||||||
|
time.textContent = `${timeToString(curTime)}/${timeToString(len)}`;
|
||||||
|
}
|
||||||
|
regenTime();
|
||||||
|
title.textContent = thing.title;
|
||||||
|
console.log(thing);
|
||||||
|
});
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface media {
|
||||||
|
src: string;
|
||||||
|
img?: {
|
||||||
|
url: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
title: string;
|
||||||
|
artist?: string;
|
||||||
|
composer?: string;
|
||||||
|
sourcy?: string;
|
||||||
|
year?: number;
|
||||||
|
copyright?: string;
|
||||||
|
length: number | Promise<number>;
|
||||||
|
}
|
||||||
|
class MediaPlayer {
|
||||||
|
lists: media[] = [];
|
||||||
|
curAudio?: HTMLElement;
|
||||||
|
private listeners: [(e: mediaEvents) => void, string][] = [];
|
||||||
|
elm: HTMLDivElement;
|
||||||
|
cur = 0;
|
||||||
|
constructor() {
|
||||||
|
this.elm = document.getElementById("player") as HTMLDivElement;
|
||||||
|
}
|
||||||
|
addElmToList(audio: media) {
|
||||||
|
this.lists.unshift(audio);
|
||||||
|
}
|
||||||
|
addUpdate(e: string, update: mediaEvents) {
|
||||||
|
for (const thing of this.listeners) {
|
||||||
|
if (thing[1] === e) {
|
||||||
|
thing[0](update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addListener(audio: string, updates: (e: mediaEvents) => void, elm: HTMLElement) {
|
||||||
|
this.listeners.push([updates, audio]);
|
||||||
|
const int = setInterval(() => {
|
||||||
|
if (!document.contains(elm)) {
|
||||||
|
clearInterval(int);
|
||||||
|
this.listeners = this.listeners.filter((_) => _[0] !== updates);
|
||||||
|
console.log("cleared data");
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
isPlaying(str: string) {
|
||||||
|
const med = this.lists[this.cur];
|
||||||
|
if (!med) return false;
|
||||||
|
return med.src === str;
|
||||||
|
}
|
||||||
|
setToTopList(audio: media) {
|
||||||
|
const med = this.lists[this.cur];
|
||||||
|
if (med) {
|
||||||
|
this.addUpdate(med.src, {type: "end"});
|
||||||
|
}
|
||||||
|
this.lists.splice(this.cur, 0, audio);
|
||||||
|
this.regenPlayer();
|
||||||
|
}
|
||||||
|
end() {
|
||||||
|
if (this.curAudio) {
|
||||||
|
this.curAudio.remove();
|
||||||
|
this.cur++;
|
||||||
|
this.regenPlayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
regenPlayer() {
|
||||||
|
this.elm.innerHTML = "";
|
||||||
|
if (this.lists.length > this.cur) {
|
||||||
|
const audio = this.lists[this.cur];
|
||||||
|
this.elm.append((this.curAudio = makePlayBox(audio, this)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static cache = new Map<string, media | Promise<media>>();
|
||||||
|
static async IdentifyFile(url: string | media): Promise<media | null> {
|
||||||
|
if (url instanceof Object) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
const cValue = this.cache.get(url);
|
||||||
|
if (cValue) {
|
||||||
|
return cValue;
|
||||||
|
}
|
||||||
|
let resMedio = (_: media) => {};
|
||||||
|
this.cache.set(url, new Promise<media>((res) => (resMedio = res)));
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
const f = await fetch(url, {
|
||||||
|
method: "get",
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
if (!f.ok || !f.body) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
const read = f.body.getReader();
|
||||||
|
let cbuff = (await read.read()).value;
|
||||||
|
const output: Partial<media> = {
|
||||||
|
src: url,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
let sizeLeft = 0;
|
||||||
|
async function next() {
|
||||||
|
return (await get8BitArray(1))[0];
|
||||||
|
}
|
||||||
|
async function get8BitArray(size: number) {
|
||||||
|
sizeLeft -= size;
|
||||||
|
const arr = new Uint8Array(size);
|
||||||
|
let arri = 0;
|
||||||
|
while (size > 0) {
|
||||||
|
if (!cbuff) throw Error("ran out of file to read");
|
||||||
|
let itter = Math.min(size, cbuff.length - index);
|
||||||
|
size -= itter;
|
||||||
|
for (let i = 0; i < itter; i++, arri++, index++) {
|
||||||
|
arr[arri] = cbuff[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size !== 0) {
|
||||||
|
cbuff = (await read.read()).value;
|
||||||
|
index = 0;
|
||||||
|
console.log("got more buffer", index, arri, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
const head = String.fromCharCode(await next(), await next(), await next());
|
||||||
|
if (head === "ID3") {
|
||||||
|
const version = (await next()) + (await next()) * 256;
|
||||||
|
|
||||||
|
if (version === 2) {
|
||||||
|
//TODO I'm like 90% I can ignore *all* of the flags, but I need to check more sometime
|
||||||
|
await next();
|
||||||
|
//debugger;
|
||||||
|
const sizes = await get8BitArray(4);
|
||||||
|
sizeLeft = (sizes[0] << 21) + (sizes[1] << 14) + (sizes[2] << 7) + sizes[3];
|
||||||
|
const mappy = new Map<string, Uint8Array>();
|
||||||
|
while (sizeLeft > 0) {
|
||||||
|
const Identify = String.fromCharCode(await next(), await next(), await next());
|
||||||
|
const sizeArr = await get8BitArray(3);
|
||||||
|
const size = (sizeArr[0] << 16) + (sizeArr[1] << 8) + sizeArr[2];
|
||||||
|
console.log(sizeLeft, size, index);
|
||||||
|
if (Identify === String.fromCharCode(0, 0, 0)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!size) {
|
||||||
|
throw Error("weirdness");
|
||||||
|
}
|
||||||
|
if (!Identify.match(/[A-Z0-9]/gm)) {
|
||||||
|
console.error(`tried to get ${Identify} which doesn't exist`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mappy.has(Identify)) {
|
||||||
|
await get8BitArray(size);
|
||||||
|
//console.warn("Got dupe", Identify);
|
||||||
|
} else {
|
||||||
|
mappy.set(Identify, await get8BitArray(size));
|
||||||
|
}
|
||||||
|
console.warn(sizeLeft);
|
||||||
|
}
|
||||||
|
const pic = mappy.get("PIC");
|
||||||
|
if (pic) {
|
||||||
|
let i = 5; //skipping info I don't need right now
|
||||||
|
const desc: number[] = [];
|
||||||
|
for (; pic[i]; i++) {
|
||||||
|
desc.push(pic[i]);
|
||||||
|
}
|
||||||
|
const description = new TextDecoder().decode(new Uint8Array(desc));
|
||||||
|
i++;
|
||||||
|
console.warn(pic, i);
|
||||||
|
const blob = new Blob([pic.slice(i, pic.length).buffer], {type: "image/jpeg"});
|
||||||
|
const urlmaker = window.URL || window.webkitURL;
|
||||||
|
const url = urlmaker.createObjectURL(blob);
|
||||||
|
output.img = {url, description};
|
||||||
|
}
|
||||||
|
function decodeText(buf: ArrayBuffer) {
|
||||||
|
let str = new TextDecoder().decode(buf);
|
||||||
|
if (str.startsWith("\u0000")) {
|
||||||
|
str = str.slice(1, str.length);
|
||||||
|
}
|
||||||
|
if (str.endsWith("\u0000")) {
|
||||||
|
str = str.slice(0, str.length - 1);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
const mapmap = {
|
||||||
|
TT2: "title",
|
||||||
|
TP1: "artist",
|
||||||
|
TCM: "composer",
|
||||||
|
TAL: "sourcy",
|
||||||
|
TCO: "type",
|
||||||
|
TEN: "copyright",
|
||||||
|
} as const;
|
||||||
|
for (const [key, ind] of Object.entries(mapmap)) {
|
||||||
|
const temp = mappy.get(key);
|
||||||
|
if (temp) {
|
||||||
|
//@ts-ignore TS is being weird about this idk why
|
||||||
|
output[ind] = decodeText(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tye = mappy.get("TYE");
|
||||||
|
if (tye) {
|
||||||
|
output.year = +decodeText(tye);
|
||||||
|
}
|
||||||
|
//TODO more thoroughly check if these two are the same format
|
||||||
|
} else if (version === 3 || version === 4) {
|
||||||
|
const flags = await next();
|
||||||
|
if (flags & 0b01000000) {
|
||||||
|
//TODO deal with the extended header
|
||||||
|
}
|
||||||
|
//debugger;
|
||||||
|
const sizes = await get8BitArray(4);
|
||||||
|
sizeLeft = (sizes[0] << 21) + (sizes[1] << 14) + (sizes[2] << 7) + sizes[3];
|
||||||
|
const mappy = new Map<string, Uint8Array>();
|
||||||
|
while (sizeLeft > 0) {
|
||||||
|
const Identify = String.fromCharCode(
|
||||||
|
await next(),
|
||||||
|
await next(),
|
||||||
|
await next(),
|
||||||
|
await next(),
|
||||||
|
);
|
||||||
|
const sizeArr = await get8BitArray(4);
|
||||||
|
const size = (sizeArr[0] << 24) + (sizeArr[1] << 16) + (sizeArr[2] << 8) + sizeArr[3];
|
||||||
|
|
||||||
|
const flags = await get8BitArray(2);
|
||||||
|
const compression = !!(flags[1] & 0b10000000);
|
||||||
|
if (compression) {
|
||||||
|
//TODO Honestly, I don't know if I can do this with normal JS
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const encyption = !!(flags[1] & 0b01000000);
|
||||||
|
if (encyption) {
|
||||||
|
//TODO not sure what this would even do
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(sizeLeft, size, index);
|
||||||
|
if (Identify === String.fromCharCode(0, 0, 0, 0)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!size) {
|
||||||
|
//throw Error("weirdness");
|
||||||
|
}
|
||||||
|
if (!Identify.match(/[A-Z0-9]/gm)) {
|
||||||
|
console.error(`tried to get ${Identify} which doesn't exist`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mappy.has(Identify)) {
|
||||||
|
await get8BitArray(size);
|
||||||
|
//console.warn("Got dupe", Identify);
|
||||||
|
} else {
|
||||||
|
mappy.set(Identify, await get8BitArray(size));
|
||||||
|
}
|
||||||
|
console.warn(sizeLeft);
|
||||||
|
}
|
||||||
|
const pic = mappy.get("APIC");
|
||||||
|
if (pic) {
|
||||||
|
const encoding = pic[0];
|
||||||
|
let i = 1; //skipping info I don't need right now
|
||||||
|
for (; pic[i]; i++) {}
|
||||||
|
i += 2;
|
||||||
|
let desc: number[] = [];
|
||||||
|
for (; pic[i]; i++) {
|
||||||
|
desc.push(pic[i]);
|
||||||
|
}
|
||||||
|
const description = new TextDecoder().decode(new Uint8Array(desc));
|
||||||
|
i++;
|
||||||
|
const blob = new Blob([pic.slice(i, pic.length).buffer], {type: "image/jpeg"});
|
||||||
|
const urlmaker = window.URL || window.webkitURL;
|
||||||
|
const url = urlmaker.createObjectURL(blob);
|
||||||
|
output.img = {url, description};
|
||||||
|
}
|
||||||
|
function decodeText(buf: ArrayBuffer) {
|
||||||
|
let str = new TextDecoder().decode(buf);
|
||||||
|
if (str.startsWith("\u0000")) {
|
||||||
|
str = str.slice(1, str.length);
|
||||||
|
}
|
||||||
|
if (str.endsWith("\u0000")) {
|
||||||
|
str = str.slice(0, str.length - 1);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
const mapmap = {
|
||||||
|
TIT2: "title",
|
||||||
|
TPE1: "artist",
|
||||||
|
TCOM: "composer",
|
||||||
|
TALB: "sourcy",
|
||||||
|
TMED: "type",
|
||||||
|
TENC: "copyright",
|
||||||
|
} as const;
|
||||||
|
for (const [key, ind] of Object.entries(mapmap)) {
|
||||||
|
const temp = mappy.get(key);
|
||||||
|
if (temp) {
|
||||||
|
//@ts-ignore TS is being weird about this idk why
|
||||||
|
output[ind] = decodeText(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const TYER = mappy.get("TYER");
|
||||||
|
if (TYER) {
|
||||||
|
console.log(decodeText(TYER));
|
||||||
|
output.year = +decodeText(TYER);
|
||||||
|
}
|
||||||
|
const TLEN = mappy.get("TLEN");
|
||||||
|
if (TLEN) {
|
||||||
|
output.length = +decodeText(TLEN);
|
||||||
|
}
|
||||||
|
console.log(mappy);
|
||||||
|
}
|
||||||
|
} //TODO implement more metadata types
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
controller.abort();
|
||||||
|
if (!output.length) {
|
||||||
|
output.length = new Promise<number>(async (res) => {
|
||||||
|
const audio = document.createElement("audio");
|
||||||
|
audio.src = url;
|
||||||
|
audio.onloadeddata = (_) => {
|
||||||
|
console.log("Loaded!", audio.duration * 1000);
|
||||||
|
output.length = audio.duration * 1000;
|
||||||
|
res(audio.duration * 1000);
|
||||||
|
};
|
||||||
|
audio.load();
|
||||||
|
console.log(audio);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!output.title) {
|
||||||
|
output.title = url.split("/").at(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resMedio(output as media);
|
||||||
|
return output as media;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {MediaPlayer, media, makePlayBox};
|
|
@ -28,6 +28,19 @@ body {
|
||||||
margin-right: 0.15in;
|
margin-right: 0.15in;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.Mplayer {
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--secondary-bg);
|
||||||
|
align-items: center;
|
||||||
|
*{
|
||||||
|
margin:2px;
|
||||||
|
}
|
||||||
|
.flexttb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
.flexltr {
|
.flexltr {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -70,6 +83,12 @@ body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
#player {
|
||||||
|
height: 128px;
|
||||||
|
}
|
||||||
|
#player:empty {
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
.messageEditContainer {
|
.messageEditContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -266,6 +285,42 @@ textarea {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
.leftArrow {
|
||||||
|
z-index: 1;
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
display: block;
|
||||||
|
background: var(--primary-text-soft);
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50vh - 42px);
|
||||||
|
left: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.rightArrow {
|
||||||
|
z-index: 1;
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
display: block;
|
||||||
|
background: var(--primary-text-soft);
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50vh - 42px);
|
||||||
|
right: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.svg-mediaButton {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background: var(--primary-text);
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
mask-size: cover !important;
|
||||||
|
}
|
||||||
|
.svg-play {
|
||||||
|
mask: url(/icons/play.svg);
|
||||||
|
}
|
||||||
|
.svg-pause {
|
||||||
|
mask: url(/icons/pause.svg);
|
||||||
|
}
|
||||||
.svg-announce {
|
.svg-announce {
|
||||||
mask: url(/icons/announce.svg);
|
mask: url(/icons/announce.svg);
|
||||||
}
|
}
|
||||||
|
@ -287,6 +342,9 @@ textarea {
|
||||||
.svg-intoMenu {
|
.svg-intoMenu {
|
||||||
mask: url(/icons/intoMenu.svg);
|
mask: url(/icons/intoMenu.svg);
|
||||||
}
|
}
|
||||||
|
.svg-leftArrow {
|
||||||
|
mask: url(/icons/leftArrow.svg);
|
||||||
|
}
|
||||||
.svg-channel {
|
.svg-channel {
|
||||||
mask: url(/icons/channel.svg);
|
mask: url(/icons/channel.svg);
|
||||||
}
|
}
|
||||||
|
@ -427,15 +485,18 @@ textarea {
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--primary-text-soft);
|
background: var(--primary-text-soft);
|
||||||
}
|
}
|
||||||
#sideDiv:empty {
|
#sideContainDiv:empty {
|
||||||
width: 0px;
|
width: 0px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
#servers::-webkit-scrollbar,
|
#servers::-webkit-scrollbar,
|
||||||
#channels::-webkit-scrollbar,
|
#channels::-webkit-scrollbar,
|
||||||
#sideDiv::-webkit-scrollbar {
|
#sideContainDiv::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
#sideContainDiv {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
#servers,
|
#servers,
|
||||||
#channels,
|
#channels,
|
||||||
#sideDiv {
|
#sideDiv {
|
||||||
|
@ -1255,6 +1316,11 @@ span.instanceStatus {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
.media-small {
|
||||||
|
max-width: 0.6in !important;
|
||||||
|
max-height: 0.4in;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
.message img {
|
.message img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -1525,7 +1591,7 @@ img.bigembedimg {
|
||||||
.acceptinvbutton:disabled {
|
.acceptinvbutton:disabled {
|
||||||
background: color-mix(in hsl, var(--green) 80%, var(--black));
|
background: color-mix(in hsl, var(--green) 80%, var(--black));
|
||||||
}
|
}
|
||||||
#sideDiv.searchDiv {
|
#sideContainDiv.searchDiv {
|
||||||
width: 30vw;
|
width: 30vw;
|
||||||
.topMessage {
|
.topMessage {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
|
@ -1541,13 +1607,13 @@ img.bigembedimg {
|
||||||
}
|
}
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
#sideDiv {
|
#sideDiv {
|
||||||
display: none;
|
|
||||||
flex: none;
|
flex: none;
|
||||||
width: 240px;
|
|
||||||
padding: 16px 8px;
|
padding: 16px 8px;
|
||||||
background: var(--sidebar-bg);
|
background: var(--sidebar-bg);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.memberList {
|
.memberList {
|
||||||
|
@ -1578,8 +1644,9 @@ img.bigembedimg {
|
||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
}
|
}
|
||||||
#page:has(#memberlisttoggle:checked) #sideDiv {
|
#page:has(#memberlisttoggle:checked) #sideContainDiv {
|
||||||
display: block;
|
width: 240px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
#page:has(#memberlisttoggle:checked) #memberlisttoggleicon span {
|
#page:has(#memberlisttoggle:checked) #memberlisttoggleicon span {
|
||||||
background: var(--primary-text-prominent);
|
background: var(--primary-text-prominent);
|
||||||
|
@ -2201,7 +2268,7 @@ fieldset input[type="radio"] {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1000px) {
|
@media screen and (max-width: 1000px) {
|
||||||
#sideDiv {
|
#sideContainDiv {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: calc(100svh - 50px);
|
height: calc(100svh - 50px);
|
||||||
|
@ -2240,7 +2307,7 @@ fieldset input[type="radio"] {
|
||||||
background: var(--primary-bg);
|
background: var(--primary-bg);
|
||||||
transition: left 0.3s;
|
transition: left 0.3s;
|
||||||
}
|
}
|
||||||
#sideDiv {
|
#sideContainDiv {
|
||||||
display: block;
|
display: block;
|
||||||
right: -100svw;
|
right: -100svw;
|
||||||
width: 100svw;
|
width: 100svw;
|
||||||
|
@ -2269,7 +2336,7 @@ fieldset input[type="radio"] {
|
||||||
#page:has(#maintoggle:checked) #mainarea {
|
#page:has(#maintoggle:checked) #mainarea {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
#page:has(#memberlisttoggle:checked) #sideDiv {
|
#page:has(#memberlisttoggle:checked) #sideContainDiv {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
#page:has(#maintoggle:checked) #maintoggleicon {
|
#page:has(#maintoggle:checked) #maintoggleicon {
|
||||||
|
@ -2476,3 +2543,4 @@ fieldset input[type="radio"] {
|
||||||
right: 0.2in;
|
right: 0.2in;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,10 @@
|
||||||
"reply": "Reply",
|
"reply": "Reply",
|
||||||
"copyrawtext": "Copy raw text",
|
"copyrawtext": "Copy raw text",
|
||||||
"copymessageid": "Copy message id",
|
"copymessageid": "Copy message id",
|
||||||
|
"media": {
|
||||||
|
"notFound": "Media could not be found",
|
||||||
|
"loading": "Loading"
|
||||||
|
},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"descriptions": {
|
"descriptions": {
|
||||||
"CREATE_INSTANT_INVITE": "Allows the user to create invites for the guild",
|
"CREATE_INSTANT_INVITE": "Allows the user to create invites for the guild",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue