@@ -0,0 +1,269 @@
var Diagnostics = require ( '../../utils/ar-camera' ) . diagnostics ;
var development = require ( '../../utils/development' ) ;
var PROBE _VERSION = 'CAM-NATIVE-20261102-04' ;
var KNOWN _FORMATS = [ 'RGBA' , 'RGBA8888' , 'RGB' , 'RGB888' , 'RGBX' , 'BGRA' , 'BGRA8888' , 'BGR' , 'BGR888' ,
'ARGB' , 'ARGB8888' , 'ABGR' , 'ABGR8888' , 'NV21' , 'NV12' , 'YUV420' , 'YUV420P' , 'YUV420SP' , 'I420' , 'YV12' , 'JPEG' , 'JPG' , 'PNG' ] ;
function message ( error , fallback ) {
var value = error && ( error . errorMessage || error . errMsg || error . message ) ;
return typeof value === 'string' ? value . slice ( 0 , 160 ) : fallback ;
}
function positive ( value ) { return typeof value === 'number' && isFinite ( value ) && value > 0 ; }
function frameMeta ( frame ) {
frame = frame || { } ;
var data = frame . data , bytes = data && data . byteLength ;
var meta = { width : positive ( frame . width ) ? frame . width : 0 , height : positive ( frame . height ) ? frame . height : 0 ,
dataType : Object . prototype . toString . call ( data ) , byteLength : positive ( bytes ) ? bytes : 0 } ;
if ( typeof frame . timestamp === 'number' && isFinite ( frame . timestamp ) ) meta . timestamp = frame . timestamp ;
if ( typeof frame . pixelFormat === 'string' ) meta . pixelFormat = frame . pixelFormat . slice ( 0 , 40 ) ;
if ( typeof frame . format === 'string' ) meta . format = frame . format . slice ( 0 , 40 ) ;
return meta ;
}
Page ( {
data : {
probeVersion : PROBE _VERSION , toolbarTop : 112 , showAutoTest : false , diagnosticText : '' , copyStatus : '' ,
cameraEnabled : false , cameraSession : 0 , devicePosition : 'front' , nativeReady : false ,
cameraStatus : '正在打开普通 camera' , contextStatus : '尚未创建上下文' ,
frameStatus : '尚未监听' , startStatus : '尚未调用' , stopStatus : '尚未调用' ,
frameCallbacks : 0 , frameCount : 0 , invalidFrameCount : 0 , firstFrameMs : '' , frameInfo : '' ,
photoBusy : false , photoStatus : '仅在点击拍照时生成临时照片' , photoPath : '' , photoPosition : ''
} ,
onLoad : function ( ) {
this . alive = true ; this . visible = false ; this . pageReady = false ;
this . generation = 0 ; this . photoOperation = null ;
this . diagnosticSystem = Diagnostics . system ( my ) ; this . copySequence = 0 ;
this . setData ( { toolbarTop : Diagnostics . toolbarTop ( my ) , showAutoTest : development . nativeFaceProbeEnabled ( ) } ) ;
this . contextCapabilities = { takePhoto : false , onCameraFrame : false , listenerStart : false , listenerStop : false } ;
this . listenerResults = { start : { state : 'not_called' , code : 'unavailable' } , stop : { state : 'not_called' , code : 'unavailable' } } ;
this . photoAttempted = false ; this . photoResultSuccess = false ; this . photoReturnedPath = false ;
} ,
onReady : function ( ) { this . pageReady = true ; this . mountCamera ( ) ; } ,
onShow : function ( ) {
this . visible = true ;
if ( this . photoOperation && this . photoOperation . timedOut ) this . setData ( { photoBusy : true , photoStatus : '拍照10秒未返回;等待原操作结束,或退出此诊断页后重试' } ) ;
this . mountCamera ( ) ;
} ,
onHide : function ( ) {
this . visible = false ; this . disposeCamera ( ) ;
this . setData ( { cameraEnabled : false , photoBusy : ! ! this . photoOperation , cameraStatus : '页面已隐藏,相机已卸载' } ) ;
} ,
onUnload : function ( ) {
this . visible = false ; this . alive = false ; this . disposeCamera ( ) ;
if ( this . photoOperation ) clearTimeout ( this . photoOperation . timer ) ;
this . photoOperation = null ; this . data . photoPath = '' ;
} ,
current : function ( generation ) { return this . alive && this . visible && this . data . cameraEnabled && this . generation === generation ; } ,
diagnosticReport : function ( ) {
var caps = this . contextCapabilities || { } , results = this . listenerResults || { } , meta = this . lastDiagnosticFrame ;
var lines = [ PROBE _VERSION ] . concat ( this . diagnosticSystem || [ ] ) ;
lines . push ( 'requestedPosition=' + ( this . data . devicePosition === 'front' || this . data . devicePosition === 'back' ? this . data . devicePosition : 'unknown' ) ) ;
lines . push ( 'actualPosition=not_verified; requestedPosition does not identify the actual lens' ) ;
lines . push ( 'context.takePhoto=' + ! ! caps . takePhoto + '; context.onCameraFrame=' + ! ! caps . onCameraFrame ) ;
lines . push ( 'listener.start=' + ! ! caps . listenerStart + '; listener.stop=' + ! ! caps . listenerStop ) ;
lines . push ( 'nativeReady=' + ! ! this . data . nativeReady + '; cameraMounted=' + ! ! this . data . cameraEnabled ) ;
lines . push ( 'frameCallbacks=' + ( this . frameCallbacks || 0 ) + '; rawFrames=' + ( this . frameCount || 0 ) + '; invalidCallbacks=' + ( this . invalidFrameCount || 0 ) ) ;
if ( meta ) lines . push ( 'frame.width=' + meta . width + '; frame.height=' + meta . height + '; frame.byteLength=' + meta . byteLength + '; frame.pixelFormat=' + meta . pixelFormat + '; frame.format=' + meta . format ) ;
[ 'start' , 'stop' ] . forEach ( function ( key ) {
var result = results [ key ] || { state : 'not_called' , code : 'unavailable' } ;
lines . push ( 'listener.' + key + '.state=' + result . state + '; listener.' + key + '.code=' + result . code ) ;
} ) ;
lines . push ( 'photo.attempted=' + ! ! this . photoAttempted + '; photo.pending=' + ! ! this . photoOperation + '; photo.success=' + ! ! this . photoResultSuccess + '; photo.returnedPath=' + ! ! this . photoReturnedPath + '; photo.previewPresent=' + ! ! this . data . photoPath ) ;
return lines . join ( '\n' ) ;
} ,
onGenerateDiagnostics : function ( ) {
if ( ! this . alive ) return ;
this . setData ( { diagnosticText : this . diagnosticReport ( ) , copyStatus : '脱敏诊断已生成,可长按下方文字选择复制。' } ) ;
} ,
onCopyDiagnostics : function ( ) {
if ( ! this . alive ) return ;
var that = this , sequence = ++ this . copySequence , report = this . diagnosticReport ( ) ;
this . setData ( { diagnosticText : report , copyStatus : '正在复制;也可长按下方脱敏文字选择复制。' } ) ;
function status ( value ) { if ( that . alive && that . visible && sequence === that . copySequence ) that . setData ( { copyStatus : value } ) ; }
try {
if ( typeof my . setClipboard !== 'function' ) { status ( '当前宿主缺少剪贴板 API,请长按下方脱敏文字选择复制。' ) ; return ; }
my . setClipboard ( { text : report , success : function ( ) { status ( '普通相机诊断已复制,仅含版本和相机状态。' ) ; } ,
fail : function ( ) { status ( '剪贴板复制失败,请长按下方脱敏文字选择复制。' ) ; } } ) ;
} catch ( _ ) { status ( '剪贴板复制失败,请长按下方脱敏文字选择复制。' ) ; }
} ,
currentEvent : function ( event ) {
if ( ! this . alive || ! this . visible || ! this . data . cameraEnabled ) return false ;
var target = event && event . currentTarget , session = target && target . dataset && target . dataset . cameraSession ;
var match = target && typeof target . id === 'string' && /^native-camera-(\d+)$/ . exec ( target . id ) ;
if ( session === undefined && match ) session = match [ 1 ] ;
return session === undefined || Number ( session ) === this . data . cameraSession ;
} ,
mountCamera : function ( ) {
if ( ! this . alive || ! this . visible || ! this . pageReady || this . data . cameraEnabled ) return ;
var that = this , generation = ++ this . generation , session = this . data . cameraSession + 1 ;
this . frameCallbacks = 0 ; this . frameCount = 0 ; this . invalidFrameCount = 0 ; this . lastFramePaint = 0 ;
this . lastDiagnosticFrame = null ;
this . contextCapabilities = { takePhoto : false , onCameraFrame : false , listenerStart : false , listenerStop : false } ;
this . listenerResults = { start : { state : 'not_called' , code : 'unavailable' } , stop : { state : 'not_called' , code : 'unavailable' } } ;
this . setData ( { cameraEnabled : true , cameraSession : session , nativeReady : false , cameraStatus : '已请求' + ( this . data . devicePosition === 'front' ? '前置' : '后置' ) + ',请观察真实取景' ,
contextStatus : '正在创建上下文' , frameStatus : '尚未监听' , startStatus : '尚未调用' , stopStatus : '尚未调用' ,
frameCallbacks : 0 , frameCount : 0 , invalidFrameCount : 0 , firstFrameMs : '' , frameInfo : '' , photoBusy : ! ! this . photoOperation } , function ( ) {
if ( ! that . current ( generation ) || session !== that . data . cameraSession ) return ;
if ( typeof my . createCameraContext !== 'function' ) { that . setData ( { contextStatus : '缺少 my.createCameraContext' , frameStatus : '无法监听' } ) ; return ; }
try {
that . cameraContext = my . createCameraContext ( 'native-camera-' + session ) ;
if ( ! that . cameraContext ) throw new Error ( '未返回 CameraContext' ) ;
var methods = Diagnostics . methods ( that . cameraContext ) ;
that . contextCapabilities . takePhoto = methods . indexOf ( 'takePhoto' ) >= 0 ;
that . contextCapabilities . onCameraFrame = methods . indexOf ( 'onCameraFrame' ) >= 0 ;
that . setData ( { contextStatus : 'takePhoto: ' + ( typeof that . cameraContext . takePhoto === 'function' ) + '; onCameraFrame: ' + ( typeof that . cameraContext . onCameraFrame === 'function' ) } ) ;
that . startFrames ( generation ) ;
} catch ( error ) { if ( that . current ( generation ) ) that . setData ( { contextStatus : message ( error , '创建相机上下文失败' ) , frameStatus : '无法监听' } ) ; }
} ) ;
} ,
onNativeReady : function ( event ) { if ( this . currentEvent ( event ) ) this . setData ( { nativeReady : true } ) ; } ,
startFrames : function ( generation ) {
if ( ! this . current ( generation ) ) return ;
var that = this , context = this . cameraContext ;
if ( ! context || typeof context . onCameraFrame !== 'function' ) { this . listenerResults . start = { state : 'missing_method' , code : 'unavailable' } ; this . setData ( { frameStatus : '当前上下文缺少 onCameraFrame,不能取得实时帧' } ) ; return ; }
this . listenerResults . start = { state : 'pending' , code : 'unavailable' } ;
this . framesActive = true ; this . framesStartedAt = Date . now ( ) ;
this . setData ( { frameStatus : '等待真实帧;start 成功也不代表收到帧' , startStatus : '正在调用' } ) ;
this . frameTimer3 = setTimeout ( function ( ) {
if ( that . current ( generation ) && that . framesActive && ! that . frameCount ) that . setData ( { frameStatus : '3秒未收到可用原始帧,继续等到10秒(回调 ' + that . frameCallbacks + ' 次)' } ) ;
} , 3000 ) ;
this . frameTimer10 = setTimeout ( function ( ) {
if ( ! that . current ( generation ) || ! that . framesActive || that . frameCount ) return ;
that . setData ( { frameStatus : '10秒仍未收到可用原始帧,本轮已停止;不能判定宿主支持' , frameCallbacks : that . frameCallbacks , invalidFrameCount : that . invalidFrameCount } ) ;
that . stopFrames ( false ) ;
} , 10000 ) ;
try {
this . frameListener = context . onCameraFrame ( function ( frame ) { that . receiveFrame ( generation , frame ) ; } ) ;
var methods = Diagnostics . methods ( this . frameListener ) ;
this . contextCapabilities . listenerStart = methods . indexOf ( 'start' ) >= 0 ; this . contextCapabilities . listenerStop = methods . indexOf ( 'stop' ) >= 0 ;
if ( ! this . frameListener || typeof this . frameListener . start !== 'function' || typeof this . frameListener . stop !== 'function' ) throw new Error ( '未返回具备 start/stop 的 CameraFrameListener' ) ;
this . frameListener . start ( {
success : function ( ) { if ( that . current ( generation ) && that . framesActive ) { that . listenerResults . start = { state : 'success' , code : 0 } ; that . setData ( { startStatus : '调用成功(需另看真实帧计数)' } ) ; } } ,
fail : function ( error ) {
if ( ! that . current ( generation ) || ! that . framesActive ) return ;
that . listenerResults . start = { state : 'failure' , code : diagnosticCode ( error ) } ;
that . setData ( { startStatus : message ( error , '监听启动失败' ) , frameStatus : '监听启动失败' } ) ; that . stopFrames ( false ) ;
}
} ) ;
} catch ( error ) {
if ( ! this . current ( generation ) ) return ;
this . listenerResults . start = { state : 'throw' , code : diagnosticCode ( error ) } ;
this . setData ( { startStatus : message ( error , '监听接口调用失败' ) , frameStatus : '监听接口调用失败' } ) ; this . stopFrames ( false ) ;
}
} ,
receiveFrame : function ( generation , frame ) {
if ( ! this . current ( generation ) || ! this . framesActive ) return ;
this . frameCallbacks ++ ;
var meta = frameMeta ( frame ) , valid = meta . width > 0 && meta . height > 0 && meta . byteLength > 0 ;
this . lastDiagnosticFrame = { width : meta . width , height : meta . height , byteLength : meta . byteLength ,
pixelFormat : diagnosticFormat ( meta . pixelFormat ) , format : diagnosticFormat ( meta . format ) } ;
var firstValid = valid && ! this . frameCount ;
if ( valid ) {
this . frameCount ++ ;
if ( this . frameCount === 1 ) {
this . clearFrameTimers ( ) ;
this . setData ( { firstFrameMs : Math . max ( 0 , Date . now ( ) - this . framesStartedAt ) + 'ms' } ) ;
}
} else this . invalidFrameCount ++ ;
// Keep no frame/pixel buffer; publish scalar metadata at most ~3 times/s.
if ( this . frameCallbacks === 1 || firstValid || Date . now ( ) - this . lastFramePaint >= 350 ) {
this . lastFramePaint = Date . now ( ) ;
this . setData ( { frameCallbacks : this . frameCallbacks , frameCount : this . frameCount , invalidFrameCount : this . invalidFrameCount ,
frameInfo : JSON . stringify ( meta ) , frameStatus : this . frameCount ? '已收到原始帧;像素格式和人脸识别能力仍需另验' : '收到回调,但尺寸或原始字节缺失,未计为可用帧' } ) ;
}
} ,
clearFrameTimers : function ( ) { clearTimeout ( this . frameTimer3 ) ; clearTimeout ( this . frameTimer10 ) ; this . frameTimer3 = null ; this . frameTimer10 = null ; } ,
stopFrames : function ( showStatus ) {
this . framesActive = false ; this . clearFrameTimers ( ) ;
var listener = this . frameListener ; this . frameListener = null ;
var that = this , generation = this . generation ;
if ( showStatus && this . current ( generation ) ) this . setData ( { frameStatus : '已主动停止监听' , frameCallbacks : this . frameCallbacks , frameCount : this . frameCount , invalidFrameCount : this . invalidFrameCount } ) ;
if ( ! listener || typeof listener . stop !== 'function' ) return ;
this . listenerResults . stop = { state : 'pending' , code : 'unavailable' } ;
if ( this . current ( generation ) ) this . setData ( { stopStatus : '正在调用' } ) ;
try {
listener . stop ( { success : function ( ) { if ( that . current ( generation ) ) { that . listenerResults . stop = { state : 'success' , code : 0 } ; that . setData ( { stopStatus : '调用成功' } ) ; } } ,
fail : function ( error ) { if ( that . current ( generation ) ) { that . listenerResults . stop = { state : 'failure' , code : diagnosticCode ( error ) } ; that . setData ( { stopStatus : message ( error , '停止监听失败' ) } ) ; } } } ) ;
} catch ( error ) { if ( this . current ( generation ) ) { this . listenerResults . stop = { state : 'throw' , code : diagnosticCode ( error ) } ; this . setData ( { stopStatus : message ( error , '停止监听异常' ) } ) ; } }
} ,
onStopFrames : function ( ) { this . stopFrames ( true ) ; } ,
disposeCamera : function ( ) {
this . generation ++ ; this . stopFrames ( false ) ; this . cameraContext = null ;
} ,
remount : function ( position ) {
if ( ! this . alive || ! this . visible ) return ;
if ( this . photoOperation ) { this . setData ( { photoStatus : '上次拍照尚未结束,请等待;若持续无响应,请退出此诊断页再进入' } ) ; return ; }
this . disposeCamera ( ) ;
var that = this ;
this . setData ( { cameraEnabled : false , devicePosition : position } , function ( ) { that . mountCamera ( ) ; } ) ;
} ,
onFlip : function ( ) { this . remount ( this . data . devicePosition === 'front' ? 'back' : 'front' ) ; } ,
onRetryCamera : function ( ) { this . remount ( this . data . devicePosition ) ; } ,
onNativeError : function ( event ) {
if ( ! this . currentEvent ( event ) ) return ;
this . disposeCamera ( ) ;
this . setData ( { cameraEnabled : false , cameraStatus : message ( event && event . detail , '相机组件报错,请检查系统相机权限' ) , frameStatus : '相机异常,监听已停止' } ) ;
} ,
onNativeStop : function ( event ) {
if ( ! this . currentEvent ( event ) ) return ;
this . disposeCamera ( ) ; this . setData ( { cameraEnabled : false , cameraStatus : '相机组件已异常停止,可点重新打开' , frameStatus : '监听已停止' } ) ;
} ,
onTakePhoto : function ( ) {
if ( ! this . alive || ! this . visible || ! this . data . cameraEnabled || this . photoOperation ) return ;
var context = this . cameraContext , that = this ;
if ( ! context || typeof context . takePhoto !== 'function' ) { this . setData ( { photoStatus : '当前上下文缺少 takePhoto' } ) ; return ; }
var operation = { generation : this . generation , position : this . data . devicePosition , done : false } ;
this . photoAttempted = true ; this . photoResultSuccess = false ; this . photoReturnedPath = false ;
this . photoOperation = operation ;
this . setData ( { photoBusy : true , photoStatus : '正在拍照;仅在本页预览' } ) ;
operation . timer = setTimeout ( function ( ) {
// A timeout cannot cancel native takePhoto. Keep the lock until its callback
// or page destruction, so a late native export cannot overlap a retry.
if ( operation . done || that . photoOperation !== operation ) return ;
operation . timedOut = true ;
if ( that . alive && that . visible ) that . setData ( { photoStatus : '拍照10秒未返回;等待原操作结束,或退出此诊断页后重试' } ) ;
} , 10000 ) ;
try {
context . takePhoto ( { quality : 'high' , success : function ( result ) { that . finishPhoto ( operation , 'success' , result ) ; } ,
fail : function ( error ) { that . finishPhoto ( operation , 'fail' , error ) ; } ,
complete : function ( ) { that . finishPhoto ( operation , 'complete' ) ; } } ) ;
} catch ( error ) { this . finishPhoto ( operation , 'fail' , error ) ; }
} ,
finishPhoto : function ( operation , kind , result ) {
if ( operation . done || this . photoOperation !== operation ) return ;
operation . done = true ; clearTimeout ( operation . timer ) ; this . photoOperation = null ;
if ( ! this . alive || ! this . visible ) return ;
this . setData ( { photoBusy : false } ) ;
if ( ! this . current ( operation . generation ) ) return ;
var file = result && ( result . tempImagePath || result . tempFilePath || result . apFilePath ) ;
this . photoReturnedPath = typeof file === 'string' && ! ! file ;
this . photoResultSuccess = kind === 'success' && this . photoReturnedPath ;
if ( kind === 'success' && typeof file === 'string' && file ) this . setData ( { photoPath : file , photoPosition : operation . position , photoStatus : '已取得临时照片,请观察照片是否来自所请求镜头' } ) ;
else this . setData ( { photoStatus : message ( result , kind === 'success' ? '拍照成功回调未返回可预览的临时路径' : '拍照未成功返回照片' ) } ) ;
} ,
onOpenAutoCapture : function ( ) {
if ( ! development . nativeFaceProbeEnabled ( ) || this . photoOperation || this . data . photoBusy ) return ;
my . redirectTo ( { url : '/pages/camera/camera?debug=local-face' } ) ;
} ,
onOpenARProbe : function ( ) {
if ( this . photoOperation || this . data . photoBusy ) return ;
my . redirectTo ( { url : '/pages/camtest/camtest' } ) ;
} ,
onClearPhoto : function ( ) { this . setData ( { photoPath : '' , photoPosition : '' , photoStatus : '本页照片预览已清除' } ) ; } ,
onPreviewError : function ( ) { if ( this . alive && this . visible ) this . setData ( { photoStatus : '已收到拍照路径,但本页图片预览加载失败' } ) ; }
} ) ;
function diagnosticFormat ( value ) {
return typeof value !== 'string' ? 'not_reported' : ( KNOWN _FORMATS . indexOf ( value . toUpperCase ( ) ) >= 0 ? value . toUpperCase ( ) : 'unrecognized' ) ;
}
function diagnosticCode ( error ) {
if ( ! error || typeof error !== 'object' ) return 'unavailable' ;
var keys = [ 'error' , 'errorCode' , 'code' ] ;
for ( var index = 0 ; index < keys . length ; index ++ ) {
var descriptor ;
try { descriptor = Object . getOwnPropertyDescriptor ( error , keys [ index ] ) ; } catch ( _ ) { continue ; }
if ( ! descriptor || ! Object . prototype . hasOwnProperty . call ( descriptor , 'value' ) ) continue ;
var value = descriptor . value ;
if ( typeof value === 'number' && isFinite ( value ) && Math . floor ( value ) === value && Math . abs ( value ) <= 999999999 ) return value ;
if ( typeof value === 'string' && /^-?\d{1,9}$/ . test ( value ) ) return Number ( value ) ;
}
return 'unavailable' ;
}