简介:面向需要为网站快速接入摄像头拍照上传功能的开发者和Flash学习者,这套源代码提供了从浏览器拍摄、页面交互到后台对接的完整参考方案。源码基于Flash实现,默认拍照分辨率为320X240,并在fla源文件中保留了完整可编辑逻辑,开发者可自行修改分辨率、调整上传地址与参数,也能在此基础上扩展更多功能。资源以zip压缩包形式发布,共包含5个文件,整体体积仅127KB,其中html文件负责页面调用与测试入口,swf为编译后的运行组件,fla为Flash源工程文件,swd用于调试排错,各文件分工清晰,便于按需查看。目前已有247人学习该资源,适合希望掌握Flash摄像头调用原理、又不想从零搭建交互流程的初中级开发者。通过学习这份资源,读者能够梳理清楚Flash与JavaScript、后端语言之间的数据交互流程,并将同一套思路迁移到asp.net、php、java等不同平台;完整的源文件也为二次开发提供了现成模板,无论是快速验证功能还是集成到实际项目,都能显著提升开发效率,尤其适合需要跨语言对接的项目场景。
1. Flash摄像头拍照上传源代码:一个组件打通三端老项目的关键
Flash摄像头拍照上传源代码,听起来是老掉牙的技术,但内网考勤、柜面拍照、身份核验这类场景里,每天仍有大量业务系统靠它跑着。拆完这份资源,核心感受是:Flash端调起摄像头很简单,真正的难点在把一帧JPEG干净地送到asp.net、PHP或Java后端,并且让每个后端都按约定收下来。它能解决的就是“前端拍照+后端落盘”这条链路的完整对接问题,适合做存量系统维护、需要快速给老业务加拍照能力的从业者。先说个反直觉的结论:架构本身很老,但它的上传协议设计思路,今天接手依然能直接用。
2. 采集端搭建:从getCamera()到画面渲染的完整链路
2.1 实例化与安全弹窗:getCamera()的最小可用写法
在AS3里,调起摄像头只需要一行:Camera.getCamera()。但这一行之后的处理直接决定采集链路能不能走通。先看完整的最小可用代码:
// 获取默认摄像头实例,没有摄像头时返回null var camera:Camera = Camera.getCamera(); if (camera == null) { trace("未检测到摄像头设备"); return; } // 非静默模式:需要用户明确授权 camera.setMode(640, 480, 15); // 带宽0表示不限制,画质取最高 camera.setQuality(0, 100); // 将摄像头画面绑定到Video对象 var video:Video = new Video(640, 480); video.attachCamera(camera); addChild(video);这段代码是采集链路的地基。getCamera()不带参数时获取系统默认摄像头,多摄像头环境下需要用索引参数指定设备;setMode(640, 480, 15)的意思是目标宽度640、目标高度480、目标帧率15帧每秒。注意“目标”二字——驱动不一定满足这个组合,设置后要读camera.width和camera.height确认实际值。setQuality(0, 100)的第一个参数是带宽上限,0表示不限;第二个参数是画质质量,100为最高。这里有个陷阱:两个参数的组合规则是“带宽优先”,如果带宽设得过小,画质会被强制压下来。多数项目里我会把带宽设成0,让画质参数单独生效。
之后是安全弹窗的问题。Flash Player首次访问摄像头时,会弹出授权对话框,而且这个弹窗只在非静默模式下出现,必须在用户交互链路中触发。常见的翻车情况是:页面加载就立刻调用Camera.getCamera(),弹窗还没出现,摄像头已经被标记为“拒绝访问”。实际做法是,把摄像头开启动作绑定到一个按钮的click事件上,用户点击后异步等待授权结果。还有一点:摄像头设备被占用时,getCamera()可能返回非null实例,但画面黑屏、currentFPS恒为0。这种状态下不要反复销毁重建Video,先检查系统层面是否有其他应用占用摄像头,关掉后刷新页面再看。
2.2 权限状态监听:StatusEvent与画面就绪判定
授权结果不是同步返回的,需要监听StatusEvent.STATUS事件。这个监听必须在setMode之前注册,否则会漏掉授权结果。
// 监听摄像头状态变化 camera.addEventListener(StatusEvent.STATUS, onCameraStatus); function onCameraStatus(event:StatusEvent):void { if (event.code == "Camera.Muted") { trace("用户拒绝了摄像头授权"); // 这里要给出UI提示,而不是继续黑屏等待 } else if (event.code == "Camera.Unmuted") { trace("摄像头授权通过"); // 此时再调用attachCamera才有画面 } }授权通过后,画面并不会立刻出现在Video上,要等驱动真正把数据流推上来。我用currentFPS这个属性做判定,它比任何自定义标志位都可靠:
addEventListener(Event.ENTER_FRAME, checkFps); function checkFps(event:Event):void { if (camera.currentFPS > 0) { trace("摄像头画面已就绪"); removeEventListener(Event.ENTER_FRAME, checkFps); } }camera.currentFPS在画面未建立时为0,建立后变成实际的帧率值。授权后5秒内如果它仍为0,基本可以判定设备被占用或驱动异常。这里有一个容易被忽略的细节:监听StatusEvent.STATUS和ENTER_FRAME两个事件时,建议使用弱引用,避免对象长期无法被垃圾回收。在反复进出的拍照页面里,这个细节会在几十次操作后变成明显的内存问题。
2.3 分辨率与显示比例:协商结果要回读,不能写死
setMode三参数之间其实是协商关系,在某些老驱动上得不到640x480,会退到320x240。取到的画面与Video宽高不匹配时,画面会被拉伸变形。两个处理思路:
// 思路一:以摄像头实际输出尺寸为基准,动态调整Video camera.setMode(640, 480, 15); var realW:int = camera.width; var realH:int = camera.height; video.width = realW; video.height = realH;第二种思路:固定Video尺寸为业务需要的显示尺寸,用等比缩放适配:
// 思路二:固定显示宽度,等比缩放高度 var targetWidth:Number = 320; var scale:Number = targetWidth / camera.width; video.scaleX = scale; video.scaleY = scale;这里必须等比缩放,否则拍出来的人脸是扁的,后端的识别系统拿到这种图直接翻车。帧率的选择逻辑更实际:做拍照场景,15帧足够,24到30帧会让CPU占用率明显升高,尤其在老工控机上。画质参数建议取100,因为拍照质量优先;但画质100意味着JPEG编码耗时变大,如果拍照按钮反馈有延迟,降到90肉眼几乎无差别。还有一种情况:一些老设备在640x480下只有10帧,这种情况下不要强行把帧率参数设高,驱动会忽略并退到更低分辨率,反而让画面变模糊。
3. 拍照与JPEG编码:截帧时机、质量参数与内存释放
3.1 截帧时机:为什么画面就绪后才能draw
拍照的本质是从摄像头画面里截一帧,这帧数据的载体是BitmapData:
var bitmapData:BitmapData = new BitmapData(320, 240, false, 0xFF000000); bitmapData.draw(video);BitmapData.draw(video)直接截取Video当前帧。第一个要点:draw必须在画面就绪后执行,如果Video里还没内容,draw出来是一张纯黑图像。第2章提到的currentFPS判定在这里就派上用场——只有currentFPS大于0之后截图才是有效帧。第二个要点:BitmapData的尺寸可以比Video小,这是性能关键点。
// 按输出尺寸创建BitmapData,draw时用Matrix做等比缩放 var outputW:int = 320; var outputH:int = 240; var bitmapData:BitmapData = new BitmapData(outputW, outputH, false, 0xFFFFFFFF); var matrix:Matrix = new Matrix(); matrix.scale(outputW / video.width, outputH / video.height); bitmapData.draw(video, matrix, null, null, null, true);Matrix的scale按video实际宽高比换算,保证截出来的图不变形。draw的第六个参数smoothing设为true,能让缩放后的画面边缘更平滑,对后续的人工审核或识别都有帮助。第三个要点:640x480的BitmapData占用约1.2MB内存,320x240只有0.3MB,内网老机器上截大图再缩小的开销远高于直接截小图,所以实践上直接按输出尺寸创建。另外,拍照场景用不透明背景(第二个参数为false),如果设成true带透明通道,JPEG编码器不支持alpha,会产生异常黑色边缘。
3.2 JPEG编码:90档是性价比拐点
BitmapData是原始像素数据,一帧320x240约0.3MB,不能直接上传,必须编码成JPEG字节流。AS3本身不直接提供JPEG编码能力,需要引入一份封装好的JPGEncoder工具类,主流as3项目里通常自带,抄过来就能用。
var encoder:JPGEncoder = new JPGEncoder(90); var bytes:ByteArray = encoder.encode(bitmapData); // 此时bytes就是完整的JPEG文件数据,可直接用于上传quality取90还是100?我的经验是:90是性价比最高的档位。100档编码时间几乎翻倍、体积增加30%以上,但在320x240这种小尺寸图上,肉眼根本分不出来。还有一点容易忽视:JPGEncoder.encode()是同步操作,在主线程执行,编码期间整个Flash界面会卡住。320x240在90质量下大约几十毫秒,能接受;如果截到1280x720以上,卡顿体感就明显了。遇到这种场景只有两个选择:用异步版本的编码器,或者把截图尺寸降下来。我一般会做一层封装,让编码器实例全局复用,不要每次拍照都new JPGEncoder,省掉重复分配内部缓冲区的开销。
3.3 回显预览:复用同一份字节流
拍完照通常要回显给用户确认。做法是:用Loader加载已经编码好的bytes,在显示列表里替换掉Video:
var loader:Loader = new Loader(); loader.loadBytes(bytes); // 拍照确认后,把Video从显示列表移除,换成Loader removeChild(video); addChild(loader);注意这里用的是同一份bytes,没有二次编码。很多新手会先把BitmapData转成Bitmap再截一次,白白损失画质还多耗一遍内存。核心原则:从截图到上传,只编码一次,后续所有组件共享同一份ByteArray。这个原则在遇到大量并发拍照的业务场景时尤其重要,重复编码不仅拖慢响应,还会让内存峰值翻倍。
3.4 内存释放:dispose与unload的调用顺序
拍照循环里最容易出问题的就是内存。每次draw就new一个BitmapData,如果不释放,几十次拍照后内存直接爆掉:
// 上传完成后,确认没有其他组件引用这张图再释放 bitmapData.dispose(); bytes.clear(); // 移除预览Loader并卸载解码图像 removeChild(loader); loader.unload(); loader = null;dispose()调用后,这个BitmapData就彻底失效了,任何再读它的操作都会抛异常。正确的顺序是:先完成编码和预览,再dispose。这里有一个很隐蔽的坑:Loader.loadBytes(bytes)内部可能会对bytes做引用,如果在loadBytes之后立刻bytes.clear(),某些情况下Loader会拿到空数据,预览出来一张破图。稳妥做法是等Loader的COMPLETE事件触发后再清理:
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function():void { // Loader已持有解码后的数据,源bytes可以安全释放 bytes.clear(); });loader.unload()会释放Loader占用的解码图像内存,比单纯从显示列表移除更彻底。如果预览后还要切换回摄像头实时画面,记得重新attachCamera,否则Video对象会被切断。
4. 上传通道设计:multipart与三类后端的对接差异
4.1 三种上传方案对比:为什么multipart是通用选择
Flash端发文件,业界实际出现过三种方案:
| 方案 | 数据格式 | 携带业务字段 | 后端适配 | 适用场景 |
|---|---|---|---|---|
| application/octet-stream | 纯JPEG二进制流 | 否 | 需自研解析 | 最简单场景 |
| Base64进URLVariables | 文本 | 是 | 需Base64解码 | 数据量小 |
| multipart/form-data | 二进制+文本混合 | 是 | 各语言原生支持 | 推荐 |
有人会把BitmapData的字节toString成Base64再放进URLVariables里传——能通,但体积膨胀33%,而且后端要额外写Base64解码,多一层坑。application/octet-stream直传整个请求体的方案体量更小,但无法同时传业务字段,且不同Flash Player版本在发送纯二进制流时header行为有差异。multipart/form-data是各语言后端原生支持的格式,业务字段和文件字段可以混在同一个请求体里,是兼容性最好的选型。
4.2 手动构造multipart请求体
AS3原生API不直接提供“ByteArray转multipart”的能力,需要手动拼请求体,这是整个上传链路里最需要细心的一段:
// 1. 生成边界字符串,注意与请求头保持一致 var boundary:String = "----FlashBoundary" + new Date().time.toString(16); // 2. 构造请求体 var postData:ByteArray = new ByteArray(); // 业务字段:userId postData.writeUTFBytes("--" + boundary + "\r\n"); postData.writeUTFBytes("Content-Disposition: form-data; name=\"userId\"\r\n\r\n"); postData.writeUTFBytes("u_10001\r\n"); // 文件字段:file postData.writeUTFBytes("--" + boundary + "\r\n"); postData.writeUTFBytes("Content-Disposition: form-data; name=\"file\"; filename=\"capture.jpg\"\r\n"); postData.writeUTFBytes("Content-Type: image/jpeg\r\n\r\n"); // 写入JPEG二进制数据 postData.writeBytes(bytes); postData.writeUTFBytes("\r\n--" + boundary + "--\r\n"); // 3. 配置URLRequest并发送 var request:URLRequest = new URLRequest(uploadURL); request.method = URLRequestMethod.POST; request.contentType = "multipart/form-data; boundary=" + boundary; request.data = postData; var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE, onUploadComplete); loader.load(request);几个必须严格的细节:boundary字符串在请求头与请求体中出现次数和格式必须完全一致;文件字段的name="file"是后端取文件的键名,改了这个值后端所有代码都要跟着改;所有字符串行以\r\n结尾,整个请求体以--boundary--收尾。任何一处不对,后端解析multipart就会失败,表现是文件字段拿不到。writeUTFBytes和writeBytes的混用尤其要小心:文本部分用writeUTFBytes,二进制JPEG数据用writeBytes,文件数据后面补一个\r\n再写结束符。
4.3 asp.net接收端:Request.Files是标准入口
asp.net(含Web Forms和MVC)接收multipart的写法比较统一:
// 处理Upload请求的Action public ActionResult Upload() { var file = Request.Files["file"]; if (file == null || file.ContentLength == 0) { return Content("{\"code\":1,\"msg\":\"未收到文件\"}", "application/json"); } var ext = Path.GetExtension(file.FileName).ToLower(); if (ext != ".jpg" && ext != ".jpeg") { return Content("{\"code\":1,\"msg\":\"仅支持jpg\"}", "application/json"); } var savePath = Server.MapPath("~/uploads/" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + ext); file.SaveAs(savePath); return Content("{\"code\":0,\"msg\":\"ok\",\"url\":\"/uploads/" + Path.GetFileName(savePath) + "\"}", "application/json"); }Request.Files["file"]的键名必须与Flash端构造的name="file"完全一致。保存路径要有按日期隔离的目录,避免所有文件堆在同一个文件夹里,后面清理时无从下手。返回JSON的Content-Type要显式指定application/json,否则asp.net默认返回text/html,虽然Flash端解析不受影响,但浏览器直接调试时看到的是乱码。还有一点:老项目如果用Web Forms的Request.Files,在IIS上要考虑上传大小限制,maxAllowedContentLength默认约30MB,JPEG文件远低于这个值,但保险起见还是确认一下。
4.4 PHP接收端:注意两个配置项
PHP接收multipart最简洁:
<?php // 键名与Flash端name字段对应 if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) { echo json_encode(['code' => 1, 'msg' => '文件上传失败']); exit; } $tmp = $_FILES['file']['tmp_name']; $name = $_FILES['file']['name']; $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); if (!in_array($ext, ['jpg', 'jpeg'])) { echo json_encode(['code' => 1, 'msg' => '仅支持jpg']); exit; } $saveName = 'camera_' . date('YmdHis') . '_' . mt_rand(1000, 9999) . '.' . $ext; if (!move_uploaded_file($tmp, './uploads/' . $saveName)) { echo json_encode(['code' => 1, 'msg' => '保存失败']); exit; } echo json_encode(['code' => 0, 'msg' => 'ok', 'url' => '/uploads/' . $saveName]);PHP这边最常踩的坑是post_max_size和upload_max_filesize两个配置。摄像头拍出的JPEG一般只有几十KB,默认2MB够用,但如果业务字段里带了Base64字符串就会超限,所以尽量别在表单里塞大字段。另一个细节:move_uploaded_file是专门处理上传临时文件的函数,不要用copy或rename替代,临时目录的权限和安全策略只有这个函数能正确处理。
4.5 Java接收端:Servlet 3.1+的标准写法
Java端使用Servlet 3.1+的Part接口:
@MultipartConfig(maxFileSize = 1024 * 1024 * 5, maxRequestSize = 1024 * 1024 * 10) public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 注意:getSubmittedFileName是Servlet 3.1新增API Part filePart = req.getPart("file"); if (filePart == null) { // 返回JSON错误 } String submittedName = filePart.getSubmittedFileName(); String ext = submittedName.substring(submittedName.lastIndexOf('.')) .toLowerCase(); if (!".jpg".equals(ext) && !".jpeg".equals(ext)) { // 返回JSON错误 } String saveName = "camera_" + System.currentTimeMillis() + ext; try (InputStream in = filePart.getInputStream()) { Files.copy(in, Paths.get(uploadDir, saveName)); } // 返回JSON成功信息 } }这里的坑有三个:一是req.getPart要求请求必须是multipart/form-data格式,如果Flash端contentType没写对,会直接抛ServletException;二是getSubmittedFileName()是Servlet 3.1新增方法,老容器只有非标准的getFileName(),各容器行为不一致,最省事的办法是在Flash端把filename固定写成ASCII的capture.jpg,后端不依赖原始文件名;三是@MultipartConfig的maxFileSize要大于实际图片大小,太小会静默截断。
4.6 前端超时与错误处理
Flash的URLLoader默认没有超时设置,需要手写计时器:
var timeoutTimer:Timer = new Timer(10000, 1); timeoutTimer.addEventListener(TimerEvent.TIMER, onTimeout); timeoutTimer.start(); function onTimeout(event:TimerEvent):void { loader.close(); trace("上传超时"); } function onUploadComplete(event:Event):void { timeoutTimer.stop(); var result:String = loader.data as String; // 解析JSON、按业务code判断成功失败 }注意loader.close()之后不能再load同一个URLLoader实例,需要重建。上传URL必须与页面同域,或者目标域配置了crossdomain.xml,否则Flash的安全沙箱会直接拒绝请求。这个跨域问题在部署环境里出现频率很高,下一章专门展开。
5. 避坑指南:摄像头拍照项目高频故障的排查记录
5.1 本地能拍照、部署到服务器就白屏
现象:本地调试时摄像头调起正常,打包部署到服务器后,页面加载Flash正常,但点击拍照没反应,摄像头权限弹窗不出现。
原因:Flash Player的安全沙箱规则。本地调试时swf位于本机文件系统或localhost,沙箱宽松;部署到服务器后,swf与摄像头设备之间的访问受安全策略限制,同时摄像头授权属于隐私设置,站点不在允许列表时弹窗不会出现。
解决:确认页面和swf同域部署;在服务器根目录放置合法的crossdomain.xml,允许策略访问;如果弹窗始终不出现,检查Flash Player设置管理器的隐私设置,把该站点加入允许列表。老项目中还有一个很隐蔽的问题:页面URL如果经过了多层代理跳转,swf实际加载的域名和浏览器地址栏不一致,也会触发沙箱拒绝。排查顺序建议是先看域名一致,再看crossdomain.xml,再看隐私设置,大部分“本地好、线上坏”的问题都能落到这三步。
5.2 拍照成功但上传后文件损坏
现象:前端显示上传成功,后端也保存了文件,但打开保存的jpg报“文件已损坏”或只能显示半张图。
原因:构造multipart请求体时边界符或结尾符写错。最常见的是请求体最后缺少--收尾,或者某一行漏了\r\n,导致后端解析时把文件截断或混入额外字节。另一个原因是writeUTFBytes和writeBytes混用时,在二进制边界处多写或少写了CRLF。
解决:把请求体落地对比。在Flash端先trace输出boundary和每个分段的长度,后端把收到的raw body原样保存成.bin文件,与构造的ByteArray做字节级diff。绝大多数情况是结尾符问题——multipart严格规定以--boundary--结束,后面跟\r\n。养成习惯:在组件里封装一个buildMultipart函数,每次改动只动这一个函数,不要在业务代码里到处拼请求体。字节级diff的定位方式至今仍然适用,只是Flash工具链维护成本高了,值得把它当成排障的保留手段。
5.3 摄像头画面左右颠倒
现象:摄像头画面是镜像的,用户挥手时画面上对应的是反过来的手。
原因:摄像头物理成像特性,前置摄像头通常输出镜像画面,后置摄像头输出原始画面。Flash Camera对象没有原生的镜像模式开关。
解决:给Video做水平翻转:
// 预览时镜像,符合用户自拍习惯 video.scaleX = -1;但保存的照片如果需要非镜像方向(比如证照),在draw时做一次补偿:
// draw时用Matrix把镜像画面还原为原始方向 var matrix:Matrix = new Matrix(); matrix.scale(-1, 1); matrix.translate(bitmapData.width, 0); bitmapData.draw(video, matrix);这个矩阵的含义是:先水平翻转一次,再沿X轴平移一个画布宽度,把坐标拉回来。实际效果取决于Video的注册点位置,接入时先在本地验证。最稳妥的做法是做一个镜像开关参数,由业务方决定保存的是镜像还是原始方向,而不是在代码里写死。
5.4 浏览器差异:Flash Player版本导致的行为不一致
现象:同一个swf,在浏览器A里拍照上传正常,在浏览器B里上传后提示找不到文件字段。
原因:不同版本Flash Player在构造multipart请求时的底层行为有细微差别,尤其在filename字段的编码处理上。filename含中文时,不同版本的编码策略不一致,导致后端解析错位或文件名乱码。
解决:filename统一用ASCII字符,构造请求体时写死capture.jpg。另外,某些老版本Flash Player在URLLoader接收非文本响应时会解析失败,所以后端返回尽可能用纯文本JSON,不要混入二进制内容。浏览器差异问题在存量系统里几乎无法根治,能做的只有把边界条件收敛——固定filename、固定Content-Type、固定返回格式,让所有版本走同一条窄路。
5.5 反复拍照后内存持续上涨
现象:连续拍几十张后,Flash占用的内存一路增加,最后变卡甚至无响应。
原因:每次draw都创建了新的BitmapData,预览用的Loader又保留了解码后的Bitmap,加上没清理的事件监听和ByteArray引用,垃圾回收来不及回收。
解决:每个拍照生命周期结束时按固定顺序清理:
// 编码完成后立刻释放像素数据 bitmapData.dispose(); // 把预览Loader从显示列表移除并卸载 removeChild(previewLoader); previewLoader.unload(); previewLoader = null; // 事件监听用弱引用 addEventListener(Event.ENTER_FRAME, onFrame, false, 0, true);unload()会释放Loader占用的解码图像内存,第五个参数true表示弱引用,能让GC更好地回收。这里还有一个容易被忽视的点:JPGEncoder实例确实可以复用,不要每次拍照都new一个,但注意它内部可能有缓存状态,在长时间运行时偶尔出现编码异常,此时重启组件比继续复用更干净。
6. 进阶:给拍照组件加一个统一回调协议,彻底解耦业务页面
同时对接多个后端时,最烦的是业务页面和拍照组件耦合太深。我在这个项目上做了一个统一回调层,让HTML页面通过ExternalInterface控制拍照,结果通过全局回调返回,业务页面完全不碰swf内部逻辑:
// 组件内部定义 private var requestId:String = ""; function capture(id:String):void { requestId = id; // 执行截帧、编码、上传 doCaptureAndUpload(); } // 上传结果统一回调 function notify(type:String, code:int, msg:String, data:Object):void { var payload:Object = { requestId: requestId, code: code, msg: msg, data: data }; ExternalInterface.call("onCaptureResult", JSON.stringify(payload)); }这里有两个关键点。第一,requestId:每次capture调用传入一个唯一ID,回调带回来,业务页面在onCaptureResult里用requestId区分当前是哪个按钮触发的拍照,避免误判。第二,所有回调数据经JSON.stringify序列化成一个字符串,减少AS3和JS之间类型转换的坑。HTML侧调用方式:
function onCaptureResult(payload) { var data = JSON.parse(payload); if (data.code === 0) { document.getElementById('photoUrl').value = data.data.url; } else { alert('拍照失败:' + data.msg); } } document.getElementById('btn').onclick = function() { swf.capture(Date.now().toString()); };外部JS调用swf的方法必须在页面完全加载、swf的ExternalInterface.addCallback注册完成之后,否则swf.capture是undefined。我会用setTimeout轮询swf.capture的类型来判断就绪,而不是用固定的延迟时间,因为在老机器上swf加载耗时波动很大。
从那以后,我每次接摄像头拍照这类需求,都强制先跟后端约定字段名、boundary格式和返回JSON结构,再动前端代码。字段名不统一的代价,是每个后端各写一套解析,翻车概率成倍上升。这套统一回调协议的做法,希望帮到你。
本文还有配套的精品资源,点击获取