webRtc生产环境实用方法

最近做了几个项目发现多个项目反反复复会出现几个高频的需求, 都依赖于获取系统采集设备和指定设备id获取媒体流;为了不在反复书写总结2个公用方法;

检索当前系统所有可用设备
typescript 复制代码
/**
   * 检索当前系统所有可用设备
   * @returns {
   *  audioInputOptions: Array [{value: string, label: string}]; 说明: 音频输入设备
   *  videoInputOptions: Array [{value: string, label: string}]; 说明: 视频输入设备
   *  audiooutputOptions: Array [{value: string, label: string}]; 说明: 音频输出设备
   *  isAudioInput: Boolean 是否有音频输入设备;
   *  isVideoInput: Boolean 是否视频输入设备;
   *  isAudioOutput: Boolean 是否有音频输出设备;
   *  audioDeviceIds: [string] // 音频DevIds;
   *  videoDeviceIds: [string] // 视频DevIds;
 * }
*/
export function GetSystemEnumerateDevices() {
    return navigator.mediaDevices.enumerateDevices().then(function(devices) {
        const audioInputOptions = [];
        const videoInputOptions = [];
        const audiooutputOptions = [];
        let isAudioInput = false;
        let isVideoInput = false;
        let isAudioOutput = false;
        const audioDeviceIds = [];
        const videoDeviceIds = [];
        let tmp = devices.filter(function(device) {
            /* 
                *  device.kind: 目前有3个值;
                * 'audioinput': 表示麦克风;
                * 'videoinput': 表示摄像头,没有安装摄像头时部分品牌机({label: 'screen-capture-recorder'},这也就是为啥没摄像头就会传会桌面画面);
                * 'audiooutput': 扬声器/听筒;
            */
           // 判断是否有摄像头设备
            if (device.kind === 'videoinput') {
                if (!isVideoInput) {
                    isVideoInput = true;
                }
                
                // 针对个别电脑优先选择摄像头设备
                if (device.label === 'screen-capture-recorder') {
                    videoDeviceIds.push(device.deviceId);
                    videoInputOptions.push({ value: device.deviceId, label: device.label });
                } else {
                    videoDeviceIds.unshift(device.deviceId);
                    videoInputOptions.unshift({ value: device.deviceId, label: device.label });
                }
            };
            // 判断是否有麦克风设备
            if (device.kind === 'audioinput') {
                if (!isAudioInput) {
                    isAudioInput = true;
                }
                audioInputOptions.push({ value: device.deviceId, label: device.label });
                audioDeviceIds.push(device.deviceId);
            };
            // 音频输出设备
            if (device.kind === 'audiooutput') {
                if (!isAudioOutput) { isAudioOutput = true };
                audiooutputOptions.push({ value: device.deviceId, label: device.label });
            };
            return false;
        });
        tmp = null;
        // console.log('音频输入设备', audioInputOptions);
        // console.log('视频输入设备', videoInputOptions);
        // console.log('音频输出设备', audiooutputOptions);
        // console.log('是否有音频输入设备', isAudioInput);
        // console.log('是否视频输入设备', isVideoInput);
        // console.log('是否有音频输出设备', isAudioOutput);
        // console.log('音频DevIds', audioDeviceIds);
        // console.log('视频DevIds', videoDeviceIds);
        return {audioInputOptions, videoInputOptions, audiooutputOptions, isAudioInput, isVideoInput, isAudioOutput, audioDeviceIds, videoDeviceIds}
    });
}
设备id获取MediaStream
typescript 复制代码
export function DeviceidToMediaStream(devId: string) {
    return new Promise((resolve, reject) => {
        if (typeof devId != 'string') {
            return reject({code: 0, message: '设备id必须是字符串'});
        }
        GetSystemEnumerateDevices().then((devInfo) => {
            const {audioDeviceIds, videoDeviceIds} = devInfo;
            const constraints = {
                audio: false,
                video: false,
            }
            let mediaType = 1;
            if (audioDeviceIds.includes(devId)) {
                constraints.audio = true;
                delete constraints.video;
            }
            if (videoDeviceIds.includes(devInfo)) {
                mediaType = 2;
                constraints.video = true;
                delete constraints.audio;
            }
            let isHas = Object.values(constraints).filter((item) => {
                return item;
            })
            if (isHas.length === 0) {
                return reject({code: 0, message: '没有找到采集设备'});
            }
            navigator.mediaDevices.getUserMedia(constraints).then((locStream) => {
                return resolve(locStream);
            }).catch((error) => {
                const cause = error.message;
                let message = mediaType === 1 ? '麦克风启动失败' : '摄像头启动失败';
                if (cause === 'Permission denied') {
                    message = '媒体采集权限被拒绝';
                } else if (cause === 'Requested device not found') {
                    message = mediaType === 1 ? '未检测到麦克风' : '未检测到采集设备';
                } else if (cause === 'Could not start video source') {
                   // QQ浏览器如果摄像头被其它应用占用会报错: Could not start video source
                    message = '摄像头启动失败,可能被其它应用占用或被拔出';
                } else if (cause === 'Device in use') {
                    // 谷歌浏览器摄像头被其它占用用会报错 'Device in use'
                    message = '设备被另一个应用或进程占用'
                } else if (cause === 'Starting videoinput failed') {
                    // 火狐摄像头被占用会报错 'Starting videoinput failed'
                    message = '设备启动失败,可能被被另一个应用或进程占用'
                };
                return reject({code: 0, message});
            })
        })
    })
}
MediaStream获取视频的宽度和高度
typescript 复制代码
export function GetMediaStreamVideoWidthHeight(streams: MediaStream) {
    return new Promise((resolve, reject) => {
        const videoTrackArr = streams.getVideoTracks();
        if (videoTrackArr.length === 0) {
            reject({code:0, message:'没有视频轨道'});
            return;
        }
        const tmp = document.createElement('video');
        tmp.srcObject = streams;
        tmp.onloadedmetadata = function() {
            resolve({width: tmp.videoWidth, height: tmp.videoHeight});
            tmp.srcObject = null;
            tmp.remove();  
        };
        tmp.onerror = function() {  
            reject({ code: 1, message: '视频加载失败' });  
            tmp.srcObject = null;  
            tmp.remove();  
        };  
    })
}
判断音频录入设备是否存在
typescript 复制代码
export function JudgeAudioInputDevIdIsExist(devId: string) {
    return new Promise((resolve, reject) => {
        if (typeof devId != 'string') {
            return reject({code: 0, message: '设备id必须是字符串'});
        };
        GetSystemEnumerateDevices().then((devInfo) => {
            const {audioDeviceIds} = devInfo;
            if (!audioDeviceIds.includes(devId)) {
                return reject({code: 0, message: '设备id不存在'});
            };
            resolve({code: 1, message: devId});
        });
    })
}
判断视频录入设备是否存在
typescript 复制代码
export function JudgeVideoInputDevIdIsExist(devId: string) {
    return new Promise((resolve, reject) => {
        if (typeof devId != 'string') {
            return reject({code: 0, message: '设备id必须是字符串'});
        };
        GetSystemEnumerateDevices().then((devInfo) => {
            const {videoDeviceIds} = devInfo;
            if (!videoDeviceIds.includes(devId)) {
                return reject({code: 0, message: '设备id不存在'});
            }
            resolve({code: 1, message: devId});
        });
    })
}
拦截音视频权限可能会报错的问题
typescript 复制代码
/**
 * 处理音视频权限错误
 *  mediaType: 1_音频; 2_视频;
*/
export function BackGetUserMediaError(cause: string, mediaType: number) {
    let message = '';
    let mediaName= mediaType === 1 ? '麦克风' : '摄像头'
    if (cause === 'Permission denied') {
        message = `${mediaName}获取权限被拒绝,请检查浏览器是否开启使用权限`;
    } else if (cause === 'Requested device not found') {
        message = `未检测到${mediaName}设备`;
    } else if (cause === 'Could not start video source') {
        /* 
            1.QQ浏览器_设备被占用会走这里; 
            2.QQ浏览器_系统设置关闭摄像头应用使用权限; 
            3.谷歌浏览器_win7或低版本谷歌会存在临时拔掉摄像头但枚举可以拿到设备调用后会报错
        */
        message = '摄像头启动失败,可能被其它应用占用或被拔出或系统关闭了摄像头调用权限';
    } else if (cause === 'Device in use') {
        // 谷歌浏览,假如设备被占用的时候会报错
        message = `${mediaName}设备被另一个应用或进程占用`;
    } else if (cause === 'Starting videoinput failed') {
        // 火狐浏览器, 假如设备被占用的时候会报错
        message = '摄像头启动失败,可能被被另一个应用或进程占用';
    } else if (cause === 'Failed to allocate videosource') {
        // 火狐-win10系统设置关闭摄像头应用使用权限
        message = '摄像头启动失败,可能系统关闭了摄像头使用权限';
    } else if (cause === 'Permission denied by system') {
        /*  存在麦克风摄像头混用报错
            1.谷歌浏览器_win10系统设置关闭摄像头应用使用权限;
            2.谷歌浏览器_win10系统设置关闭麦克风应用使用权限;
            3.QQ浏览器_系统win10系统设置关闭麦克风使用权限
        */
        message = `${mediaName}启动失败,可能系统关闭了设备使用权限`;
    } else {
        message = `${mediaName}启动失败`
    };

    return {code: 0, cause, message};
}
相关推荐
Myli_ing27 分钟前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风29 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave36 分钟前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟38 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾1 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧1 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
asleep7012 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm2 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王2 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue