mirror of
https://github.com/balzack/databag.git
synced 2025-05-04 15:35:16 +00:00
added audio playback
This commit is contained in:
parent
ff63f1c832
commit
990f01c9e5
@ -1,4 +1,5 @@
|
|||||||
.asset {
|
.asset {
|
||||||
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -8,14 +9,77 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play {
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumb {
|
.thumb {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 128px;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,19 +1,90 @@
|
|||||||
import React from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { MediaAsset } from '../../conversation/Conversation';
|
import { MediaAsset } from '../../conversation/Conversation';
|
||||||
import { useAudioAsset } from './useAudioAsset.hook';
|
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 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 }) {
|
export function AudioAsset({ topicId, asset }: { topicId: string, asset: MediaAsset }) {
|
||||||
const { state, actions } = useAudioAsset(topicId, asset);
|
const { state, actions } = useAudioAsset(topicId, asset);
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const [showAudio, setShowAudio] = useState(false);
|
||||||
const { label } = asset.encrypted || asset.audio || { label: '' };
|
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 (
|
return (
|
||||||
<div className={classes.asset}>
|
<div>
|
||||||
<Image className={classes.thumb} src={audio} fit="contain" />
|
<div className={classes.asset} onClick={show}>
|
||||||
<div className={classes.label}>{ label }</div>
|
<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>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,10 @@ import { MediaAsset } from '../../conversation/Conversation';
|
|||||||
export function useAudioAsset(topicId: string, asset: MediaAsset) {
|
export function useAudioAsset(topicId: string, asset: MediaAsset) {
|
||||||
const app = useContext(AppContext) as ContextType
|
const app = useContext(AppContext) as ContextType
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
|
thumbUrl: null,
|
||||||
|
dataUrl: null,
|
||||||
|
loading: false,
|
||||||
|
loadPercent: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -14,14 +18,41 @@ export function useAudioAsset(topicId: string, asset: MediaAsset) {
|
|||||||
setState((s) => ({ ...s, ...value }))
|
setState((s) => ({ ...s, ...value }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions = {
|
const setThumb = async () => {
|
||||||
getAssetUrl: async (topicId: string, assetId: string) => {
|
const { focus } = app.state;
|
||||||
const { focus } = app.state;
|
const assetId = asset.audio ? asset.audio.thumb : asset.encrypted ? asset.encrypted.thumb : null;
|
||||||
if (!focus) {
|
if (focus && assetId != null) {
|
||||||
throw new Error('no channel in focus');
|
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 }
|
return { state, actions }
|
||||||
|
@ -41,7 +41,7 @@ export function VideoAsset({ topicId, asset }: { topicId: string, asset: MediaAs
|
|||||||
|
|
||||||
{ showModal && (
|
{ showModal && (
|
||||||
<div className={classes.modal} style={ showVideo ? { opacity: 1} : { opacity: 0 }}>
|
<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} />
|
<Image ref={ref} className={classes.image} fit="contain" src={state.thumbUrl} />
|
||||||
</div>
|
</div>
|
||||||
{ state.dataUrl && (
|
{ state.dataUrl && (
|
||||||
|
13384
app/client/web/yarn.lock
13384
app/client/web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user