如何修改Chrome画中画扩展实现无固定宽高比调整窗口?
修改Google PIP扩展实现自由拉伸窗口(无固定宽高比)
要实现PIP窗口自由拉伸不锁宽高比,你不能只改现有的ResizeObserver逻辑——因为Chrome原生的Picture-in-Picture API会强制保持视频元素的固有宽高比,直接操作video元素的PIP绕不开这个限制。得换个思路:用Canvas中转视频流,把Canvas放进PIP窗口,这样就能自由调整尺寸了。
具体修改方案
替换原扩展中requestPictureInPicture和maybeUpdatePictureInPictureVideo函数,同时添加必要的辅助逻辑:
// 全局变量:中转用Canvas、当前关联视频、帧绘制定时器 let pipCanvas = null; let currentPipVideo = null; let drawFrameTimer = null; async function requestPictureInPicture(video) { // 清理之前的PIP资源 if (pipCanvas) cleanupPipResources(); currentPipVideo = video; // 创建隐藏的Canvas元素 pipCanvas = document.createElement('canvas'); pipCanvas.style.display = 'none'; document.body.appendChild(pipCanvas); // 初始同步视频尺寸到Canvas updateCanvasSize(video.videoWidth, video.videoHeight); // 绑定视频播放状态监听,控制帧绘制 video.addEventListener('play', startDrawingFrames); video.addEventListener('pause', stopDrawingFrames); video.addEventListener('ended', stopDrawingFrames); // 请求Canvas进入PIP模式 await pipCanvas.requestPictureInPicture(); pipCanvas.setAttribute('__pip__', true); // 监听PIP退出事件,清理资源 pipCanvas.addEventListener('leavepictureinpicture', cleanupPipResources, { once: true }); // 监听Canvas尺寸变化(PIP窗口被拉伸时) new ResizeObserver(handlePipResize).observe(pipCanvas); } // 处理PIP窗口拉伸:直接同步Canvas绘制尺寸,不保持比例 function handlePipResize(entries) { const canvas = entries[0].target; const size = entries[0].contentBoxSize[0]; canvas.width = size.inlineSize; canvas.height = size.blockSize; } // 更新Canvas基础尺寸 function updateCanvasSize(width, height) { if (pipCanvas) { pipCanvas.width = width; pipCanvas.height = height; } } // 开始定时绘制视频帧到Canvas(拉伸填充) function startDrawingFrames() { if (!drawFrameTimer && currentPipVideo && pipCanvas) { drawFrameTimer = setInterval(() => { const ctx = pipCanvas.getContext('2d'); // 拉伸视频帧填充整个Canvas,忽略原宽高比 ctx.drawImage(currentPipVideo, 0, 0, pipCanvas.width, pipCanvas.height); }, 16); // 约60fps的绘制频率 } } // 停止帧绘制 function stopDrawingFrames() { if (drawFrameTimer) { clearInterval(drawFrameTimer); drawFrameTimer = null; } } // 清理所有PIP相关资源,避免内存泄漏 function cleanupPipResources() { if (currentPipVideo) { currentPipVideo.removeEventListener('play', startDrawingFrames); currentPipVideo.removeEventListener('pause', stopDrawingFrames); currentPipVideo.removeEventListener('ended', stopDrawingFrames); currentPipVideo.removeAttribute('__pip__'); currentPipVideo = null; } if (pipCanvas) { stopDrawingFrames(); pipCanvas.removeAttribute('__pip__'); document.body.removeChild(pipCanvas); pipCanvas = null; } } // 保留原逻辑,仅替换触发的requestPictureInPicture函数 function maybeUpdatePictureInPictureVideo(entries, observer) { const observedVideo = entries[0].target; if (!document.querySelector('[__pip__]')) { observer.unobserve(observedVideo); return; } const video = findLargestPlayingVideo(); if (video && !video.hasAttribute('__pip__')) { observer.unobserve(observedVideo); requestPictureInPicture(video); } }
关键逻辑说明
- Canvas中转:Chrome允许Canvas元素进入PIP模式,且Canvas的尺寸可以完全自定义,不受原视频宽高比约束。
- 帧绘制:通过
setInterval定时将视频帧绘制到Canvas,用drawImage的拉伸模式填充整个Canvas,实现任意比例显示。 - 资源清理:退出PIP时必须清理定时器、事件监听和DOM元素,避免内存泄漏。
- 原逻辑兼容:保留
maybeUpdatePictureInPictureVideo的原有逻辑,确保扩展自动切换视频的功能正常工作。
内容的提问来源于stack exchange,提问作者secundinofelipe
相关产品推荐
相关产品推荐

