音视频开发远端未发布视频占位图2
·
音视频开发时候,如果对方未发布视频,或者后面才发布视频,或停止发送流时,占位图的优势就有了;
考虑到大部分音视频SDK都会有play方法,但不一定有成员状态,如果我们自己创建canvas和视频有概率会有布局问题;最好还是给他弄成背景图片放后面,不管是对方流是空的(网络不好无法传流,或者有个别设备摄像头坏了无法编解码,或者本身就没摄像头给的空轨道);这个时候有个保底占位图就很舒服了;
总结一句话就是:比黑屏好;
效果图

调用API
/* 先new一个
@params: curDrawDiv: HTMLElement; 说明:需要绘制的dev;
@params: useName: String; 说明:用户姓名;
*/
const avatarSpace = new AvatarCanvasSpaceBj(curDrawDiv, useName);
// 开始绘制
avatarSpace.createCanvasBackground();
// 单独更新名字_并绘制
avatarSpace.updateUserName(useName);
// 销毁
avatarSpace.destroy();
核心方法
// 头像占位
class AvatarCameraSpace {
myCanvas: HTMLCanvasElement | null;
ctx: CanvasRenderingContext2D | null;
curDrawDiv: HTMLElement;
useName: string;
constructor(curDrawDiv: HTMLElement, useName: string) {
this.curDrawDiv = curDrawDiv;
this.useName = useName;
this.myCanvas = document.createElement('canvas');
this.ctx = this.myCanvas.getContext('2d');
}
// 更新用户名(相同则不处理)
updateUserName(useName: string) {
// 检查用户名是否相同,相同则不处理
if (useName === this.useName) return;
this.useName = useName;
this.drawCanvas();
}
// 创建canvas并设置为parentDiv的背景
createCanvasBackground() {
this.drawCanvas();
}
// 绘制用户头像占位并设置为背景
drawCanvas() {
// 先把原背景图给他置空掉
this.clearResources();
try {
if (!this.myCanvas || !this.ctx || !this.parentDivIsExists()) return;
const canvas = this.myCanvas;
const ctx = this.ctx;
const useName = this.useName;
const canvasWidth = 1280;
const canvasHeight = 720;
canvas.width = 1280;
canvas.height = 720;
// 保存上下文初始状态(重要:确保每次绘制都是干净的状态)
ctx.save();
// 重置变换矩阵
ctx.setTransform(1, 0, 0, 1, 0, 0);
// 清空画布
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// 绘制黑色背景
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// 计算蓝色方框的位置(居中)
const boxSize = Math.min(120, canvasWidth * 0.2, canvasHeight * 0.2); // 自适应大小
const boxX = (canvasWidth - boxSize) / 2;
const boxY = (canvasHeight - boxSize) / 2 - 50; // 稍微向上移动一点,给下方名字留出空间
// 圆角半径
const borderRadius = 15;
// 绘制蓝色方框
ctx.fillStyle = '#1689F4';
ctx.beginPath();
// 左上角圆角
ctx.moveTo(boxX + borderRadius, boxY);
// 上边缘
ctx.lineTo(boxX + boxSize - borderRadius, boxY);
// 右上角圆角
ctx.arcTo(boxX + boxSize, boxY, boxX + boxSize, boxY + borderRadius, borderRadius);
// 右边缘
ctx.lineTo(boxX + boxSize, boxY + boxSize - borderRadius);
// 右下角圆角
ctx.arcTo(boxX + boxSize, boxY + boxSize, boxX + boxSize - borderRadius, boxY + boxSize, borderRadius);
// 下边缘
ctx.lineTo(boxX + borderRadius, boxY + boxSize);
// 左下角圆角
ctx.arcTo(boxX, boxY + boxSize, boxX, boxY + boxSize - borderRadius, borderRadius);
// 左边缘
ctx.lineTo(boxX, boxY + borderRadius);
// 左上角收尾圆角
ctx.arcTo(boxX, boxY, boxX + borderRadius, boxY, borderRadius);
// 闭合路径并填充
ctx.closePath();
ctx.fill();
// 获取名字的第一个字
const firstChar = useName.charAt(0);
// 在蓝色方框中绘制第一个字(居中)
ctx.fillStyle = '#FFFFFF'; // 白色文字
ctx.font = `${Math.min(80, boxSize * 0.6)}px Arial`; // 自适应字体大小
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(firstChar, boxX + boxSize / 2, boxY + boxSize / 2);
// 在蓝色方框下方绘制全名
const nameFontSize = Math.min(40, boxSize * 0.3);
ctx.font = `${nameFontSize}px Arial`; // 自适应字体大小
const displayName = useName.length > 12 ? `${useName.substring(0, 12)}...` : useName;
ctx.fillText(displayName, canvasWidth / 2, boxY + boxSize + 60);
// 恢复上下文初始状态(确保后续绘制不受当前状态影响)
ctx.restore();
// 绘制完成后设置为背景
this.setCanvasAsBackground();
} catch(error) {
console.warn('绘制失败了', error);
}
}
// 将Canvas内容设置为parentDiv的背景图片
setCanvasAsBackground() {
try {
if (!this.myCanvas || !this.parentDivIsExists()) return;
// 将Canvas转换为图片URL
const dataUrl = this.myCanvas.toDataURL('image/png');
// 设置为parentDiv的背景图片
this.curDrawDiv.style.backgroundImage = `url(${dataUrl})`;
this.curDrawDiv.style.backgroundSize = 'cover';
this.curDrawDiv.style.backgroundPosition = 'center';
this.curDrawDiv.style.backgroundRepeat = 'no-repeat';
} catch(error) {
console.warn('设置背景失败了', error);
}
}
// 父元素是否还存在于DOM中
parentDivIsExists() {
return document.body.contains(this.curDrawDiv);
}
// 清除背景和资源
clearResources() {
try {
// 清除背景图
this.curDrawDiv.style.backgroundImage = 'none';
} catch(error) {}
}
// 销毁实例
destroy() {
this.clearResources();
// 释放引用
this.myCanvas = null;
this.ctx = null;
}
}
export default AvatarCameraSpace;
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)