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 {
|
||||
images: string[];
|
||||
files: File[];
|
||||
index = 0;
|
||||
constructor(srcs: string[], index = 0) {
|
||||
this.images = srcs;
|
||||
constructor(files: File[], index = 0) {
|
||||
this.files = files;
|
||||
this.index = index;
|
||||
}
|
||||
weakbg = new WeakRef<HTMLElement>(document.createElement("div"));
|
||||
|
@ -14,19 +16,64 @@ class ImagesDisplay {
|
|||
}
|
||||
makeHTML(): HTMLElement {
|
||||
//TODO this should be able to display more than one image at a time lol
|
||||
const image = document.createElement("img");
|
||||
image.src = this.images[this.index];
|
||||
const image = this.files[this.index].getHTML(false, true);
|
||||
image.classList.add("imgfit", "centeritem");
|
||||
return image;
|
||||
}
|
||||
show() {
|
||||
this.background = document.createElement("div");
|
||||
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.hide();
|
||||
};
|
||||
document.body.append(this.background);
|
||||
this.background.setAttribute("tabindex", "0");
|
||||
this.background.focus();
|
||||
}
|
||||
hide() {
|
||||
if (this.background) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import {getapiurls, getInstances} from "./utils/utils.js";
|
|||
import {Guild} from "./guild.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
import {ImagesDisplay} from "./disimg.js";
|
||||
import {File} from "./file.js";
|
||||
|
||||
class Embed {
|
||||
type: string;
|
||||
|
@ -169,7 +170,9 @@ class Embed {
|
|||
const img = document.createElement("img");
|
||||
img.classList.add("messageimg");
|
||||
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();
|
||||
};
|
||||
img.src = this.json.thumbnail.proxy_url;
|
||||
|
@ -205,7 +208,9 @@ class Embed {
|
|||
if (this.json.thumbnail) {
|
||||
img.classList.add("embedimg");
|
||||
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();
|
||||
};
|
||||
img.src = this.json.thumbnail.proxy_url;
|
||||
|
@ -381,7 +386,9 @@ class Embed {
|
|||
};
|
||||
} else {
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {Message} from "./message.js";
|
||||
import {filejson} from "./jsontypes.js";
|
||||
import {ImagesDisplay} from "./disimg.js";
|
||||
|
||||
import {makePlayBox, MediaPlayer} from "./media.js";
|
||||
class File {
|
||||
owner: Message | null;
|
||||
id: string;
|
||||
|
@ -24,7 +24,7 @@ class File {
|
|||
this.content_type = fileJSON.content_type;
|
||||
this.size = fileJSON.size;
|
||||
}
|
||||
getHTML(temp: boolean = false): HTMLElement {
|
||||
getHTML(temp: boolean = false, fullScreen = false): HTMLElement {
|
||||
const src = this.proxy_url || this.url;
|
||||
if (this.width && this.height) {
|
||||
let scale = 1;
|
||||
|
@ -37,19 +37,33 @@ class File {
|
|||
if (this.content_type.startsWith("image/")) {
|
||||
const div = document.createElement("div");
|
||||
const img = document.createElement("img");
|
||||
if (!fullScreen) {
|
||||
img.classList.add("messageimg");
|
||||
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();
|
||||
} else {
|
||||
const full = new ImagesDisplay([this]);
|
||||
full.show();
|
||||
}
|
||||
};
|
||||
img.src = src;
|
||||
div.append(img);
|
||||
if (this.width) {
|
||||
if (this.width && !fullScreen) {
|
||||
div.style.width = this.width + "px";
|
||||
div.style.height = this.height + "px";
|
||||
}
|
||||
if (!fullScreen) {
|
||||
return div;
|
||||
} else {
|
||||
return img;
|
||||
}
|
||||
} else if (this.content_type.startsWith("video/")) {
|
||||
const video = document.createElement("video");
|
||||
const source = document.createElement("source");
|
||||
|
@ -63,17 +77,15 @@ class File {
|
|||
}
|
||||
return video;
|
||||
} else if (this.content_type.startsWith("audio/")) {
|
||||
const audio = document.createElement("audio");
|
||||
const source = document.createElement("source");
|
||||
source.src = src;
|
||||
audio.append(source);
|
||||
source.type = this.content_type;
|
||||
audio.controls = !temp;
|
||||
return audio;
|
||||
return this.getAudioHTML();
|
||||
} else {
|
||||
return this.createunknown();
|
||||
}
|
||||
}
|
||||
private getAudioHTML() {
|
||||
const src = this.proxy_url || this.url;
|
||||
return makePlayBox(src, player);
|
||||
}
|
||||
upHTML(files: Blob[], file: globalThis.File): HTMLElement {
|
||||
const div = document.createElement("div");
|
||||
const contained = this.getHTML(true);
|
||||
|
@ -149,4 +161,6 @@ class File {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const player = new MediaPlayer();
|
||||
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 class="flexttb" id="sideContainDiv">
|
||||
<div class="flexttb" id="sideDiv"></div>
|
||||
<div id="player" class="flexttb"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -357,7 +357,7 @@ type filejson = {
|
|||
content_type: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
proxy_url: string | undefined;
|
||||
proxy_url?: string;
|
||||
url: string;
|
||||
size: number;
|
||||
};
|
||||
|
|
|
@ -2326,9 +2326,10 @@ class Localuser {
|
|||
})
|
||||
.filter((_) => _ !== undefined);
|
||||
const sideDiv = document.getElementById("sideDiv");
|
||||
if (!sideDiv) return;
|
||||
const sideContainDiv = document.getElementById("sideContainDiv");
|
||||
if (!sideDiv || !sideContainDiv) return;
|
||||
sideDiv.innerHTML = "";
|
||||
sideDiv.classList.add("searchDiv");
|
||||
sideContainDiv.classList.add("searchDiv");
|
||||
let channel: Channel | undefined = undefined;
|
||||
for (const message of messages) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
.Mplayer {
|
||||
padding: 4px;
|
||||
border-radius: 3px;
|
||||
background: var(--secondary-bg);
|
||||
align-items: center;
|
||||
*{
|
||||
margin:2px;
|
||||
}
|
||||
.flexttb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.flexltr {
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
|
@ -70,6 +83,12 @@ body {
|
|||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
#player {
|
||||
height: 128px;
|
||||
}
|
||||
#player:empty {
|
||||
height: 0px;
|
||||
}
|
||||
.messageEditContainer {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
@ -266,6 +285,42 @@ textarea {
|
|||
cursor: pointer;
|
||||
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 {
|
||||
mask: url(/icons/announce.svg);
|
||||
}
|
||||
|
@ -287,6 +342,9 @@ textarea {
|
|||
.svg-intoMenu {
|
||||
mask: url(/icons/intoMenu.svg);
|
||||
}
|
||||
.svg-leftArrow {
|
||||
mask: url(/icons/leftArrow.svg);
|
||||
}
|
||||
.svg-channel {
|
||||
mask: url(/icons/channel.svg);
|
||||
}
|
||||
|
@ -427,15 +485,18 @@ textarea {
|
|||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--primary-text-soft);
|
||||
}
|
||||
#sideDiv:empty {
|
||||
#sideContainDiv:empty {
|
||||
width: 0px;
|
||||
padding: 0;
|
||||
}
|
||||
#servers::-webkit-scrollbar,
|
||||
#channels::-webkit-scrollbar,
|
||||
#sideDiv::-webkit-scrollbar {
|
||||
#sideContainDiv::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
#sideContainDiv {
|
||||
width: 0;
|
||||
}
|
||||
#servers,
|
||||
#channels,
|
||||
#sideDiv {
|
||||
|
@ -1255,6 +1316,11 @@ span.instanceStatus {
|
|||
margin-top: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.media-small {
|
||||
max-width: 0.6in !important;
|
||||
max-height: 0.4in;
|
||||
object-fit: contain;
|
||||
}
|
||||
.message img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@ -1525,7 +1591,7 @@ img.bigembedimg {
|
|||
.acceptinvbutton:disabled {
|
||||
background: color-mix(in hsl, var(--green) 80%, var(--black));
|
||||
}
|
||||
#sideDiv.searchDiv {
|
||||
#sideContainDiv.searchDiv {
|
||||
width: 30vw;
|
||||
.topMessage {
|
||||
margin-top: 2px;
|
||||
|
@ -1541,13 +1607,13 @@ img.bigembedimg {
|
|||
}
|
||||
/* Sidebar */
|
||||
#sideDiv {
|
||||
display: none;
|
||||
flex: none;
|
||||
width: 240px;
|
||||
padding: 16px 8px;
|
||||
background: var(--sidebar-bg);
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.memberList {
|
||||
|
@ -1578,8 +1644,9 @@ img.bigembedimg {
|
|||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
#page:has(#memberlisttoggle:checked) #sideDiv {
|
||||
display: block;
|
||||
#page:has(#memberlisttoggle:checked) #sideContainDiv {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#page:has(#memberlisttoggle:checked) #memberlisttoggleicon span {
|
||||
background: var(--primary-text-prominent);
|
||||
|
@ -2201,7 +2268,7 @@ fieldset input[type="radio"] {
|
|||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
#sideDiv {
|
||||
#sideContainDiv {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
height: calc(100svh - 50px);
|
||||
|
@ -2240,7 +2307,7 @@ fieldset input[type="radio"] {
|
|||
background: var(--primary-bg);
|
||||
transition: left 0.3s;
|
||||
}
|
||||
#sideDiv {
|
||||
#sideContainDiv {
|
||||
display: block;
|
||||
right: -100svw;
|
||||
width: 100svw;
|
||||
|
@ -2269,7 +2336,7 @@ fieldset input[type="radio"] {
|
|||
#page:has(#maintoggle:checked) #mainarea {
|
||||
left: 0;
|
||||
}
|
||||
#page:has(#memberlisttoggle:checked) #sideDiv {
|
||||
#page:has(#memberlisttoggle:checked) #sideContainDiv {
|
||||
right: 0;
|
||||
}
|
||||
#page:has(#maintoggle:checked) #maintoggleicon {
|
||||
|
@ -2476,3 +2543,4 @@ fieldset input[type="radio"] {
|
|||
right: 0.2in;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
"reply": "Reply",
|
||||
"copyrawtext": "Copy raw text",
|
||||
"copymessageid": "Copy message id",
|
||||
"media": {
|
||||
"notFound": "Media could not be found",
|
||||
"loading": "Loading"
|
||||
},
|
||||
"permissions": {
|
||||
"descriptions": {
|
||||
"CREATE_INSTANT_INVITE": "Allows the user to create invites for the guild",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue