Merge branch 'main' into jose/markers

This commit is contained in:
Jose B
2023-08-18 13:18:34 -05:00
63 changed files with 1451 additions and 282 deletions

View File

@@ -0,0 +1,129 @@
// Override the startRecording method so we can pass the desired stream
// Checkout: https://github.com/katspaugh/wavesurfer.js/blob/fa2bcfe/src/plugins/record.ts
import RecordPlugin from "wavesurfer.js/dist/plugins/record";
const MIME_TYPES = [
"audio/webm",
"audio/wav",
"audio/mpeg",
"audio/mp4",
"audio/mp3",
];
const findSupportedMimeType = () =>
MIME_TYPES.find((mimeType) => MediaRecorder.isTypeSupported(mimeType));
class CustomRecordPlugin extends RecordPlugin {
static create(options) {
return new CustomRecordPlugin(options || {});
}
render(stream) {
if (!this.wavesurfer) return () => undefined;
const container = this.wavesurfer.getWrapper();
const canvas = document.createElement("canvas");
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
canvas.style.zIndex = "10";
container.appendChild(canvas);
const canvasCtx = canvas.getContext("2d");
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
const analyser = audioContext.createAnalyser();
analyser.fftSize = 2 ** 5;
source.connect(analyser);
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
let animationId, previousTimeStamp;
const DATA_SIZE = 128.0;
const BUFFER_SIZE = 2 ** 8;
const dataBuffer = new Array(BUFFER_SIZE).fill(DATA_SIZE);
const drawWaveform = (timeStamp) => {
if (!canvasCtx) return;
analyser.getByteTimeDomainData(dataArray);
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
canvasCtx.fillStyle = "#cc3347";
if (previousTimeStamp === undefined) {
previousTimeStamp = timeStamp;
dataBuffer.push(Math.min(...dataArray));
dataBuffer.splice(0, 1);
}
const elapsed = timeStamp - previousTimeStamp;
if (elapsed > 10) {
previousTimeStamp = timeStamp;
dataBuffer.push(Math.min(...dataArray));
dataBuffer.splice(0, 1);
}
// Drawing
const sliceWidth = canvas.width / dataBuffer.length;
let x = 0;
for (let i = 0; i < dataBuffer.length; i++) {
const y = (canvas.height * dataBuffer[i]) / (2 * DATA_SIZE);
const sliceHeight =
((1 - canvas.height) * dataBuffer[i]) / DATA_SIZE + canvas.height;
canvasCtx.fillRect(x, y, (sliceWidth * 2) / 3, sliceHeight);
x += sliceWidth;
}
animationId = requestAnimationFrame(drawWaveform);
};
drawWaveform();
return () => {
if (animationId) {
cancelAnimationFrame(animationId);
}
if (source) {
source.disconnect();
source.mediaStream.getTracks().forEach((track) => track.stop());
}
if (audioContext) {
audioContext.close();
}
canvas?.remove();
};
}
startRecording(stream) {
this.preventInteraction();
this.cleanUp();
const onStop = this.render(stream);
const mediaRecorder = new MediaRecorder(stream, {
mimeType: this.options.mimeType || findSupportedMimeType(),
audioBitsPerSecond: this.options.audioBitsPerSecond,
});
const recordedChunks = [];
mediaRecorder.addEventListener("dataavailable", (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
});
mediaRecorder.addEventListener("stop", () => {
onStop();
this.loadBlob(recordedChunks, mediaRecorder.mimeType);
this.emit("stopRecording");
});
mediaRecorder.start();
this.emit("startRecording");
this.mediaRecorder = mediaRecorder;
}
}
export default CustomRecordPlugin;

View File

