cat-catch/catch-script/recorder2.js

257 lines
9.2 KiB
JavaScript

(function () {
console.log("recorder2.js Start");
if (document.getElementById("catCatchRecorder2")) {
return;
}
if (!navigator.mediaDevices) {
alert("当前网页不支持屏幕分享");
return;
}
let language = navigator.language.replace("-", "_");
if (window.CatCatchI18n) {
if (!window.CatCatchI18n.languages.includes(language)) {
language = language.split("_")[0];
if (!window.CatCatchI18n.languages.includes(language)) {
language = "en";
}
}
}
// 添加style
const style = document.createElement("style");
style.innerHTML = `
@keyframes color-change{
0% { outline: 4px solid rgb(26, 115, 232); }
50% { outline: 4px solid red; }
100% { outline: 4px solid rgb(26, 115, 232); }
}
#catCatchRecorder2 {
font-weight: bold;
position: absolute;
cursor: move;
z-index: 999999999;
outline: 4px solid rgb(26, 115, 232);
resize: both;
overflow: hidden;
height: 720px;
width: 1024px;
top: 30%;
left: 30%;
pointer-events: none;
font-size: 10px;
}
#catCatchRecorderHeader {
background: rgb(26, 115, 232);
color: white;
text-align: center;
height: 20px;
cursor: pointer;
display: flex;
justify-content: space-evenly;
align-items: center;
pointer-events: auto;
}
#catCatchRecorderTitle {
cursor: move;
user-select: none;
width: 45%;
}
#catCatchRecorderinnerCropArea {
height: calc(100% - 20px);
width: 100%;
}
.animation {
animation: color-change 5s infinite;
}
.input-group {
display: flex;
align-items: center;
}
.input-group label {
margin-right: 5px;
}
#videoBitrate, #audioBitrate {
width: 4rem;
}
.input-group label{
width: 5rem;
}`;
// 添加div
let cat = document.createElement("div");
cat.setAttribute("id", "catCatchRecorder2");
cat.innerHTML = `<div id="catCatchRecorderinnerCropArea"></div>
<div id="catCatchRecorderHeader">
<div class="input-group">
<select id="videoBits">
<option value="2500000" data-i18n="videoBits">视频码率</option>
<option value="2500000">2.5 Mbps</option>
<option value="5000000">5 Mbps</option>
<option value="8000000">8 Mbps</option>
<option value="16000000">16 Mbps</option>
</select>
</div>
<div class="input-group">
<select id="audioBits">
<option value="128000" data-i18n="audioBits">视频码率</option>
<option value="128000">128 kbps</option>
<option value="256000">256 kbps</option>
</select>
</div>
<div id="catCatchRecorderStart" data-i18n="startRecording">开始录制</div>
<div id="catCatchRecorderTitle" data-i18n="dragWindow">拖动窗口</div>
<div id="catCatchRecorderClose" data-i18n="close">关闭</div>
</div>`;
// 创建 Shadow DOM 放入CatCatch
const divShadow = document.createElement('div');
const shadowRoot = divShadow.attachShadow({ mode: 'closed' });
shadowRoot.appendChild(cat);
shadowRoot.appendChild(style);
document.getElementsByTagName('html')[0].appendChild(divShadow);
// 事件绑定
const catCatchRecorderStart = cat.querySelector("#catCatchRecorderStart");
catCatchRecorderStart.onclick = function () {
if (recorder) {
recorder.stop();
return;
}
try { startRecording(); } catch (e) { console.log(e); return; }
}
cat.querySelector("#catCatchRecorderClose").onclick = function () {
recorder && recorder.stop();
cat.remove();
}
// 拖动div
const catCatchRecorderinnerCropArea = cat.querySelector("#catCatchRecorderinnerCropArea");
cat.querySelector("#catCatchRecorderTitle").onpointerdown = (e) => {
let pos1, pos2, pos3, pos4;
pos3 = e.clientX;
pos4 = e.clientY;
if (pos3 - cat.offsetWidth - cat.offsetLeft > - 20 &&
pos4 - cat.offsetHeight - cat.offsetTop > - 20) {
return;
}
document.onpointermove = (e) => {
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
cat.style.top = cat.offsetTop - pos2 + "px";
cat.style.left = cat.offsetLeft - pos1 + "px";
}
document.onpointerup = () => {
document.onpointerup = null;
document.onpointermove = null;
}
}
// document.getElementsByTagName('html')[0].appendChild(cat);
// 初始化位置
const video = document.querySelector("video");
if (video) {
// 调整和video一样大小
if (video.clientHeight >= 0 && video.clientWidth >= 0) {
cat.style.height = video.clientHeight + 20 + "px";
cat.style.width = video.clientWidth + "px";
}
// 调整到video的位置
const videoOffset = getElementOffset(video);
if (videoOffset.top >= 0 && videoOffset.left >= 0) {
cat.style.top = videoOffset.top + "px";
cat.style.left = videoOffset.left + "px";
}
// 防止遮挡菜单
let catAttr = cat.getBoundingClientRect();
if (document.documentElement.scrollTop + catAttr.bottom > document.documentElement.scrollTop + window.innerHeight) {
cat.style.top = document.documentElement.scrollTop + window.innerHeight - catAttr.height + "px";
}
}
// 录制
var recorder;
async function startRecording() {
const buffer = [];
let option = {
mimeType: 'video/webm;codecs=vp8,opus',
videoBitsPerSecond: +cat.querySelector("#videoBits").value,
audioBitsPerSecond: +cat.querySelector("#audioBits").value
};
if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9,opus')) {
option.mimeType = 'video/webm;codecs=vp9,opus';
} else if (MediaRecorder.isTypeSupported('video/webm;codecs=h264')) {
option.mimeType = 'video/webm;codecs=h264';
}
const cropTarget = await CropTarget.fromElement(catCatchRecorderinnerCropArea);
const stream = await navigator.mediaDevices
.getDisplayMedia({
preferCurrentTab: true,
video: {
cursor: "never"
},
audio: {
sampleRate: 48000,
sampleSize: 16,
channelCount: 2
}
});
const [track] = stream.getVideoTracks();
await track.cropTo(cropTarget);
recorder = new MediaRecorder(stream, option);
recorder.start();
recorder.onstart = function (e) {
buffer.slice(0);
catCatchRecorderStart.innerHTML = i18n("stopRecording", "停止录制");
cat.classList.add("animation");
}
recorder.ondataavailable = function (e) {
buffer.push(e.data);
}
recorder.onstop = function () {
const fileBlob = new Blob(buffer, { type: option });
const a = document.createElement('a');
a.href = URL.createObjectURL(fileBlob);
a.download = `${document.title}.webm`;
a.click();
a.remove();
buffer.slice(0);
stream.getTracks().forEach(track => track.stop());
recorder = undefined;
catCatchRecorderStart.innerHTML = i18n("startRecording", "开始录制");
cat.classList.remove("animation");
}
}
function getElementOffset(el) {
const rect = el.getBoundingClientRect();
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
return {
top: rect.top + scrollTop,
left: rect.left + scrollLeft
};
}
// i18n
if (window.CatCatchI18n) {
CatCatch.querySelectorAll('[data-i18n]').forEach(function (element) {
const translation = window.CatCatchI18n[element.dataset.i18n]?.[language];
if (translation) {
element.innerHTML = translation;
}
});
CatCatch.querySelectorAll('[data-i18n-outer]').forEach(function (element) {
const outerTranslation = window.CatCatchI18n[element.dataset.i18nOuter]?.[language];
if (outerTranslation) {
element.outerHTML = outerTranslation;
}
});
}
function i18n(key, original = "") {
if (!window.CatCatchI18n) { return original };
return window.CatCatchI18n[key][language];
}
})();