更新记录

1.0.7(2023-08-03)

取消预览成功提示

1.0.6(2022-06-30)

修复Demo中的Bug

1.0.2(2022-05-18)

发布新版本

查看更多

平台兼容性

Android Android CPU类型 iOS
适用版本区间:5.0 - 11.0 armeabi-v7a:支持,arm64-v8a:未测试,x86:未测试 ×

原生插件通用使用流程:

  1. 购买插件,选择该插件绑定的项目。
  2. 在HBuilderX里找到项目,在manifest的app原生插件配置中勾选模块,如需要填写参数则参考插件作者的文档添加。
  3. 根据插件作者的提供的文档开发代码,在代码中引用插件,调用插件功能。
  4. 打包自定义基座,选择插件,得到自定义基座,然后运行时选择自定义基座,进行log输出测试。
  5. 开发完毕后正式云打包

付费原生插件目前不支持离线打包。
Android 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/android
iOS 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/ios

注意事项:使用HBuilderX2.7.14以下版本,如果同一插件且同一appid下购买并绑定了多个包名,提交云打包界面提示包名绑定不一致时,需要在HBuilderX项目中manifest.json->“App原生插件配置”->”云端插件“列表中删除该插件重新选择


注意:Android打包需设置:minSdkVersion>=21、targetSdkVersion>=26;文件必须写为".nvue"。

写在前面

因为有不少网友反馈没法使用,因此建议先测试你所使用的的摄像头是否是UVC摄像头。测试方法可以下载下面的这个APK安装包,安装到手机上,插入摄像头之后启动APP,看能否正常使用,能正常使用就说明是UVC摄像头,如果不能使用的话也没必要试用该插件了,以免给你造成困扰。

测试APK下载链接: https://pan.baidu.com/s/18sH3svBykgVEz_uJJSNsfg 提取码: f524

插件添加

0、点击右侧“试用”,选择你对应的工程,将插件添加到项目

1、添加插件之后,在HBuildX中编辑项目的manifest.json文件,打开云端插件,如下图:

2、在“App常用其他设置”指定sdk版本,并勾选CPU类型

3、切换到“源码视图”,在distribute->android->permissions节点下增加以下权限:

"<uses-permission android:name=\"android.hardware.usb.host\"/>",
"<uses-permission android:name=\"android.hardware.usb.accessory\"/>",

4、制作自定义基座,可以看DCloud自定义基座详细

5、选择自定义基座运行,在真机上进行测试。

插件说明

添加插件之后可以只用使用sintrb-uvcviewerUVC显示组件,只能在.nvue中用,该组件有以下方法:

let iv = this.$refs.iv; // 先获取组件
iv.test(callback); // 测试用,无意义
iv.start(callback); // 开始
iv.stop(callback); // 停止
iv.restart(callback); // 重启摄像头
iv.snap(options, callback); // 截图,options无意义,可传{}
iv.getSupportedPreviewSizes(callback); // 获取当前摄像头支持的预览尺寸
iv.setPreviewSize(options, callback); // 设置当前摄像头的预览尺寸,options参数类似{index:0},其中index参数为getSupportedPreviewSizes方法回调得到的尺寸列表下标序号

该组件有以下属性:

  • showControlBar: bool 是否显示控制功能
  • rotation: float 旋转角度
  • showFps: bool 是否显示FPS
  • previewSizeIndex: int 预览尺寸的索引,默认为0(一般就是摄像头支持的最高分辨率)
  • deviceId: int UVC设备ID,即手机分配给USB设备的ID。

该组件有以下事件@onStatusChange,当状态发送变化是通过该事件进行通知,可通过事件对象event.detail.status得到当前状态,状态值定义如下

private final int STATUS_NONE = 0;  // 初始状态
private final int STATUS_INITING = 1000; // 正在初始化中
private final int STATUS_RETRY = 1100;  // 正在重试中
private final int STATUS_CAM_GETTING = 2000; // 正在获取摄像头
private final int STATUS_CAM_OPENING = 2100; // 正在打开摄像头
private final int STATUS_CAM_INITING = 2200; // 摄像头初始化中
private final int STATUS_CAM_DETACHED = 2300; // 摄像头已移除
private final int STATUS_WAIT_PLAY = 5000; // 摄像头打开成,等待预览
private final int STATUS_PLAYING = 5100; // 正在预览
private final int STATUS_STOPED = 6000; // 预览已停止
private final int STATUS_ERROR = -1; // 操作出错

辅助模块sintrb-uvcmodule可用于获取当前设备所连接的USB设备(可以在.js、.vue、.nvue中用),用法如下:

const iuvc = uni.requireNativePlugin("sintrb-uvcmodule");
// 获取UVC设备列表(仅bDeviceClass为239、bDeviceSubclass为2的设备)
iuvc.getUvcDevices({}, res => {
    console.log(JSON.stringify(res))
});

// 获取所有USB设备列表()
iuvc.getUsbDevices({}, res => {
    console.log(JSON.stringify(res))
});

反复打开关闭摄像头,有时候会出现状态显示为成功,但实际看不到图像,这种情况下可以先把组件卸载了重新再加载试试(用一个变量结合v-if控制试试)。有时候摄像头正常但一直显示打开失败(类似错误码-99),可以试试重启程序和重新拔插一下摄像头。

更多信息可以看例子里面的代码,文件必须写为.nvue。

