Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
不多說,我們直奔主題。這篇文章教大家如何編寫一個視頻聊天應用,使已連接的兩用戶端能共享視頻和音頻。操作很簡單,非常適合JavaScript語言訓練——更準確地說是WebRTC技術和Node.js。
什么是WebRTC?
Web Real-Time Communications 網頁實時通信,簡稱WebRTC。WebRTC是一個HTML5規范,它允許用戶在瀏覽器之間直接進行實時通信,不需要任何第三方插件。WebRTC可用于多種情境(比如文件共享),但端對端實時音頻和視頻通信是其主要功能。本文將著重為大家介紹這兩項。
WebRTC所做的就是允許接入設備。你可以借WebRTC來實時使用麥克風、攝像頭和分享你的屏幕。
所以,WebRTC可以用最簡單的方式在網頁中實現音頻和視頻通信。
WebRTC JavaScript API
WebRTC說起來很復雜,它涉及到很多技術。但建立連接、通信和傳輸數據的操作是通過一套JS API來實現的,還比較簡單。其中主要的API包括:
RTCPeerConnection:創建和導航端對端連接。
RTCSessionDescription:描述連接(或潛在連接)的一端,以及它的配置方式。
navigator.getUserMedia:捕捉音頻和視頻。
為什么選擇Node.js?
若要在兩個或多個設備之間進行遠程連接,你就需要一個服務器。在這種情況下,你也需要一個處理實時通信的服務器。Node.js是為實時可擴展的應用而構建的。要開發自由數據交換的雙向連接應用程序,你可能會用到WebSockets,它允許在客戶端和服務器之間建立一個會話窗口。來自客戶端的請求會以循環的方式,更準確地說是事件循環進行處理,這時Node.js是我們很好的一個選擇,因為它采取 “非阻塞(non-blocking) “的方式來解決請求。這樣我們在這該過程中就能實現低延遲和高吞吐量。
如果你對開發微服務感興趣的話,一定要看看查看我們內含650多位微服務專家意見的2020年微服務狀態報告!
思路拓展:我們要創建的是什么?
我們會創建一個非常簡單的應用程序,它能讓我們將音頻和視頻流傳輸到連接的設備——一個基礎款視頻聊天應用程序。我們會用到的技術有:
Express庫,提供靜態文件,比如代表用戶界面(UI)的HTML文件;
socket.io庫,在兩個設備之間用WebSockets建立連接;
WebRTC,允許媒體設備(攝像頭和麥克風)在連接的設備之間傳輸音頻和視頻流。
實現視頻會話
我們要做的第一件事是給我們的應用程序提供一個作為UI的HTML文件。讓我們通過運行:npm init.js來初始化新的node.js項目。然后,我們需要通過運行:npm i -D typescript ts-node nodemon @types/express @types/socket.io安裝一些開發依賴項,運行:npm i express socket.io安裝生產依賴項。
之后我們就可以在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"
}
}
當我們運行npm run dev命令時,nodemon會監控src文件夾中每個以.ts結尾的文件有無任何變化。現在,我們要創建一個src文件夾。在這個文件夾中,我們會創建兩個typescript文件:index.ts和server.ts。
在server.ts中,我們會創建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)
);
}
}
為正常運行服務器,我們需要在index.ts文件中創建一個新的Server類實例并調用listen方法。
import { Server } from "./server";
const server = new Server();
server.listen(port => {
console.log(`Server is listening on http://localhost:${port}`);
});
現在,如果我們運行:npm run dev會看到下面這樣的情景:
當打開瀏覽器,輸入http://localhost:5000,我們應該注意到左上的 “Hello World “信息。
然后我們就可以在public/index.html中創建一個新的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>
在這個新文件中,我們創建了兩個視頻元素:一個用于遠程視頻連接,另一個用于本地視頻。你可能已經注意到我們也在導入本地腳本了。現在我們就來創建一個新的文件夾“腳本”,并在這個目錄下創建index.js文件。至于樣式,你可以從GitHub庫中下載它們。
接下來你需要給瀏覽器提供index.html。首先,你需要告訴express你想提供哪些靜態文件。為了實現這一點,我們決定在Server類中實現一個新方法。
private configureApp(): void {
this.app.use(express.static(path.join(__dirname, "../public")));
}
不要忘記在initialize中調用configureApp。
private initialize(): void {
this.app = express();
this.httpServer = createServer(this.app);
this.io = socketIO(this.httpServer);
this.configureApp();
this.handleSocketConnection();
}
當你輸入http://localhost:5000后,你應該能看到你的index.html文件在運行。
下一步要實現的是允許攝像頭和視頻訪問并將其流式傳輸到local-video元素。要做到這一點,你需要打開public/scripts/index.js文件,并用以下方法實現它。
navigator.getUserMedia(
{ video: true, audio: true },
stream => {
const localVideo = document.getElementById("local-video");
if (localVideo) {
localVideo.srcObject = stream;
}
},
error => {
console.warn(error.message);
}
);
當回到瀏覽器時,界面會出現一個提示請求訪問你的媒體設備,在接受請求后,你電腦的攝像頭就開始工作了。
更多細節詳見A simple guide to concurrency in Node.js and a few traps that come with it。
如何處理socket連接?
接下來我們講講如何處理socket連接。我們需要將客戶端與服務器連接起來。為此,我們將使用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存儲在內存中,這只是為了保留唯一連接。所以,我們需要在Server類中添加一個新的私有字段,如下:
private activeSockets: string[] = [];
然后我們需要在socket連接中檢查socket是否已經存在。如果不存在,把新的socket推送到內存中,并向已連接的用戶發送數據。
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斷開連接時及時響應,所以在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 函數:
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函數:
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;
}
需要注意的是,我們給用戶容器元素添加了一個可以調用callUser函數的點擊監聽器——但現在,它可以是一個空的函數。接下來,當運行兩個瀏覽器窗口(其中一個作為私人窗口)時,你應該注意到你的Web應用程序中有兩個已經連接的socket。
點擊列表中的活躍用戶,這時我們需要調用callUser函數。但是在實現之前,你還需要在window對象中聲明兩個類。
const { RTCPeerConnection, RTCSessionDescription } = window;
我們會在callUser函數用到這兩個類:
async function callUser(socketId) {
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(new RTCSessionDescription(offer));
socket.emit("call-user", {
offer,
to: socketId
});
}
現在我們要創建一個本地請求并發送給選定的用戶。服務器會監聽一個叫做call-user的事件、攔截請求并將其轉發給選定的用戶。讓我們用server.ts來實現該操作:
socket.on("call-user", data => {
socket.to(data.to).emit("call-made", {
offer: data.offer,
socket: socket.id
});
});
對于客戶端,你需要就call-made事件作出調整:
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
});
});
之后,在你從服務器得到的請求上設置一個遠程描述,并為這個請求創建一個答復。對于服務器端,你只需要將適當的數據傳遞給選定的用戶即可。然后我們再在server.ts里面添加一個監聽器。
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;
}
});
我們可以使用標志isAlreadyCalling,它能幫助確保我們只需調用一次用戶。
最后你需要做的是添加本地軌道,包括音頻和視頻到你的連接端。只有做到這一點,我們才能夠與連接的用戶共享視頻和音頻。要做到這一點,我們需要在navigator.getMediaDevice回調中調用peerConnection對象的addTrack函數。
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事件添加一個適當的處理程序。
peerConnection.ontrack = function({ streams: [stream] }) {
const remoteVideo = document.getElementById("remote-video");
if (remoteVideo) {
remoteVideo.srcObject = stream;
}
};
如圖示,我們已經從傳遞的對象中獲取了流,并改變了遠程視頻中的srcObject來使用接收到的流。所以現在當你點擊活躍用戶后,你應該建立一個視頻和音頻連接,像下圖這樣:
欲了解細節,請參閱:Node.js and dependency injection – friends or foes?
現在你知道如何編寫一個視頻聊天應用了吧!
WebRTC是一個很大的話題,內容非常龐雜。如果你想了解它的運作原理,就需要花很大功夫。幸運的是,我們可以訪問易于使用的JavaScript API,它可以幫助我們創建很簡潔的應用程序,例如視頻共享、聊天應用程序等等。
如果你想深入了解WebRTC,點擊此WebRTC官方文檔的鏈接。另外,我也推薦你閱讀MDN的文檔說明,它能幫助你更加了解此技術。
聞一
中國移動公布中期業績:凈利潤606億元 同比增長5.6%
8月11日,中國移動今日發布2016年中期財報。今年上半年,中國移動營收3704億元,同比增長7.1%,凈利潤606億元,同比增長5.6%。
中國移動上半年通信服務收入為3254億元,同比增長6.9%,這一收入主要包括兩方面:1、語音收入1200億元,同比下滑14.2%;2、流量收入1950億元,同比增長26.7%,其中手機上網流量同比增長133.9%。
用戶數方面,移動用戶達到8.37億戶,同比增長2.4%。具體到4G,新增4G基站超過20萬個,總計達132萬個,4G用戶總數達到4.29億,滲透率達到51.2%,4G網絡流量占比也提升到88%。寬帶方面,上半年凈增1081萬戶,總數達到6584萬戶。
新聞二
迅雷第二季度凈虧損200萬美元
北京時間8月10日晚間消息,迅雷(Nasdaq:XNET)公布了截至6月30日的2016財年第二季度未經審計財報。第二季度,迅雷總營收為3810萬美元,同比增長22.3%,環比下滑0.9%。
凈虧損400萬美元,而上一財季凈虧損為540萬美元。不按照美國通用會計準則,凈虧損200萬美元,而上一財季凈虧損290萬美元。截至2016年6月30日,迅雷所持有的現金、現金等價物和短期投資總額為3.91億美元,相比之下截至2015年12月31日為4.321億美元。
業績展望:迅雷預計,2016財年第三季度總營收將達到3800萬美元至4200萬美元,中間值同比增長19.4%。
新聞三
谷歌新專利:用無人機搭建視頻會議系統
據外媒報道,谷歌新獲得一項專利顯示,未來將用無人機搭建視頻會議系統。而且該設備將能解決視頻不流暢、相機角度不合適的問題,使用戶感覺合作伙伴與自己身處同一間辦公室。
ebCodecs 是什么
Web 音視頻 API 存在什么問題
音視頻技術在 Web 平臺上的應用非常廣泛,已有許多 Web API 間接調用了編解碼器來實現特定功能:
但沒有方法可以靈活配置或直接訪問編解碼器,所以許多應用使用 JS 或 WASM (比如 ffmpeg.wasm)來實現編解碼功能,盡管存在諸多缺陷或限制:
這么做的原因是以前的 Web API 在特定場景都存在難以克服的障礙:
總結:目前 API 在特定場景做到簡單、夠用,但無法實現高效且精細地控制
WebCodecs 設計目標
非 WebCodecs 目標
以上總結于 譯 WebCodecs 說明(https://hughfenghen.github.io/posts/2023/10/02/webcodecs-explainer/),讓大家快速了解 WebCodecs API 的背景和目標
WebCodecs 能做什么
WebCodecs API 介紹
先了解 WebCodecs API 在視頻生產消費鏈路所處的位置
由圖可知 WebCodecs API 提供的能力:
以上就是 WebCodecs 提供的核心 API,新增 API 的數量非常少,主要難點在音視頻相關的背景知識。
利用 mp4box.js 解封裝 mp4 文件,得到 EncodedVideoChunk 后給 WebCodecs 解碼,即可實現 mp4 -> 圖像幀。
WebCodecs 不涉及環節
音視頻生產消費鏈路中,由其他 Web API 提供,包括:
相關 Web API
基于底層 API 可以構建的基礎能力
基于 Web 平臺已有的能力,加上 WebCodecs 提供的編解碼能力,能幫助開發者實現那些功能呢?
DEMO 演示及實現
WebCodecs 是相對底層 API,簡單功能可能也需要寫非常多的輔助代碼,可以借助 WebAV 封裝的工具函數來快速實現功能
WebAV 基于 WebCodecs,提供簡單易用的 API 在瀏覽器中處理音視頻數據
接下來演示 DEMO 效果以及基于 WebAV 的代碼實現
1.可控解碼
以設備最快的速度解碼一個 20s 的視頻,并將視頻幀繪制到 Canvas 上
可控解碼的意義不只是它能實現超快速或逐幀播放視頻,而在于它能快速遍歷所有幀,這是視頻處理的基礎
,時長00:02
首先從 WebAV 導出一個 MP4Clip 對象,它只需要一個 MP4文件 URL 進行初始化
然后使用 tick 方法獲取到視頻幀,再繪制到 canvas 上
while true 表示不做任何等待,所以到底有多快取決于網絡下載和設備解碼的速度
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
先把文字轉換成圖片,這樣很容易借助 css 實現各種文字效果;
然后控制圖片按照一定規則移動,這里省略了動畫的配置;
動畫配置方法跟 css 的動畫幾乎是一樣的,只需提供 0%,50% 特定時機的坐標就行了,WebAV 會自動計算出中間狀態的坐標值,來實現動畫效果;
最后將 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. 綠幕摳圖
帶綠幕的數字人形象與背景圖片合成視頻,使用 WebGL 對每幀圖像進行處理,將人物背景修改為透明效果
摳圖實現參考文章:WebGL Chromakey 實時綠幕摳圖(https://hughfenghen.github.io/posts/2023/07/07/webgl-chromakey/)
,時長00:06
// 創建摳圖工具函數
const chromakey = createChromakey(/* 綠幕摳圖配置 */)
// 背景綠幕的測試視頻
const clip = new MP4Clip((await fetch('<mp4 url>')).body)
// MP4 的每一幀 都會經過 tickInterceptor
clip.tickInterceptor = async (_, tickRet) => {
if (tickRet.video == null) return tickRet
return {
...tickRet,
// 摳圖之后再返回
video: await chromakey(tickRet.video)
}
}
4. 花影
在瀏覽器中運行的視頻錄制工具,可用于視頻課程制作、直播推流工作臺
視頻演示視頻課程制作的基本操作,包含 “添加攝像頭、分享屏幕、修改素材層級、剪切視頻片段、預覽導出視頻” 五個步驟
,時長00:53
WebCodecs 的應用場景
應用場景預測
視頻生產:從零到一
由于缺失編碼能力,導致 Web 端少有視頻生產工具;
現有的 Web 視頻剪輯工具都強依賴服務端能力支持,交互體驗存在優化空間;
在 Web 頁面借助 Canvas 制作動畫是非常簡單的,借助 WebCodecs 的編碼能力,現在就能將動畫快速保存為視頻。
視頻裁剪、添加水印、內嵌字幕等基礎視頻剪輯能力,沒有 WebCodecs 都是難以實現的,WebCodecs 將填補該領域的空白。
視頻消費:能力增強
借助 HTMLMediaElement、MSE,Web 平臺的視頻消費應用已經非常成熟;
以上 API 雖然簡單易用,但無法控制細節,常有美中不足之感
比如,緩沖延遲控制、逐幀播放、超快速播放、解碼控制等
WebCodecs 將支持構建更強、體驗更好的視頻消費應用
算力轉移:成本體驗雙贏
目前 Web 使用的音視頻服務,其處理過程都是在服務器上完成的
比如,眾多在線視頻處理工具提供的:壓縮(降低分辨率、碼率)、水印、變速、預覽圖 功能
處理流程:用戶上傳視頻 -> 服務器處理 -> 用戶下載視頻;
整個過程消耗了服務器的計算成本、帶寬成本,用戶上傳下載的等待時間
WebCodecs 能讓更多的任務在本地運行,不僅降低了服務運營成本,還能提升用戶體驗
案例分享
沒有 WebCodecs 以上的工具已經存在了,為什么相信它們會應用 WebCodecs?
首先,有了 WebCodecs 之后這些工具能做到體驗更好、更便宜、迭代更快;
再結合以往經驗和 Web 平臺所具備優勢,相信 WebCodecs 未來會得到廣泛應用
分享兩個例子
1. 用戶視頻消費行為變化
2. 富文本編輯
Web 開放了幾個核心 API,讓大部分文字編輯轉移到線上,產生大量優秀的知識管理應用
借助 Web 的易訪問性、搭配協同編輯,將生產溝通效率提升了一個等級
還有大量產品案例:Notion、Figma、VSCode...
總結:一旦 Web 平臺具備某個領域的基礎能力,相關產品不可避免的 Web 化
WebCodecs 的優勢與限制
優勢
性能
ffmpeg.wasm 最大的障礙就是性能問題,導致難以大規模應用,主要是因為它不能使用硬件加速所以編解碼非常慢
測試簡單的視頻編碼場景,WebCodecs 的性能是 ffmpeg.wasm 的 20 倍
Web 平臺
Web 平臺天然具有的優勢:跨平臺、便捷性、迭代效率
再加上底層能力越來越完善,已具備構建大型、專業軟件的條件;
相信 WebCodecs 也能憑借 Web 平臺的加持,獲得更大的應用空間
限制
生態不成熟只需要時間和更多開發者的積極參與,一般 to B 產品對兼容性會更寬容一些,to C 的產品可以降級到服務端實現
比較麻煩的是 Web 平臺提供的編解碼器相對 Native 直接調用來說,還是有一些差距
如果需要自定義編解碼器,或對編解碼器的參數配置有非常高的要求,技術方案選擇的時候需要慎重考慮 WebCodecs
愿景
附錄
作者:劉俊
來源:微信公眾號:嗶哩嗶哩技術
出處:https://mp.weixin.qq.com/s/d28Xq9dticMdbO0s0N5MzA
*請認真填寫需求信息,我們會在24小時內與您取得聯系。