精华内容
下载资源
问答
  • 前端实践之调用手机摄像头
    千次阅读
    2021-04-19 18:11:09

    HTML5通过navigator.mediaDevices.getUserMedia调用手机摄像头问题

    这篇文章主要介绍了HTML5通过navigator.mediaDevices.getUserMedia调用手机摄像头问题,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

    navigator.mediaDevices.getUserMedia

    应项目要求,需要实现移动端app嵌入H5页面完成实人认证的功能。打开getUserMedia文档,链接如下:
    https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia
    看上去很简单,最终却写的怀疑人生。

    API环境

    问题一:(为什么不管怎么配置都显示前置摄像头)

    想正常使用API必须在https环境下进行,否则你会发现不管怎么写,都只能调用默认的摄像头(大部分都是前置,只有少部分是后置)
    前端开发者可以将文件上传至"码云"仓库,获取https链接然后在手机上预览
    链接:码云仓库入口

    问题二:(API在安卓和ios效果一样吗?)

    根据官方文档,目前navigator.mediaDevices.getUserMedia在ios上只支持11版本以上,且只能在safari正常运行。安卓目前没有发现版本限制,需要兼容的代码如下

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    if (navigator.mediaDevices === undefined) {

        navigator.mediaDevices = {};

     }

    if (navigator.mediaDevices.getUserMedia === undefined) {

        navigator.mediaDevices.getUserMedia = function (constraints) {

        var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;

        if (!getUserMedia) {

            return Promise.reject(new Error('getUserMedia is not implemented in this browser'));

        }

        return new Promise(function (resolve, reject) {

            getUserMedia.call(navigator, constraints, resolve, reject);

        });

      }

    }

    问题三:(第一次启用成功调用前置摄像头,第二次需要调用后置却黑屏或者失败)

    失败的原因很多,列举两个一开始我遇到的问题
    1.前置摄像头调用后,摄像功能需要关闭后才能正常执行第二次调用,否则会报错:设备被占用。解决方法,在每次执行调用方法前,先关闭摄像设备。

    1

    2

    3

    4

    5

    if (window.stream) {

        window.stream.getTracks().forEach(track => {

              track.stop();

        });

    }

    亲测有用,别的找了很多停止的方法都没有效果。
    2.调用后置API的方法还是无法唤醒后置摄像头,于是我找到另外一个方法,通过查看手机摄像头ID,来直接唤醒后置。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    var deviceInfoId="", //摄像头ID

        num = 0, //摄像头数量

        carema = []; //摄像头ID数组

        //在页面加载完成后获得设备ID数组

    window.onload = navigator.mediaDevices.enumerateDevices().then(gotDevices);

    function gotDevices(deviceInfos) {

            for (let i = 0; i < deviceInfos.length; ++i) {

                if (deviceInfos[i].kind === 'videoinput') {

                    carema.push(deviceInfos[i].deviceId)

                  }

            }

            deviceInfoId = carema[后置位置];

    }

                var constraints = {

                    audio: false,

                    video: {

                        deviceId: deviceInfoId,

                        //放在app里面需要下面配置一下

                        "permissions": {

                            "audio-capture": {

                                "description": "Required to capture audio using getUserMedia()"

                            },

                            "video-capture": {

                                "description": "Required to capture video using getUserMedia()"

                            }

                        }

                    }

                };

                navigator.mediaDevices.getUserMedia(constraints)

                    .then(function (stream) {

                        var video = document.getElementById('video');

                        try {

                            window.stream = stream;

                            video.srcObject = stream;

                        } catch (error) {

                            video.src = window.URL.createObjectURL(stream);

                        }

                        this.localMediaStream = stream;

                        // video.play();   这个加不加好像没有影响

                    })

                    .catch(function (err) {

                        console.log(err.name + ": " + err.message);

                    });

    如果只是一部手机可以这样,但是测试了多部手机发现摄像头数组毫无规律可循,这个方法慎用。
    如果页面上添加选择摄像设备的按钮的话,这个方法还是不错的。查看设备能调用几个摄像头链接如下:Select audio and video sources
    由于我们的项目页面不希望出现切换按钮,面对后置出现的众多BUG,最终选择放弃,使用input调用摄像头。

    1

    <input class="card_input" v-on:change="appCapture($event)" type="file" accept="image/*" capture="camera" />

    成功调用后用canvas实现成像并适应屏幕大小

    我这里的代码是取video的宽高然后复制给canvas,这样可以让canvas和video保持一致,只用给video设置宽度100%,高度调节成合适的值,就实现了适应手机屏幕。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    var video = document.getElementById('video');

              var canvas = document.getElementById('canvas'),

                  ctx = canvas.getContext('2d'),

                  CHeight = video.clientHeight, //获取屏幕大小让canvas自适应

                  CWidth = video.clientWidth;

              canvas.width = CWidth;

              canvas.height = CHeight;

              //localMediaStream 在data里定义一个{}

              if (localMediaStream) {

                  ctx.drawImage(video, 0, 0, CWidth, CHeight);

                  var dataURL = canvas.toDataURL('image/jpeg'); //dataURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA'

                  img.src = dataURL;

    video成像镜像问题

    API唤醒的前置摄像头是相反的,很不舒服很不舒服。
    之后用css处理一下给video添加 transform: rotate(180deg),可以实现反转,但是还是没有达到和手机一样的效果。
    这时候可以选择通过设备ID调用前置摄像头,前置摄像头的laval一直都是“default”,也有的是空值,但是也能实现。
    配置代码如下:

    1

    2

    3

    4

    5

    6

    7

    var constraints = window.constraints = {

                   audio: false,

                   video: {

                       sourceId: 'default',

                       facingMode:  { exact: "user" }

                   }

                 };

    完美调用自己手机的前置摄像头!!!

    完整代码如下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    <div @click='moveToCameraAVG()' v-cloak>

         <img v-if="imginfo!==''" :src="imginfo" />

         <div class="warm_title2">点击自拍一张头像</div>

    </div>

    <video id="video" class="pic_video" playsinline autoplay x5-video-player-type="h5" style='object-fit:fill'></video>

    <canvas id="canvas" class="canvas_pic" style='margin: 0;padding: 0;'></canvas>

    <div class="bottom_div">

        <div>拍照</div>

        <img src='images/pic_btn.png' class="capture-btn" @click='captureAvg' />

    </div>

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    // 头像相机

           moveToCameraAVG() {

               var self = this;

               if (navigator.mediaDevices === undefined) {

                   navigator.mediaDevices = {};

               }

               if (navigator.mediaDevices.getUserMedia === undefined) {

                   navigator.mediaDevices.getUserMedia = function (constraints) {

                       var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;

                       if (!getUserMedia) {

                           return Promise.reject(new Error('getUserMedia is not implemented in this browser'));

                       }

                       return new Promise(function (resolve, reject) {

                           getUserMedia.call(navigator, constraints, resolve, reject);

                       });

                   }

               }

               if (window.stream) {

                   window.stream.getTracks().forEach(track => {

                       track.stop();

                   });

               }

               var constraints = window.constraints = {

                   audio: false,

                   video: {

                       sourceId: 'default',

                       facingMode:  { exact: "user" }

                   }

                 };

               navigator.mediaDevices.getUserMedia(constraints)

                   .then(function (stream) {

                       var video = document.getElementById('video');

                       try {

                           window.stream = stream;

                           video.srcObject = stream;

                       } catch (error) {

                           video.src = window.URL.createObjectURL(stream);

                       }

                       self.localMediaStream = stream;

                       video.play();

                   })

                   .catch(function (err) {

                       alert(err.name + ": " + err.message);

                   });

           },

           //停止摄像机

           stopCapture: function () {

               var video = document.getElementById('video');

               if (!video.srcObject) return

               let stream = video.srcObject

               let tracks = stream.getTracks();

               tracks.forEach(track => {

                   track.stop()

               })

           },

           // 头像照片

           captureAvg() {

               var vm = this;

               var video = document.getElementById('video');

               var canvas = document.getElementById('canvas'),

                   ctx = canvas.getContext('2d'),

                   CHeight = video.clientHeight, //获取屏幕大小让canvas自适应

                   CWidth = video.clientWidth;

               canvas.width = CWidth;

               canvas.height = CHeight;

               if (vm.localMediaStream) {

                   ctx.drawImage(video, 0, 0, CWidth, CHeight);

                   var dataURL = canvas.toDataURL('image/jpeg'); //dataURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA'

                   vm.imginfo = dataURL;

                   // 停止摄像机

                   video.pause();

                   this.stopCapture();

               }

           },

    转载地址:https://www.jb51.net/html5/722394.html

    更多相关内容
  • 应项目要求,需要实现移动端app嵌入H5页面完成实人认证的功能。打开getUserMedia文档,链接如下: https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia 看上去很简单,最终却写的怀疑人生...
  • 移动设备和桌面电脑上的客户端API...最初总是移动设备上先拥有某些功能和相应的API,但慢慢的,这些API会出现在桌面电脑上。其中一个应用接口技术就是getUserMedia API,它能让应用开发者访问用户的摄像头或内置相机
  • //高亮 ->打开android手机的拍摄功能 //打开摄像头 camera = Camera.open(); //获取窗口管理器 WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); //通过窗口管理器获取屏幕 Display ...

    SurfaceView可以直接访问一个画布,SurfaceView是提供给需要直接画像素而不是使用窗体部件的应用使用的。Android图形系统中的一个重要概念是Surface,View及其子类(如TextView和Button)要画在Surface上。每个Surface创建一个Canvas对象(但属性时常改变),用来管理View在Surface上的绘图操作。

    在使用SurfaceView开发时需要注意的是,使用它绘图时,一般都是出现在最顶层。使用时还需要对其进行创建、销毁,情况改变时使用监视,这就是实现SurfaceHolder.Callback接口,如果要对被绘制的画布进行裁剪、控制大小时都需要使用SurfaceHolder来完成处理。在程序中,SurfaceHolder对象需要通过getHolder方法来获得,同时还需要addCallback方法来添加“回调函数”。

    实现SurfaceHolder.Callback代码参考:

    //在Surface大小发生改变时激发

    @Override

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    //在创建Surface时激发

    @Override

    public void surfaceCreated(SurfaceHolder holder) {

    }

    //在销毁Surface时激发

    @Override

    public void surfaceDestroyed(SurfaceHolder holder) {

    }

    好了,介绍了SurfaceView,相信你们也有一定的了解了吧。

    日常生活中,我们常用自己的手机来拍摄照片,现在我们也可以自己来开发一个android手机拍照吧。

    ->设置拍摄时时android手机全屏

    代码如下:

    Window window = getWindow();

    requestWindowFeature(Window.FEATURE_NO_TITLE);//没有标题

    window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);// 设置全屏

    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//高亮

    ->打开android手机的拍摄功能

    //打开摄像头

    camera = Camera.open();

    //获取窗口管理器

    WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

    //通过窗口管理器获取屏幕

    Display display = wm.getDefaultDisplay();

    Camera.Parameters parameters = camera.getParameters();

    parameters.setPreviewSize(display.getWidth(), display.getHeight());//设置预览照片的大小

    parameters.setPreviewFrameRate(3);//每秒3帧

    parameters.setPictureFormat(PixelFormat.JPEG);//设置照片的输出格式

    parameters.set("jpeg-quality", 85);//照片质量

    parameters.setPictureSize(display.getWidth(), display.getHeight());//设置拍出照片的大小

    camera.setParameters(parameters);

    camera.setPreviewDisplay(surfaceView.getHolder());//通过SurfaceView显示取景画面

    camera.startPreview();//开始预览

    ->点击android手机的拍摄按钮或者时方向按钮的中间OK键进行拍摄

    android手机的按钮点击事件,是按下去触发,就应该重写onKeyDown方法。

    @Override

    public boolean onKeyDown(int keyCode, KeyEvent event) {

    if(camera!=null && event.getRepeatCount()==0){

    switch (keyCode) {

    case KeyEvent.KEYCODE_SEARCH:

    camera.autoFocus(null);//自动对焦

    break;

    case KeyEvent.KEYCODE_CAMERA:

    case KeyEvent.KEYCODE_DPAD_CENTER:

    //自己去写TakePictureCallback类,该类是实现PictureCallback

    camera.takePicture(null, null, new TakePictureCallback());

    break;

    }

    }

    return true;

    }

    ->拍摄后生成图片,并保存在android手机的SD卡指定目录中。

    //生成位图,是通过BitmapFactory来获取的

    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);

    //创建一个文件来保存拍摄后的图片,这里是用系统时间来命名的

    File file = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis()+".jpg");

    FileOutputStream outStream = new FileOutputStream(file);

    //对拍摄的照片进行压缩

    bitmap.compress(CompressFormat.JPEG, 100, outStream);

    //关闭流

    outStream.close();

    最后,注意android手机的权限问题:

    附上一些代码吧:

    surfaceView =(SurfaceView)this.findViewById(R.id.surfaceView);

    surfaceView.getHolder().setFixedSize(176, 144); //设置分辨率

    /*下面设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前*/

    surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    surfaceView.getHolder().addCallback(new SurfaceCallback());

    可以了。0b1331709591d260c1c78e86d0c51c18.png

    展开全文
  • 1、禁止使用摄像头,听到这里是不是很懵逼,怎么能让手机摄像头不让用呢? 2、禁止屏幕截图,当时就是一脑袋❓ 3、禁止连接数据线传输数据 当时看到这三个要求我就是个懵逼状态,最好终于找到了解决办法。 查阅...

    今天接触一个新的项目,项目很奇葩也是第一次接触这样类型,一直以为iOS开发是不允许修改系统权限的,但是今天确实有点颠覆我的认知。

    项目类型,工厂要用户要屏蔽手机的一些功能(防止泄漏机密),功能要求:

    1、禁止使用摄像头,听到这里是不是很懵逼,让手机摄像头怎么能不让用呢?

    2、禁止屏幕截图,看到这当时就是一脑袋❓

    3、禁止连接数据线传输数据(心态崩了😭)

    当时看到这三个要求我就是个懵逼状态,在百度上找了很久也没有找到,最后在Apple官网技术支持终于找到了解决办法。

    查阅官方资料发现,苹果本身就已经考虑到这种需求,已经为我们提供了解决方案,其实接触后发现很简单。

    首先,我们要下载一个mac电脑上应用 Apple Configurator2,先给大家看一个效果图。

    下载玩这个应用,基本就完成了一大半,你要的所有需求在了都能实现。

    剩下的就是生成描述文件了,这个app就是专门生成描述文件的,下面是官网给的教程链接

    连接:https://support.apple.com/zh-cn/guide/apple-configurator-2/pmd85719196/mac

    下面看一下操作步骤:

    第一步,打开Apple Configurator,点击文件->新建->新建描述文件

    第二部,点击新建描述文件后,会进入下面的界面,我们选择通用,在这里我们就可以把想要取消的功能选择,不勾选☑️就代表禁止了

    第三部,我们选择文件->储存即可

     

    我们下载成功,就可以给iPhone、iPad等进行安装了

    iPhone安装步骤

    1、点击描述文件后,我们按照步骤选择安装。

    2、安装后我们需要信任一下,我们打开iPhone设置->通用->设备管理,找到对应的描述文件(和安装企业级app一致,需要手动信任一波)。

    3、点击这个描述文件,后我按照步骤点击安装,然后输入手机密码,最后点击安装即可,到此就完成了。

     

    安装后的效果

    1、手机系统相机,直接在手机上消失

    2、搜有使用摄像头的app的,摄像头功能全部不好用了

     

    到此安装的小伙伴可能懵了,摄像头没了怎么办呢,不要急往下看。

     

    那如何恢复摄像头呢,恢复摄像头其实很简单。我们只要找到描述文件删除即可,所有和摄像头相关的功能会全部恢复。

     

    注意⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️

    在使用过程中,测试发现,会出现描述安装完成后,描述会直接消失的现象,我们重启手机也不能出现,手机摄像头功能直接禁用,由于描述文件消失了,所以我们无法删除描述文件让摄像头恢复,那这样就没有办法解决了吗?

    其实也有的。一共有两个方法,但是都是不太友好的解决方案。

    第一个、还原手机所有设置,打开设置->通用->还原->选择抹掉所有内容和设置,选择其他还原亲测都是不好用的

    第二个、刷机(爱思助手)

     

    到这里基本就完成了想要的结果,但是我的项目中还需要判断摄像头是否禁用成功

    下面附上代码

    /// 获取手机摄像头权限 , YES 摄像头可以使用
    + (int)determineThePhoneHasTheCameraOn
    { /// 先判断摄像头硬件是否好用
        __block int type = 11111;
        if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {//相机权限
                  if ( granted ) {
                      NSLog(@"已授权");
                      type = 1;
                  }else{
                      NSLog(@"未授权");
                      type = 2;
                  }
            }];
        } else {
            //系统摄像头禁止使用,硬件上就已经禁止
            type = 0;
        }
        return type;
    }
    

     

    这两种方式都是非常不友好的,建议慎用,到此我的这个项目的解决方案就彻底完成了,到此记录一下,希望对由此要求的小伙伴游泳。

     

    展开全文
  • 现有的智能手机是可以充当Wifi摄像头来使用的,这就需要装一个App就能实现了,如果是用别的下载来APP安装用来会不会不放心呢,如果自己有能力,那就可以通过开发Android App项目过程来实现视频监控,有兴趣的来看看...

    闲置在家不用的Android手机有一两个都蒙尘了,想要把它们充分利用起来,可知道,现有的智能手机是可以充当Wifi摄像头来使用的,这就需要装一个App就能实现了,如果是用别的下载来APP安装用来会不会不放心呢,如果自己有能力,那就可以通过开发Android App项目过程来实现视频监控,有兴趣的来看看接下来的实现方案,

    要完成整个过程,至少需要两部手机,一个手机用来充当WIFI摄像头(可以开启WIFI热点),另一个手机当视频监控用的,还是建议用WIFI路由器,就看中它信号强,网络又稳定

    关于能看懂此文章的条件

    1. 会使用Android Studio开发工具
    2. 熟悉Java编程语言,开发过Android App
    3. 对WIFI路由器设置和网络信息收发报文TCPUDP原理有过了解

    1.首先,打开Android Studio开发工具,选择新建Android 项目,使用Java语言,模板就选择 Emtpy Activity,在activity_main.xml文件中做好布局,具体布局内容太多这里就不贴了,自己布局就好,拖放组件是很简单的操作,只需要放三个按钮组件即可,分别是扫描摄像头开启摄像头退出APP,其它的不重要
    在这里插入图片描述
    2. 然后,在MainActivity.class上写代码,实现能打开按钮对应的页面即可,请看如下代码,其中用到的一些类,例如DeviceInfo.class, Common.class, BaseBackActivity.class这些就不贴了,看注释,具体的请等在后面提供的项目源码里看

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //...这里省略了,只是处理了对标题栏的隐藏
            setContentView(R.layout.activity_main);
            //获取布局中的按钮组件
            Button btnScan = findViewById(R.id.button_scan);
            Button btnPreview = findViewById(R.id.button_preview);
            Button btnExit = findViewById(R.id.buttonExit);
    
            final Context context = MainActivity.this;
            //设置点击事件
            btnExit.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    MainActivity.this.finish();//退出
                }
            });
            btnScan.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                	//初始化设备信息,包括了手机的摄像头相关属性,如名称,IP,数量
                    DeviceInfo di = DeviceInfo.init(context);
                    //...省略了一些判断细节,如判断IP是否正确,判断摄像头的网络状态
                    //打开扫描局域网内的摄像头页面
                    BaseBackActivity.navigateTo(MainActivity.this, ScanActivity.class, di);
                }
            });
            btnPreview.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
    	            //初始化设备信息
                    DeviceInfo di = DeviceInfo.init(context);
                    //...省略了一些判断细节,这一步是判断摄像头的授权
                    if(Common.requestCameraPermission(MainActivity.this)){
                    	//打开WIFI摄像头的预览页面
                        BaseBackActivity.navigateTo(MainActivity.this, PreviewActivity.class, di);
                    }
                }
            });
        }
    }
    
    1. 接下来,做一个扫描摄像头页面的布局,文件是activity_scan.xml,大致布局如下图所示,运行后的效果图,就一个ListView展示列表的组件,还有标题栏上的搜索图标,那是扫描按钮
      在这里插入图片描述

    2. 接着,创建一个对应页面的类ScanActivity.class 文件后,写上代码,如下

    /**
     * 扫描摄像头窗口
     * */
    public class ScanActivity extends BaseBackActivity {
    	//定义扫描线程
        private ScanThread thread;
       	//定义列表组件
        private ListView list;
        //定义对初始化扫描的判断值
        private boolean isFirstScan = false;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_scan);
    		//省略了,处理初始化标题栏的
            //获取上一页传来的设备信息对象, getSerializable()是来自父类BaseBackActivity的方法
            DeviceInfo di = (DeviceInfo) getSerializable();
    		//创建线程时,传入设备信息对象
            thread = new ScanThread(this, di, new Handler(){
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    //处理线程传来的消息
                    switch (msg.what) {
                    	//扫描完成通知
                        case BaseThread.MESSAGE_SUCCESS:
                        {
                        	//获取扫描后的局域网内所有可用的摄像头
                            ArrayList<RemoteCamera> cameras = thread.getCameras();
                            if(cameras.isEmpty()) {
                            	//showToast方法来自父类,弹出提示
                                showToast(ScanActivity.this, "找不到可用的摄像头!");
                            }else{
                            	//更新摄像头列表显示的
                                CamerasAdapter adapter = new CamerasAdapter(ScanActivity.this, cameras);
                                list.setAdapter(adapter);
                                list.invalidate();
                                showToast(ScanActivity.this, "扫描完成!");
                            }
                        }
                            break;
                        //扫描失败,或更新状态
                        case ScanThread.MESSAGE_FAIL:
                        case ScanThread.MESSAGE_LOADING:
                            showToast(ScanActivity.this, (String) msg.obj);
                            break;
                        default:
                    }
                }
            });
    
            list = findViewById(R.id.listview);
    		//列表的点击事件
            list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                    RemoteCamera camera = thread.getCameras().get(i);
                    //打开远程摄像头连接页面,传递一个摄像头信息camera
                    navigateTo(ScanActivity.this, RemoteActivity.class, camera);
                }
            });
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            //...此处省略,加载菜单布局的
            return super.onCreateOptionsMenu(menu);
        }
    
        @Override
        public boolean onOptionsItemSelected(@NonNull MenuItem item) {
            //监听菜单按钮
            switch (item.getItemId()) {
            	//扫描图标按钮被点击
                case R.id.app_bar_search:
                    thread.startScanCamera();
                    return true;
                default:
            }
            return super.onOptionsItemSelected(item);
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            //第一次打开页面就扫描
            if (!isFirstScan) {
                thread.startScanCamera();
                isFirstScan = true;
            }
        }
    }
    
    1. 看上面就会发现,扫描的处理操作是比较耗时的,放在线程ScanThread.class里处理是合理的,这样用户操作就不会觉得卡,处理操作的方法大致讲一下
    public class ScanThread extends BaseThread {
    
        private ArrayList<RemoteCamera> cameras;
        private DeviceInfo info;
        //定义一个扫描线程
        private Thread scanThread = null;
    
        public ScanThread(Activity context, DeviceInfo info, Handler handler) {
        	//传参给父类BaseThread的构造方法,初始化
            super(context, handler);
            this.info = info;
            this.cameras = new ArrayList<RemoteCamera>();
    		//来自父类的线程,用于处理接收的
            thread = new Thread(new Runnable() {
    
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                    	//初始化端口
                        mSocket = new DatagramSocket(null);
                        //...
                        mSocket.bind(new InetSocketAddress(SenderThread.FIND_CAMERA_PORT));
                        while(!Thread.interrupted()) {
                        	//定一个空的数据报文
                            DatagramPacket pack = new DatagramPacket(new byte[1028], 1028);
                            //用空数据报文来接收数据,这时会一直等待,阻塞
                            mSocket.receive(pack);
                            //收到时,将报文里的数据转换成字符串
                            String s = new String(pack.getData(), 0, pack.getLength());
                            //在把字符串转成字符串数组,将接收到数据按照约定的协议转换一下
                            String[] datas = Common.getDeviceData(s);
                            //...此处省略,处理拿到count, 是摄像头数量,添加到cameras中
                            cameras.add(new RemoteCamera(cameras.size(), datas[0], count, datas[2]));
                            //发完成提示消息
                            showToast("扫到一个摄像头", MESSAGE_SUCCESS);
                        }
                    } catch (Exception e) {
                        showToast(e.getMessage());//遇到错误!
                    } finally {
                        cancelScan(true);
                    }
                }
    
            });
            thread.start();
        }
    
        public void startScanCamera() {
            //...次数省略判断的细节,下一步提示用户扫描中,建一个线程处理
            showToast("扫描中...", MESSAGE_LOADING);
            scanThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //...此处省略一些细节,定义data数据
                        // 定义局域网的广播地址,这样表示 *.*.*.255,
                        InetAddress cameraAddress = InetAddress.getByName(Common.getWanIP(info.getLocalIp())+"255");
                        // 将data数据封装到报文中,还有IP地址,FIND_CAMERA_PORT 是 30000
                        DatagramPacket pack = new DatagramPacket(data, data.length, cameraAddress, FIND_CAMERA_PORT);
                        //将数据报文发送到广播地址,只要是连接到此局域网内的所有设备开放的30000端口都会收到该广播报文
                        mSocket.send(pack);
                    } catch (Exception e) {
                        showToast(e.getMessage());//遇到错误!
                    } finally {
                    	//处理完后取消操作
                        cancelScan(false);
                    }
                }
            });
            scanThread.start();
        }
    	//判断是否在扫描
        public boolean isScaning() {
            return scanThread!=null;
        }
    
        public ArrayList<RemoteCamera> getCameras() {
            return cameras;
        }
    	//取消扫描
        public void cancelScan(boolean isCancelAll) {
            if(isCancelAll) {
            	//处理来自父类的方法
                cancelThread();
            }
            if (isScaning()) {
                scanThread.interrupt();
                scanThread = null;
            }
        }
    }
    

    💡小提示
    注意到创建的页面都有继承BaseBackActivity.class类,创建的线程都有继承类BaseThread.class,具体怎么写的,这里不详细讲了,那说下它的作用,它是相当于一个可以复用的类吧,类似模板,可以这样理解,稍微能明白,实现不会复杂

    1. 把需要添加的权限都写上,在AndroidManifest.xml文件中,添加如下关键的代码
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="...">
    
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.CAMERA" />
    
        <!-- 此处省略... -->
    </manifest>
    
    1. 接下来,打开视频监控,也就是远程摄像头预览的页面,大致布局如下图所示,文件是activity_remote.xml,放一个展示状态的TextView组件,还有一个预览画面的SurfaceView组件放在中间,宽高分别是固定的320dp,240dp
      在这里插入图片描述

    💡小提示
    有没有注意到,看视频监控上的状态栏,网络保持在23.3K/s每秒,这已经是一帧一帧的传输图像了,图像是320x240分辨率的,传输量会不会低了,可能有点卡吧,跟网络传输延迟有关的

    1. 接着,创建一个对应页面的类,在RemoteActivity.class 文件里,写上代码,如下
    public class RemoteActivity extends BaseBackActivity {
    	//定义一个网络接收的线程
        private ReceiveThread thread;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_remote);
            //...省略了,处理初始化标题栏的
            //获取上一页传来的设备信息对象, getSerializable()是来自父类BaseBackActivity的方法
            RemoteCamera remote = (RemoteCamera) getSerializable();
    		//从布局中获取组件
            SurfaceView view = findViewById(R.id.surfaceView2);
            final TextView showState = findViewById(R.id.textView_state2);
    		//将远程设备信息设置到标题栏上
            setTitle("远程摄像头:"+remote.toString());
            //先获取焦点,然后设置屏幕长亮
            view.setFocusable(true);
            view.setKeepScreenOn(true);
    		//建立一个接收线程,传一个远程设备信息对象,还有预览组件的holder用于更新画面
            thread = new ReceiveThread(this, new Handler(){
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    //...处理线程发来的消息提示
                }
            }, remote, view.getHolder());
        }
    
        @Override
        protected void onPostResume() {
            super.onPostResume();
            //让线程开始接收工作
            thread.startReceive();
        }
    
        @Override
        protected void onDestroy() {
    	    //当前页面关闭时,让线程结束工作
            thread.cancelReceive();
            super.onDestroy();
        }
    }
    
    1. 看上一步就会发现,关键的处理接收方法都放在线程ReceiveThread.class里,那是比较耗时的操作,大致讲一下
    public class ReceiveThread extends BaseThread {
    
        private RemoteCamera remote;
        private SurfaceHolder holder;
    
        public ReceiveThread(Activity context, Handler handler, RemoteCamera remote, SurfaceHolder holder) {
            super(context, handler);
            this.remote = remote;
            this.holder = holder;
        }
    
        public void startReceive() {
            if (thread!=null) {
                return;
            }
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    String errMsg = "未知错误";
                    //...
                    try {
                        //...
                        if (mSocket==null) {
                            mSocket = new DatagramSocket(null);
                            //用开放30000端口来接收  FIND_CAMERA_PORT
                            mSocket.bind(new InetSocketAddress(FIND_CAMERA_PORT));
                            //...
                            showToast("连接中...", MESSAGE_UPDATE_STATE);
                            //发送请求接收下一帧图片
                            sendRet(mSocket, baos);
                            //...
                            showToast("等待接收...", MESSAGE_UPDATE_STATE);
                            while(mSocket!=null) {
                                //...定义空的数据报文packet
                                try {
                                    //接收中,等待,此处阻塞
                                    mSocket.receive(packet);
                                }catch (SocketTimeoutException te) {
                                    showToast("连接超时..."+getLocalDateTime(), MESSAGE_UPDATE_STATE);
                                    //再次发送请求
                                    sendRet(mSocket, baos);
                                    //...继续循环,重新接收
                                    continue;
                                }
                                //判断一帧图片baos数据是否接收完成
                                if(packet.getLength() == endlen) {
                                    String end = new String(packet.getData(), 0, endlen);
                                    if(end.startsWith(PACKET_END)) {
                                        //...获取time时间数据,下一步更新显示
                                        updateViewDisplay(baos, time);
                                        //设置接收下一帧等待时长,至少每100ms接收下一帧,可以设置更小,让视频看着更流畅
                                        Thread.sleep(100);
                                        baos.flush();
                                        //再次发送请求
                                        sendRet(mSocket, baos);
                                        //...
                                        showToast("接收中..."+getLocalDateTime(), MESSAGE_UPDATE_STATE);
                                        continue;
                                    }
                                }
                                //接收一帧图片数据流
                                baos.write(packet.getData(), 0, packet.getLength());
                            }
                        }
                    } catch (Exception e) {
                        errMsg = e.getMessage();//断开连接!;
                    } finally {
                        //...
                        cancelThread(errMsg);
                    }
                }
            });
            thread.start();
        }
    
        private void sendRet(DatagramSocket dSocket, ByteArrayOutputStream baos) throws IOException, Exception {
            //省略...处理发送接收下一帧图片请求
            InetAddress address = InetAddress.getByName(remote.getIp());
            DatagramPacket pack = new DatagramPacket(data, data.length, address, FIND_CAMERA_PORT);
            dSocket.send(pack);
            //...
        }
    
        private void updateViewDisplay(final ByteArrayOutputStream baos, final String time) {
            //省略...处理转换图片
            context.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    //...锁定中,从组件中获取画布Canvas
                    Canvas canvas = holder.lockCanvas(null);
                    //...将图片画组件中,让用户可以看到
                    canvas.drawBitmap(bitmap2, 0, 0, null);
                    //...画上时间
                    canvas.drawText(time, 20, 30, p);
                    //解除锁定
                    holder.unlockCanvasAndPost(canvas);
                    //...
                }
    
            });
        }
    
        public void cancelReceive() {
            cancelThread();
        }
    }
    
    1. 接下来,做一个开启摄像头页面的布局,文件是activity_preview.xml,大致布局如下图所示,是运行后的效果图,同上面讲过,跟远程摄像头页面布局那个是一样的,现在是有多放了一个选择摄像头的下拉框组件Spinner
      在这里插入图片描述

    2. 接着,创建一个对应的页面类PreviewActivity.class文件,写上代码,参考如下

    public class PreviewActivity extends BaseBackActivity {
    
        private Camera camera = null;
        private SurfaceHolder holder;
        private Spinner seletep;
        private int selectCameraId = 0;
        //定义发送图片的线程
        private SenderThread thread;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_preview);
            //...
            final DeviceInfo info = (DeviceInfo) getSerializable();
            //...获取布局中的组件,SurfaceView是绘制组件
            final SurfaceView view = findViewById(R.id.surfaceView);
            seletep = findViewById(R.id.spinner);
            final TextView stateView = findViewById(R.id.textView_state);
    		//创建一个发送图片的线程,传入设备信息对象
            thread = new SenderThread(this, info, new Handler(){
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    //...处理线程发来的消息
                }
            });
    
            String localIp = thread.getLocalIp();
            String name = thread.getDeviceName();
            setTitle("设备名:"+name+ ", 局域网IP:"+localIp);
    
            seletep.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                @Override
                public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                    //...切换摄像头
                }
            });
    
            //先获取焦点
            view.setFocusable(true);
            //然后设置屏幕长亮
            view.setKeepScreenOn(true);
    
            holder = view.getHolder();
            holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            holder.addCallback(new SurfaceHolder.Callback() {
    
                @Override
                public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
                    //绘制组件创建,准备摄像头
                    int cameraCount = Camera.getNumberOfCameras();
                    //...
                    thread.setCameraCount(cameraCount);
                }
    
                @Override
                public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                    //绘制组件大小改变,重置摄像头
                    //...
                    openCamera();
                }
    
                @Override
                public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
                    thread.cancelThread();
                    //绘制组件销毁,释放摄像头资源
                    closeCamera();
                }
            });
        }
        
        @Override
        protected void onPostResume() {
            super.onPostResume();
            //可被局域网内发现摄像头
            thread.canFind(true);
        }
    
        private void openCamera() {
            //...
            try{
                camera = Camera.open(selectCameraId);
                Camera.Parameters params = camera.getParameters();
                List<Camera.Size> sizes = params.getSupportedPictureSizes();
                //图像大小
                final int PICTURE_WIDTH = 320, PICTURE_HEIGHT = 240;
                Camera.Size size = null;
                //省略细节...查找摄像头配置参数,赋值图像大小
                params.setPreviewSize(size.width, size.height);
                params.setPreviewFrameRate(20);
                params.setPictureFormat(PixelFormat.YCbCr_420_SP);
                camera.setParameters(params);
                //讲摄像头的图像设置到绘制组件中
                camera.setPreviewDisplay(holder);
                camera.setPreviewCallback(new Camera.PreviewCallback(){
    
                    @Override
                    public void onPreviewFrame(byte[] bytes, Camera camera) {
                        //...省略细节...处理摄像头传来的图片,将bytes转换成image,当然可以不转换,直接发送更高效吧
                        //交给线程去发送
                        thread.setSendCameraImage(image);
                    }
                });
                camera.startPreview();
            } catch (Exception e) {
                showToast(this, "开启摄像头遇到了错误!");
            }
        }
    
        void closeCamera() {
            //...
            camera.stopPreview();
            camera.setPreviewCallback(null);
            camera.release();
            camera = null;
        }
    
    }
    
    1. 看上一步就会发现,关键的处理发送方法都放在线程SenderThread.class里,那也是比较耗时的操作,大致讲一下
    public class SenderThread extends BaseThread {
        private int cameraCount = 0;
        public boolean isSending = false;
        private DeviceInfo info;
        private YuvImage image = null;
    
        public SenderThread(Activity context, DeviceInfo info, Handler handler){
            super(context, handler);
            this.info = info;
        }
        //...
        public void setCameraCount(int cameraCount) {
            this.cameraCount = cameraCount;
        }
    
        public void canFind(boolean isFind) {
            if(isFind==true && thread==null) {
                this.thread = new Thread(new Runnable() {
    
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        String errMsg = "未知错误";
                        try {
                            mSocket = new DatagramSocket(null);
                            //...绑定开放的30000端口
                            mSocket.bind(new InetSocketAddress(FIND_CAMERA_PORT));
                            do {
                                //...
                                DatagramPacket pack = new DatagramPacket(new byte[1028], 1028);
                                try {
                                	//接收数据,等待中,会阻塞
                                    mSocket.receive(pack);
                                }catch (Exception e){
                                    e.printStackTrace();
                                    throw e;
                                }
                                //获取发来请求的设备地址
                                SocketAddress sendAddress = pack.getSocketAddress();
                                //...
                                ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(pack.getData()));
                                Integer code = (Integer) ois.readObject();
                                //...
                                switch (code){
                                    case GET_CAMERA_IP:
                                    {
                                        //...判断请求1,将摄像头的数据包装成data,封装在报文中回发过去
                                        DatagramPacket packet = new DatagramPacket(data, data.length, sendAddress);
                                        mSocket.send(packet);
                                    }
                                    break;
                                    case RET_CAMERA_IP:
                                    {
                                        isSending = true;
                                        //...判断请求2,处理一帧图片回发过去
                                        sendImage(image, sendAddress, sendTime);
                                        isSending = false;
                                    }
                                    break;
                                    default:
                                }
                            }while (!thread.isInterrupted() && mSocket!=null);
                        } catch (Exception e) {
                            errMsg = e.getMessage();
                        } finally {
                            cancelThread(errMsg);
                        }
                    }
    
                });
                this.thread.start();
            }else{
                cancelThread();
            }
        }
    
        private void sendImage(YuvImage image, SocketAddress sendAddress, String sendTime) throws Exception {
    	    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
    	    //将图片转换成数据流,压缩了图片就变小,减少传输量
            image.compressToJpeg(new Rect(0,0, image.getWidth(), image.getHeight()), 80, outStream);
            //...定义缓存大小,转换图片数据流
            byte[] buffer = new byte[1024];
            ByteArrayInputStream bais = new ByteArrayInputStream(outStream.toByteArray());
            try{
                int len;
                DatagramPacket pack;
                //...读取图片数据流,并拆分几次分发出去
                while((len = bais.read(buffer, 0, buffer.length)) != -1) {
                    pack = new DatagramPacket(buffer, len, sendAddress);
                    mSocket.send(pack);
                }
                //分发完成后,最后发一个结束信息,告诉接收方这一帧图片已发完
                byte[] end = (PACKET_END+sendTime).getBytes();
                pack = new DatagramPacket(end, end.length, sendAddress);
                mSocket.send(pack);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                bais.close();
            }
        }
    
        public void setSendCameraImage(YuvImage image) {
            if (isSending()) {
                return;
            }
            this.image = image;
        }
    
        public boolean isSending() {
            return isSending;
        }
    }
    
    1. 接下来,剩下的完善细节不再是重点就不讲了,有个清晰的思路就好,需要自己完善一下细节,到最后能将Android项目顺利编译运行起来,接下来,做个实验测试几遍,准备一个WIFI路由器,用一个网线连接上电脑上(或者WIFI连接也可以),只要能登录路由器的控制页面,找到如下图所示,看看是否已取消勾选开启AP隔离,再点保存就可以了,取消AP隔离这样能让局域网的各种设备可互相连通,不需要连接到互联网
      在这里插入图片描述

    💡 小提示

    • 为了安全起见,路由器中不建议对访客开放的WIFI网络中禁用AP隔离哦,
    • 有些路由器中有访客WIFI开关,这个是没有AP隔离可禁用的
    • 没有WIFI路由器的话,可用其中的一个手机开启WIFI热点功能代替,然后安装上面开发的APP,点击开启摄像头按钮就可以了,其它的手机都能扫描到这个摄像头的
    1. 不知不觉发现写了很多,就讲到这里了,关于此Android项目源代码就在这里点此查看,在里面可找到,请放心下载,感谢耐心看完,若觉得此文章很有帮助,请点个赞❤再走,TA远方在此谢过~

    在这里插入图片描述

    展开全文
  • 这款手机首次搭载了屏下摄像头,完全消除了刘海和边框。APEX 2020 最大的亮点就是这颗摄像头,在一块6.45英寸的120度一体屏上,屏下摄像头的位置,屏幕透光率是其他区域的6倍,加上1600万像素,能够实现屏下成像的...
  • 在当下,手机自身的拍照功能已经非常强大,除非对微距、长焦、大光圈造虚化有很高的要求,否则手机是能满足业务需求。尤其让我们想要得到的,是手机自身拍照App的美颜、美化成果。以上的技术方式都无法得到手机App的...
  • 1,首先来说打开摄像头 html部分 <div style="height:300px;width:600px;"> <video id="video" width="300px" height="300px" autoplay="autoplay"> </video> <canvas class="zp" id=...
  • JS 能不能调用摄像头并拍照可以调用,不过适合HTML5,浏览器版本也要高点,有些低版本的估计不支持 var promisifiedOldGUM = function(constraints) { // 第一个拿到getUserMedia,如果存在 var getUserMedia = ...
  • 在日常开发中可能会遇到需要调用摄像头拍照的功能,下面为大家讲解一下在react项目当中如何实现拍照的功能。 一、如何调用摄像头 调用摄像头只需要用到JS原生的api就可以了navigator.mediaDevices.getUserMedia,...
  • 页面结构效果预览 打开摄像头关闭摄像头拍照js部分export default {data() {return {videoWidth: 250,videoHeight: 350,imgSrc: "",thisCancas: null,thisContext: null,thisVideo: null,openVideo:false};...
  • 手机qq摄像头背景主要为了走路的时候玩手机防止摔倒看不到路,这种功能对于小编来说比较打酱油,没什么多大用,只是为了体验新功能的童鞋们开启之后却不知道该如何关闭。接下来小编将为大家带来qq摄像头背景关闭方法...
  • getUserMedia API简介HTML5的getUserMedia API为用户提供访问硬件设备媒体(摄像头、视频、音频、地理位置等)的接口,基于该接口,开发者可以在不依赖任何浏览器插件的条件下访问硬件媒体设备。getUserMedia API最初...
  • HTML5如何通过navigator.mediaDevices.getUserMedia调用手机摄像头发布时间:2021-05-23 15:03:11来源:亿速云阅读:76作者:小新这篇文章主要介绍了HTML5如何通过navigator.mediaDevices.getUserMedia调用手机...
  • 用户在使用APP时,如果首次进入用摄像头的地方,手机会提示是否允许该应用使用摄像头。有些用户小手一抖、或者压根就不想开启摄像头,咔擦,就给你关了,那好了。下回再进入该功能,就会出现APP一片黑,或者崩溃的...
  • /** * 启动系统相机 * * @param view */ public void takePhoto(View view) { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);... // 关闭此输出流并释放与此流有关的所有系统资源 bos.close(); }
  • HTML5,调用手机前置摄像头,调用手机后置摄像头
  • OpenCV 调用 Android智能手机摄像头

    千次阅读 2019-03-05 19:14:48
    关闭摄像头并停止预览 当一帧一帧的图像数据通过callback从摄像头传递过来时,图像数据将会通过OpenCV转换为RGBA32格式 CvCameraViewListener2 这个是Activity需要实现的接口类 implement这个接口类后,...
  • VUE实现调用摄像头和拍照功能

    千次阅读 2022-01-10 16:39:26
    import Vue from 'vue' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import App from './App.vue' Vue.use(ElementUI) new Vue({ el: '...效果预览 打开摄像头 关闭摄像头 拍照
  • 原生js调用手机拍照功能

    千次阅读 2019-12-11 15:44:09
    原生js调用手机拍照功能功能主要用于混合App开发时手机拍照上传图片的功能! 话不多说直接上代码! 注:此代码在浏览器中运行时会报错,必须在打开调试在手机上运行才可以! // html部分 //主要就是给他一个...
  • 所以就觉得前置摄像头去掉闪光灯功能 Camera2 app的路径为: vendor\sprd\platform\packages\apps\DreamCamera2 而PhotoModule.java作为拍照的Module类 就先从这里寻找解决入口 vendor/sprd/platform/packages/apps/...
  • 虚拟机的朋友别忘了打开摄像头连接 在虚拟机关闭的情况下打开工具栏 虚拟机>设置 重新开机 接下来提示无脑确定。 再运行上面的代码 ,摄像头应该就能开了。(插一嘴 ctrl+c中止不会有人不知道吧 也可以用可视化qt...
  • 用原生的input调起手机摄像头 <input type="file" class="upImage" accept="image/*" @change="onFileChange" /> input的 capture=“camera” 这个属性 可以调起手机摄像头 但是在安卓加上capture 可以同时...
  • js获取摄像头权限实现拍照功能

    千次阅读 2020-12-11 11:26:09
    首先说一下js打开...使用stream.getTracks()[0].stop()关闭摄像头。 1.开启摄像头 开启摄像头主要有两种方法,一个是旧方法window.navigator.getUserMedia()方法,还有一个就是新方法navigator.mediaDevices.get
  • 手机摄像头工作原理

    千次阅读 2013-12-14 15:04:40
    1 手机摄像头概述 1.1 手机摄像头概述 手机的数码相机功能指的是手机是否可以通过内置或是外接的数码相机进行拍摄静态图片或短片拍摄,作为手机的一项新的附加功能,手机的数码相机功能得到了迅速的发展。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,842
精华内容 4,336
关键字:

关闭手机摄像头功能