@@ -0,0 +1,129 @@
// Override the startRecording method so we can pass the desired stream
// Checkout: https://github.com/katspaugh/wavesurfer.js/blob/fa2bcfe/src/plugins/record.ts
import RecordPlugin from "wavesurfer.js/dist/plugins/record";
const MIME_TYPES = [
"audio/webm",
"audio/wav",
"audio/mpeg",
"audio/mp4",
"audio/mp3",
];
const findSupportedMimeType = () =>
MIME_TYPES.find((mimeType) => MediaRecorder.isTypeSupported(mimeType));
class CustomRecordPlugin extends RecordPlugin {
static create(options) {
return new CustomRecordPlugin(options || {});
}
render(stream) {
if (!this.wavesurfer) return () => undefined;
const container = this.wavesurfer.getWrapper();
const canvas = document.createElement("canvas");
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
canvas.style.zIndex = "10";
container.appendChild(canvas);
const canvasCtx = canvas.getContext("2d");
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
const analyser = audioContext.createAnalyser();
analyser.fftSize = 2 ** 5;
source.connect(analyser);
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
let animationId, previousTimeStamp;
const DATA_SIZE = 128.0;
const BUFFER_SIZE = 2 ** 8;
const dataBuffer = new Array(BUFFER_SIZE).fill(DATA_SIZE);
const drawWaveform = (timeStamp) => {
if (!canvasCtx) return;
analyser.getByteTimeDomainData(dataArray);
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
canvasCtx.fillStyle = "#cc3347";
if (previousTimeStamp === undefined) {
previousTimeStamp = timeStamp;
dataBuffer.push(Math.min(...dataArray));
dataBuffer.splice(0, 1);
}
const elapsed = timeStamp - previousTimeStamp;
if (elapsed > 10) {
previousTimeStamp = timeStamp;
dataBuffer.push(Math.min(...dataArray));
dataBuffer.splice(0, 1);
}
// Drawing
const sliceWidth = canvas.width / dataBuffer.length;
let x = 0;
for (let i = 0; i < dataBuffer.length; i++) {
const y = (canvas.height * dataBuffer[i]) / (2 * DATA_SIZE);
const sliceHeight =
((1 - canvas.height) * dataBuffer[i]) / DATA_SIZE + canvas.height;
canvasCtx.fillRect(x, y, (sliceWidth * 2) / 3, sliceHeight);
x += sliceWidth;
}
animationId = requestAnimationFrame(drawWaveform);
};
drawWaveform();
return () => {
if (animationId) {
cancelAnimationFrame(animationId);
}
if (source) {
source.disconnect();
source.mediaStream.getTracks().forEach((track) => track.stop());
}
if (audioContext) {
audioContext.close();
}
canvas?.remove();
};
}
startRecording(stream) {
this.preventInteraction();
this.cleanUp();
const onStop = this.render(stream);
const mediaRecorder = new MediaRecorder(stream, {
mimeType: this.options.mimeType || findSupportedMimeType(),
audioBitsPerSecond: this.options.audioBitsPerSecond,
});
const recordedChunks = [];
mediaRecorder.addEventListener("dataavailable", (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
});
mediaRecorder.addEventListener("stop", () => {
onStop();
this.loadBlob(recordedChunks, mediaRecorder.mimeType);
this.emit("stopRecording");
});
mediaRecorder.start();
this.emit("startRecording");
this.mediaRecorder = mediaRecorder;
}
}
export default CustomRecordPlugin;

View File

@@ -0,0 +1,12 @@
import RegionsPlugin from "wavesurfer.js/dist/plugins/regions";
class CustomRegionsPlugin extends RegionsPlugin {
static create(options) {
return new CustomRegionsPlugin(options);
}
avoidOverlapping(region) {
// Prevent overlapping regions
}
}
export default CustomRegionsPlugin;

50
www/app/lib/fief.ts Normal file
View File

@@ -0,0 +1,50 @@
"use client";
import { Fief, FiefUserInfo } from "@fief/fief";
import { FiefAuth, IUserInfoCache } from "@fief/fief/nextjs";
export const SESSION_COOKIE_NAME = "reflector-auth";
export const fiefClient = new Fief({
baseURL: process.env.FIEF_URL ?? "",
clientId: process.env.FIEF_CLIENT_ID ?? "",
clientSecret: process.env.FIEF_CLIENT_SECRET ?? "",
});
class MemoryUserInfoCache implements IUserInfoCache {
private storage: Record<string, any>;
constructor() {
this.storage = {};
}
async get(id: string): Promise<FiefUserInfo | null> {
const userinfo = this.storage[id];
if (userinfo) {
return userinfo;
}
return null;
}
async set(id: string, userinfo: FiefUserInfo): Promise<void> {
this.storage[id] = userinfo;
}
async remove(id: string): Promise<void> {
this.storage[id] = undefined;
}
async clear(): Promise<void> {
this.storage = {};
}
}
export const fiefAuth = new FiefAuth({
client: fiefClient,
sessionCookieName: SESSION_COOKIE_NAME,
redirectURI:
process.env.NEXT_PUBLIC_AUTH_CALLBACK_URL ||
"http://localhost:3000/auth-callback",
logoutRedirectURI:
process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000",
userInfoCache: new MemoryUserInfoCache(),
});

18
www/app/lib/getApi.ts Normal file
View File

@@ -0,0 +1,18 @@
import { Configuration } from "../api/runtime";
import { DefaultApi } from "../api/apis/DefaultApi";
import { useFiefAccessTokenInfo } from "@fief/fief/nextjs/react";
export default function getApi(): DefaultApi {
const accessTokenInfo = useFiefAccessTokenInfo();
const apiConfiguration = new Configuration({
basePath: process.env.NEXT_PUBLIC_API_URL,
accessToken: accessTokenInfo
? "Bearer " + accessTokenInfo.access_token
: undefined,
});
const api = new DefaultApi(apiConfiguration);
return api;
}

View File

@@ -1,15 +1,15 @@
export function getRandomNumber(min, max) {
export function getRandomNumber(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export function SeededRand(seed) {
export function SeededRand(seed: number): number {
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return seed / 2 ** 32;
}
export function Mulberry32(seed) {
export function Mulberry32(seed: number) {
return function () {
var t = (seed += 0x6d2b79f5);
t = Math.imul(t ^ (t >>> 15), t | 1);

View File

@@ -1,4 +1,4 @@
export const formatTime = (seconds) => {
export const formatTime = (seconds: number): string => {
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds % 3600) / 60);
let secs = Math.floor(seconds % 60);

21
www/app/lib/user.ts Normal file
View File

@@ -0,0 +1,21 @@
export async function getCurrentUser(): Promise<any> {
try {
const response = await fetch("/api/current-user");
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
// Ensure the data structure is as expected
if (data.userinfo && data.access_token_info) {
return data;
} else {
throw new Error("Unexpected data structure");
}
} catch (error) {
console.error("Error fetching the user data:", error);
throw error; // or you can return an appropriate fallback or error indicator
}
}