Una guía para la visualización de audio con JavaScript y GSAP (Parte 2)

 

 

 

  • Clase magistral de diseño para una interfaz de usuario compleja, con Vitaly Friedman
  • ¡Registro!

  • Índice
    1. Pausar una grabación
    2. Rellenando las imágenes
      1. Cómo terminamos
    3. Terminando la grabación
    4. Limpiando los valores en la reproducción
    5. Reproducción de audio de otras fuentes
    6. Turning This Into A React Application

    Lo que comenzó como un estudio de caso se convirtió en una guía para visualizar audio con JavaScript. Aunque las demostraciones de salida están en React, Jhey Tompkins no se detendrá demasiado en el lado de React. Las técnicas subyacentes funcionan con o sin React.

     

    La semana pasada, en la Parte 1 , expliqué la idea de cómo grabar la entrada de audio de los usuarios y luego pasé a la visualización . Después de todo, sin ninguna visualización, cualquier tipo de interfaz de usuario de grabación de audio no es muy atractivo, ¿verdad? ¡Hoy profundizaremos en más detalles en términos de agregar funciones y cualquier tipo de toque adicional que te guste!

    Cubriremos lo siguiente:

    • Cómo pausar una grabación
    • Cómo completar las imágenes
    • Cómo terminar la grabación
    • Limpiando los valores en la reproducción
    • Reproducción de audio de otras fuentes
    • Convertir esto en una aplicación React

    Tenga en cuenta que para ver las demostraciones en acción, deberá abrirlas y probarlas directamente en el sitio web de CodePen.

    Pausar una grabación

    Pausar una grabación no requiere mucho código.

    // Pause a recorderrecorder.pause()// Resume a recordingrecorder.resume()

    De hecho, la parte más complicada de integrar la grabación es diseñar la interfaz de usuario. Una vez que tengas un diseño de interfaz de usuario, probablemente se tratará más de los cambios que necesitas para ello.

    Además, pausar una grabación no pausa nuestra animación. Así que debemos asegurarnos de detener eso también. Sólo queremos agregar nuevas barras mientras grabamos. Para determinar en qué estado se encuentra la grabadora, podemos utilizar la statepropiedad mencionada anteriormente. Aquí está nuestra funcionalidad de alternancia actualizada:

    const RECORDING = recorder.state === 'recording'// Pause or resume recorder based on state.TOGGLE.style.setProperty('--active', RECORDING ? 0 : 1)timeline[RECORDING ? 'pause' : 'play']()recorder[RECORDING ? 'pause' : 'resume']()

    Y así es como podemos determinar si agregar nuevas barras en el reportero o no.

     

    REPORT = () = { if (recorder recorder.state === 'recording') {

    Desafío : ¿Podríamos eliminar también la función de informe gsap.tickerpara obtener un rendimiento adicional? Pruébalo.

    Para nuestra demostración, lo cambiamos para que el botón de grabación se convierta en un botón de pausa. Y una vez que ha comenzado una grabación, aparece un botón de parada. Esto necesitará algún código adicional para manejar ese estado. React es una buena opción para esto, pero podemos apoyarnos en el recorder.statevalor.

    Ver la pluma [15. Pausar una grabación](https://codepen.io/smashingmag/pen/BamgQEP) por Jhey .

    Rellenando las imágenes

    A continuación, debemos completar nuestras imágenes. ¿A qué nos referimos con eso? Bueno, pasamos de un lienzo vacío a barras que fluyen a través de ella. Es un gran contraste y sería bueno tener el lienzo lleno de barras de volumen cero al inicio. Tampoco hay ninguna razón por la que no podamos hacer esto en función de cómo generamos nuestras barras. Comencemos creando una función de relleno padTimeline:

    // Move BAR_DURATION out of scope so it’s a shared variable.const BAR_DURATION = CANVAS.width / ((CONFIG.barWidth + CONFIG.barGap) * CONFIG.fps)const padTimeline = () = { // Doesn’t matter if we have more bars than width. We will shift them over to the correct spot const padCount = Math.floor(CANVAS.width / CONFIG.barWidth) for (let p = 0; p padCount; p++) { const BAR = { x: CANVAS.width + CONFIG.barWidth / 2, // Note the volume is 0 size: gsap.utils.mapRange( 0, 100, CANVAS.height * CONFIG.barMinHeight, CANVAS.height * CONFIG.barMaxHeight )(volume), } // Add to bars Array BARS.push(BAR) // Add the bar animation to the timeline // The actual pixels per second is (1 / fps * shift) * fps // if we have 50fps, the bar needs to have moved bar width before the next comes in // 1/50 = 4 === 50 * 4 = 200 timeline.to( BAR, { x: `-=${CANVAS.width + CONFIG.barWidth}`, ease: 'none', duration: BAR_DURATION, }, BARS.length * (1 / CONFIG.fps) ) } // Sets the timeline to the correct spot for being added to timeline.totalTime(timeline.totalDuration() - BAR_DURATION)}

    El truco aquí consiste en agregar nuevas barras y luego configurar el cabezal de reproducción de la línea de tiempo donde las barras llenan el lienzo. En el momento de rellenar la línea de tiempo, sabemos que solo tenemos barras de relleno, por lo que totalDurationpodemos usarlas.

    timeline.totalTime(timeline.totalDuration() - BAR_DURATION)

    ¿Observas que esa funcionalidad se parece mucho a lo que hacemos dentro de la REPORTfunción? Tenemos una buena oportunidad para refactorizar aquí. Creemos una nueva función llamada addBar. Esto agrega una nueva barra basada en lo aprobado volume.

    const addBar = (volume = 0) = { const BAR = { x: CANVAS.width + CONFIG.barWidth / 2, size: gsap.utils.mapRange( 0, 100, CANVAS.height * CONFIG.barMinHeight, CANVAS.height * CONFIG.barMaxHeight )(volume), } BARS.push(BAR) timeline.to( BAR, { x: `-=${CANVAS.width + CONFIG.barWidth}`, ease: 'none', duration: BAR_DURATION, }, BARS.length * (1 / CONFIG.fps) )}

    Ahora nuestras funciones padTimeliney REPORTpueden hacer uso de esto:

     

    const padTimeline = () = { const padCount = Math.floor(CANVAS.width / CONFIG.barWidth) for (let p = 0; p padCount; p++) { addBar() } timeline.totalTime(timeline.totalDuration() - BAR_DURATION)}REPORT = () = { if (recorder recorder.state === 'recording') { ANALYSER.getByteFrequencyData(DATA_ARR) const VOLUME = Math.floor((Math.max(...DATA_ARR) / 255) * 100) addBar(VOLUME) } if (recorder || visualizing) { drawBars() }}

    Ahora, durante la carga, podemos hacer una representación inicial invocando padTimelineseguido de drawBars.

    padTimeline()drawBars()

    ¡Juntarlo todo es otra característica interesante!

    Ver la pluma [16. Completando la línea de tiempo](https://codepen.io/smashingmag/pen/OJOebYE) por Jhey .

    Cómo terminamos

    ¿Quiere bajar el componente o rebobinarlo, tal vez desplegarlo? ¿Cómo afecta esto al rendimiento? Una implementación es más fácil. Pero rebobinar es más complicado y puede tener resultados positivos.

    Terminando la grabación

    Puedes terminar tu grabación como quieras. Podrías detener la animación y dejarla allí. O, si detenemos la animación, podríamos retroceder la animación hasta el inicio. Esto se usa a menudo en varios diseños de UI/UX. Y la API GSAP nos ofrece una forma sencilla de hacerlo. En lugar de borrar nuestra línea de tiempo al detenerla, podemos moverla al lugar donde iniciamos una grabación para restablecer la línea de tiempo. Pero, una vez que hayamos terminado una grabación, mantengamos la animación para poder usarla.

    STOP.addEventListener('click', () = { if (recorder) recorder.stop() AUDIO_CONTEXT.close() // Pause the timeline timeline.pause() // Animate the playhead back to the START_POINT gsap.to(timeline, { totalTime: START_POINT, onComplete: () = { gsap.ticker.remove(REPORT) } })})

    En este código, interpolamos la totalTimeparte posterior donde configuramos el cabezal de reproducción padTimeline. Eso significa que necesitábamos crear una variable para compartir eso.

    let START_POINT

    Y podemos establecer eso dentro padTimeline.

    const padTimeline = () = { const padCount = Math.floor(CANVAS.width / CONFIG.barWidth) for (let p = 0; p padCount; p++) { addBar() } START_POINT = timeline.totalDuration() - BAR_DURATION // Sets the timeline to the correct spot for being added to timeline.totalTime(START_POINT)}

    Podemos borrar la línea de tiempo dentro de la RECORDfunción cuando iniciamos una grabación:

     

    // Reset the timelinetimeline.clear()

    Y esto nos da lo que se está convirtiendo en un visualizador de audio bastante interesante:

    Ver la pluma [17. Rebobinando al detener](https://codepen.io/smashingmag/pen/LYOKbKW) por Jhey .

    Limpiando los valores en la reproducción

    Ahora que tenemos nuestra grabación, podemos reproducirla con el audioelemento. Pero nos gustaría sincronizar nuestra visualización con la reproducción de la grabación. Con la API de GSAP, esto es mucho más fácil de lo que cabría esperar.

    const SCRUB = (time = 0, trackTime = 0) = { gsap.to(timeline, { totalTime: time, onComplete: () = { AUDIO.currentTime = trackTime gsap.ticker.remove(REPORT) }, })}const UPDATE = e = { switch (e.type) { case 'play': timeline.totalTime(AUDIO.currentTime + START_POINT) timeline.play() gsap.ticker.add(REPORT) break case 'seeking': case 'seeked': timeline.totalTime(AUDIO.currentTime + START_POINT) break case 'pause': timeline.pause() break case 'ended': timeline.pause() SCRUB(START_POINT) break }}// Set up AUDIO scrubbing['play', 'seeking', 'seeked', 'pause', 'ended'] .forEach(event = AUDIO.addEventListener(event, UPDATE))

    Hemos refactorizado la funcionalidad que utilizamos cuando nos detenemos para borrar la línea de tiempo. Y luego se trata de escuchar diferentes eventos en el audioelemento. Cada evento requiere actualizar el cabezal de reproducción de la línea de tiempo. Podemos agregar y eliminar REPORTsegún tickercuándo reproducimos y detenemos el audio. Pero esto tiene un caso límite. Si busca después de que el audio haya "finalizado", la visualización no mostrará actualizaciones. Y eso es porque lo eliminamos REPORTdel tickerin SCRUB. Puede optar por no eliminar REPORTnada hasta que comience una nueva grabación o pase a otro estado en su aplicación. Es una cuestión de monitorear el desempeño y lo que se siente bien.

    Sin embargo, lo divertido aquí es que si haces una grabación, puedes borrar la visualización cuando busques

    Ver la pluma [18. Sincronización con reproducción](https://codepen.io/smashingmag/pen/qBVzRaj) por Jhey .

    En este punto, ya sabes todo lo que necesitas saber. Pero, si quieres aprender sobre algunas cosas adicionales, sigue leyendo.

    Reproducción de audio de otras fuentes

    Una cosa que no hemos analizado es cómo visualizar el audio desde una fuente que no sea un dispositivo de entrada. Por ejemplo, un archivo mp3. Y esto plantea un desafío o problema interesante en el que pensar.

    Consideremos una demostración en la que tenemos la URL de un archivo de audio y queremos visualizarlo con nuestra visualización. Podemos configurar explícitamente nuestros AUDIOelementos srcantes de visualizarlos.

    AUDIO.src = 'https://assets.codepen.io/605876/lobo-loco-spencer-bluegrass-blues.mp3'// NOTE:: This is required in some circumstances due to CORSAUDIO.crossOrigin = 'anonymous'

    Ya no necesitamos pensar en configurar la grabadora o usar los controles para activarla. Como tenemos un elemento de audio, podemos configurar la visualización para que se conecte directamente a la fuente.

     

    const ANALYSE = stream = { if (AUDIO_CONTEXT) return AUDIO_CONTEXT = new AudioContext() ANALYSER = AUDIO_CONTEXT.createAnalyser() ANALYSER.fftSize = CONFIG.fft const DATA_ARR = new Uint8Array(ANALYSER.frequencyBinCount) SOURCE = AUDIO_CONTEXT.createMediaElementSource(AUDIO) const GAIN_NODE = AUDIO_CONTEXT.createGain() GAIN_NODE.value = 0.5 GAIN_NODE.connect(AUDIO_CONTEXT.destination) SOURCE.connect(GAIN_NODE) SOURCE.connect(ANALYSER) // Reset the bars and pad them out... if (BARS BARS.length 0) { BARS.length = 0 padTimeline() } REPORT = () = { if (!AUDIO.paused || !played) { ANALYSER.getByteFrequencyData(DATA_ARR) const VOLUME = Math.floor((Math.max(...DATA_ARR) / 255) * 100) addBar(VOLUME) drawBars() } } gsap.ticker.add(REPORT)}

    Al hacer esto podemos conectar nuestro AudioContextal audioelemento. Hacemos esto usando createMediaElementSource(AUDIO)en lugar de createMediaStreamSource(stream). Y luego los audiocontroles de los elementos activarán la transferencia de datos al analizador. De hecho, sólo necesitamos crear AudioContextuna vez. Porque una vez que hemos reproducido la pista de audio, no estaremos trabajando con una pista de audio diferente después. Por tanto, el returnsi AUDIO_CONTEXTexiste.

    if (AUDIO_CONTEXT) return

    Otra cosa a tener en cuenta aquí. Debido a que estamos conectando el audioelemento a un AudioContext, necesitamos crear un nodo de ganancia. Este nodo de ganancia nos permite escuchar la pista de audio.

    SOURCE = AUDIO_CONTEXT.createMediaElementSource(AUDIO)const GAIN_NODE = AUDIO_CONTEXT.createGain()GAIN_NODE.value = 0.5GAIN_NODE.connect(AUDIO_CONTEXT.destination)SOURCE.connect(GAIN_NODE)SOURCE.connect(ANALYSER)

    Las cosas cambian un poco en la forma en que procesamos eventos en el elemento de audio. De hecho, para este ejemplo, cuando hayamos terminado la pista de audio, podemos eliminarla REPORTdel archivo ticker. Pero agregamos drawBarsal ticker. Esto es así si volvemos a reproducir la pista o buscamos, etc. no necesitamos procesar el audio nuevamente. Así es como manejamos la reproducción de la visualización con la grabadora. Innovacion y creatividad

    Esta actualización ocurre dentro de la SCRUBfunción y también puedes ver una nueva playedvariable. Podemos usar esto para determinar si hemos procesado toda la pista de audio.

    const SCRUB = (time = 0, trackTime = 0) = { gsap.to(timeline, { totalTime: time, onComplete: () = { AUDIO.currentTime = trackTime if (!played) { played = true gsap.ticker.remove(REPORT) gsap.ticker.add(drawBars) } }, })}

    ¿ Por qué no agregar y eliminar drawBarssegún lo tickerque estemos haciendo con el elemento de audio? Podríamos hacer esto. Podríamos mirar gsap.ticker._listenersy determinar si drawBarsya se usó o no. Podemos optar por agregar y eliminar al reproducir y pausar. Y luego también podríamos agregar y quitar al buscar y terminar de buscar. El truco sería asegurarnos de no añadir tickerdemasiado al “buscar”. Y aquí sería donde comprobar si drawBarsya formaba parte del ticker. Por supuesto, esto depende del rendimiento. ¿Valdrá la pena esa optimización por la mínima ganancia de rendimiento? Todo se reduce a qué debe hacer exactamente tu aplicación. Para esta demostración, una vez que se procesa el audio, desactivamos la tickerfunción. Esto se debe a que no necesitamos procesar el audio nuevamente. Y dejar drawBarscorrer en los tickerprogramas no afecta el rendimiento.

     

    const UPDATE = e = { switch (e.type) { case 'play': if (!played) ANALYSE() timeline.totalTime(AUDIO.currentTime + START_POINT) timeline.play() break case 'seeking': case 'seeked': timeline.totalTime(AUDIO.currentTime + START_POINT) break case 'pause': timeline.pause() break case 'ended': timeline.pause() SCRUB(START_POINT) break }}

    Nuestra switchdeclaración es muy parecida, pero solo la hacemos ANALYSEsi no tenemos playedla pista.

    Y esto nos da la siguiente demostración:

    Ver la pluma [19. Procesamiento de archivos de audio](https://codepen.io/smashingmag/pen/rNYEjWe) por Jhey .

    Desafío : ¿Podrías ampliar esta demostración para que admita diferentes pistas? Intente ampliar la demostración para aceptar diferentes pistas de audio. Tal vez un usuario pueda seleccionar del menú desplegable o ingresar una URL.

    Esta demostración conduce a un problema interesante que surgió mientras trabajaba en “Record a Call” para Kent C. Dodds . No es algo con lo que haya tenido que lidiar antes. En la demostración anterior, comience a reproducir el audio y busque hacia adelante en la pista antes de que termine de reproducirse. Buscar hacia delante rompe la visualización porque nos estamos adelantando en el tiempo. Y eso significa que nos saltamos el procesamiento de ciertas partes del audio.

    ¿Cómo puedes resolver esto? Es un problema interesante. Desea crear la línea de tiempo de la animación antes de reproducir el audio. Pero, para construirlo, primero debes reproducir el audio. ¿Podrías desactivar la “búsqueda” hasta que hayas jugado una vez? Tú podrías. En este punto, es posible que empieces a adentrarte en el mundo de los reproductores de audio personalizados. Definitivamente fuera del alcance de este artículo. En un escenario del mundo real, es posible que pueda implementar el procesamiento del lado del servidor. Esto podría brindarle una manera de obtener los datos de audio con anticipación antes de reproducirlos.

    Para “ Grabar una llamada ” de Kent , podemos adoptar un enfoque diferente. Estamos procesando el audio a medida que se graba. Y cada barra queda representada por un número. Si creamos un Arrayconjunto de números que representan las barras, ya tenemos los datos para construir la animación. Cuando se envía una grabación, los datos pueden acompañarla. Luego, cuando solicitamos audio, también podemos obtener esos datos y crear la visualización antes de la reproducción.

    Podemos usar la addBarfunción que definimos anteriormente mientras recorremos los datos de audio Array.

     

    // Given an audio data Array exampleconst AUDIO_DATA = [100, 85, 43, 12, 36, 0, 0, 0, 200, 220, 130]const buildViz = DATA = { DATA.forEach(bar = addBar(bar))}buildViz(AUDIO_DATA)

    Crear nuestras visualizaciones sin volver a procesar el audio es una gran ganancia de rendimiento.

    Considere esta demostración ampliada de nuestra demostración de grabación. Cada grabación se almacena en localStorage. Y podemos cargar una grabación para reproducirla. Pero, en lugar de procesar el audio para reproducirlo, creamos una nueva animación de barras y configuramos el elemento de audio src.

    Nota : Debe desplazarse hacia abajo para ver las grabaciones almacenadas en los elementos detalles y summary.

    Ver la pluma [20. Grabaciones guardadas ✨](https://codepen.io/smashingmag/pen/KKyjaaP) por Jhey .

    ¿Qué debe suceder aquí para almacenar y reproducir grabaciones? Bueno, no se necesita mucho ya que tenemos la mayor parte de la funcionalidad implementada. Y como hemos refactorizado las cosas en funciones de mini utilidades, esto facilita las cosas.

    Comencemos con cómo vamos a almacenar las grabaciones en formato localStorage. Al cargar la página, vamos a hidratar una variable de localStorage. Si no hay nada con qué hidratarnos, podemos crear una instancia de la variable con un valor predeterminado.

    const INITIAL_VALUE = { recordings: []}const KEY = 'recordings'const RECORDINGS = window.localStorage.getItem(KEY) ? JSON.parse(window.localStorage.getItem(KEY)) : INITIAL_VALUE

    Ahora. Vale la pena señalar que esta guía no trata de crear una aplicación o experiencia pulida. Te brinda las herramientas que necesitas para comenzar y hacerlo tuyo. Digo esto porque es posible que desee implementar parte de la UX de una manera diferente.

    Para guardar una grabación, podemos activar un guardado en el ondataavailablemétodo que hemos estado usando.

    recorder.ondataavailable = (event) = { // All the other handling code // save the recording if (confirm('Save Recording?')) { saveRecording() }}

    El proceso de guardar una grabación requiere un pequeño “truco”. Necesitamos convertir nuestro AudioBloben una cadena. De esa forma podremos guardarlo en localStorage. Para hacer esto, utilizamos la FileReaderAPI para convertir la URL AudioBloben una URL de datos. Una vez que tengamos eso, podemos crear un nuevo objeto de grabación y conservarlo en localStorage.

    const saveRecording = async () = { const reader = new FileReader() reader.onload = e = { const audioSafe = e.target.result const timestamp = new Date() RECORDINGS.recordings = [ ...RECORDINGS.recordings, { audioBlob: audioSafe, metadata: METADATA, name: timestamp.toUTCString(), id: timestamp.getTime(), }, ] window.localStorage.setItem(KEY, JSON.stringify(RECORDINGS)) renderRecordings() alert('Recording Saved') } await reader.readAsDataURL(AUDIO_BLOB)}

    Puedes crear cualquier tipo de formato que desees aquí. Para mayor comodidad, estoy usando el tiempo como id. El metadatacampo es el que Arrayutilizamos para construir nuestra animación. El timestampcampo se utiliza como un "nombre". Pero podrías hacer algo como nombrarlo según la cantidad de grabaciones. Luego, podría actualizar la interfaz de usuario para permitir a los usuarios cambiar el nombre de la grabación. O incluso puedes hacerlo mediante el paso de guardar con window.prompt.

     

    De hecho, esta demostración utiliza la window.promptUX para que puedas ver cómo funcionaría.

    Ver la pluma [21. Solicitud de nombre de grabación ](https://codepen.io/smashingmag/pen/oNorBwp) por Jhey .

    Quizás te preguntes qué renderRecordingshace. Bueno, como no utilizamos un marco, debemos actualizar la interfaz de usuario nosotros mismos. Llamamos a esta función al cargar y cada vez que guardamos o eliminamos una grabación.

    La idea es que si tenemos grabaciones, las recorramos y creemos elementos de lista para agregarlos a nuestra lista de grabaciones. Si no tenemos ninguna grabación, le mostramos un mensaje al usuario.

    Para cada grabación, creamos dos botones. Uno para reproducir la grabación y otro para eliminar la grabación.

    const renderRecordings = () = { RECORDINGS_LIST.innerHTML = '' if (RECORDINGS.recordings.length 0) { RECORDINGS_MESSAGE.style.display = 'none' RECORDINGS.recordings.reverse().forEach(recording = { const LI = document.createElement('li') LI.className = 'recordings__recording' LI.innerHTML = `span${recording.name}/span` const BTN = document.createElement('button') BTN.className = 'recordings__play recordings__control' BTN.setAttribute('data-recording', recording.id) BTN.title = 'Play Recording' BTN.innerHTML = SVGIconMarkup LI.appendChild(BTN) const DEL = document.createElement('button') DEL.setAttribute('data-recording', recording.id) DEL.className = 'recordings__delete recordings__control' DEL.title = 'Delete Recording' DEL.innerHTML = SVGIconMarkup LI.appendChild(DEL) BTN.addEventListener('click', playRecording) DEL.addEventListener('click', deleteRecording) RECORDINGS_LIST.appendChild(LI) }) } else { RECORDINGS_MESSAGE.style.display = 'block' }}

    Reproducir una grabación significa configurar el AUDIOelemento srcy generar la visualización. Antes de reproducir una grabación o cuando eliminamos una grabación, restablecemos el estado de la interfaz de usuario con una resetfunción.

    const reset = () = { AUDIO.src = null BARS.length = 0 gsap.ticker.remove(REPORT) REPORT = null timeline.clear() padTimeline() drawBars()}const playRecording = (e) = { const idToPlay = parseInt(e.currentTarget.getAttribute('data-recording'), 10) reset() const RECORDING = RECORDINGS.recordings.filter(recording = recording.id === idToPlay)[0] RECORDING.metadata.forEach(bar = addBar(bar)) REPORT = drawBars AUDIO.src = RECORDING.audioBlob AUDIO.play()}

    El método real de reproducción y visualización de la visualización se reduce a cuatro líneas.

    RECORDING.metadata.forEach(bar = addBar(bar))REPORT = drawBarsAUDIO.src = RECORDING.audioBlobAUDIO.play()
    1. Recorra la matriz de metadatos para construir el archivo timeline.
    2. Establezca la REPORTfunción en drawBars.
    3. Establezca el AUDIOsrc.
    4. Reproduzca el audio que a su vez activa la timelinereproducción de la animación.

    Desafío : ¿Puedes detectar algún caso extremo en la UX? ¿Algún problema que pueda surgir? ¿Qué pasa si estamos grabando y luego elegimos reproducir una grabación? ¿Podemos desactivar los controles cuando estamos en modo grabación?

     

    Para eliminar una grabación, utilizamos el mismo resetmétodo pero establecemos un nuevo valor localStoragepara nuestras grabaciones. Una vez que hayamos hecho eso, debemos renderRecordingsmostrar las actualizaciones.

    const deleteRecording = (e) = { if (confirm('Delete Recording?')) { const idToDelete = parseInt(e.currentTarget.getAttribute('data-recording'), 10) RECORDINGS.recordings = [...RECORDINGS.recordings.filter(recording = recording.id !== idToDelete)] window.localStorage.setItem(KEY, JSON.stringify(RECORDINGS)) reset() renderRecordings() }}

    En esta etapa, tenemos una aplicación de grabación de voz funcional que utiliza localStorage. Es un punto de partida interesante que puede tomar, agregar nuevas funciones y mejorar la UX. Por ejemplo, ¿qué tal si hacemos posible que los usuarios descarguen sus grabaciones? ¿O qué pasaría si diferentes usuarios pudieran tener diferentes temas para su visualización? Puede almacenar colores, velocidades, etc. en las grabaciones. Entonces se trataría de actualizar las propiedades del lienzo y atender los cambios en la construcción de la línea de tiempo. Para "Grabar una llamada", admitimos diferentes colores de lienzo según el equipo del que formaba parte el usuario.

    Esta demostración admite la descarga de pistas en el .oggformato.

    See the Pen [22. Downloadable Recordings ](https://codepen.io/smashingmag/pen/bGYPgqJ) by Jhey.

    But you could take this app in various directions. Here are some ideas to think about:

    • Reskin the app with a different “look and feel”
    • Support different playback speeds
    • Create different visualization styles. For example, how might you record the metadata for a waveform type visualization?
    • Displaying the recordings count to the user
    • Improve the UX catching edge cases such as the recording to playback scenario from earlier.
    • Allow users to choose their audio input device
    • Take your visualizations 3D with something like ThreeJS
    • Limit the recording time. This would be vital in a real-world app. You would want to limit the size of the data getting sent to the server. It would also enforce recordings to be concise.
    • Currently, downloading would only work in .ogg format. We can’t encode the recording to mp3 in the browser. But you could use serverless with ffmpeg to convert the audio to .mp3 for the user and return it.

    Turning This Into A React Application

    Well. If you’ve got this far, you have all the fundamentals you need to go off and have fun making audio recording apps. But, I did me






    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Una guía para la visualización de audio con JavaScript y GSAP (Parte 2)

    Una guía para la visualización de audio con JavaScript y GSAP (Parte 2)

    Clase magistral de diseño para una interfaz de usuario compleja, con Vitaly Friedman ¡Registro! Índice

    programar

    es

    https://aprendeprogramando.es/static/images/programar-una-guia-para-la-visualizacion-de-audio-con-javascript-y-gsap-parte-2-1132-0.jpg

    2024-04-04

     

    Una guía para la visualización de audio con JavaScript y GSAP (Parte 2)
    Una guía para la visualización de audio con JavaScript y GSAP (Parte 2)

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20