提交 28f4122c authored 作者: 王鹏飞's avatar 王鹏飞

feat: 增强直播功能,支持动态上传选项和流重启

上级 483b0ac7
...@@ -31,7 +31,7 @@ watch( ...@@ -31,7 +31,7 @@ watch(
() => { () => {
props.onStatsChange && props.onStatsChange({ ...stats, viewers: viewers.value.length }) props.onStatsChange && props.onStatsChange({ ...stats, viewers: viewers.value.length })
}, },
{ immediate: true, deep: true } { immediate: true, deep: true },
) )
const detail = inject('detail') const detail = inject('detail')
...@@ -71,11 +71,13 @@ const { start: startSpeechTranscriber, stop: stopSpeechTranscriber } = useSpeech ...@@ -71,11 +71,13 @@ const { start: startSpeechTranscriber, stop: stopSpeechTranscriber } = useSpeech
const isOSSUpload = true const isOSSUpload = true
let ossResult = null let ossResult = null
let totalSize = 0
const { const {
stream, stream,
start: startLive, start: startLive,
stop: stopLive, stop: stopLive,
restartStream,
startTime, startTime,
endTime, endTime,
duration, duration,
...@@ -83,6 +85,7 @@ const { ...@@ -83,6 +85,7 @@ const {
switchCamera, switchCamera,
isMobile, isMobile,
} = useLive({ } = useLive({
keepChunks: props.isLocalUpload,
onStart: () => { onStart: () => {
console.log('开始直播') console.log('开始直播')
props.onStart && props.onStart() props.onStart && props.onStart()
...@@ -92,6 +95,7 @@ const { ...@@ -92,6 +95,7 @@ const {
} }
}, },
onRecord: async (blob) => { onRecord: async (blob) => {
totalSize += blob.size
if (props.isLocalUpload) return if (props.isLocalUpload) return
if (isOSSUpload) { if (isOSSUpload) {
ossResult = await appendUpload(blob, { ossResult = await appendUpload(blob, {
...@@ -114,7 +118,7 @@ const { ...@@ -114,7 +118,7 @@ const {
if (enabled.value) return if (enabled.value) return
props.onStop && props.onStop(blob) props.onStop && props.onStop(blob)
stopChat() stopChat()
handleUpdateRecord({ live_video_size: blob.size.toString() }) handleUpdateRecord({ live_video_size: (props.isLocalUpload ? blob.size : totalSize).toString() })
// 保存录像到本地 // 保存录像到本地
if (props.isLocalUpload) { if (props.isLocalUpload) {
saveAs(blob, `${fileName.value}.mp4`) saveAs(blob, `${fileName.value}.mp4`)
...@@ -201,12 +205,24 @@ const toggleLive = () => { ...@@ -201,12 +205,24 @@ const toggleLive = () => {
} }
watch(enabled, toggleLive) watch(enabled, toggleLive)
function start() { async function start() {
if (video.value) enabled.value = true if (!video.value) return
if (window.parent !== window) {
window.parent.postMessage({ action: 'monitor-pause' }, '*')
// 等父页面释放摄像头后再重新获取
await new Promise((r) => setTimeout(r, 500))
await restartStream()
}
enabled.value = true
} }
function stop() { function stop() {
if (video.value) { if (!video.value) return
enabled.value = false enabled.value = false
if (window.parent !== window) {
// 等录制完全停止后通知父页面恢复监控
setTimeout(() => {
window.parent.postMessage({ action: 'monitor-resume' }, '*')
}, 500)
} }
} }
defineExpose({ enabled, start, stop }) defineExpose({ enabled, start, stop })
...@@ -228,9 +244,18 @@ defineExpose({ enabled, start, stop }) ...@@ -228,9 +244,18 @@ defineExpose({ enabled, start, stop })
size="small" size="small"
@click="switchCamera" @click="switchCamera"
v-if="isMobile && !enabled && !recordId"> v-if="isMobile && !enabled && !recordId">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/> xmlns="http://www.w3.org/2000/svg"
<circle cx="12" cy="13" r="4"/> width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
<circle cx="12" cy="13" r="4" />
</svg> </svg>
</el-button> </el-button>
</div> </div>
......
...@@ -20,12 +20,13 @@ export const readBlobAsBase64 = (blob: Blob): Promise<string> => { ...@@ -20,12 +20,13 @@ export const readBlobAsBase64 = (blob: Blob): Promise<string> => {
interface UseLiveProps { interface UseLiveProps {
enabledUserMedia: boolean enabledUserMedia: boolean
keepChunks?: boolean
onStart?: () => void onStart?: () => void
onRecord?: (data: Blob) => void onRecord?: (data: Blob) => void
onStop?: (blob: Blob) => void onStop?: (blob: Blob) => void
} }
export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }: UseLiveProps) { export function useLive({ enabledUserMedia = true, keepChunks = true, onStart, onRecord, onStop }: UseLiveProps) {
const startTime = ref(0) const startTime = ref(0)
const endTime = ref(0) const endTime = ref(0)
const duration = computed(() => (endTime.value - startTime.value) / 1000) const duration = computed(() => (endTime.value - startTime.value) / 1000)
...@@ -52,7 +53,7 @@ export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }: ...@@ -52,7 +53,7 @@ export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }:
}) })
// 创建媒体流 // 创建媒体流
const { stream } = useUserMedia({ const { stream, restart: restartStream } = useUserMedia({
enabled: enabledUserMedia, enabled: enabledUserMedia,
constraints: constraints as any, constraints: constraints as any,
}) })
...@@ -75,7 +76,7 @@ export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }: ...@@ -75,7 +76,7 @@ export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }:
// 数据可用时处理 // 数据可用时处理
const handleDataAvailable = (event: BlobEvent) => { const handleDataAvailable = (event: BlobEvent) => {
if (event.data.size > 0) { if (event.data.size > 0) {
recordedChunks.value.push(event.data) if (keepChunks) recordedChunks.value.push(event.data)
onRecord && onRecord(event.data) onRecord && onRecord(event.data)
} }
} }
...@@ -125,5 +126,18 @@ export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }: ...@@ -125,5 +126,18 @@ export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }:
nextTick(() => initializeMediaRecorder()) nextTick(() => initializeMediaRecorder())
} }
return { stream, start, stop, startTime, endTime, duration, currentTime, switchCamera, facingMode, isFrontCamera, isMobile } return {
stream,
start,
stop,
restartStream,
startTime,
endTime,
duration,
currentTime,
switchCamera,
facingMode,
isFrontCamera,
isMobile,
}
} }
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论