feature (audio): revamp audio player - #497
@ -1,4 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 47.607 47.607" style="enable-background:new 0 0 47.607 47.607;">
|
||||
<path d="M17.991,40.976c0,3.662-2.969,6.631-6.631,6.631l0,0c-3.662,0-6.631-2.969-6.631-6.631V6.631C4.729,2.969,7.698,0,11.36,0 l0,0c3.662,0,6.631,2.969,6.631,6.631V40.976z" fill="#6F6F6F"/>
|
||||
<path d="M42.877,40.976c0,3.662-2.969,6.631-6.631,6.631l0,0c-3.662,0-6.631-2.969-6.631-6.631V6.631 C29.616,2.969,32.585,0,36.246,0l0,0c3.662,0,6.631,2.969,6.631,6.631V40.976z" fill="#6F6F6F"/>
|
||||
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<path style="fill:#6f6f6f;fill-opacity:1;stroke-width:1.06176" d="m 320.29305,81.98884 v 345.78144 c 0,9.34336 7.64456,16.98788 16.98786,16.98788 h 33.97572 c 9.34333,0 16.98786,-7.64452 16.98786,-16.98788 V 81.98884 c 0,-9.343332 -7.64453,-16.987868 -16.98786,-16.987868 h -33.97572 c -9.3433,0 -16.98786,7.644536 -16.98786,16.987868 z m -185.87858,0 v 345.78144 c 0,9.34336 7.64453,16.98788 16.98786,16.98788 h 33.97572 c 9.3433,0 16.98786,-7.64452 16.98786,-16.98788 V 81.98884 c 0,-9.343332 -7.64456,-16.987868 -16.98786,-16.987868 h -33.97572 c -9.34333,0 -16.98786,7.644536 -16.98786,16.987868 z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 516 B After Width: | Height: | Size: 678 B |
@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 232.153 232.153" style="enable-background:new 0 0 232.153 232.153;">
|
||||
<path fill="#6F6F6F" style="fill-rule:evenodd;clip-rule:evenodd;" d="M203.791,99.628L49.307,2.294c-4.567-2.719-10.238-2.266-14.521-2.266 c-17.132,0-17.056,13.227-17.056,16.578v198.94c0,2.833-0.075,16.579,17.056,16.579c4.283,0,9.955,0.451,14.521-2.267 l154.483-97.333c12.68-7.545,10.489-16.449,10.489-16.449S216.471,107.172,203.791,99.628z" />
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="#6f6f6f" stroke-width="2.5" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 8.9281724,2.5998029 C 8.2216149,2.1532873 7.0721143,2.3920918 7.0718277,3.4001971 l -0.00489,17.2050459 c -2.889e-4,1.015715 1.2121979,1.160372 1.8661307,0.789513 C 23.97574,8.7289856 23.930152,14.104463 8.9281584,2.5998029 Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 359 B |
5
client/assets/img/volume.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="#6f6f6f" stroke-width="2.5" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 16.350225,8.193787 c 1.449626,1.933559 1.449626,5.678884 0,7.612446" />
|
||||
<path d="m 19.612703,4.3875644 c 4.336919,4.1411704 4.363014,11.1109086 0,15.2248876" />
|
||||
<path d="M 1.1253356,15.217897 V 8.7810328 c 0,-0.6242205 0.4871967,-1.1309917 1.0874923,-1.1309917 H 6.1125748 A 1.0657422,1.0657422 0 0 0 6.8814318,7.3183562 L 10.143909,3.6339328 c 0.68512,-0.713395 1.856345,-0.2077111 1.856345,0.8003942 v 15.131368 c 0,1.015715 -1.185362,1.517045 -1.866131,0.789513 L 6.882519,16.69145 A 1.0657422,1.0657422 0 0 0 6.1038748,16.349975 H 2.2128279 c -0.6002956,0 -1.0874923,-0.506768 -1.0874923,-1.132078 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 741 B |
4
client/assets/img/volume_low.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="#6f6f6f" stroke-width="2.5" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 16.350225,8.193787 c 1.449626,1.933559 1.449626,5.678884 0,7.612446" />
|
||||
<path d="M 1.1253356,15.217897 V 8.7810328 c 0,-0.6242205 0.4871967,-1.1309917 1.0874923,-1.1309917 H 6.1125748 A 1.0657422,1.0657422 0 0 0 6.8814318,7.3183562 L 10.143909,3.6339328 c 0.68512,-0.713395 1.856345,-0.2077111 1.856345,0.8003942 v 15.131368 c 0,1.015715 -1.185362,1.517045 -1.866131,0.789513 L 6.882519,16.69145 A 1.0657422,1.0657422 0 0 0 6.1038748,16.349975 H 2.2128279 c -0.6002956,0 -1.0874923,-0.506768 -1.0874923,-1.132078 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 650 B |
4
client/assets/img/volume_mute.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="#6f6f6f" stroke-width="2.5" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 21.983883,14.883883 -6,-5.9999995 m 6,0 -6,5.9999995" />
|
||||
<path d="M 1.1253356,15.217897 V 8.7810328 c 0,-0.6242205 0.4871967,-1.1309917 1.0874923,-1.1309917 H 6.1125748 A 1.0657422,1.0657422 0 0 0 6.8814318,7.3183562 L 10.143909,3.6339328 c 0.68512,-0.713395 1.856345,-0.2077111 1.856345,0.8003942 v 15.131368 c 0,1.015715 -1.185362,1.517045 -1.866131,0.789513 L 6.882519,16.69145 A 1.0657422,1.0657422 0 0 0 6.1038748,16.349975 H 2.2128279 c -0.6002956,0 -1.0874923,-0.506768 -1.0874923,-1.132078 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 635 B |
@ -42,6 +42,10 @@ import img_stop from "../assets/img/stop.svg";
|
||||
import img_refresh from "../assets/img/refresh.svg";
|
||||
import img_copy from "../assets/img/copy.svg";
|
||||
import img_eye from "../assets/img/eye.svg";
|
||||
import img_volume from "../assets/img/volume.svg";
|
||||
import img_volume_mute from "../assets/img/volume_mute.svg";
|
||||
import img_volume_low from "../assets/img/volume_low.svg";
|
||||
|
||||
export const img_placeholder = "/assets/icons/placeholder.png";
|
||||
|
||||
export const Icon = (props) => {
|
||||
@ -141,6 +145,12 @@ export const Icon = (props) => {
|
||||
img = "/assets/icons/empty_search.svg";
|
||||
} else if (props.name === "eye") {
|
||||
img = img_eye;
|
||||
} else if (props.name === "volume") {
|
||||
img = img_volume;
|
||||
} else if (props.name === "volume_mute") {
|
||||
img = img_volume_mute;
|
||||
} else if (props.name === "volume_low") {
|
||||
img = img_volume_low;
|
||||
} else {
|
||||
throw (new Error(`unknown icon: "${props.name}"`));
|
||||
}
|
||||
|
||||
@ -3,11 +3,14 @@ import WaveSurfer from "wavesurfer.js";
|
||||
|
||||
import { MenuBar } from "./menubar";
|
||||
import { NgIf, Icon } from "../../components/";
|
||||
import { settings_get, settings_put } from "../../helpers/";
|
||||
import "./audioplayer.scss";
|
||||
|
||||
export function AudioPlayer({ filename, data }) {
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [purcentLoading, setPurcentLoading] = useState(0);
|
||||
const [volume, setVolume] = useState(settings_get("volume") === null ? 50 : settings_get("volume"));
|
||||
const [error, setError] = useState(null);
|
||||
const wavesurfer = useRef(null);
|
||||
|
||||
@ -15,19 +18,30 @@ export function AudioPlayer({ filename, data }) {
|
||||
wavesurfer.current = WaveSurfer.create({
|
||||
container: "#waveform",
|
||||
waveColor: "#323639",
|
||||
progressColor: "#6f6f6f",
|
||||
cursorColor: "#323639",
|
||||
cursorWidth: 2,
|
||||
height: 250,
|
||||
progressColor: "#808080",
|
||||
cursorColor: "#6f6f6f",
|
||||
cursorWidth: 3,
|
||||
height: 200,
|
||||
barWidth: 1,
|
||||
});
|
||||
wavesurfer.current.load(data);
|
||||
|
||||
let $currentTime = document.getElementById("currentTime");
|
||||
let $totalDuration = document.getElementById("totalDuration");
|
||||
wavesurfer.current.on("ready", () => {
|
||||
setIsLoading(false);
|
||||
wavesurfer.current.setVolume(volume / 100);
|
||||
$totalDuration.innerHTML = formatTimecode(wavesurfer.current.getDuration());
|
||||
});
|
||||
wavesurfer.current.on("audioprocess", () => {
|
||||
$currentTime.innerHTML = formatTimecode(wavesurfer.current.getCurrentTime());
|
||||
})
|
||||
wavesurfer.current.on("loading", (n) => {
|
||||
setPurcentLoading(n);
|
||||
});
|
||||
wavesurfer.current.on("error", (err) => {
|
||||
setIsLoading(false);
|
||||
setError(err)
|
||||
setError(err);
|
||||
});
|
||||
return () => wavesurfer.current.destroy();
|
||||
}, []);
|
||||
@ -50,14 +64,26 @@ export function AudioPlayer({ filename, data }) {
|
||||
e.stopPropagation();
|
||||
wavesurfer.current.play();
|
||||
setIsPlaying(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onPause = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
wavesurfer.current.pause();
|
||||
setIsPlaying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onVolumeChange = (e) => {
|
||||
const v = Number(e.target.value);
|
||||
settings_put("volume", v);
|
||||
setVolume(v);
|
||||
wavesurfer.current.setVolume(v / 100);
|
||||
};
|
||||
const onVolumeClick = () => {
|
||||
onVolumeChange({ target: { value: 0 }});
|
||||
};
|
||||
|
||||
const formatTimecode = (seconds) => (new Date(seconds * 1000).toISOString().substr(11, 8));
|
||||
|
||||
return (
|
||||
<div className="component_audioplayer">
|
||||
@ -67,25 +93,36 @@ export function AudioPlayer({ filename, data }) {
|
||||
{error}
|
||||
</NgIf>
|
||||
<NgIf cond={error === null}>
|
||||
<NgIf cond={isLoading === true}>
|
||||
<div className="audioplayer_box">
|
||||
<NgIf cond={isLoading}>
|
||||
<div className="audioplayer_loader" style={{width: purcentLoading + "%"}}></div>
|
||||
<Icon name="loading"/>
|
||||
<span className="percent">{purcentLoading}%</span>
|
||||
</NgIf>
|
||||
<div
|
||||
className="audioplayer_box"
|
||||
style={{ opacity: isLoading? "0" : "1" }}>
|
||||
<div className="audioplayer_control">
|
||||
<NgIf cond={isPlaying === false}>
|
||||
<span onClick={onPlay}>
|
||||
<Icon name="play"/>
|
||||
</span>
|
||||
</NgIf>
|
||||
<NgIf cond={isPlaying === true}>
|
||||
<div id="waveform"></div>
|
||||
<div className="audioplayer_control" style={{ opacity: isLoading? 0 : 1 }}>
|
||||
<div className="buttons">
|
||||
{
|
||||
isPlaying ? (
|
||||
<span onClick={onPause}>
|
||||
<Icon name="pause"/>
|
||||
</span>
|
||||
</NgIf>
|
||||
) : (
|
||||
<span onClick={onPlay}>
|
||||
<Icon name="play"/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
<span><Icon onClick={onVolumeClick} name={volume === 0 ? "volume_mute" : volume < 50 ? "volume_low" : "volume"}/></span>
|
||||
<input onChange={onVolumeChange} type="range" min="0" max="100" value={volume}/>
|
||||
</div>
|
||||
|
||||
<div className="timecode">
|
||||
<span id="currentTime">00:00:00</span>
|
||||
<span id="separator">/</span>
|
||||
<span id="totalDuration">00:00:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="waveform"></div>
|
||||
</div>
|
||||
</NgIf>
|
||||
</div>
|
||||
|
||||
@ -9,8 +9,6 @@
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
|
||||
|
||||
text-align: center;
|
||||
background: #525659;
|
||||
height: 100%;
|
||||
@ -26,21 +24,81 @@
|
||||
background: #f1f1f1;
|
||||
box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
padding: 10px 0 30px 0;
|
||||
|
||||
.audioplayer_control {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 2;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
|
||||
> div {
|
||||
display: inline;
|
||||
> span {
|
||||
padding-top: 20px;
|
||||
img {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
float: left;
|
||||
padding-left: 15px;
|
||||
margin-top: -10px;
|
||||
display: flex;
|
||||
> span {
|
||||
margin-right: 10px;
|
||||
}
|
||||
input[type="range"] {
|
||||
margin-left: -5px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: #6f6f6f;
|
||||
margin-top: -6px;
|
||||
}
|
||||
&::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: #6f6f6f;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timecode {
|
||||
float: right;
|
||||
padding-right: 25px;
|
||||
margin-top: -5px;
|
||||
#separator {
|
||||
padding: 0 5px;
|
||||
}
|
||||
@media screen and (max-width: 450px) {
|
||||
#separator, #currentTime { display: none; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.audioplayer_loader {
|
||||
height: 260px;
|
||||
background: var(--color);
|
||||
position: absolute;
|
||||
opacity: 0.1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.percent {
|
||||
position: absolute;
|
||||
margin: 100px 0px;
|
||||
width: 120px;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.component_icon[alt="loading"] {
|
||||
position: absolute;
|
||||
margin: 50px 0px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +69,7 @@ const config = {
|
||||
{ from: "locales/*.json", to: "assets/" },
|
||||
{ from: "worker/*.js", to: "assets/" },
|
||||
{ from: "assets/logo/*" },
|
||||
{ from: "assets/img/*" },
|
||||
{ from: "assets/icons/*" },
|
||||
{ from: "assets/fonts/*" },
|
||||
], { context: path.join(__dirname, "client") }),
|
||||
|
||||