// ==UserScript==
// @name 巴哈姆特動畫瘋GIF截圖工具
// @namespace 巴哈:aa24281024/GitHub:Mystic0428
// @version 1.1
// @description 把動畫瘋內容片段轉成GIF
// @author 巴哈:aa24281024(Mystic)/GitHub:Mystic0428
// @match https://ani.gamer.com.tw/animeVideo.php?sn=*
// @icon https://www.google.com/s2/favicons?sz=64&domain=gamer.com.tw
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const style = document.createElement('style');
style.textContent = `
.wrapper {
--input-focus: #2d8cf0;
--font-color: #323232;
--font-color-sub: #666;
--bg-color: #fff;
--bg-color-alt: #666;
--main-color: #323232;
}
.flip-card__popup {
width: 1200px;
height: auto;
padding: 20px;
display: none;
position: fixed;
left: 50%;
top: 50%;
flex-direction: column;
justify-content: center;
background: lightgrey;
gap: 20px;
border-radius: 5px;
border: 2px solid var(--main-color);
box-shadow: 4px 4px var(--main-color);
backface-visibility: hidden;
transform: translate(-50%, -50%);
z-index:100;
}
.flip-card__form {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.flip-card__input {
width: 250px;
height: 40px;
border-radius: 5px;
border: 2px solid var(--main-color);
background-color: var(--bg-color);
box-shadow: 4px 4px var(--main-color);
font-size: 15px;
font-weight: 600;
color: var(--font-color);
padding: 5px 10px;
outline: none;
}
.flip-card__input::placeholder {
color: var(--font-color-sub);
opacity: 0.8;
}
.flip-card__input:focus {
border: 2px solid var(--input-focus);
}
.flip-card__btn {
margin: 20px 0 0;
width: 120px;
height: 40px;
border-radius: 5px;
border: 2px solid var(--main-color);
background-color: var(--bg-color);
box-shadow: 4px 4px var(--main-color);
font-size: 17px;
font-weight: 600;
color: var(--font-color);
cursor: pointer;
}
.modal__header {
padding: 1rem 0rem;
border-bottom: 1px solid #231b1b;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
z-index: 1;
top: -20px;
}
.modal__title {
font-size: 25px;
font-weight: 900;
color: var(--main-color);
flex-grow: 1;
text-align: center;
}
.close-button {
appaerance: none;
font: inherit;
border: none;
background: none;
cursor: pointer;
}
.close-button--icon {
width: 2.5rem;
height: 2.5rem;
background-color: transparent;
border-radius: 0.25rem;
position: absolute;
right: 0;
}
.range-wrapper {
border-radius: 10px;
padding: 5px 25px 40px;
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.1);
}
.range-wrapper h2,
.range-wrapper span {
font-weight: 900;
font-size: 15.5px;
}
.price-input {
width: 100%;
display: flex;
margin: 30px 0 45px;
}
.price-input .field {
display: flex;
width: 100%;
height: 45px;
align-items: center;
}
.field input {
width: 100%;
height: 100%;
outline: none;
font-size: 19px;
margin-left: 12px;
border-radius: 5px;
text-align: center;
border: 1px solid #999;
-moz-appearance: textfield;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
}
.price-input .separator {
width: 130px;
display: flex;
font-size: 19px;
align-items: center;
justify-content: center;
}
.slider {
height: 5px;
position: relative;
background: #ddd;
border-radius: 5px;
}
.slider .progress {
height: 100%;
left: 25%;
right: 25%;
position: absolute;
border-radius: 5px;
background: #17a2b8;
}
.range-input {
position: relative;
}
.range-input input {
position: absolute;
width: 100%;
height: 5px;
top: -6px;
left: -2px;
background: none;
pointer-events: none;
-webkit-appearance: none;
-moz-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
height: 17px;
width: 5px;
border-radius: 80%;
background: #17a2b8;
pointer-events: auto;
-webkit-appearance: none;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.05);
}
input[type="range"]::-moz-range-thumb {
height: 17px;
width: 17px;
border: none;
border-radius: 50%;
background: #17a2b8;
pointer-events: auto;
-moz-appearance: none;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.05);
}
.d-flex {
width: 90%;
}
.control-btn {
margin: 20px 0 0;
display: flex;
justify-content: center;
}
.card {
--bg-card: #27272a;
--primary: #6d28d9;
--primary-800: #4c1d95;
--primary-shadow: #2e1065;
--light: #d9d9d9;
--zinc-800: #18181b;
--bg-linear: linear-gradient(0deg, var(--primary) 50%, var(--light) 125%);
position: relative;
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem;
background-color: var(--bg-card);
border-radius: 1rem;
}
.image_container {
overflow: hidden;
cursor: pointer;
position: relative;
z-index: 5;
width: 300px;
height: 200px;
background-color: var(--primary-800);
border-radius: 0.5rem;
}
.image_container .image {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
fill: var(--light);
}
.title {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
font-weight: 600;
color: var(--light);
text-transform: capitalize;
text-wrap: nowrap;
text-overflow: ellipsis;
}
.cart-button {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
gap: 0.25rem;
padding: 0.5rem;
width: 100%;
background-image: var(--bg-linear);
font-size: 0.75rem;
font-weight: 500;
color: var(--light);
text-wrap: nowrap;
border: 2px solid hsla(262, 83%, 58%, 0.5);
border-radius: 0.5rem;
box-shadow: inset 0 0 0.25rem 1px var(--light);
}
.imgs-container {
display: flex;
flex-direction: row;
overflow-x: auto;
gap: 20px;
align-items: center;
justify-content: center;
width: 90%;
padding-top: 10px;
padding-bottom: 10px;
}
.imgs-container::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
background-color: #F5F5F5;
}
.imgs-container::-webkit-scrollbar {
width: 12px;
}
.imgs-container::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
background-color: #555;
}
.content-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.result-container {
width: 90%;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.1);
border-radius: 10px;
}
#progressBar {
width: 40%;
height: 25px;
background-color: #e0e0e0;
border-radius: 10px;
position: relative;
overflow: hidden;
}
#progress {
height: 100%;
background-color: #568358;
width: 0%;
border-radius: 10px;
}
#percentage {
font-size: 14px;
color: #333;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
white-space: nowrap;
}
#resolutionProgressBar {
width: 40%;
height: 25px;
background-color: #e0e0e0;
border-radius: 10px;
position: relative;
overflow: hidden;
margin-right: 40px;
}
#resolutionProgress {
height: 100%;
background-color: #6e8f8f;
width: 0%;
border-radius: 10px;
}
#resolutionPercentage {
font-size: 14px;
color: #333;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
white-space: nowrap;
}
.progress-text {
font-weight: 600;
font-size: 19px;
}
.form-title {
margin-bottom: 30px;
text-align: center;
}
.form-title h2{
font-weight: 900;
font-size: 24px;
}
.image-container-override {
justify-content: normal;
}
`;
document.head.appendChild(style);
const popupHTML = `
<div class="wrapper flip-card__popup" id="popup">
<div class="modal__header">
<span class="modal__title">動畫瘋GIF截圖工具</span>
<button class="close-button close-button--icon" id="closePopupBtn">
<svg width="24" viewBox="0 0 24 24" height="24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" d="M0 0h24v24H0V0z"></path>
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z">
</path>
</svg>
</button>
</div>
<form class="flip-card__form" action="">
<div class="d-flex">
<div class="range-wrapper">
<div class="form-title">
<h2>時間範圍</h2>
</div>
<div style="display: flex; flex-direction: row;align-items: center;justify-content: space-between;">
<span>解析進度:</span>
<div id="resolutionProgressBar">
<div id="resolutionProgress"><span class="progress-text" id="resolutionPercentage">0%</span>
</div>
</div>
<span>生成進度:</span>
<div id="progressBar">
<div id="progress"><span class="progress-text" id="percentage">0%</span></div>
</div>
</div>
<div class="price-input">
<div class="field">
<span>開始</span>
<input type="text" class="input-min flip-card__input" value="00:00:00:000">
</div>
<div class="separator">-</div>
<div class="field">
<span>結束</span>
<input type="text" class="input-max flip-card__input" value="00:00:15:000">
</div>
</div>
<div class="slider">
<div class="progress" style="left: 0%; right: 98.6437%;"></div>
</div>
<div class="range-input">
<input type="range" class="range-min" min="0" max="1420000" value="0" step="100">
<input type="range" class="range-max" min="0" max="1420000" value="15000" step="100">
</div>
<div class="control-btn">
<button type="button" class="flip-card__btn" id="generateButton">生成</button>
<button type="button" class="flip-card__btn" id="reset-btn">重置</button>
</div>
</div>
</div>
</form>
<div class="content-container">
<div class="result-container">
<span class="modal__title" style="margin-top: 10px;">生成結果</span>
<div class="imgs-container">
</div>
<div style="height:30px;"></div>
</div>
</div>
</div>
`;
let rangeInput, timeInput, range, imgsContainer, resetButton;
let timeGap = 500;
let timeRange = 15000;
function handleTimeChange(e) {
let startTime = timeToMilliseconds(timeInput[0].value)
, endTime = timeToMilliseconds(timeInput[1].value);
if (startTime === null || endTime === null) {
if (e.target.classList.contains("input-min")) {
timeInput[0].value = formatTime(rangeInput[0].value);
} else {
timeInput[1].value = formatTime(rangeInput[1].value);
}
return;
}
if (startTime < 0 || endTime > rangeInput[1].max || startTime > rangeInput[0].max) {
if (e.target.classList.contains("input-min")) {
timeInput[0].value = formatTime(rangeInput[0].value);
} else {
timeInput[1].value = formatTime(rangeInput[1].value);
}
return;
}
if (endTime - startTime <= 0) {
if (e.target.classList.contains("input-min")) {
rangeInput[0].value = startTime;
if (startTime + timeRange > rangeInput[1].max) {
rangeInput[1].value = rangeInput[1].max;
timeInput[1].value = formatTime(rangeInput[1].max);
} else {
rangeInput[1].value = startTime + timeRange;
timeInput[1].value = formatTime(startTime + timeRange);
}
} else {
rangeInput[1].value = endTime;
if (endTime - timeRange > rangeInput[0].min) {
rangeInput[0].value = endTime - timeRange;
timeInput[0].value = formatTime(rangeInput[0].value);
} else {
rangeInput[0].value = rangeInput[0].min;
timeInput[0].value = formatTime(rangeInput[0].min);
}
rangeInput[1].value = endTime;
}
updatePercentage(rangeInput[0].value / rangeInput[0].max, rangeInput[1].value / rangeInput[1].max);
return;
}
if (endTime - startTime >= timeGap && endTime <= rangeInput[1].max) {
if (endTime - startTime > timeRange) {
if (e.target.classList.contains("input-min")) {
rangeInput[0].value = startTime;
rangeInput[1].value = startTime + timeRange;
timeInput[1].value = formatTime(startTime + timeRange);
} else {
rangeInput[0].value = endTime - timeRange;
rangeInput[1].value = endTime;
timeInput[0].value = formatTime(endTime - timeRange);
}
updatePercentage(rangeInput[0].value / rangeInput[0].max, rangeInput[1].value / rangeInput[1].max);
} else {
if (e.target.classList.contains("input-min")) {
rangeInput[0].value = startTime;
range.style.left = (startTime / rangeInput[0].max) * 100 + "%";
} else {
rangeInput[1].value = endTime;
range.style.right = 100 - (endTime / rangeInput[1].max) * 100 + "%";
}
}
}
}
function updatePercentage(rangeInput1, rangeInput2) {
let leftPercentage = rangeInput1 * 100;
let rightPercentage = 100 - rangeInput2 * 100;
if (leftPercentage > 90) {
leftPercentage -= 0.3;
}
if (rightPercentage > 90) {
rightPercentage -= 0.3;
}
range.style.left = leftPercentage + "%";
range.style.right = rightPercentage + "%";
}
function formatTime(milliseconds) {
const hours = Math.floor(milliseconds / 3600000);
const remainingMillisecondsAfterHours = milliseconds % 3600000;
const minutes = Math.floor(remainingMillisecondsAfterHours / 60000);
const remainingMillisecondsAfterMinutes = remainingMillisecondsAfterHours % 60000;
const seconds = Math.floor(remainingMillisecondsAfterMinutes / 1000);
const remainingMilliseconds = remainingMillisecondsAfterMinutes % 1000;
const formattedHours = String(hours).padStart(2, '0');
const formattedMinutes = String(minutes).padStart(2, '0');
const formattedSeconds = String(seconds).padStart(2, '0');
const formattedMilliseconds = String(remainingMilliseconds).padStart(3, '0');
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}:${formattedMilliseconds}`;
}
function validateTimeFormat(time) {
const regex = /^([0-1]?\d|2[0-3]):([0-5]?\d):([0-5]?\d):(\d{1,3})$/;
if (!regex.test(time)) {
return false;
}
const match = time.match(regex);
if (!match) {
return false;
}
const [, hours, minutes, seconds, milliseconds] = match;
return parseInt(milliseconds) % 100 === 0;
}
function timeToMilliseconds(time) {
if (!validateTimeFormat(time)) {
return null;
}
const [, hours, minutes, seconds, milliseconds] = time.match(/^([0-1]?\d|2[0-3]):([0-5]?\d):([0-5]?\d):(\d{1,3})$/);
return (
parseInt(hours) * 60 * 60 * 1000 +
parseInt(minutes) * 60 * 1000 +
parseInt(seconds) * 1000 +
parseInt(milliseconds)
);
}
function resetTime() {
if (gifRenderingInProgress || isParsing) {
console.log('正在進行中');
return;
}
rangeInput[0].value = 0;
rangeInput[1].value = 15000;
startTime = 0;
endTime = 15;
timeInput[0].value = '00:00:00:000';
timeInput[1].value = '00:00:15:000';
updatePercentage(0 / rangeInput[0].max, 15000 / rangeInput[1].max);
}
document.body.insertAdjacentHTML('beforeend', popupHTML);
// 關閉按鈕事件
document.getElementById('closePopupBtn').addEventListener('click', closePopup);
// 設置生成按鈕的事件監聽器
document.getElementById('generateButton').addEventListener('click', function () {
if (gifRenderingInProgress || isParsing) {
console.log('正在進行中');
return;
}
resetProgress();
startTime = rangeInput[0].value / 1000;
endTime = rangeInput[1].value / 1000;
video.currentTime = startTime;
video.playbackRate = 0.3; // 減慢影片播放速度至 0.3x,以免遺漏幀
video.muted = true;
video.play();
captureFrames();
});
const popup = document.getElementById('popup');
const progressElement = document.getElementById('progress');
const percentage = document.getElementById('percentage');
const resolutionProgressElement = document.getElementById('resolutionProgress');
const resolutionPercentage = document.getElementById('resolutionPercentage');
let isParsing = false;
function showPopup() {
if (document.querySelector('.video-adHandler-background-blocker')) {
return;
}
popup.style.display = 'flex';
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
function closePopup() {
if (isParsing) {
return;
}
popup.style.display = 'none';
}
document.addEventListener('keydown', function (event) {
// 當按下 Shift + G
if (event.shiftKey && event.code === 'KeyG') {
showPopup();
}
// 當按下 ESC
if (event.key === 'Escape') {
closePopup();
}
});
window.onload = function () {
rangeInput = document.querySelectorAll(".range-input input"),
timeInput = document.querySelectorAll(".price-input input"),
range = document.querySelector(".slider .progress"),
imgsContainer = document.querySelector('.imgs-container'),
resetButton = document.querySelector('#reset-btn');
//把GIF圖示插入到Control Bar
const targetContainer = document.querySelector('.control-bar-rightbtn');
const newDiv = document.createElement('div');
newDiv.className = 'vjs-menu-button vjs-menu-button-popup vjs-control vjs-button vjs-visible-text vjs-res-button';
newDiv.innerHTML = `<button class="vjs-menu-button vjs-menu-button-popup vjs-button" type="button" aria-disabled="false"aria-haspopup="true" aria-expanded="false" title="GIF">
<span class="vjs-icon-placeholder" aria-hidden="true"></span>
<span class="vjs-control-text" aria-live="polite">GIF</span>
</button> `;
if (targetContainer) {
targetContainer.appendChild(newDiv);
const button = newDiv.querySelector('button');
button.addEventListener('click', function () {
showPopup();
});
} else {
console.error('找不到 .control-bar-rightbtn 容器');
}
timeInput.forEach((input) => {
input.addEventListener("blur", (e) => {
handleTimeChange(e);
});
input.addEventListener("keydown", function (e) {
if (event.keyCode == 13) {
e.preventDefault();
handleTimeChange(e);
}
});
});
rangeInput.forEach((input) => {
input.addEventListener("input", (e) => {
let minVal = parseInt(rangeInput[0].value),
maxVal = parseInt(rangeInput[1].value);
if (maxVal - minVal <= timeGap) {
if (e.target.className === "range-min") {
rangeInput[0].value = maxVal - timeGap;
timeInput[0].value = formatTime(maxVal - timeGap);
timeInput[1].value = formatTime(maxVal);
} else {
rangeInput[1].value = minVal + timeGap;
timeInput[0].value = formatTime(minVal);
timeInput[1].value = formatTime(minVal + timeGap);
}
} else {
if (maxVal - minVal > timeRange) {
if (e.target.className === "range-min") {
rangeInput[0].value = minVal;
rangeInput[1].value = minVal + timeRange;
timeInput[0].value = formatTime(minVal);
timeInput[1].value = formatTime(minVal + timeRange);
} else {
rangeInput[0].value = maxVal - timeRange;
rangeInput[1].value = maxVal;
timeInput[0].value = formatTime(maxVal - timeRange);
timeInput[1].value = formatTime(maxVal);
}
updatePercentage(rangeInput[0].value / rangeInput[0].max, rangeInput[1].value / rangeInput[1].max);
return;
}
timeInput[0].value = formatTime(minVal);
timeInput[1].value = formatTime(maxVal);
updatePercentage(minVal / rangeInput[0].max, maxVal / rangeInput[1].max);
}
});
});
imgsContainer.addEventListener('click', (e) => {
if (e.target.closest('#delete-a')) {
const card = e.target.closest('.card');
if (card) {
card.remove();
}
if (imgsContainer.querySelectorAll('.card').length < 3) {
imgsContainer.classList.remove('image-container-override');
}
}
});
resetButton.addEventListener('click', (e) => {
resetTime();
});
};
// 動態創建 <script> 元素並加載 gif.js
var script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/gif.min.js';
document.head.appendChild(script);
const videoResolutions = [
{ width: 1920, height: 1080, label: "1080p" },
{ width: 1280, height: 720, label: "720p" },
{ width: 960, height: 540, label: "540p" },
{ width: 640, height: 360, label: "360p" }
];
const video = document.getElementById('ani_video_html5_api');
let videoDuration = 1420;
video.addEventListener('loadedmetadata', () => {
if (rangeInput) {
rangeInput.forEach((input) => {
input.max = Math.floor(video.duration) * 1000;
});
updatePercentage(rangeInput[0].value / rangeInput[0].max, rangeInput[1].value / rangeInput[1].max);
}
});
// 等待 gif.js 加載完成
script.onload = function () {
// 檢查 GIF 類是否可用
window.gifList = new Map();
if (typeof GIF !== 'undefined') {
let gifLoading = fetch('https://cdn.jsdelivr.net/npm/[email protected]/dist/gif.worker.js')
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not OK");
}
window.workerBlob = response.blob();
return window.workerBlob;
}).then(workerBlob => {
for (let i = 0; i < 4; i++) {
let gif = new GIF({
workers: 4,
workerScript: URL.createObjectURL(workerBlob),
quality: 0,
repeat: 0,
width: videoResolutions[i].width,
height: videoResolutions[i].height,
background: '#ffffff'
});
gif.on('finished', function (blob) {
const gifUrl = URL.createObjectURL(blob);
let displayStartTime = formatTime(startTime * 1000);
let displayEndTime = formatTime(endTime * 1000);
let fileName = document.title.match(/(.+?\[\d+\])/);
if (fileName) {
fileName = fileName[0] + ' ' + displayStartTime + '-' + displayEndTime;
} else {
fileName = displayStartTime + '-' + displayEndTime;
}
const cardHTML = `
<div class="card">
<div class="image_container">
<img class="image" src="${gifUrl}" alt="Image Description" />
</div>
<div class="title">
<span>${displayStartTime} - ${displayEndTime}</span>
</div>
<a href="${gifUrl}" download="${fileName}">
<button class="cart-button">
<span>下載</span>
</button>
</a>
<a id="delete-a">
<button class="cart-button"">
<span>刪除</span>
</button>
</a>
</div>
`;
imgsContainer.innerHTML += cardHTML;
window.gifList.get(videoResolutions[i].width).abort();
window.gifList.get(videoResolutions[i].width).frames = [];
gifRenderingInProgress = false;
if (imgsContainer.querySelectorAll('.card').length >= 3) {
imgsContainer.classList.add('image-container-override');
imgsContainer.scrollLeft = imgsContainer.scrollWidth;
}
});
gif.on('progress', function (progress) {
progressElement.style.width = `${Math.round(progress * 100)}%`; // 直接同步進度條
percentage.textContent = `${Math.round(progress * 100)}%`; // 更新百分比顯示
});
window.gifList.set(videoResolutions[i].width, gif);
}
}).catch(error => console.error("Error loading GIF worker:", error));
} else {
console.log('Failed to find GIF class!');
}
};
let startTime = 0;
let endTime = 15;
let gifRenderingInProgress = false; // 用來標記是否正在進行渲染
let lastExpectedDisplayTime = null;
let frameDisplayDurations = [];
let currentWidth=1920;
function captureFrames() {
isParsing = true;
// 這個回調將每一幀都調用
function frameCallback(now, metadata) {
// 確保捕捉只在設定的開始時間之後觸發
if (video.currentTime < startTime) {
//如果影片尚未達到開始捕捉的時間,則繼續等待
video.requestVideoFrameCallback(frameCallback);
return;
}
currentWidth= metadata.width;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = metadata.width;
canvas.height = metadata.height;
context.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageURL = canvas.toDataURL('image/png');
const img = new Image();
img.src = imageURL;
const currentExpectedDisplayTime = metadata.mediaTime * 1000;
if (lastExpectedDisplayTime !== null) {
// 計算前一幀顯示時長
const displayDuration = currentExpectedDisplayTime - lastExpectedDisplayTime;
frameDisplayDurations.push(displayDuration);
}
// 更新上一幀的 expectedDisplayTime
lastExpectedDisplayTime = currentExpectedDisplayTime;
window.gifList.get(canvas.width).addFrame(img, { delay: 125 });
// 如果影片播放時間達到停止的時間,則停止捕捉
if (video.currentTime >= endTime || video.currentTime >= Math.floor(video.duration)) {
generateGif(canvas.width);
return;
}
const progress = (video.currentTime - startTime) / (endTime - startTime);
resolutionProgressElement.style.width = `${Math.round(progress * 100)}%`;
resolutionPercentage.textContent = `${Math.round(progress * 100)}%`;
// 持續捕捉每一幀
video.requestVideoFrameCallback(frameCallback);
}
// 等待影片達到開始捕捉的時間,然後開始捕捉
video.requestVideoFrameCallback(frameCallback);
}
function resetProgress() {
progressElement.style.width = `${0}%`; // 直接同步進度條
percentage.textContent = `${0}%`; // 更新百分比顯示
resolutionProgressElement.style.width = `${0}%`;
resolutionPercentage.textContent = `${0}%`;
}
video.addEventListener('ended', () => {
if(!window.gifList.get(currentWidth).running){
generateGif(currentWidth);
}
});
function generateGif(videoWidth){
frameDisplayDurations.push(frameDisplayDurations[frameDisplayDurations.length - 1]);
const averageFrameDisplayDuration = frameDisplayDurations.reduce((total, duration) => total + duration, 0) / frameDisplayDurations.length;
video.playbackRate = 1; //調整影片為正常撥放速率
isParsing = false;
for (let i = 0; i < window.gifList.get(videoWidth).frames.length; i++) {
window.gifList.get(videoWidth).frames[i].delay = averageFrameDisplayDuration;
}
resolutionProgressElement.style.width = `${100}%`;
resolutionPercentage.textContent = `${100}%`;
window.gifList.get(videoWidth).render();
gifRenderingInProgress = true;
frameDisplayDurations = [];
lastExpectedDisplayTime = null;
video.muted = false;
};
})();