added audio playback

This commit is contained in:
Roland Osborne 2024-12-19 13:09:35 -08:00
parent ff63f1c832
commit 990f01c9e5
5 changed files with 5543 additions and 8039 deletions

View File

@ -1,4 +1,5 @@
.asset {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
@ -8,14 +9,77 @@
position: absolute;
top: 0;
width: 100%;
overflow: hidden;
text-align: center;
font-size: 12px;
}
.play {
position: absolute;
}
.thumb {
width: auto;
height: 128px;
border-radius: 4px;
}
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(8,8,8, .5);
backdrop-filter: blur(33px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
transition: all 400ms;
.progress {
position: absolute;
bottom: 16px;
width: 25%;
}
.label {
position: absolute;
top: 0px;
}
.close {
position: absolute;
top: 16px;
right: 16px;
}
.audio {
display: none;
}
.play {
position: absolute;
}
.frame {
position: absolute;
width: 80vw;
height: 80vh;
max-width: 300px;
max-height: 300px;
display: flex;
align-items: center;
justify-content: center;
.image {
width: 80vw;
height: 80vh;
max-width: 300px;
max-height: 300px;
background-color: transparent;
object-fit: contain;
}
}
}

View File

@ -1,19 +1,90 @@
import React from 'react';
import React, { useState, useRef, useEffect } from 'react';
import { MediaAsset } from '../../conversation/Conversation';
import { useAudioAsset } from './useAudioAsset.hook';
import audio from '../../images/audio.png'
import { Progress, ActionIcon, Image } from '@mantine/core'
import classes from './AudioAsset.module.css'
import { Image } from '@mantine/core'
import { IconPlayerPlayFilled, IconPlayerPauseFilled, IconX } from '@tabler/icons-react'
import audio from '../../images/audio.png'
export function AudioAsset({ topicId, asset }: { topicId: string, asset: MediaAsset }) {
const { state, actions } = useAudioAsset(topicId, asset);
const [showModal, setShowModal] = useState(false);
const [showAudio, setShowAudio] = useState(false);
const { label } = asset.encrypted || asset.audio || { label: '' };
const [ loaded, setLoaded ] = useState(false);
const [ playing, setPlaying ] = useState(false);
const player = useRef();
const show = () => {
setShowModal(true);
}
const hide = () => {
setShowModal(false);
}
const play = () => {
if (player.current) {
player.current.play();
}
}
const pause = () => {
if (player.current) {
player.current.pause();
}
}
useEffect(() => {
if (showModal) {
setLoaded(false);
setPlaying(false);
setShowAudio(true);
actions.loadAudio();
} else {
setShowAudio(false);
actions.unloadAudio();
}
}, [showModal]);
return (
<div className={classes.asset}>
<Image className={classes.thumb} src={audio} fit="contain" />
<div className={classes.label}>{ label }</div>
<div>
<div className={classes.asset} onClick={show}>
<Image radius="sm" className={classes.thumb} src={audio} fit="contain" />
<div className={classes.label}>{ label }</div>
<IconPlayerPlayFilled className={classes.play} size={32} />
</div>
{ showModal && (
<div className={classes.modal} style={ showAudio ? { opacity: 1} : { opacity: 0 }}>
<div className={classes.frame}>
<Image radius="sm" className={classes.image} src={audio} fit="contain" />
<div className={classes.label}>{ label }</div>
{ loaded && !playing && (
<IconPlayerPlayFilled className={classes.play} size={64} onClick={play} />
)}
{ loaded && playing && (
<IconPlayerPauseFilled className={classes.play} size={64} onClick={pause} />
)}
</div>
{ state.dataUrl && (
<div className={classes.audio}>
<video ref={player} className={classes.image} controls src={state.dataUrl} playsInline={true} autoPlay={true}
onLoadedData={() => setLoaded(true)}
onPlay={() => setPlaying(true)}
onPause={() => setPlaying(false)}
/>
</div>
)}
{ state.loading && state.loadPercent > 0 && (
<Progress className={classes.progress} value={state.loadPercent} />
)}
<ActionIcon className={classes.close} variant="subtle" size="lg" onClick={hide}>
<IconX size="lg" />
</ActionIcon>
</div>
)}
</div>
)
);
}

View File

@ -7,6 +7,10 @@ import { MediaAsset } from '../../conversation/Conversation';
export function useAudioAsset(topicId: string, asset: MediaAsset) {
const app = useContext(AppContext) as ContextType
const [state, setState] = useState({
thumbUrl: null,
dataUrl: null,
loading: false,
loadPercent: 0,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -14,14 +18,41 @@ export function useAudioAsset(topicId: string, asset: MediaAsset) {
setState((s) => ({ ...s, ...value }))
}
const actions = {
getAssetUrl: async (topicId: string, assetId: string) => {
const { focus } = app.state;
if (!focus) {
throw new Error('no channel in focus');
const setThumb = async () => {
const { focus } = app.state;
const assetId = asset.audio ? asset.audio.thumb : asset.encrypted ? asset.encrypted.thumb : null;
if (focus && assetId != null) {
try {
const thumbUrl = await focus.getTopicAssetUrl(topicId, assetId);
updateState({ thumbUrl });
} catch (err) {
console.log(err);
}
return await focus.getTopicAssetUrl(topicId, assetId, (percent: number)=>true);
}
};
useEffect(() => {
setThumb();
}, [asset]);
const actions = {
unloadAudio: () => {
updateState({ dataUrl: null });
},
loadAudio: async () => {
const { focus } = app.state;
const assetId = asset.audio ? asset.audio.hd : asset.encrypted ? asset.encrypted.parts : null;
if (focus && assetId != null && !state.loading) {
updateState({ loading: true, loadPercent: 0 });
try {
const dataUrl = await focus.getTopicAssetUrl(topicId, assetId, (loadPercent: number)=>{ updateState({ loadPercent }) });
updateState({ dataUrl, loading: false });
} catch (err) {
updateState({ loading: false });
console.log(err);
}
}
}
}
return { state, actions }

View File

@ -41,7 +41,7 @@ export function VideoAsset({ topicId, asset }: { topicId: string, asset: MediaAs
{ showModal && (
<div className={classes.modal} style={ showVideo ? { opacity: 1} : { opacity: 0 }}>
<div className={classes.frame}>
<div className={classes.frame} style={state.dataUrl? {opacity: 0} : {opacity: 1}}>
<Image ref={ref} className={classes.image} fit="contain" src={state.thumbUrl} />
</div>
{ state.dataUrl && (

File diff suppressed because it is too large Load Diff