uni.createInnerAudioContext() 报错{“errMsg“:“MediaError“,“errCode“:-99}

该文章已生成可运行项目,

 报错原因:
uniapp的uni.createInnerAudioContext不支持直接播放base64音频文件,所以需要把base64音频转为本地音频文件再播放,缺点是转换会导致延迟,音频播放会有延迟
utils文件夹下新建tool.js文件

tool.js文件代码: 

export const base64ToFile = (base64Str, fileName, callback) => {
  var index = base64Str.indexOf(',');
  var base64Str = base64Str.slice(index + 1, base64Str.length);

  plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function(fs) {
    fs.root.getFile(fileName, {
      create: true
    }, function(entry) {
      var fullPath = entry.fullPath;
      let platform = uni.getSystemInfoSync().platform;
      if (platform == 'android') {
        var Base64 = plus.android.importClass("android.util.Base64");
        var FileOutputStream = plus.android.importClass("java.io.FileOutputStream");
        try {
          var out = new FileOutputStream(fullPath);
          var bytes = Base64.decode(base64Str, Base64.DEFAULT);
          out.write(bytes);
          out.close();
          callback && callback(entry.toLocalURL());
        } catch (e) {
          console.log(e.message);
        }
      } else if (platform == 'ios') {
        var NSData = plus.ios.importClass('NSData');
        var nsData = new NSData();
        nsData = nsData.initWithBase64EncodedStringoptions(base64Str, 0);
        if (nsData) {
          nsData.plusCallMethod({
            writeToFile: fullPath,
            atomically: true
          });
          plus.ios.deleteObject(nsData);
        }
        callback && callback(entry.toLocalURL());
      }
    });
  });
}; 

在script下面导入 

<script>
import { base64ToFile } from "@/utils/tools.js";

 方法中使用:

      const base64Audio = uni.arrayBufferToBase64(response.data);
        const base64WithPrefix = `data:audio/mp3;base64,${base64Audio}`;
        
        // 使用 Promise 包装回调式的 base64ToFile
        const filePath = await new Promise((resolve) => {
          const fileName = `${word}_${Date.now()}.mp3`;
          base64ToFile(base64WithPrefix, fileName, (path) => {
            resolve(path);
          });
        });

参考文献:
uniapp开发安卓和ios播放base64音频(mp3,wav都可以),或者处理其它类型的base64数据成为url_uniapp 播放base64-优快云博客 uniapp能播放base64格式的音频吗? - DCloud问答

