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需要重新打包,使用麦克风功能只能在,安装包中使用,无法在自定义调试基座中使用

相关推荐
00后程序员张17 小时前
苹果App Store上架指南:费用、原理与步骤详解
android·ios·小程序·https·uni-app·iphone·webview
特创数字科技1 天前
【uni-app x】告别系统相机限制!三端通用自定义相机组件:任意布局、静默拍照、录像、水印叠加、连拍回图一次搞定
前端·数码相机·uni-app
Martin -Tang1 天前
uniapp app嵌套webview 弹窗方式
android·ios·uni-app
paopaokaka_luck1 天前
个人综合管理小程序(echarts数据看板、健康数据记录与趋势查看、目标拆解和进度管理、日程待办与番茄钟、习惯打卡、收支统计、个人生活记录、跨模块智能提醒)
前端·javascript·spring·小程序·数据分析·echarts·生活
m0_587383001 天前
广州24小时自助健身房解决方案实战指南与系统部署要点
java·spring·小程序·架构·需求分析
郑州光合科技余经理1 天前
同城电商系统:库存变更怎么同步到订单
java·开发语言·前端·后端·uni-app·php·ai编程
m0_587383002 天前
西安同城拼车软件开发实战指南:从零搭建高效系统
人工智能·小程序·数据挖掘·系统架构·需求分析
weixin_lynhgworld2 天前
抽取类盲盒小程序的五条合规红线意味着什么?
小程序
现任明教教主~2 天前
运营级多门店洗车贴膜汽车在线核销保养服务系统洗车预约洗车系统消费分佣扫码核销系统
微信小程序·小程序·汽车