前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >开发一个 Streamlit 录音组件

开发一个 Streamlit 录音组件

原创
作者头像
dandelion1990
发布2024-03-27 11:59:00
1310
发布2024-03-27 11:59:00
举报

用 Vue 写一个录音组件

先写一个 Vue 组件,用于录音功能。以下是一个简单的录音组件示例:

代码语言:vue
复制
<template>
  <div class="button-container">
    <button @mousedown="startRecording" @mouseup="stopRecording" @mouseleave="stopRecording" class="red-round-button"></button>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';

const mediaRecorder = ref<MediaRecorder | null>(null);
  const audioChunks = ref<Blob[]>([]);


async function startRecording() {
  if (!navigator.mediaDevices || !window.MediaRecorder) {
    alert('Media Devices API or MediaRecorder API not supported in this browser.');
    return;
  }

  const audioConstraints = {
    audio: {
      sampleRate: 16000,
      channelCount: 1,
      volume: 1.0
    }
  };

  try {
    const stream = await navigator.mediaDevices.getUserMedia(audioConstraints);
    mediaRecorder.value = new MediaRecorder(stream);
    audioChunks.value = [];

    mediaRecorder.value.ondataavailable = (event) => {
      audioChunks.value.push(event.data);
    };

    mediaRecorder.value.start();
  } catch (error) {
    console.error('Error accessing the microphone', error);
  }
}

function stopRecording() {
  if (mediaRecorder.value && mediaRecorder.value.state !== 'inactive') {
    mediaRecorder.value.stop();
    mediaRecorder.value.onstop = async () => {
      const audioBlob = new Blob(audioChunks.value, { type: 'audio/wav' });
      const audioUrl = URL.createObjectURL(audioBlob);
      const audio = new Audio(audioUrl);
      await audio.play();
    };
  }
}

onMounted(() => {
  // You can perform actions after the component has been mounted
});

onUnmounted(() => {
  // Perform cleanup tasks, such as stopping the media recorder if it's still active
  if (mediaRecorder.value) {
    mediaRecorder.value.stop();
  }
});
</script>

<style scoped>
.button-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh; /* This assumes you want to center the button vertically in the viewport */
}

.red-round-button {
  background-color: red;
  border: none;
  color: white;
  padding: 10px 20px;
  border-radius: 50%; /* This creates the round shape */
  cursor: pointer;
  outline: none;
  font-size: 16px;
  /* Adjust width and height to make the button round, they must be equal */
  width: 50px;
  height: 50px;
  /* Optional: Add some transition for interactions */
  transition: background-color 0.3s;
}

.red-round-button:hover {
  background-color: darkred;
}
</style>

注意

  1. 浏览器只支持以默认 sample rate 以及 webm 格式录制音频,经 Chrome/Firefox 测试上面传入的 sampleRate 其实无效
  2. 参考 Supported Audio Constraints in getUserMedia()Recording Audio in the Browser Using the MediaStream Recorder API,Chrome 的默认格式是 webm/opus,Firefox 的默认格式是 ogg/opus,采样率都是 48000 Hz
  3. 这里可以查看当前浏览器支持的 constraint:MediaDevices: getSupportedConstraints() method

封装成 streamlit 组件

项目结构可以参考模板库

在封装过程中,我们需要做以下几点改动:

  1. 给组件加上 streamlit 相关依赖
  2. 将录音数据的 Blob 转换为 base64 编码,以便传回 Python 函数。
  3. 修改组件的样式和属性,以适应 Streamlit 的使用场景。
代码语言:vue
复制
<template>
  <div class="button-container">
    <button @mousedown="startRecording" @mouseup="stopRecording" @mouseleave="stopRecording" class="record-button" :style="dynamicStyles"></button>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted , computed } from 'vue';
import { Streamlit } from "streamlit-component-lib"
import { useStreamlit } from "./streamlit"

const props = defineProps(['args'])

const dynamicStyles = computed(() => {
  return {
    width: props.args.width,
    height: props.args.height,
  };
});

useStreamlit();

const mediaRecorder = ref(null);
const audioChunks = ref([]);