<template>
    <view style="display: flex;flex-direction: column; font-size: 12px;">
        <view class="previews" v-if="show">
            <view class="preview-wrap">
                <sintrb-uvcviewer ref="iv" class="preview" :rotation="rotation" @onStatusChange="onStatusChange">
                </sintrb-uvcviewer>
            </view>
        </view>
        <view v-if="previewSizeList.length" style="display: flex; flex-direction: row; flex-wrap: wrap;">
            <view :class="{selected:previewSizeI === i}" @tap="previewSizeI = i"
                style="border: 2rpx solid #eee; padding: 5rpx;" v-for="s,i in previewSizeList">{{s.width}}x{{s.height}}
            </view>
        </view>
        <view class="flex btns">
            <button class="grow1 button" size="mini" type="default" @tap="show = !show">{{show?"关闭":"显示"}}</button>
            <button class="grow1 button" size="mini" type="default" @tap="doIVAction('test')">测试</button>
            <button class="grow1 button" size="mini" type="default" @tap="doIVAction('start')">开始</button>
            <button class="grow1 button" size="mini" type="default" @tap="doIVAction('stop')">停止</button>
            <button class="grow1 button" size="mini" type="default" @tap="getSnap()">截图</button>
            <button class="grow1 button" size="mini" type="default" @tap="rotation = (rotation + 90) % 360">旋转</button>
            <button class="grow1 button" size="mini" type="default" @tap="getSupportedPreviewSizes()">获取支持的尺寸</button>
            <button class="grow1 button" size="mini" type="default" @tap="getUvcDevices()">获取USB设备列表</button>
        </view>

        <scroll-view v-if="images.length" scroll-x="true" style="flex-direction: row;margin-top: 5px;">
            <view style="display: flex;flex-direction: row;">
                <image v-for="it,ix in images" @tap="viewImg(it,ix)" :key="it.key" :src="it.src" mode="heightFix"
                    style="max-width: 60px; height: 60px;border: 1px solid red; margin-right: 1px;"></image>
            </view>
        </scroll-view>
        <scroll-view class="logs" scroll-y="true" style="flex-direction: column;margin-top: 5px;">
            <view style="display: flex;flex-direction: column;">
                <view v-for="l in logs"
                    style="margin-top: 1rpx; font-size: 8px; width: auto; border: 1rpx solid #EEEEEE; padding: 10rpx;">
                    <text>{{l}}</text>
                </view>
            </view>
        </scroll-view>
    </view>
</template>

<script>
    const iuvc = uni.requireNativePlugin("sintrb-uvcmodule")
    export default {
        data() {
            return {
                show: true,
                previewSizeI: -1,
                previewSizeList: [],
                rotation: 0,
                logs: [],
                images: [],
            }
        },
        computed: {

        },
        watch: {
            previewSizeI() {
                // console.log("previewSizeI", this.previewSizeI);
                this.addLog("previewSizeI " + this.previewSizeI);
                // let size = this.previewSizeList[this.previewSizeI];
                this.doIVAction("setPreviewSize", {
                    index: this.previewSizeI
                });
            }
        },
        methods: {
            getUvcDevices() {
                iuvc.getUvcDevices({}, res => {
                    res.data.devices.map(dev => {
                        let ndev = JSON.parse(JSON.stringify(dev));
                        dev.showJson = false;
                        dev.showPreview = false;
                        return dev;
                    })

                    console.log(JSON.stringify(res))
                    this.devices = res.data.devices
                    this.addLog(res);
                });
            },
            getSupportedPreviewSizes() {
                this.doIVAction("getSupportedPreviewSizes", null, res => {
                    if (res && res.data) {
                        this.previewSizeList = res.data.items;
                    }
                })
            },
            getSnap() {
                this.doIVAction("snap", {}, res => {
                    this.addLog(res);
                    if (res && res.data) {
                        this.addImg(res.data.path);
                    }
                })
            },
            async doIVAction(action, options, cbk) {
                let iv = this.$refs.iv;
                if (!iv) {
                    this.res = "没有iv " + Object.keys(this.$refs).join(",")
                    return;
                }
                let func = iv[action];
                if (!func) {
                    this.addLog("没有iv." + action + " " + Object.keys(iv).join(","));
                    return;
                }
                // this.res = 'R ' + action + ' : ' + func;
                let args = [];
                if (options) {
                    args.push(options);
                }
                args.push(res => {
                    this.addLog(res);
                    if (cbk) {
                        cbk(res);
                    }
                })
                this.res = args;
                try {
                    func.apply(iv, args);
                } catch (e) {
                    this.addLog("ERR " + e);
                }
            },
            onStatusChange(e) {
                this.addLog(e.detail);
                if (e.detail.status === 5100 && !this.previewSizeList.length) {
                    // 预览成功,获取分辨率
                    this.getSupportedPreviewSizes()
                }
            },
            addLog(l) {
                if (typeof(l) !== "string") {
                    l = JSON.stringify(l);
                }
                this.logs.unshift(l);
            },
            viewImg(it, ix) {
                uni.previewImage({
                    urls: this.images.map(r => r.src),
                    index: ix,
                })
            },
            addImg(img) {
                this.images.splice(0, 0, {
                    src: img,
                    key: Date.now(),
                })
                this.addLog(img);
            },
        }
    }
</script>

<style lang="scss">
    .mini-btn {
        padding: 5rpx;
    }

    .btns {
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
    }

    .button {
        // width: 100rpx;
        padding: 3px 5px;
    }

    .previews {
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        justify-content: center;
        margin-bottom: 10px;
    }

    .preview-wrap {
        background: black;
        margin: 2px;
        min-width: 320px;
        min-height: 240px;
    }

    .preview {
        width: 400px;
        height: 300px;
    }

    .selected {
        background: red;
    }

    .logs {
        // border: 1rpx solid #eee;
        // padding: 5px;
        // margin: 5px;
    }
</style>

隐私、权限声明

1. 本插件需要申请的系统权限列表:

USB-HOST权限 文件读写权限

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问