不多說,我們直奔主題。這篇文章教大家如何編寫一個視頻聊天應(yīng)用,使已連接的兩用戶端能共享視頻和音頻。操作很簡單,非常適合JavaScript語言訓(xùn)練——更準(zhǔn)確地說是WebRTC技術(shù)和Node.js。
什么是WebRTC?
Web Real-Time Communications 網(wǎng)頁實時通信,簡稱WebRTC。WebRTC是一個HTML5規(guī)范,它允許用戶在瀏覽器之間直接進行實時通信,不需要任何第三方插件。WebRTC可用于多種情境(比如文件共享),但端對端實時音頻和視頻通信是其主要功能。本文將著重為大家介紹這兩項。
WebRTC所做的就是允許接入設(shè)備。你可以借WebRTC來實時使用麥克風(fēng)、攝像頭和分享你的屏幕。
所以,WebRTC可以用最簡單的方式在網(wǎng)頁中實現(xiàn)音頻和視頻通信。
WebRTC JavaScript API
WebRTC說起來很復(fù)雜,它涉及到很多技術(shù)。但建立連接、通信和傳輸數(shù)據(jù)的操作是通過一套JS API來實現(xiàn)的,還比較簡單。其中主要的API包括:
RTCPeerConnection:創(chuàng)建和導(dǎo)航端對端連接。
RTCSessionDescription:描述連接(或潛在連接)的一端,以及它的配置方式。
navigator.getUserMedia:捕捉音頻和視頻。
為什么選擇Node.js?
若要在兩個或多個設(shè)備之間進行遠程連接,你就需要一個服務(wù)器。在這種情況下,你也需要一個處理實時通信的服務(wù)器。Node.js是為實時可擴展的應(yīng)用而構(gòu)建的。要開發(fā)自由數(shù)據(jù)交換的雙向連接應(yīng)用程序,你可能會用到WebSockets,它允許在客戶端和服務(wù)器之間建立一個會話窗口。來自客戶端的請求會以循環(huán)的方式,更準(zhǔn)確地說是事件循環(huán)進行處理,這時Node.js是我們很好的一個選擇,因為它采取 “非阻塞(non-blocking) “的方式來解決請求。這樣我們在這該過程中就能實現(xiàn)低延遲和高吞吐量。
如果你對開發(fā)微服務(wù)感興趣的話,一定要看看查看我們內(nèi)含650多位微服務(wù)專家意見的2020年微服務(wù)狀態(tài)報告!
思路拓展:我們要創(chuàng)建的是什么?
我們會創(chuàng)建一個非常簡單的應(yīng)用程序,它能讓我們將音頻和視頻流傳輸?shù)竭B接的設(shè)備——一個基礎(chǔ)款視頻聊天應(yīng)用程序。我們會用到的技術(shù)有:
Express庫,提供靜態(tài)文件,比如代表用戶界面(UI)的HTML文件;
socket.io庫,在兩個設(shè)備之間用WebSockets建立連接;
WebRTC,允許媒體設(shè)備(攝像頭和麥克風(fēng))在連接的設(shè)備之間傳輸音頻和視頻流。
實現(xiàn)視頻會話
我們要做的第一件事是給我們的應(yīng)用程序提供一個作為UI的HTML文件。讓我們通過運行:npm init.js來初始化新的node.js項目。然后,我們需要通過運行:npm i -D typescript ts-node nodemon @types/express @types/socket.io安裝一些開發(fā)依賴項,運行:npm i express socket.io安裝生產(chǎn)依賴項。
之后我們就可以在package.json文件中定義腳本,來運行我們的項目了。
{
"scripts": {
"start": "ts-node src/index.ts",
"dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts"
},
"devDependencies": {
"@types/express": "^4.17.2",
"@types/socket.io": "^2.1.4",
"nodemon": "^1.19.4",
"ts-node": "^8.4.1",
"typescript": "^3.7.2"
},
"dependencies": {
"express": "^4.17.1",
"socket.io": "^2.3.0"
}
}
當(dāng)我們運行npm run dev命令時,nodemon會監(jiān)控src文件夾中每個以.ts結(jié)尾的文件有無任何變化。現(xiàn)在,我們要創(chuàng)建一個src文件夾。在這個文件夾中,我們會創(chuàng)建兩個typescript文件:index.ts和server.ts。
在server.ts中,我們會創(chuàng)建server類,并使其與express和socket.io一起工作。
import express, { Application } from "express";
import socketIO, { Server as SocketIOServer } from "socket.io";
import { createServer, Server as HTTPServer } from "http";
export class Server {
private httpServer: HTTPServer;
private app: Application;
private io: SocketIOServer;
private readonly DEFAULT_PORT = 5000;
constructor() {
this.initialize();
this.handleRoutes();
this.handleSocketConnection();
}
private initialize(): void {
this.app = express();
this.httpServer = createServer(this.app);
this.io = socketIO(this.httpServer);
}
private handleRoutes(): void {
this.app.get("/", (req, res) => {
res.send(`<h1>Hello World</h1>`);
});
}
private handleSocketConnection(): void {
this.io.on("connection", socket => {
console.log("Socket connected.");
});
}
public listen(callback: (port: number) => void): void {
this.httpServer.listen(this.DEFAULT_PORT, () =>
callback(this.DEFAULT_PORT)
);
}
}
為正常運行服務(wù)器,我們需要在index.ts文件中創(chuàng)建一個新的Server類實例并調(diào)用listen方法。
import { Server } from "./server";
const server = new Server();
server.listen(port => {
console.log(`Server is listening on http://localhost:${port}`);
});
現(xiàn)在,如果我們運行:npm run dev會看到下面這樣的情景:
當(dāng)打開瀏覽器,輸入http://localhost:5000,我們應(yīng)該注意到左上的 “Hello World “信息。
然后我們就可以在public/index.html中創(chuàng)建一個新的HTML文件了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Dogeller</title>
<link
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,500,700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="./styles.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
</head>
<body>
<div class="container">
<header class="header">
<div class="logo-container">
<img src="./img/doge.png" alt="doge logo" class="logo-img" />
<h1 class="logo-text">
Doge<span class="logo-highlight">ller</span>
</h1>
</div>
</header>
<div class="content-container">
<div class="active-users-panel" id="active-user-container">
<h3 class="panel-title">Active Users:</h3>
</div>
<div class="video-chat-container">
<h2 class="talk-info" id="talking-with-info">
Select active user on the left menu.
</h2>
<div class="video-container">
<video autoplay class="remote-video" id="remote-video"></video>
<video autoplay muted class="local-video" id="local-video"></video>
</div>
</div>
</div>
</div>
<script src="./scripts/index.js"></script>
</body>
</html>
在這個新文件中,我們創(chuàng)建了兩個視頻元素:一個用于遠程視頻連接,另一個用于本地視頻。你可能已經(jīng)注意到我們也在導(dǎo)入本地腳本了。現(xiàn)在我們就來創(chuàng)建一個新的文件夾“腳本”,并在這個目錄下創(chuàng)建index.js文件。至于樣式,你可以從GitHub庫中下載它們。
接下來你需要給瀏覽器提供index.html。首先,你需要告訴express你想提供哪些靜態(tài)文件。為了實現(xiàn)這一點,我們決定在Server類中實現(xiàn)一個新方法。
private configureApp(): void {
this.app.use(express.static(path.join(__dirname, "../public")));
}
不要忘記在initialize中調(diào)用configureApp。
private initialize(): void {
this.app = express();
this.httpServer = createServer(this.app);
this.io = socketIO(this.httpServer);
this.configureApp();
this.handleSocketConnection();
}
當(dāng)你輸入http://localhost:5000后,你應(yīng)該能看到你的index.html文件在運行。
下一步要實現(xiàn)的是允許攝像頭和視頻訪問并將其流式傳輸?shù)絣ocal-video元素。要做到這一點,你需要打開public/scripts/index.js文件,并用以下方法實現(xiàn)它。
navigator.getUserMedia(
{ video: true, audio: true },
stream => {
const localVideo = document.getElementById("local-video");
if (localVideo) {
localVideo.srcObject = stream;
}
},
error => {
console.warn(error.message);
}
);
當(dāng)回到瀏覽器時,界面會出現(xiàn)一個提示請求訪問你的媒體設(shè)備,在接受請求后,你電腦的攝像頭就開始工作了。
更多細節(jié)詳見A simple guide to concurrency in Node.js and a few traps that come with it。
如何處理socket連接?
接下來我們講講如何處理socket連接。我們需要將客戶端與服務(wù)器連接起來。為此,我們將使用socket.io。在public/scripts/index.js中,添加以下代碼:
this.io.on("connection", socket => {
const existingSocket = this.activeSockets.find(
existingSocket => existingSocket === socket.id
);
if (!existingSocket) {
this.activeSockets.push(socket.id);
socket.emit("update-user-list", {
users: this.activeSockets.filter(
existingSocket => existingSocket !== socket.id
)
});
socket.broadcast.emit("update-user-list", {
users: [socket.id]
});
}
}
頁面刷新后,電腦會彈出一條消息,顯示 “Socket已連接”
然后我們回到server.ts中,把已連接的socket存儲在內(nèi)存中,這只是為了保留唯一連接。所以,我們需要在Server類中添加一個新的私有字段,如下:
private activeSockets: string[] = [];
然后我們需要在socket連接中檢查socket是否已經(jīng)存在。如果不存在,把新的socket推送到內(nèi)存中,并向已連接的用戶發(fā)送數(shù)據(jù)。
this.io.on("connection", socket => {
const existingSocket = this.activeSockets.find(
existingSocket => existingSocket === socket.id
);
if (!existingSocket) {
this.activeSockets.push(socket.id);
socket.emit("update-user-list", {
users: this.activeSockets.filter(
existingSocket => existingSocket !== socket.id
)
});
socket.broadcast.emit("update-user-list", {
users: [socket.id]
});
}
}
你還需要在socket斷開連接時及時響應(yīng),所以在socket連接中,你需要添加:
socket.on("disconnect", () => {
this.activeSockets = this.activeSockets.filter(
existingSocket => existingSocket !== socket.id
);
socket.broadcast.emit("remove-user", {
socketId: socket.id
});
});
客戶端(即public/scripts/index.js)這邊,你需要妥善處理那些信息:
socket.on("update-user-list", ({ users }) => {
updateUserList(users);
});
socket.on("remove-user", ({ socketId }) => {
const elToRemove = document.getElementById(socketId);
if (elToRemove) {
elToRemove.remove();
}
});
以下是 updateUserList 函數(shù):
function updateUserList(socketIds) {
const activeUserContainer = document.getElementById("active-user-container");
socketIds.forEach(socketId => {
const alreadyExistingUser = document.getElementById(socketId);
if (!alreadyExistingUser) {
const userContainerEl = createUserItemContainer(socketId);
activeUserContainer.appendChild(userContainerEl);
}
});
}
以及createUserItemContainer函數(shù):
function createUserItemContainer(socketId) {
const userContainerEl = document.createElement("div");
const usernameEl = document.createElement("p");
userContainerEl.setAttribute("class", "active-user");
userContainerEl.setAttribute("id", socketId);
usernameEl.setAttribute("class", "username");
usernameEl.innerHTML = `Socket: ${socketId}`;
userContainerEl.appendChild(usernameEl);
userContainerEl.addEventListener("click", () => {
unselectUsersFromList();
userContainerEl.setAttribute("class", "active-user active-user--selected");
const talkingWithInfo = document.getElementById("talking-with-info");
talkingWithInfo.innerHTML = `Talking with: "Socket: ${socketId}"`;
callUser(socketId);
});
return userContainerEl;
}
需要注意的是,我們給用戶容器元素添加了一個可以調(diào)用callUser函數(shù)的點擊監(jiān)聽器——但現(xiàn)在,它可以是一個空的函數(shù)。接下來,當(dāng)運行兩個瀏覽器窗口(其中一個作為私人窗口)時,你應(yīng)該注意到你的Web應(yīng)用程序中有兩個已經(jīng)連接的socket。
點擊列表中的活躍用戶,這時我們需要調(diào)用callUser函數(shù)。但是在實現(xiàn)之前,你還需要在window對象中聲明兩個類。
const { RTCPeerConnection, RTCSessionDescription } = window;
我們會在callUser函數(shù)用到這兩個類:
async function callUser(socketId) {
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(new RTCSessionDescription(offer));
socket.emit("call-user", {
offer,
to: socketId
});
}
現(xiàn)在我們要創(chuàng)建一個本地請求并發(fā)送給選定的用戶。服務(wù)器會監(jiān)聽一個叫做call-user的事件、攔截請求并將其轉(zhuǎn)發(fā)給選定的用戶。讓我們用server.ts來實現(xiàn)該操作:
socket.on("call-user", data => {
socket.to(data.to).emit("call-made", {
offer: data.offer,
socket: socket.id
});
});
對于客戶端,你需要就call-made事件作出調(diào)整:
socket.on("call-made", async data => {
await peerConnection.setRemoteDescription(
new RTCSessionDescription(data.offer)
);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(new RTCSessionDescription(answer));
socket.emit("make-answer", {
answer,
to: data.socket
});
});
之后,在你從服務(wù)器得到的請求上設(shè)置一個遠程描述,并為這個請求創(chuàng)建一個答復(fù)。對于服務(wù)器端,你只需要將適當(dāng)?shù)臄?shù)據(jù)傳遞給選定的用戶即可。然后我們再在server.ts里面添加一個監(jiān)聽器。
socket.on("make-answer", data => {
socket.to(data.to).emit("answer-made", {
socket: socket.id,
answer: data.answer
});
});
對于客戶端,我們需要處理 answer-made 事件。
socket.on("answer-made", async data => {
await peerConnection.setRemoteDescription(
new RTCSessionDescription(data.answer)
);
if (!isAlreadyCalling) {
callUser(data.socket);
isAlreadyCalling = true;
}
});
我們可以使用標(biāo)志isAlreadyCalling,它能幫助確保我們只需調(diào)用一次用戶。
最后你需要做的是添加本地軌道,包括音頻和視頻到你的連接端。只有做到這一點,我們才能夠與連接的用戶共享視頻和音頻。要做到這一點,我們需要在navigator.getMediaDevice回調(diào)中調(diào)用peerConnection對象的addTrack函數(shù)。
navigator.getUserMedia(
{ video: true, audio: true },
stream => {
const localVideo = document.getElementById("local-video");
if (localVideo) {
localVideo.srcObject = stream;
}
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
},
error => {
console.warn(error.message);
}
);
另外,我們還需要為ontrack事件添加一個適當(dāng)?shù)奶幚沓绦颉?/p>
peerConnection.ontrack = function({ streams: [stream] }) {
const remoteVideo = document.getElementById("remote-video");
if (remoteVideo) {
remoteVideo.srcObject = stream;
}
};
如圖示,我們已經(jīng)從傳遞的對象中獲取了流,并改變了遠程視頻中的srcObject來使用接收到的流。所以現(xiàn)在當(dāng)你點擊活躍用戶后,你應(yīng)該建立一個視頻和音頻連接,像下圖這樣:
欲了解細節(jié),請參閱:Node.js and dependency injection – friends or foes?
現(xiàn)在你知道如何編寫一個視頻聊天應(yīng)用了吧!
WebRTC是一個很大的話題,內(nèi)容非常龐雜。如果你想了解它的運作原理,就需要花很大功夫。幸運的是,我們可以訪問易于使用的JavaScript API,它可以幫助我們創(chuàng)建很簡潔的應(yīng)用程序,例如視頻共享、聊天應(yīng)用程序等等。
如果你想深入了解WebRTC,點擊此WebRTC官方文檔的鏈接。另外,我也推薦你閱讀MDN的文檔說明,它能幫助你更加了解此技術(shù)。
聞一
中國移動公布中期業(yè)績:凈利潤606億元 同比增長5.6%
8月11日,中國移動今日發(fā)布2016年中期財報。今年上半年,中國移動營收3704億元,同比增長7.1%,凈利潤606億元,同比增長5.6%。
中國移動上半年通信服務(wù)收入為3254億元,同比增長6.9%,這一收入主要包括兩方面:1、語音收入1200億元,同比下滑14.2%;2、流量收入1950億元,同比增長26.7%,其中手機上網(wǎng)流量同比增長133.9%。
用戶數(shù)方面,移動用戶達到8.37億戶,同比增長2.4%。具體到4G,新增4G基站超過20萬個,總計達132萬個,4G用戶總數(shù)達到4.29億,滲透率達到51.2%,4G網(wǎng)絡(luò)流量占比也提升到88%。寬帶方面,上半年凈增1081萬戶,總數(shù)達到6584萬戶。
新聞二
迅雷第二季度凈虧損200萬美元
北京時間8月10日晚間消息,迅雷(Nasdaq:XNET)公布了截至6月30日的2016財年第二季度未經(jīng)審計財報。第二季度,迅雷總營收為3810萬美元,同比增長22.3%,環(huán)比下滑0.9%。
凈虧損400萬美元,而上一財季凈虧損為540萬美元。不按照美國通用會計準(zhǔn)則,凈虧損200萬美元,而上一財季凈虧損290萬美元。截至2016年6月30日,迅雷所持有的現(xiàn)金、現(xiàn)金等價物和短期投資總額為3.91億美元,相比之下截至2015年12月31日為4.321億美元。
業(yè)績展望:迅雷預(yù)計,2016財年第三季度總營收將達到3800萬美元至4200萬美元,中間值同比增長19.4%。
新聞三
谷歌新專利:用無人機搭建視頻會議系統(tǒng)
據(jù)外媒報道,谷歌新獲得一項專利顯示,未來將用無人機搭建視頻會議系統(tǒng)。而且該設(shè)備將能解決視頻不流暢、相機角度不合適的問題,使用戶感覺合作伙伴與自己身處同一間辦公室。
ebCodecs 是什么
Web 音視頻 API 存在什么問題
音視頻技術(shù)在 Web 平臺上的應(yīng)用非常廣泛,已有許多 Web API 間接調(diào)用了編解碼器來實現(xiàn)特定功能:
但沒有方法可以靈活配置或直接訪問編解碼器,所以許多應(yīng)用使用 JS 或 WASM (比如 ffmpeg.wasm)來實現(xiàn)編解碼功能,盡管存在諸多缺陷或限制:
這么做的原因是以前的 Web API 在特定場景都存在難以克服的障礙:
總結(jié):目前 API 在特定場景做到簡單、夠用,但無法實現(xiàn)高效且精細地控制
WebCodecs 設(shè)計目標(biāo)
非 WebCodecs 目標(biāo)
以上總結(jié)于 譯 WebCodecs 說明(https://hughfenghen.github.io/posts/2023/10/02/webcodecs-explainer/),讓大家快速了解 WebCodecs API 的背景和目標(biāo)
WebCodecs 能做什么
WebCodecs API 介紹
先了解 WebCodecs API 在視頻生產(chǎn)消費鏈路所處的位置
由圖可知 WebCodecs API 提供的能力:
以上就是 WebCodecs 提供的核心 API,新增 API 的數(shù)量非常少,主要難點在音視頻相關(guān)的背景知識。
利用 mp4box.js 解封裝 mp4 文件,得到 EncodedVideoChunk 后給 WebCodecs 解碼,即可實現(xiàn) mp4 -> 圖像幀。
WebCodecs 不涉及環(huán)節(jié)
音視頻生產(chǎn)消費鏈路中,由其他 Web API 提供,包括:
相關(guān) Web API
基于底層 API 可以構(gòu)建的基礎(chǔ)能力
基于 Web 平臺已有的能力,加上 WebCodecs 提供的編解碼能力,能幫助開發(fā)者實現(xiàn)那些功能呢?
DEMO 演示及實現(xiàn)
WebCodecs 是相對底層 API,簡單功能可能也需要寫非常多的輔助代碼,可以借助 WebAV 封裝的工具函數(shù)來快速實現(xiàn)功能
WebAV 基于 WebCodecs,提供簡單易用的 API 在瀏覽器中處理音視頻數(shù)據(jù)
接下來演示 DEMO 效果以及基于 WebAV 的代碼實現(xiàn)
1.可控解碼
以設(shè)備最快的速度解碼一個 20s 的視頻,并將視頻幀繪制到 Canvas 上
可控解碼的意義不只是它能實現(xiàn)超快速或逐幀播放視頻,而在于它能快速遍歷所有幀,這是視頻處理的基礎(chǔ)
,時長00:02
首先從 WebAV 導(dǎo)出一個 MP4Clip 對象,它只需要一個 MP4文件 URL 進行初始化
然后使用 tick 方法獲取到視頻幀,再繪制到 canvas 上
while true 表示不做任何等待,所以到底有多快取決于網(wǎng)絡(luò)下載和設(shè)備解碼的速度
import { MP4Clip } from '@webav/av-cliper'
// 傳入一個 mp4 文件流即可初始化
const clip = new MP4Clip((await fetch('<mp4 url>')).body)
await clip.ready
let time = 0
// 最快速度渲染視頻所有幀
while (true) {
const { state, video: videoFrame } = await clip.tick(time)
if (state === 'done') break
if (videoFrame != null && state === 'success') {
ctx.clearRect(0, 0, cvs.width, cvs.height)
// 繪制到 Canvas
ctx.drawImage(videoFrame, 0, 0, videoFrame.codedWidth, videoFrame.codedHeight)
// 注意,用完立即 close
videoFrame.close()
}
// 時間單位是 微秒,所以差不多每秒取 30 幀,丟掉多余的幀
time += 33000
}
clip.destroy()
2. 添加水印
給視頻添加隨時間移動的半透明文字水印
,時長00:23
先把文字轉(zhuǎn)換成圖片,這樣很容易借助 css 實現(xiàn)各種文字效果;
然后控制圖片按照一定規(guī)則移動,這里省略了動畫的配置;
動畫配置方法跟 css 的動畫幾乎是一樣的,只需提供 0%,50% 特定時機的坐標(biāo)就行了,WebAV 會自動計算出中間狀態(tài)的坐標(biāo)值,來實現(xiàn)動畫效果;
最后將 MP4Clip 跟 ImgClip 合成輸出一個新的視頻流
const spr1 = new OffscreenSprite(
new MP4Clip((await fetch('<mp4 url>')).body)
)
const spr2 = new OffscreenSprite(
new ImgClip('水印')
)
spr2.setAnimation(/* animation config */)
const com = new Combinator()
await com.add(spr1, { main: true })
await com.add(spr2, { offset: 0 })
// com.ouput() => 輸出視頻流
3. 綠幕摳圖
帶綠幕的數(shù)字人形象與背景圖片合成視頻,使用 WebGL 對每幀圖像進行處理,將人物背景修改為透明效果
摳圖實現(xiàn)參考文章:WebGL Chromakey 實時綠幕摳圖(https://hughfenghen.github.io/posts/2023/07/07/webgl-chromakey/)
,時長00:06
// 創(chuàng)建摳圖工具函數(shù)
const chromakey = createChromakey(/* 綠幕摳圖配置 */)
// 背景綠幕的測試視頻
const clip = new MP4Clip((await fetch('<mp4 url>')).body)
// MP4 的每一幀 都會經(jīng)過 tickInterceptor
clip.tickInterceptor = async (_, tickRet) => {
if (tickRet.video == null) return tickRet
return {
...tickRet,
// 摳圖之后再返回
video: await chromakey(tickRet.video)
}
}
4. 花影
在瀏覽器中運行的視頻錄制工具,可用于視頻課程制作、直播推流工作臺
視頻演示視頻課程制作的基本操作,包含 “添加攝像頭、分享屏幕、修改素材層級、剪切視頻片段、預(yù)覽導(dǎo)出視頻” 五個步驟
,時長00:53
WebCodecs 的應(yīng)用場景
應(yīng)用場景預(yù)測
視頻生產(chǎn):從零到一
由于缺失編碼能力,導(dǎo)致 Web 端少有視頻生產(chǎn)工具;
現(xiàn)有的 Web 視頻剪輯工具都強依賴服務(wù)端能力支持,交互體驗存在優(yōu)化空間;
在 Web 頁面借助 Canvas 制作動畫是非常簡單的,借助 WebCodecs 的編碼能力,現(xiàn)在就能將動畫快速保存為視頻。
視頻裁剪、添加水印、內(nèi)嵌字幕等基礎(chǔ)視頻剪輯能力,沒有 WebCodecs 都是難以實現(xiàn)的,WebCodecs 將填補該領(lǐng)域的空白。
視頻消費:能力增強
借助 HTMLMediaElement、MSE,Web 平臺的視頻消費應(yīng)用已經(jīng)非常成熟;
以上 API 雖然簡單易用,但無法控制細節(jié),常有美中不足之感
比如,緩沖延遲控制、逐幀播放、超快速播放、解碼控制等
WebCodecs 將支持構(gòu)建更強、體驗更好的視頻消費應(yīng)用
算力轉(zhuǎn)移:成本體驗雙贏
目前 Web 使用的音視頻服務(wù),其處理過程都是在服務(wù)器上完成的
比如,眾多在線視頻處理工具提供的:壓縮(降低分辨率、碼率)、水印、變速、預(yù)覽圖 功能
處理流程:用戶上傳視頻 -> 服務(wù)器處理 -> 用戶下載視頻;
整個過程消耗了服務(wù)器的計算成本、帶寬成本,用戶上傳下載的等待時間
WebCodecs 能讓更多的任務(wù)在本地運行,不僅降低了服務(wù)運營成本,還能提升用戶體驗
案例分享
沒有 WebCodecs 以上的工具已經(jīng)存在了,為什么相信它們會應(yīng)用 WebCodecs?
首先,有了 WebCodecs 之后這些工具能做到體驗更好、更便宜、迭代更快;
再結(jié)合以往經(jīng)驗和 Web 平臺所具備優(yōu)勢,相信 WebCodecs 未來會得到廣泛應(yīng)用
分享兩個例子
1. 用戶視頻消費行為變化
2. 富文本編輯
Web 開放了幾個核心 API,讓大部分文字編輯轉(zhuǎn)移到線上,產(chǎn)生大量優(yōu)秀的知識管理應(yīng)用
借助 Web 的易訪問性、搭配協(xié)同編輯,將生產(chǎn)溝通效率提升了一個等級
還有大量產(chǎn)品案例:Notion、Figma、VSCode...
總結(jié):一旦 Web 平臺具備某個領(lǐng)域的基礎(chǔ)能力,相關(guān)產(chǎn)品不可避免的 Web 化
WebCodecs 的優(yōu)勢與限制
優(yōu)勢
性能
ffmpeg.wasm 最大的障礙就是性能問題,導(dǎo)致難以大規(guī)模應(yīng)用,主要是因為它不能使用硬件加速所以編解碼非常慢
測試簡單的視頻編碼場景,WebCodecs 的性能是 ffmpeg.wasm 的 20 倍
Web 平臺
Web 平臺天然具有的優(yōu)勢:跨平臺、便捷性、迭代效率
再加上底層能力越來越完善,已具備構(gòu)建大型、專業(yè)軟件的條件;
相信 WebCodecs 也能憑借 Web 平臺的加持,獲得更大的應(yīng)用空間
限制
生態(tài)不成熟只需要時間和更多開發(fā)者的積極參與,一般 to B 產(chǎn)品對兼容性會更寬容一些,to C 的產(chǎn)品可以降級到服務(wù)端實現(xiàn)
比較麻煩的是 Web 平臺提供的編解碼器相對 Native 直接調(diào)用來說,還是有一些差距
如果需要自定義編解碼器,或?qū)幗獯a器的參數(shù)配置有非常高的要求,技術(shù)方案選擇的時候需要慎重考慮 WebCodecs
愿景
附錄
作者:劉俊
來源:微信公眾號:嗶哩嗶哩技術(shù)
出處:https://mp.weixin.qq.com/s/d28Xq9dticMdbO0s0N5MzA
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。