本文章已经生成可运行项目
// utils/mediaSelector.js /** * 媒体选择统一服务(支持 UniApp + 企业微信小程序)—— 支持半成功返回 */ export class MediaSelector { /** * 选择手机相册中的图片或视频(可多选) */ static async selectFromAlbum(options = {}) { return new Promise(resolve => { // 注意:这里不再轻易 reject,而是 resolve({ successList, failedList }) uni.chooseMedia({ count: options.count || 1, mediaType: options.mediaType || ['image', 'video'], sourceType: ['album'], maxDuration: options.maxDuration, ...options, success: res => { uni.showLoading({ title: '加载中' }) const { tempFiles } = res const successList = [] const failedList = [] tempFiles.forEach((file, index) => { try { if (!file.tempFilePath) { throw new Error('empty_path') } const item = { path: file.tempFilePath, type: file.width ? 'local-image' : 'local-video', size: file.size, duration: file.duration || 0, index } successList.push(item) } catch (e) { failedList.push({ original: file, index, error: e.message, reason: 'invalid_file_path_or_metadata' }) } }) resolve({ successList, failedList }) }, fail: err => { console.error('相册选择失败:', err) // 完全失败的情况 resolve({ successList: [], failedList: [{ error: 'select_from_album_failed', detail: err }] }) } }) }) } /** * 直接调用相机拍照 */ static async takePhoto(options = {}) { return new Promise((resolve, reject) => { uni.chooseImage({ count: 1, sourceType: ['camera'], ...options, success: res => { uni.showLoading({ title: '加载中' }) const paths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePath] const files = paths.map(path => ({ path, type: 'local-image' })) resolve({ successList: files, failedList: [] }) }, fail: err => { console.log('拍照取消或出错', err) resolve({ successList: [], failedList: [{ error: 'take_photo_failed', detail: err }] }) } }) }) } /** * 直接录制视频 */ static async recordVideo(options = {}) { return new Promise(resolve => { uni.chooseVideo({ sourceType: ['camera'], compressed: options.compressed ?? true, maxDuration: options.maxDuration, camera: options.camera || 'back', ...options, success: res => { uni.showLoading({ title: '加载中' }) const file = { path: res.tempFilePath, size: res.size, duration: res.duration, type: 'local-video' } if (!res.tempFilePath) { resolve({ successList: [], failedList: [{ error: 'recorded_video_path_empty', detail: res }] }) } else { resolve({ successList: [file], failedList: [] }) } }, fail: err => { console.log('录制失败', err) resolve({ successList: [], failedList: [{ error: 'record_video_failed', detail: err }] }) } }) }) } /** * 获取企业微信用户 session_key(用于后续 API 调用) */ static async getQyWxSession() { return new Promise((resolve, reject) => { wx.qy.login({ success: loginRes => { if (!loginRes.code) { reject(new Error('login_failed')) return } wx.request({ url: 'https://qyapi.weixin.qq.com/cgi-bin/gettoken', data: { corpid: 'ww89375d6c15dd0e96', corpsecret: 'QIBK6NaGBFFWpOSY1qBTXDpe7MvtgCUTLbr0vEIOQzI' }, success: tokenRes => { if (!tokenRes.data.access_token) { reject(new Error('get_access_token_failed')) return } wx.request({ url: 'https://qyapi.weixin.qq.com/cgi-bin/miniprogram/jscode2session', data: { access_token: tokenRes.data.access_token, js_code: loginRes.code, grant_type: 'authorization_code' }, success: sessionRes => { if (sessionRes.data.errcode) { reject(new Error(`session_error: ${sessionRes.data.errmsg}`)) } else { resolve(sessionRes.data) } }, fail: () => reject(new Error('jscode2session_request_failed')) }) }, fail: () => reject(new Error('get_token_request_failed')) }) }, fail: () => reject(new Error('wx_qy_login_failed')) }) }) } /** * 将 chooseMessageFile 包装为 Promise 并支持部分失败 */ static async chooseMessageFile(options) { const res = await this.getQyWxSession() // console.log('🚀 ~ MediaSelector ~ chooseMessageFile ~ res:', res) return new Promise(resolve => { if (!wx || !wx.qy || !wx.qy.chooseMessageFile) { return resolve({ successList: [], failedList: [{ error: 'sdk_not_ready', detail: 'wx.qy.chooseMessageFile is not available' }] }) } wx.qy.chooseMessageFile({ count: options.count || 1, type: ['image', 'video'].includes(options.type) ? options.type : 'file', extension: options.extension || [ '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.mp4', '.avi', '.mov', '.wmv', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.zip', '.rar' ], success: res => { uni.showLoading({ title: '加载中' }) const { tempFiles } = res const successList = [] const failedList = [] tempFiles.forEach((file, index) => { try { if (!file.path) { throw new Error('file_path_missing') } const item = { path: file.path, name: file.name, size: file.size, type: file.type, index } successList.push(item) } catch (e) { failedList.push({ original: file, index, error: e.message, reason: 'parse_failed' }) } }) resolve({ successList, failedList }) }, fail: err => { console.error('选择失败', err) resolve({ successList: [], failedList: [{ error: 'choose_message_file_failed', detail: err.errMsg }] }) } }) }) } /** * 映射企微文件类型到本地类型 */ static getMimeType(qyType, fileName) { console.log('🚀 ~ MediaSelector ~ getMimeType ~ qyType:', qyType) switch (qyType) { case 'image': return 'local-image' case 'video': return 'local-video' default: return 'local-file' } } /** * 主入口:根据 type 执行对应操作(返回结构化结果) */ static async select(type, options = {}) { try { let result = { successList: [], failedList: [] } switch (type) { case 'mobileImage': case 'mobileVideo': // case 'mobileMix': result = await this.selectFromAlbum({ mediaType: type === 'mobileImage' ? ['image'] : type === 'mobileVideo' ? ['video'] : ['image', 'video'], ...options }) break case 'cameraImage': result = await this.takePhoto(options) break case 'cameraVideo': result = await this.recordVideo(options) break case 'messageImage': case 'messageVideo': // case 'messageMix': case 'messageFile': result = await this.chooseMessageFile({ type: type === 'messageImage' ? 'image' : type === 'messageVideo' ? 'video' : type === 'messageFile' ? 'file' : 'all', ...options }) break default: return { success: false, error: 'unsupported_media_type', data: null } } // 统一返回格式 uni.hideLoading() return { success: result.failedList.length === 0, partial: result.successList.length > 0 && result.failedList.length > 0, data: { successList: result.successList, failedList: result.failedList, successCount: result.successList.length, failCount: result.failedList.length } } } catch (err) { console.error('[MediaSelector] 操作失败:', err.message) uni.hideLoading() return { success: false, partial: false, error: err.message, data: { successList: [], failedList: [{ error: 'unexpected_catch_error', message: err.message }] } } } } } 加的 uni.showLoading({ title: '加载中' }) 但是有时候不展示,有时候就展示一下
最新发布
09-26
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值