视频连线
This commit is contained in:
@@ -95,7 +95,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="controls-row controls-row-bottom">
|
<view class="controls-row controls-row-bottom">
|
||||||
<view class="control-item" @click="takeSnapshot">
|
<view class="control-item" @click="takeSnapshot" @tap="takeSnapshot">
|
||||||
<view class="control-btn dark-btn text-icon-btn">
|
<view class="control-btn dark-btn text-icon-btn">
|
||||||
<image v-if="!captureIconError" class="control-image" :src="captureIcon" mode="aspectFit" @error="captureIconError = true"></image>
|
<image v-if="!captureIconError" class="control-image" :src="captureIcon" mode="aspectFit" @error="captureIconError = true"></image>
|
||||||
<text v-else class="text-icon">截</text>
|
<text v-else class="text-icon">截</text>
|
||||||
@@ -121,6 +121,17 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view v-if="snapshotPreviewUrl" class="snapshot-preview-mask" @click="closeSnapshotPreview">
|
||||||
|
<view class="snapshot-preview-panel" @click.stop>
|
||||||
|
<image class="snapshot-preview-image" :src="snapshotPreviewUrl" mode="aspectFit"></image>
|
||||||
|
<text class="snapshot-preview-tip">手机端请长按图片保存,电脑端可点击下载图片</text>
|
||||||
|
<view class="snapshot-preview-actions">
|
||||||
|
<button class="snapshot-preview-btn" @click="downloadSnapshotPreview">下载图片</button>
|
||||||
|
<button class="snapshot-preview-btn secondary" @click="closeSnapshotPreview">关闭</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -176,6 +187,8 @@ const captureIcon = '/static/capture.png'
|
|||||||
const flipCameraIcon = '/static/camera.png'
|
const flipCameraIcon = '/static/camera.png'
|
||||||
const captureIconError = ref(false)
|
const captureIconError = ref(false)
|
||||||
const flipCameraIconError = ref(false)
|
const flipCameraIconError = ref(false)
|
||||||
|
const snapshotPreviewUrl = ref('')
|
||||||
|
const snapshotBusy = ref(false)
|
||||||
|
|
||||||
const digitsOf = (value) => String(value || '').replace(/\D/g, '').slice(0, 9)
|
const digitsOf = (value) => String(value || '').replace(/\D/g, '').slice(0, 9)
|
||||||
const sameUser = (a, b) => String(a || '') === String(b || '')
|
const sameUser = (a, b) => String(a || '') === String(b || '')
|
||||||
@@ -448,10 +461,62 @@ const toggleSpeaker = () => {
|
|||||||
setSpeakerMuted(!speakerMuted.value)
|
setSpeakerMuted(!speakerMuted.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMobileSnapshotBrowser = () => {
|
||||||
|
// #ifdef H5
|
||||||
|
const ua = navigator?.userAgent || ''
|
||||||
|
return /Android|iPhone|iPad|iPod|Mobile|MicroMessenger/i.test(ua)
|
||||||
|
// #endif
|
||||||
|
// #ifndef H5
|
||||||
|
return false
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
const findSnapshotVideo = () => {
|
||||||
|
// #ifdef H5
|
||||||
|
const selectors = [
|
||||||
|
'.cloud-law-call .main-video video',
|
||||||
|
'.cloud-law-call .remote-stage video',
|
||||||
|
'.cloud-law-call video'
|
||||||
|
]
|
||||||
|
for (const selector of selectors) {
|
||||||
|
const list = Array.from(document.querySelectorAll(selector))
|
||||||
|
const video = list.find(el => el && el.videoWidth && el.videoHeight && el.readyState >= 2)
|
||||||
|
if (video) return video
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadSnapshotDataUrl = (dataUrl) => {
|
||||||
|
// #ifdef H5
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = dataUrl
|
||||||
|
a.download = `cloud-law-video-${Date.now()}.png`
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
document.body.removeChild(a)
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSnapshotPreview = () => {
|
||||||
|
snapshotPreviewUrl.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadSnapshotPreview = () => {
|
||||||
|
if (!snapshotPreviewUrl.value) return
|
||||||
|
downloadSnapshotDataUrl(snapshotPreviewUrl.value)
|
||||||
|
uni.showToast({ title: isMobileSnapshotBrowser() ? '如未下载,请长按图片保存' : '截屏已下载', icon: 'none' })
|
||||||
|
}
|
||||||
|
|
||||||
const takeSnapshot = () => {
|
const takeSnapshot = () => {
|
||||||
|
if (snapshotBusy.value) return
|
||||||
|
snapshotBusy.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
snapshotBusy.value = false
|
||||||
|
}, 600)
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
try {
|
try {
|
||||||
const video = document.querySelector('.cloud-law-call .main-video video') || document.querySelector('.cloud-law-call video')
|
const video = findSnapshotVideo()
|
||||||
if (!video || !video.videoWidth || !video.videoHeight) {
|
if (!video || !video.videoWidth || !video.videoHeight) {
|
||||||
uni.showToast({ title: '当前无视频画面可截屏', icon: 'none' })
|
uni.showToast({ title: '当前无视频画面可截屏', icon: 'none' })
|
||||||
return
|
return
|
||||||
@@ -461,18 +526,22 @@ const takeSnapshot = () => {
|
|||||||
canvas.height = video.videoHeight
|
canvas.height = video.videoHeight
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
|
||||||
const a = document.createElement('a')
|
const dataUrl = canvas.toDataURL('image/png')
|
||||||
a.href = canvas.toDataURL('image/png')
|
snapshotPreviewUrl.value = dataUrl
|
||||||
a.download = `cloud-law-video-${Date.now()}.png`
|
if (isMobileSnapshotBrowser()) {
|
||||||
document.body.appendChild(a)
|
uni.showToast({ title: '截屏成功,长按图片保存', icon: 'none' })
|
||||||
a.click()
|
} else {
|
||||||
document.body.removeChild(a)
|
downloadSnapshotDataUrl(dataUrl)
|
||||||
uni.showToast({ title: '截屏已保存', icon: 'success' })
|
uni.showToast({ title: '截屏已下载', icon: 'success' })
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[CloudLawVideoCall] snapshot failed', err)
|
console.warn('[CloudLawVideoCall] snapshot failed', err)
|
||||||
uni.showToast({ title: '截屏失败', icon: 'none' })
|
uni.showToast({ title: '截屏失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
// #ifndef H5
|
||||||
|
uni.showToast({ title: '当前环境暂不支持截屏', icon: 'none' })
|
||||||
|
// #endif
|
||||||
}
|
}
|
||||||
|
|
||||||
const flipCameraByCommand = async (value) => {
|
const flipCameraByCommand = async (value) => {
|
||||||
@@ -1100,6 +1169,61 @@ onUnload(() => {
|
|||||||
.loading {
|
.loading {
|
||||||
opacity: 0.68;
|
opacity: 0.68;
|
||||||
}
|
}
|
||||||
|
.snapshot-preview-mask {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 99;
|
||||||
|
background: rgba(0, 0, 0, 0.78);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 36rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.snapshot-preview-panel {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 720rpx;
|
||||||
|
max-height: 88vh;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
background: #111827;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
.snapshot-preview-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 58vh;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #000000;
|
||||||
|
}
|
||||||
|
.snapshot-preview-tip {
|
||||||
|
color: rgba(255, 255, 255, 0.82);
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.45;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.snapshot-preview-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 18rpx;
|
||||||
|
}
|
||||||
|
.snapshot-preview-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 72rpx;
|
||||||
|
line-height: 72rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #2563eb;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
.snapshot-preview-btn.secondary {
|
||||||
|
color: rgba(255, 255, 255, 0.86);
|
||||||
|
background: rgba(255, 255, 255, 0.14);
|
||||||
|
}
|
||||||
.clean-video-tile :deep(.footer) {
|
.clean-video-tile :deep(.footer) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user