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

相关推荐
2501_9160074712 小时前
使用Apple Dashboards显示和自定iOS应用性能指标与数据可视化指南
android·ios·小程序·https·uni-app·iphone·webview
软件技术新观察13 小时前
企业数字化开发科普:软件定制、小程序、APP 开发全解析
人工智能·小程序
凡泰AI14 小时前
小程序容器技术解析:一个能够同时在多端APP运行同一个小程序的SDK需要关注哪些内容?
开发语言·小程序·mpaas·uni·技术实践
爱折腾的编程老炮14 小时前
潮玩小程序订单模型-一张表装下所有玩法
spring boot·小程序·uni-app
万岳科技程序员小金14 小时前
同城O2O外卖系统源码开发详解:从APP、小程序到后台管理平台的完整架构解析
小程序·架构·同城外卖系统源码·外卖app开发·外卖小程序开发·外卖软件开发·外卖平台搭建
两个人的幸福online14 小时前
UniApp 对接 IM 即时通讯全攻略(下篇)
uni-app
博、、14 小时前
全民健身解决方案小程序系统开发实战:从架构设计到上线指南
小程序
2501_9151063215 小时前
Flutter iOS混淆打包详细教程与步骤
android·flutter·ios·小程序·uni-app·iphone·webview
DK185838322521 天前
【源码开源部署】电竞代练护航陪玩小程序:全游戏服务平台完整解决方案(附宝塔部署教程)
游戏·微信小程序·uni-app·开源·php
码云数智-大飞1 天前
2026 年跨端开发决战:小程序原生 vs uni-app vs Taro 深度对比
小程序·uni-app·taro