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;"> | <svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"> | ||||||
|   <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 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" /> | ||||||
|   <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> | </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;"> | <svg viewBox="0 0 24 24" fill="none" stroke="#6f6f6f" stroke-width="2.5" xmlns="http://www.w3.org/2000/svg"> | ||||||
|   <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" /> |   <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> | </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_refresh from "../assets/img/refresh.svg"; | ||||||
| import img_copy from "../assets/img/copy.svg"; | import img_copy from "../assets/img/copy.svg"; | ||||||
| import img_eye from "../assets/img/eye.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 img_placeholder = "/assets/icons/placeholder.png"; | ||||||
|  |  | ||||||
| export const Icon = (props) => { | export const Icon = (props) => { | ||||||
| @ -141,6 +145,12 @@ export const Icon = (props) => { | |||||||
|         img = "/assets/icons/empty_search.svg"; |         img = "/assets/icons/empty_search.svg"; | ||||||
|     } else if (props.name === "eye") { |     } else if (props.name === "eye") { | ||||||
|         img = img_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 { |     } else { | ||||||
|         throw (new Error(`unknown icon: "${props.name}"`)); |         throw (new Error(`unknown icon: "${props.name}"`)); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -3,11 +3,14 @@ import WaveSurfer from "wavesurfer.js"; | |||||||
|  |  | ||||||
| import { MenuBar } from "./menubar"; | import { MenuBar } from "./menubar"; | ||||||
| import { NgIf, Icon } from "../../components/"; | import { NgIf, Icon } from "../../components/"; | ||||||
|  | import { settings_get, settings_put } from "../../helpers/"; | ||||||
| import "./audioplayer.scss"; | import "./audioplayer.scss"; | ||||||
|  |  | ||||||
| export function AudioPlayer({ filename, data }) { | export function AudioPlayer({ filename, data }) { | ||||||
|     const [isPlaying, setIsPlaying] = useState(false); |     const [isPlaying, setIsPlaying] = useState(false); | ||||||
|     const [isLoading, setIsLoading] = useState(true); |     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 [error, setError] = useState(null); | ||||||
|     const wavesurfer = useRef(null); |     const wavesurfer = useRef(null); | ||||||
|  |  | ||||||
| @ -15,19 +18,30 @@ export function AudioPlayer({ filename, data }) { | |||||||
|         wavesurfer.current = WaveSurfer.create({ |         wavesurfer.current = WaveSurfer.create({ | ||||||
|             container: "#waveform", |             container: "#waveform", | ||||||
|             waveColor: "#323639", |             waveColor: "#323639", | ||||||
|             progressColor: "#6f6f6f", |             progressColor: "#808080", | ||||||
|             cursorColor: "#323639", |             cursorColor: "#6f6f6f", | ||||||
|             cursorWidth: 2, |             cursorWidth: 3, | ||||||
|             height: 250, |             height: 200, | ||||||
|             barWidth: 1, |             barWidth: 1, | ||||||
|         }); |         }); | ||||||
|         wavesurfer.current.load(data); |         wavesurfer.current.load(data); | ||||||
|  |  | ||||||
|  |         let $currentTime = document.getElementById("currentTime"); | ||||||
|  |         let $totalDuration = document.getElementById("totalDuration"); | ||||||
|         wavesurfer.current.on("ready", () => { |         wavesurfer.current.on("ready", () => { | ||||||
|             setIsLoading(false); |             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) => { |         wavesurfer.current.on("error", (err) => { | ||||||
|             setIsLoading(false); |             setIsLoading(false); | ||||||
|             setError(err) |             setError(err); | ||||||
|         }); |         }); | ||||||
|         return () => wavesurfer.current.destroy(); |         return () => wavesurfer.current.destroy(); | ||||||
|     }, []); |     }, []); | ||||||
| @ -50,14 +64,26 @@ export function AudioPlayer({ filename, data }) { | |||||||
|         e.stopPropagation(); |         e.stopPropagation(); | ||||||
|         wavesurfer.current.play(); |         wavesurfer.current.play(); | ||||||
|         setIsPlaying(true); |         setIsPlaying(true); | ||||||
|     } |     }; | ||||||
|  |  | ||||||
|     const onPause = (e) => { |     const onPause = (e) => { | ||||||
|         e.preventDefault(); |         e.preventDefault(); | ||||||
|         e.stopPropagation(); |         e.stopPropagation(); | ||||||
|         wavesurfer.current.pause(); |         wavesurfer.current.pause(); | ||||||
|         setIsPlaying(false); |         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 ( |     return ( | ||||||
|         <div className="component_audioplayer"> |         <div className="component_audioplayer"> | ||||||
| @ -67,25 +93,36 @@ export function AudioPlayer({ filename, data }) { | |||||||
|                     {error} |                     {error} | ||||||
|                 </NgIf> |                 </NgIf> | ||||||
|                 <NgIf cond={error === null}> |                 <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"/> |                             <Icon name="loading"/> | ||||||
|  |                             <span className="percent">{purcentLoading}%</span> | ||||||
|                         </NgIf> |                         </NgIf> | ||||||
|                     <div |                         <div id="waveform"></div> | ||||||
|                         className="audioplayer_box" |                         <div className="audioplayer_control" style={{ opacity: isLoading? 0 : 1 }}> | ||||||
|                         style={{ opacity: isLoading? "0" : "1" }}> |                             <div className="buttons"> | ||||||
|                         <div className="audioplayer_control"> |                                 { | ||||||
|                             <NgIf cond={isPlaying === false}> |                                     isPlaying ? ( | ||||||
|                                 <span onClick={onPlay}> |  | ||||||
|                                     <Icon name="play"/> |  | ||||||
|                                 </span> |  | ||||||
|                             </NgIf> |  | ||||||
|                             <NgIf cond={isPlaying === true}> |  | ||||||
|                                         <span onClick={onPause}> |                                         <span onClick={onPause}> | ||||||
|                                             <Icon name="pause"/> |                                             <Icon name="pause"/> | ||||||
|                                         </span> |                                         </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> | ||||||
|                         <div id="waveform"></div> |  | ||||||
|                     </div> |                     </div> | ||||||
|                 </NgIf> |                 </NgIf> | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
| @ -9,8 +9,6 @@ | |||||||
|         flex-direction: column; |         flex-direction: column; | ||||||
|         flex: 1; |         flex: 1; | ||||||
|         width: 100%; |         width: 100%; | ||||||
|  |  | ||||||
|  |  | ||||||
|         text-align: center; |         text-align: center; | ||||||
|         background: #525659; |         background: #525659; | ||||||
|         height: 100%; |         height: 100%; | ||||||
| @ -26,21 +24,81 @@ | |||||||
|             background: #f1f1f1; |             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; |             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; |             position: relative; | ||||||
|  |             border-radius: 3px; | ||||||
|  |             padding: 10px 0 30px 0; | ||||||
|  |  | ||||||
|             .audioplayer_control { |             .audioplayer_control { | ||||||
|                 position: absolute; |                 padding-top: 20px; | ||||||
|                 top: 10px; |                 img { | ||||||
|                 right: 10px; |                     height: 25px; | ||||||
|                 z-index: 2; |                     width: 25px; | ||||||
|                 height: 30px; |  | ||||||
|                 width: 30px; |  | ||||||
|  |  | ||||||
|                 > div { |  | ||||||
|                     display: inline; |  | ||||||
|                     > span { |  | ||||||
|                     cursor: pointer; |                     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: "locales/*.json", to: "assets/" }, | ||||||
|             { from: "worker/*.js", to: "assets/" }, |             { from: "worker/*.js", to: "assets/" }, | ||||||
|             { from: "assets/logo/*" }, |             { from: "assets/logo/*" }, | ||||||
|  |             { from: "assets/img/*" }, | ||||||
|             { from: "assets/icons/*" }, |             { from: "assets/icons/*" }, | ||||||
|             { from: "assets/fonts/*" }, |             { from: "assets/fonts/*" }, | ||||||
|         ], { context: path.join(__dirname, "client") }), |         ], { context: path.join(__dirname, "client") }), | ||||||
|  | |||||||
 Mickael Kerjean
					Mickael Kerjean