Vue3 終于可以共享自己的屏幕給別人看了!
作者:林三心不學(xué)挖掘機(jī)
現(xiàn)代瀏覽器通過 ??MediaDevices?? 接口提供媒體設(shè)備訪問能力,其中 getDisplayMedia 方法專門用于獲取屏幕共享流。該方法會(huì)觸發(fā)用戶授權(quán)流程,成功后可獲得包含屏幕內(nèi)容的媒體流對象。
一、實(shí)現(xiàn)原理與技術(shù)背景
1.1 瀏覽器媒體能力基礎(chǔ)
現(xiàn)代瀏覽器通過 MediaDevices
接口提供媒體設(shè)備訪問能力,其中 getDisplayMedia 方法專門用于獲取屏幕共享流。該方法會(huì)觸發(fā)用戶授權(quán)流程,成功后可獲得包含屏幕內(nèi)容的媒體流對象。
1.2 響應(yīng)式設(shè)計(jì)目標(biāo)
我們需要實(shí)現(xiàn)以下核心功能:
- 響應(yīng)式狀態(tài)管理: 實(shí)時(shí)反映屏幕共享狀態(tài)
- 自動(dòng)資源回收: 組件卸載時(shí)自動(dòng)釋放媒體資源
- 錯(cuò)誤處理機(jī)制: 友好的錯(cuò)誤提示與恢復(fù)能力
- 跨瀏覽器兼容: 統(tǒng)一API差異處理
二、核心實(shí)現(xiàn)步驟分解
2.1 響應(yīng)式狀態(tài)容器
// 創(chuàng)建響應(yīng)式狀態(tài)容器
import { ref, reactive } from 'vue';
const state = reactive({
stream: null, // 當(dāng)前媒體流對象
isActive: false, // 是否正在共享
isSupported: !!navigator.mediaDevices?.getDisplayMedia, // 瀏覽器支持狀態(tài)
error: null // 錯(cuò)誤信息
});
2.2 屏幕捕獲核心邏輯
// 啟動(dòng)屏幕共享
asyncfunction startCapture(options = { video: true, audio: false }) {
try {
// 請求屏幕共享權(quán)限
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
displaySurface: 'browser', // 捕獲瀏覽器標(biāo)簽頁
logicalSurface: true, // 包含滾動(dòng)區(qū)域
...options.video
},
audio: options.audio
});
// 更新響應(yīng)式狀態(tài)
state.stream = stream;
state.isActive = true;
// 監(jiān)聽停止事件(用戶點(diǎn)擊瀏覽器停止按鈕)
stream.getTracks().forEach(track => {
track.onended = () => stopCapture();
});
} catch (err) {
handleError(err);
}
}
// 停止屏幕共享
function stopCapture() {
state.stream?.getTracks().forEach(track => track.stop());
state.stream = null;
state.isActive = false;
}
2.3 視頻元素綁定
// 自動(dòng)綁定媒體流到video元素
export function useVideoBinding(videoRef) {
watch(() => state.stream, (newStream) => {
if (videoRef.value && newStream) {
videoRef.value.srcObject = newStream;
videoRef.value.play().catch(err => {
console.error('視頻播放失敗:', err);
});
}
});
}
三、完整實(shí)現(xiàn)代碼示例
3.1 屏幕捕獲Composable
// screen-capture.js
import { ref, reactive, watch, onUnmounted } from'vue';
// 響應(yīng)式狀態(tài)管理
const state = reactive({
stream: null,
isActive: false,
isSupported: checkSupport(),
error: null
});
// 瀏覽器支持檢測
function checkSupport() {
return !!(
navigator.mediaDevices &&
navigator.mediaDevices.getDisplayMedia &&
window.MediaStream
);
}
// 錯(cuò)誤處理中心
function handleError(error) {
state.error = {
name: error.name,
message: error.message || '屏幕共享失敗',
details: error
};
console.error('屏幕共享錯(cuò)誤:', error);
}
// 啟動(dòng)屏幕共享
asyncfunction start(options = {}) {
if (!state.isSupported) {
thrownewError('當(dāng)前瀏覽器不支持屏幕共享');
}
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
displaySurface: 'browser',
logicalSurface: true,
...(options.video || {})
},
audio: !!options.audio
});
// 初始化狀態(tài)
state.stream = stream;
state.isActive = true;
state.error = null;
// 自動(dòng)停止監(jiān)聽
stream.getTracks().forEach(track => {
track.onended = () => stop();
});
return stream;
} catch (error) {
handleError(error);
throw error;
}
}
// 停止共享
function stop() {
if (state.stream) {
state.stream.getTracks().forEach(track => track.stop());
state.stream = null;
state.isActive = false;
}
}
// 自動(dòng)清理資源
onUnmounted(() => {
stop();
});
exportfunction useScreenCapture() {
return {
state,
start,
stop
};
}
3.2 組件集成示例
<template>
<div class="screen-share">
<video ref="videoEl" autoplay muted playsinline />
<button @click="toggleShare" :disabled="!state.isSupported">
{{ state.isActive ? '停止共享' : '開始共享' }}
</button>
<div v-if="state.error" class="error">
{{ state.error.message }}
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from'vue';
import { useScreenCapture, useVideoBinding } from'./screen-capture';
const videoEl = ref(null);
const { state, start, stop } = useScreenCapture();
// 自動(dòng)綁定視頻元素
useVideoBinding(videoEl);
const toggleShare = async () => {
if (state.isActive) {
stop();
} else {
try {
await start({
video: {
width: { ideal: 1920 },
frameRate: { ideal: 30 }
}
});
} catch (error) {
console.error('共享失敗:', error);
}
}
};
</script>
責(zé)任編輯:武曉燕
來源:
前端之神