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};
}
相关推荐
GIS程序媛—椰子5 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00111 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端14 分钟前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x17 分钟前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟100918 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤439128 分钟前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习