uniapp小程序/App使用webview打通麦克风权限实现录音功能

html 复制代码
<template>
  <view class="container">
    <!-- 1. WebView 组件 -->
    <!-- 使用 @load 监听 H5 加载完成的瞬间 -->
    <web-view 
      v-if="urlPath" 
      :src="urlPath" 
      @load="onWebViewLoad"
    ></web-view>

    <!-- 2. 自定义加载过渡页 -->
    <!-- 当 showLoading 为 true 时,遮盖整个屏幕 -->
    <view v-if="showLoading" class="loading-mask">
      <view class="loading-content">
        <!-- 这里可以换成你自己的 Logo 图片 -->
        <image class="logo" src="loading.png" mode="aspectFit"></image>
        <view class="spinner"></view>
        <text class="loading-text">正在连接 AI 医生...</text>
      </view>
    </view>
  </view>
</template>

<script>
import { getUserInfo } from '@/api/user.js';

export default {
  data() {
    return {
      urlPath: '',
      showLoading: true, // 控制过渡页显示
      systemPermissionReady: false,
    }
  },
  async onShow() {
    // 每次进入页面,先展示加载状态(如果 URL 即将改变)
    // 处理权限
    // #ifdef APP-PLUS
    if (plus.os.name === 'Android' && !this.systemPermissionReady) {
      const granted = await this.checkAndRequestAndroidPermission();
      this.systemPermissionReady = granted;
    }
    // #endif

    this.fetchUserInfo();
  },
  methods: {
    // 申请权限逻辑
    checkAndRequestAndroidPermission() {
      return new Promise((resolve) => {
        plus.android.requestPermissions(['android.permission.RECORD_AUDIO'], (e) => {
          resolve(e.granted.length > 0);
        }, () => resolve(false));
      });
    },

    fetchUserInfo() {
      // 不使用 uni.showLoading 弹窗,改用我们自己的全屏遮罩
      getUserInfo().then(res => {
        const { nickname, avatar, uid } = res.data;
        const safeAvatar = encodeURIComponent(avatar || '');
        const safeNickname = encodeURIComponent(nickname || '');
        const safeUid = encodeURIComponent(uid || '');
        
        const newUrl = `H5网页地址,适配移动端`;

        if (this.urlPath !== newUrl) {
          this.showLoading = true; // URL 变了,需要重新加载
          this.urlPath = newUrl;
          
          // #ifdef APP-PLUS
          this.$nextTick(() => {
            this.grantWebviewPermission();
          });
          // #endif
        } else {
          // 如果 URL 没变(比如只是从后台切回来),直接关闭加载层
          this.showLoading = false;
        }
      }).catch(() => {
        this.showLoading = false;
      });
    },

    // 当 WebView 内部的 HTML 加载完成时触发
    onWebViewLoad() {
      console.log("H5 页面渲染完成");
      // 延迟 300ms 关闭,给 H5 内部脚本执行留一点缓冲,避免视觉闪烁
      setTimeout(() => {
        this.showLoading = false;
      }, 300);
    },

    grantWebviewPermission() {
      let retryCount = 0;
      const timer = setInterval(() => {
        const pages = getCurrentPages();
        const page = pages[pages.length - 1];
        if (!page) return;

        const currentWebview = page.$getAppWebview();
        const children = currentWebview.children();

        if (children && children.length > 0) {
          clearInterval(timer);
          const wv = children[0];

          if (plus.os.name === 'Android') {
            const realWebView = wv.nativeInstanceObject();
            if (realWebView) {
              const settings = realWebView.getSettings();
              settings.setDomStorageEnabled(true);
              settings.setMediaPlaybackRequiresUserGesture(false);

              const WebChromeClient = plus.android.importClass('android.webkit.WebChromeClient');
              const customClient = plus.android.implements('android.webkit.WebChromeClient', {
                "onPermissionRequest": function(request) {
                  request.grant(request.getResources());
                }
              });
              realWebView.setWebChromeClient(customClient);
            }
          }
        }
        if (retryCount++ > 20) clearInterval(timer);
      }, 200);
    }
  }
}
</script>

<style scoped>
.container {
  width: 100vw;
  height: 100vh;
  background-color: #FFFFFF;
}

/* 过渡遮罩层样式 */
.loading-mask {
  position: fixed;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  background-color: #FFFFFF; /* 建议使用和 App 主色调一致的背景 */
  z-index: 999; /* 确保在最上层 */
  display: flex;
  justify-content: center;
  align-items: center;
}

.loading-content {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.logo {
  width: 160rpx;
  height: 160rpx;
  margin-bottom: 40rpx;
  border-radius: 20rpx;
}

.loading-text {
  font-size: 28rpx;
  color: #666666;
  margin-top: 20rpx;
}

/* 简单的转圈动画 */
.spinner {
  width: 50rpx;
  height: 50rpx;
  border: 4rpx solid #f3f3f3;
  border-top: 4rpx solid #5555ff; /* 这里换成你的主题色 */
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>

代码逻辑

1.如果是安卓的app手机,则检查是否赋予麦克风权限,没有赋予则会自动拉取麦克风权限授权,并强制将麦克风权限注入H5网页

2.ios的app是在证书层面配置授予手机麦克风权限,是默认就有的

3.小程序端是默认打通了麦克风权限,不需要配置,另外还需要在manifest中配置相应的麦克风权限

html 复制代码
   /* 应用发布信息 */
        "distribute" : {
            /* android打包配置 */
            "android" : {
                "permissions" : [  
                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>"
                ],

配置完manifest.json文件之后,安卓的apk需要重新打包,使用麦克风功能只能在,安装包中使用,无法在自定义调试基座中使用

相关推荐
CHB1 天前
uni-app x 蒸汽模式 iOS版 性能测试基准报告 Benchmark
ios·uni-app·swiftui
衍生星球1 天前
SpringBoot3 + Vue3 + 微信小程序如何学习,以及学哪些技术,组件
sql·微信小程序·uni-app·vue·springboot
weixin_451431561 天前
落地到一个真实项目里——一个 AI驱动的冰箱食材管理小程序
人工智能·ai·小程序
老白讲技术1 天前
2026年国内APP开发与软件定制服务商能力观察:从AI应用到行业系统开发
大数据·人工智能·ai·app·软件需求·app开发
私人珍藏库1 天前
[Android] 一木百宝箱-万能百宝箱+抖音去水印等几百种功能
android·人工智能·app·软件·多功能
hnxaoli2 天前
统信小程序(十六)xls转xlsx
开发语言·python·小程序
卓怡学长2 天前
w272基于springboot便民医疗服务小程序
java·spring boot·spring·小程序·intellij-idea
新华财经频道2 天前
2026微信小程序搭建平台实测测评,优缺点解析
微信小程序·小程序
编程猪猪侠2 天前
UniAppx安卓app实现左侧导航与右侧内容联动滚动
android·uni-app
kidding7232 天前
旋转大转盘小程序
前端·css·微信小程序·小程序·前端框架