updates and audio player

This commit is contained in:
BFlat authored and Brandon Thomas committed 2023-10-18 04:15:11 -04:00
1 parent a7a946780e
commit ff529ebf31
8 files changed
+277 -48

No files matched your search

@@ -1,9 +1,11 @@
import React from "react";
import "./DataTable.scss";
import { Link } from "react-router-dom";
type Data = {
property: string;
value: string;
link?: string;
};
interface DataTableProps {
@@ -17,7 +19,13 @@ export default function DataTable({ data }: DataTableProps) {
{data.map((row, index) => (
<tr key={index}>
<td className="data-table-property">{row.property}</td>
<td>{row.value}</td>
{row.link ? (
<td>
<Link to={row.link}>{row.value}</Link>
</td>
) : (
<td>{row.value}</td>
)}
</tr>
))}
</tbody>
@@ -14,6 +14,8 @@ interface PageHeaderProps {
buttonLabel?: string;
buttonVariant?: "primary" | "secondary" | "danger";
buttonTo?: string;
buttonIcon?: IconDefinition;
buttonOnClick?: () => void;
}
export default function PageHeader({
@@ -26,6 +28,8 @@ export default function PageHeader({
buttonLabel,
buttonVariant = "primary",
buttonTo,
buttonIcon,
buttonOnClick,
}: PageHeaderProps) {
const icon = (
<>{titleIcon && <FontAwesomeIcon icon={titleIcon} className="me-3" />}</>
@@ -44,9 +48,11 @@ export default function PageHeader({
<div className="d-none d-md-block">
{showButton && (
<Button
icon={buttonIcon}
variant={buttonVariant}
label={buttonLabel}
to={buttonTo}
onClick={buttonOnClick}
/>
)}
</div>
@@ -1,20 +0,0 @@
import MediaData from "./MediaDataTypes";
import React from "react";
interface AudioComponentProps {
mediaData: MediaData;
}
const AudioComponent: React.FC<AudioComponentProps> = ({ mediaData }) => {
return (
<div className="w-100">
<audio className="w-100" controls>
<source src={mediaData.public_bucket_path} />
Your browser does not support the audio element.
</audio>
</div>
);
};
export default AudioComponent;
@@ -0,0 +1,17 @@
import MediaData from "./MediaDataTypes";
import React from "react";
import AudioPlayer from "./MediaAudioPlayer";
interface MediaAudioComponentProps {
mediaData: MediaData;
}
export default function MediaAudioComponent({
mediaData,
}: MediaAudioComponentProps) {
return (
<div className="w-100">
<AudioPlayer mediaData={mediaData} />
</div>
);
}
@@ -0,0 +1,150 @@
import React from "react";
import WaveSurfer from "wavesurfer.js";
import { useEffect, useState } from "react";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlay, faPause } from "@fortawesome/pro-solid-svg-icons";
import MediaData from "./MediaDataTypes";
enum PlaybackSpeed {
HALF,
NORMAL,
DOUBLE,
}
interface MediaAudioPlayerProps {
mediaData: MediaData;
}
export default function MediaAudioPlayer({ mediaData }: MediaAudioPlayerProps) {
let [isPlaying, setIsPlaying] = useState(false);
let [isRepeating, setIsRepeating] = useState(false);
let [playbackSpeed, setPlaybackSpeed] = useState(PlaybackSpeed.NORMAL);
let [waveSurfer, setWaveSurfer] = useState<WaveSurfer | null>(null);
useEffect(() => {
const wavesurferInstance = WaveSurfer.create({
container: "#waveform", // Previousy I used 'this.ref.current' and React.createRef()
height: 200,
responsive: true,
waveColor: "#cbcbcb",
progressColor: "#fc8481",
cursorColor: "#fc6b68",
cursorWidth: 2,
normalize: false,
});
setWaveSurfer(wavesurferInstance);
}, []);
useEffect(() => {
const audioLink = new BucketConfig().getGcsUrl(
mediaData.public_bucket_path
);
if (waveSurfer) {
waveSurfer.load(audioLink);
}
}, [waveSurfer, mediaData]);
useEffect(() => {
if (waveSurfer) {
waveSurfer.unAll(); // NB: Otherwise we keep reinstalling the hooks and cause chaos
waveSurfer.on("pause", () => {
setIsPlaying(waveSurfer!.isPlaying());
});
waveSurfer.on("play", () => {
setIsPlaying(waveSurfer!.isPlaying());
});
waveSurfer.on("finish", () => {
if (waveSurfer && isRepeating) {
waveSurfer!.play();
}
});
}
}, [waveSurfer, isRepeating]);
const togglePlayPause = () => {
if (waveSurfer) {
waveSurfer.playPause();
}
};
const toggleIsRepeating = () => {
setIsRepeating(!isRepeating);
};
const togglePlaybackSpeed = () => {
let nextSpeed = PlaybackSpeed.NORMAL;
switch (playbackSpeed) {
case PlaybackSpeed.NORMAL:
nextSpeed = PlaybackSpeed.DOUBLE;
waveSurfer!.setPlaybackRate(1.5); // Okay, so a lie...
break;
case PlaybackSpeed.DOUBLE:
nextSpeed = PlaybackSpeed.HALF;
waveSurfer!.setPlaybackRate(0.5);
break;
case PlaybackSpeed.HALF:
nextSpeed = PlaybackSpeed.NORMAL;
waveSurfer!.setPlaybackRate(1.0);
break;
}
setPlaybackSpeed(nextSpeed);
};
let playButtonText = (
<>
<FontAwesomeIcon icon={faPlay} />
</>
);
if (isPlaying) {
playButtonText = (
<>
<FontAwesomeIcon icon={faPause} />
</>
);
}
let repeatButtonText = isRepeating ? "Repeat" : "NoRepeat";
let speedButtonText = "1x";
switch (playbackSpeed) {
case PlaybackSpeed.NORMAL:
speedButtonText = "1x";
break;
case PlaybackSpeed.DOUBLE:
speedButtonText = "2x";
break;
case PlaybackSpeed.HALF:
speedButtonText = "1/2x";
break;
}
return (
<div>
<div id="waveform"></div>
<div className="d-flex gap-4 flex-column justify-content-center align-items-center mt-4">
<div className="d-flex gap-2">
<button className="btn btn-primary" onClick={() => togglePlayPause()}>
{playButtonText}
</button>
<button
className="btn btn-secondary"
onClick={() => toggleIsRepeating()}
>
{repeatButtonText}
</button>
<button
className="btn btn-secondary"
onClick={() => togglePlaybackSpeed()}
>
{speedButtonText}
</button>
</div>
</div>
</div>
);
}
@@ -1,21 +1,21 @@
import React from "react";
import MediaData from "./MediaDataTypes";
interface ImageComponentProps {
interface MediaImageComponentProps {
mediaData: MediaData;
}
const ImageComponent: React.FC<ImageComponentProps> = ({ mediaData }) => {
export default function MediaImageComponent({
mediaData,
}: MediaImageComponentProps) {
return (
<div>
<img
src={mediaData.public_bucket_path}
alt="test" // Make sure to provide alt text
alt="test"
width="400"
height="300"
/>
</div>
);
};
export default ImageComponent;
}
@@ -1,8 +1,8 @@
import React, { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import AudioComponent from "./AudioComponent";
import VideoComponent from "./VideoComponent";
import ImageComponent from "./ImageComponent";
import MediaAudioComponent from "./MediaAudioComponent";
import MediaVideoComponent from "./MediaVideoComponent";
import MediaImageComponent from "./MediaImageComponent";
import MediaData from "./MediaDataTypes";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import Container from "components/common/Container";
@@ -10,11 +10,16 @@ import Panel from "components/common/Panel";
import PageHeader from "components/layout/PageHeader";
import Skeleton from "components/common/Skeleton";
import Button from "components/common/Button";
import { faCircleExclamation } from "@fortawesome/pro-solid-svg-icons";
import {
faCircleExclamation,
faArrowDownToLine,
} from "@fortawesome/pro-solid-svg-icons";
import Accordion from "components/common/Accordion";
import DataTable from "components/common/DataTable";
import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
import useTimeAgo from "hooks/useTimeAgo";
// import { RatingButtons } from "v2/view/_common/ratings/RatingButtons";
// import { RatingStats } from "v2/view/_common/ratings/RatingStats";
interface MediaPageProps {
sessionWrapper: SessionWrapper;
@@ -69,16 +74,16 @@ export default function MediaPage({ sessionWrapper }: MediaPageProps) {
function renderMediaComponent(data: MediaData) {
switch (data.media_type) {
case "audio":
return <AudioComponent mediaData={data} />;
return <MediaAudioComponent mediaData={data} />;
case "video":
return (
<div className="ratio ratio-16x9">
<VideoComponent mediaData={data} />
<MediaVideoComponent mediaData={data} />
</div>
);
case "image":
return <ImageComponent mediaData={data} />;
return <MediaImageComponent mediaData={data} />;
default:
return <div>Unsupported media type</div>;
}
@@ -124,29 +129,94 @@ export default function MediaPage({ sessionWrapper }: MediaPageProps) {
</Container>
);
const mediaDetails = [
const audioDetails = [
{ property: "Type", value: mediaData.media_type },
{ property: "Created at", value: mediaData.created_at },
{ property: "Visibility", value: mediaData.creator_set_visibility },
{
property: "Model",
value: mediaData.model_used,
link: mediaData.model_link,
},
{
property: "Vocoder",
value: "Test",
},
{
property: "Language",
value: mediaData.model_used,
},
{
property: "Model",
value: mediaData.model_used,
},
];
const videoDetails = [
{ property: "Type", value: mediaData.media_type },
{ property: "Created at", value: mediaData.created_at },
{ property: "Visibility", value: mediaData.creator_set_visibility },
];
let pageTitle = "Result";
let pageSubText = "This is the result of your media.";
let mediaDetails = undefined;
switch (mediaData.media_type) {
case "audio":
pageTitle = "Audio Result";
pageSubText = mediaData.token;
mediaDetails = (
<Accordion.Item title="Audio Details" defaultOpen={true}>
<DataTable data={audioDetails} />
</Accordion.Item>
);
break;
case "video":
pageTitle = "Video Result";
pageSubText = mediaData.token;
mediaDetails = (
<Accordion.Item title="Video Details" defaultOpen={true}>
<DataTable data={videoDetails} />
</Accordion.Item>
);
break;
case "image":
pageTitle = "Image Model";
pageSubText = "Image Model SubText";
break;
default:
}
// const resultRatings = (
// <div className="d-flex flex-column flex-lg-row flex-column-reverse gap-3">
// <div className="d-flex gap-3">
// <RatingButtons entity_type="v2v_model" entity_token="test" />
// </div>
// <RatingStats positive_votes={100} negative_votes={0} total_votes={100} />
// </div>
// );
return (
<Container type="panel">
<PageHeader
title="Model name"
subText={mediaData.maybe_creator_user.display_name}
/>
<PageHeader title={pageTitle} subText={pageSubText} />
<Panel padding={true}>
<div className="row g-4">
<div className="col-12 col-xl-8">
{renderMediaComponent(mediaData)}
<div className="media-wrapper">
{renderMediaComponent(mediaData)}
</div>
</div>
<div className="col-12 col-xl-4">
<div className="d-flex mb-3 gap-2">
<div className="d-flex mb-4">
<Button
icon={faArrowDownToLine}
label="Download Result"
onClick={() => {}}
/>
</div>
<Accordion>{mediaDetails}</Accordion>
<div className="d-flex mt-4 gap-2">
<Gravatar
size={48}
username={mediaData.maybe_creator_user.display_name}
@@ -167,11 +237,6 @@ export default function MediaPage({ sessionWrapper }: MediaPageProps) {
{timeCreated}
</div>
</div>
<Accordion>
<Accordion.Item title="Details" defaultOpen={true}>
<DataTable data={mediaDetails} />
</Accordion.Item>
</Accordion>
</div>
</div>
</Panel>
@@ -1,10 +1,13 @@
import React from "react";
import MediaData from "./MediaDataTypes";
interface VideoComponentProps {
interface MediaVideoComponentProps {
mediaData: MediaData;
}
export default function VideoComponent({ mediaData }: VideoComponentProps) {
export default function MediaVideoComponent({
mediaData,
}: MediaVideoComponentProps) {
return (
<video className="rounded" controls width="100%" height="auto">
<source src={mediaData.public_bucket_path} />