|
|
@ -210,16 +210,78 @@ const uploadMediaFileViaBlob = async ({ mediaFile, fileName, mimeType }) => { |
|
|
return uploadBlobViaFetch({ blob, fileName, mimeType }) |
|
|
return uploadBlobViaFetch({ blob, fileName, mimeType }) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 录制视频(使用 Cordova 原生 Media Capture API)
|
|
|
// 通过 WebView 文件选择器调起系统摄像头录像(推荐方式)
|
|
|
export const startVideoRecorder = async ({ |
|
|
// 原理:Android WebView 实现 onShowFileChooser 后,<input capture="camcorder"> 会直接调起摄像头录像(不弹相册/文件选择),
|
|
|
|
|
|
// 返回的 File 通过 content URI 授权,页面可直接读取/上传,彻底绕开 Cordova 在分区存储下的 EACCES 问题
|
|
|
|
|
|
export const startVideoRecorderViaInput = () => { |
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
|
const input = document.createElement('input') |
|
|
|
|
|
input.type = 'file' |
|
|
|
|
|
input.setAttribute('accept', 'video/*') |
|
|
|
|
|
// capture 属性强制现场录制;camcorder 指定调起录像
|
|
|
|
|
|
input.setAttribute('capture', 'camcorder') |
|
|
|
|
|
input.style.display = 'none' |
|
|
|
|
|
document.body.appendChild(input) |
|
|
|
|
|
|
|
|
|
|
|
let settled = false |
|
|
|
|
|
const cleanup = () => { |
|
|
|
|
|
window.removeEventListener('focus', onFocus) |
|
|
|
|
|
input.remove() |
|
|
|
|
|
} |
|
|
|
|
|
const settleResolve = (payload) => { |
|
|
|
|
|
if (settled) return |
|
|
|
|
|
settled = true |
|
|
|
|
|
cleanup() |
|
|
|
|
|
resolve(payload) |
|
|
|
|
|
} |
|
|
|
|
|
const settleReject = (err) => { |
|
|
|
|
|
if (settled) return |
|
|
|
|
|
settled = true |
|
|
|
|
|
cleanup() |
|
|
|
|
|
reject(err) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
input.addEventListener('change', () => { |
|
|
|
|
|
const file = input.files && input.files[0] |
|
|
|
|
|
if (!file) { |
|
|
|
|
|
settleReject(new Error('未录制视频')) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
console.log('[w6sMedia] input 录像返回 File:', file.name, 'size:', file.size, 'type:', file.type) |
|
|
|
|
|
settleResolve({ |
|
|
|
|
|
video_path: '', |
|
|
|
|
|
file, // 标准 File 对象,可直接 FormData 上传
|
|
|
|
|
|
name: file.name, |
|
|
|
|
|
size: file.size, |
|
|
|
|
|
type: file.type || 'video/mp4', |
|
|
|
|
|
}) |
|
|
|
|
|
}) |
|
|
|
|
|
// 用户取消录制时部分 WebView 会触发 cancel 事件
|
|
|
|
|
|
input.addEventListener('cancel', () => settleReject(new Error('已取消录制'))) |
|
|
|
|
|
// 兜底:摄像头关闭回到页面后若仍无文件,视为取消
|
|
|
|
|
|
const onFocus = () => { |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
if (!input.files || input.files.length === 0) { |
|
|
|
|
|
settleReject(new Error('已取消录制')) |
|
|
|
|
|
} |
|
|
|
|
|
}, 1500) |
|
|
|
|
|
} |
|
|
|
|
|
window.addEventListener('focus', onFocus) |
|
|
|
|
|
|
|
|
|
|
|
input.click() |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 录制视频(Cordova Media Capture 方式,备选)
|
|
|
|
|
|
// 注意:Android 10+ 分区存储下,公共目录文件的 FileTransfer/copyTo/FileReader 全部会 EACCES,
|
|
|
|
|
|
// 仅在设备允许旧版存储(legacy storage)时可用
|
|
|
|
|
|
export const startVideoRecorderCordova = ({ |
|
|
duration = 0, |
|
|
duration = 0, |
|
|
quality = 1, |
|
|
quality = 1, |
|
|
front = false, |
|
|
front = false, |
|
|
syncSystemAlbum = false, |
|
|
syncSystemAlbum = false, |
|
|
} = {}) => { |
|
|
} = {}) => { |
|
|
console.log('[w6sMedia] startVideoRecorder 开始, 入参:', { duration, quality, front, syncSystemAlbum }) |
|
|
console.log('[w6sMedia] startVideoRecorderCordova 开始, 入参:', { duration, quality, front, syncSystemAlbum }) |
|
|
await ensureW6sAuth() |
|
|
|
|
|
console.log('[w6sMedia] 录制前鉴权完成, 准备调起录制') |
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
return new Promise((resolve, reject) => { |
|
|
if (!navigator?.device?.capture?.captureVideo) { |
|
|
if (!navigator?.device?.capture?.captureVideo) { |
|
|
console.error('[w6sMedia] navigator.device.capture.captureVideo 不可用') |
|
|
console.error('[w6sMedia] navigator.device.capture.captureVideo 不可用') |
|
|
@ -231,9 +293,6 @@ export const startVideoRecorder = async ({ |
|
|
console.log('[w6sMedia] captureVideo success, mediaFiles:', mediaFiles) |
|
|
console.log('[w6sMedia] captureVideo success, mediaFiles:', mediaFiles) |
|
|
if (mediaFiles && mediaFiles.length > 0) { |
|
|
if (mediaFiles && mediaFiles.length > 0) { |
|
|
const file = mediaFiles[0] |
|
|
const file = mediaFiles[0] |
|
|
console.log('[w6sMedia] 视频文件: fullPath=', file.fullPath, 'localURL=', file.localURL, 'name=', file.name, 'size=', file.size) |
|
|
|
|
|
// 优先把文件复制到应用私有目录,后续上传时传复制后的路径
|
|
|
|
|
|
// 如果复制失败,fallback 返回原始 localURL,让上传端再尝试 fetch+Blob 方案
|
|
|
|
|
|
try { |
|
|
try { |
|
|
const appDirPath = await copyMediaFileToAppDir(file) |
|
|
const appDirPath = await copyMediaFileToAppDir(file) |
|
|
console.log('[w6sMedia] 视频已复制到应用私有目录:', appDirPath) |
|
|
console.log('[w6sMedia] 视频已复制到应用私有目录:', appDirPath) |
|
|
@ -242,7 +301,7 @@ export const startVideoRecorder = async ({ |
|
|
name: file.name, |
|
|
name: file.name, |
|
|
size: file.size, |
|
|
size: file.size, |
|
|
type: file.type || 'video/mp4', |
|
|
type: file.type || 'video/mp4', |
|
|
_mediaFile: file, // 附带原始 MediaFile,供 fetch+Blob 上传兜底
|
|
|
_mediaFile: file, |
|
|
}) |
|
|
}) |
|
|
} catch (copyErr) { |
|
|
} catch (copyErr) { |
|
|
console.warn('[w6sMedia] 复制视频到应用私有目录失败,返回原始 MediaFile 作兜底:', copyErr) |
|
|
console.warn('[w6sMedia] 复制视频到应用私有目录失败,返回原始 MediaFile 作兜底:', copyErr) |
|
|
@ -267,6 +326,16 @@ export const startVideoRecorder = async ({ |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 录制视频(对外统一入口)
|
|
|
|
|
|
// 默认 mode='input':WebView 文件选择器 + capture 强制现场录像,返回可读 File 对象(推荐)
|
|
|
|
|
|
// mode='cordova':Cordova MediaCapture + 复制私有目录(部分设备受分区存储限制不可用)
|
|
|
|
|
|
export const startVideoRecorder = ({ mode = 'input', duration = 0, quality = 1 } = {}) => { |
|
|
|
|
|
if (mode === 'cordova') { |
|
|
|
|
|
return startVideoRecorderCordova({ duration, quality }) |
|
|
|
|
|
} |
|
|
|
|
|
return startVideoRecorderViaInput() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 获取定位(经纬度)
|
|
|
// 获取定位(经纬度)
|
|
|
export const getLocation = async () => { |
|
|
export const getLocation = async () => { |
|
|
await ensureW6sAuth() |
|
|
await ensureW6sAuth() |
|
|
@ -276,15 +345,21 @@ export const getLocation = async () => { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 上传本地文件到后端 /app-enf/common/file/upload
|
|
|
// 上传本地文件到后端 /app-enf/common/file/upload
|
|
|
// 默认使用 w6s.file.upload(Cordova FileTransfer)
|
|
|
// 优先级:file(WebView 文件选择器返回的标准 File,FormData 直传)> FileTransfer > fetch+Blob
|
|
|
// 如果传入 mediaFile(视频场景),FileTransfer 失败(如 EACCES)时自动 fallback 到 fetch+Blob 方案
|
|
|
|
|
|
export const uploadLocalFileToBackend = async ({ |
|
|
export const uploadLocalFileToBackend = async ({ |
|
|
fileURL, |
|
|
fileURL, |
|
|
fileName = 'upload', |
|
|
fileName = 'upload', |
|
|
mimeType = 'image/jpeg', |
|
|
mimeType = 'image/jpeg', |
|
|
|
|
|
file = null, |
|
|
mediaFile = null, |
|
|
mediaFile = null, |
|
|
}) => { |
|
|
}) => { |
|
|
console.log('[w6sMedia] uploadLocalFileToBackend 开始, 入参:', { fileURL, fileName, mimeType, hasMediaFile: !!mediaFile }) |
|
|
console.log('[w6sMedia] uploadLocalFileToBackend 开始, 入参:', { fileURL, fileName, mimeType, hasFile: !!file, hasMediaFile: !!mediaFile }) |
|
|
|
|
|
|
|
|
|
|
|
// 标准 File 对象(WebView 文件选择器返回,带 content URI 授权)直接 FormData 上传,无需鉴权
|
|
|
|
|
|
if (file) { |
|
|
|
|
|
return uploadBlobViaFetch({ blob: file, fileName, mimeType: mimeType || file.type }) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
await ensureW6sAuth() |
|
|
await ensureW6sAuth() |
|
|
|
|
|
|
|
|
// 优先用 FileTransfer(图片已验证能成功)
|
|
|
// 优先用 FileTransfer(图片已验证能成功)
|
|
|
|