async function startRecording() {
  if (!navigator.mediaDevices || !window.MediaRecorder) {
    alert('Media Devices API or MediaRecorder API not supported in this browser.');
    return;
  }

  try {
    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
    mediaRecorder.value = new MediaRecorder(stream);
    audioChunks.value = [];

    mediaRecorder.value.ondataavailable = (event) => {
      audioChunks.value.push(event.data);
    };

    mediaRecorder.value.start();
  } catch (error) {
    console.error('Error accessing the microphone', error);
  }
}

function stopRecording() {
  if (mediaRecorder.value && mediaRecorder.value.state !== 'inactive') {
    mediaRecorder.value.stop();
    mediaRecorder.value.onstop = async function() {
      const audioBlob = new Blob(audioChunks.value, { type: 'audio/wav' });
      const reader = new FileReader();
      reader.onload = function(event) {
        if (event.target && event.target.result) {
          const dataUrl = event.target.result;
          const base64 = dataUrl.split(',')[1]; // Extract the base64 part
          Streamlit.setComponentValue(base64);
        } else {
          console.log('Failed to read Blob as base64');
        }
      };
      reader.readAsDataURL(audioBlob);
    };
    
  }
}

onMounted(() => {
  // You can perform actions after the component has been mounted
});

onUnmounted(() => {
  // Perform cleanup tasks, such as stopping the media recorder if it's still active
  if (mediaRecorder.value) {
    mediaRecorder.value.stop();
  }
});
</script>

<style scoped>
.button-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh; /* center the button vertically in the viewport */
}

.record-button {
  background-color: rgb(255, 100, 100);
  border: none;
  color: white;
  padding: 10px 20px;
  border-radius: 50%; /* This creates the round shape */
  cursor: pointer;
  outline: none;
  font-size: 16px;
  width: 80px;
  height: 80px;
  /* Optional: Add some transition for interactions */
  transition: background-color 0.3s;
}

.record-button:active {
  background-color: darkred;
}
</style>

Python 端的封装与使用

在 Python 端,我们需要创建一个 Streamlit 应用,用于接收和处理前端传递的 base64 编码数据。以下是 Python 端的代码示例:

代码语言:python
复制
import base64
import os
import streamlit.components.v1 as components

_RELEASE = True

if not _RELEASE:
    _component_func = components.declare_component(
        "record_button",
        url="http://localhost:5173",
    )
else:
    parent_dir = os.path.dirname(os.path.abspath(__file__))
    build_dir = os.path.join(parent_dir, "frontend/dist")
    _component_func = components.declare_component(
        "record_button",
        path=build_dir
    )


def record_button(size, key=None):
    component_value = _component_func(size=size, key=key, default=None)
    if component_value:
        component_value = base64.b64decode(component_value)
    return component_value
  1. 修改 _RELEASE 参数,调试时可以设为 False ,使用本地 npm run dev 来调试。发布时先运行 npm run build 打包 frontend,然后将_RELEASE 设为 True
  2. 修改代码中对应的组件名称、返回默认值
  3. 使用 vite 打包时需要在 tsconfig.json 中加上 "compilerOptions.baseUrl": ".",以及在 vite.config.ts 中加上 `base: './',不然路径会有问题,参考 how-to-view-a-component-in-release-mode-using-vite

打包与发布

最后,我们将这个 Streamlit 组件打包并发布到 PyPI,以便其他用户可以轻松地安装和使用。打包和发布的过程涉及到一些配置和命令,具体步骤可以参考 Streamlit 官方文档(Streamlit Document - Publish a Component)和 Python 打包用户指南(Python Packaging User Guide)。

在发布之前,确保修改 MANIFEST.in 文件,将前端构建文件夹包含在内。同时,根据你的组件信息修改 pyproject.toml 文件。

代码语言:sh
复制
python3 -m build # 打包
python3 -m twine upload --repository testpypi dist/* # 上传

注意文件夹名称一致,否则 pip 安装成功后 import 时也会出现 Module Name Not Found的错误

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 用 Vue 写一个录音组件
  • 封装成 streamlit 组件
  • Python 端的封装与使用
  • 打包与发布
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档