소스 검색

audio recording example (#2861)

* audio recording example

* code review and refactoring

---------

Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Mansar Youness 1 년 전
부모
커밋
16427ee74c
4개의 변경된 파일144개의 추가작업 그리고 0개의 파일을 삭제
  1. 26 0
      examples/audio_recorder/audio_recorder.py
  2. 103 0
      examples/audio_recorder/audio_recorder.vue
  3. 14 0
      examples/audio_recorder/main.py
  4. 1 0
      website/examples.py

+ 26 - 0
examples/audio_recorder/audio_recorder.py

@@ -0,0 +1,26 @@
+from typing import Callable, Optional
+import base64
+
+from nicegui import ui, events
+
+
+class AudioRecorder(ui.element, component='audio_recorder.vue'):
+
+    def __init__(self, *, on_audio_ready: Optional[Callable] = None) -> None:
+        super().__init__()
+        self.recording = b''
+
+        def handle_audio(e: events.GenericEventArguments) -> None:
+            self.recording = base64.b64decode(e.args['audioBlobBase64'].encode())
+            if on_audio_ready:
+                on_audio_ready(self.recording)
+        self.on('audio_ready', handle_audio)
+
+    def start_recording(self) -> None:
+        self.run_method('startRecording')
+
+    def stop_recording(self) -> None:
+        self.run_method('stopRecording')
+
+    def play_recorded_audio(self) -> None:
+        self.run_method('playRecordedAudio')

+ 103 - 0
examples/audio_recorder/audio_recorder.vue

@@ -0,0 +1,103 @@
+<template>
+  <div>
+    <button class="record-button" @mousedown="startRecording" @mouseup="stopRecording">Hold to Record</button>
+    <audio ref="audioPlayer"></audio>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      isRecording: false,
+      audioChunks: [],
+      mediaRecorder: null,
+      stream: null,
+      audioURL: null,
+      audioBlob: null,
+    };
+  },
+  mounted() {
+    this.requestMicrophonePermission();
+  },
+  watch: {
+    audioBlob(newBlob, oldBlob) {
+      if (newBlob && newBlob !== oldBlob) {
+        this.emitBlob(); // Emit the blob when it's non-null and changes
+      }
+    },
+  },
+  methods: {
+    async requestMicrophonePermission() {
+      try {
+        this.stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+      } catch (error) {
+        console.error("Error accessing microphone:", error);
+      }
+    },
+    async startRecording() {
+      try {
+        if (!this.stream) {
+          await this.requestMicrophonePermission();
+        }
+        this.audioChunks = [];
+        this.mediaRecorder = new MediaRecorder(this.stream);
+        this.mediaRecorder.addEventListener("dataavailable", (event) => {
+          if (event.data.size > 0) {
+            this.audioChunks.push(event.data);
+          }
+        });
+        this.mediaRecorder.start();
+        this.isRecording = true;
+      } catch (error) {
+        console.error("Error accessing microphone:", error);
+      }
+    },
+    stopRecording() {
+      if (this.isRecording) {
+        this.mediaRecorder.addEventListener("stop", () => {
+          this.isRecording = false;
+          this.saveBlob();
+          // this.playRecordedAudio();
+        });
+        this.mediaRecorder.stop();
+      }
+    },
+    async playRecordedAudio() {
+      this.audioURL = window.URL.createObjectURL(this.audioBlob);
+      this.$refs.audioPlayer.src = this.audioURL;
+      this.$refs.audioPlayer.play();
+    },
+    saveBlob() {
+      this.audioBlob = new Blob(this.audioChunks, { type: "audio/wav" });
+    },
+    emitBlob() {
+      const reader = new FileReader();
+      reader.onload = () => {
+        const base64Data = reader.result.split(",")[1]; // Extracting base64 data from the result
+        this.$emit("audio_ready", { audioBlobBase64: base64Data });
+      };
+      reader.readAsDataURL(this.audioBlob);
+    },
+  },
+};
+</script>
+
+<style scoped>
+.record-button {
+  width: 100px;
+  height: 100px;
+  border-radius: 50%;
+  background-color: red;
+  color: white;
+  border: 2px solid white;
+  font-size: 16px;
+  box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
+  transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.record-button:active {
+  transform: translateY(2px);
+  box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3);
+}
+</style>

+ 14 - 0
examples/audio_recorder/main.py

@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+from audio_recorder import AudioRecorder
+from nicegui import ui
+
+with ui.row().classes('w-full justify-center'):
+    audio_recorder = AudioRecorder(on_audio_ready=lambda data: ui.notify(f'Recorded {len(data)} bytes'))
+
+with ui.row().classes('w-full justify-center'):
+    ui.button('Play', on_click=audio_recorder.play_recorded_audio) \
+        .bind_enabled_from(audio_recorder, 'recording')
+    ui.button('Download', on_click=lambda: ui.download(audio_recorder.recording, 'audio.ogx')) \
+        .bind_enabled_from(audio_recorder, 'recording')
+
+ui.run()

+ 1 - 0
website/examples.py

@@ -64,4 +64,5 @@ examples: List[Example] = [
     Example('Pyserial', 'communicate with a serial device'),
     Example('Webserial', 'communicate with a serial device using the WebSerial API'),
     Example('Websockets', 'use [websockets library](https://websockets.readthedocs.io/) to start a websocket server'),
+    Example('Audio Recorder', 'Record audio, play it back or download it'),
 ]