+
스톱워치로 측정
볼링공이 파울라인을 지날 때 '시작'을 누르고, 헤드핀에 도달할 때 '정지'를 누르세요.
00:00.000
@@ -237,6 +347,24 @@
@@ -490,19 +618,19 @@
// 스톱워치 관련 변수
let stopwatchInterval;
- let startTime;
- let elapsedTime = 0;
- let isRunning = false;
+ let wStartTime;
+ let wElapsedTime = 0;
+ let wIsRunning = false;
// 스톱워치 시작/정지 토글 함수
function toggleStopwatch() {
const toggleBtn = document.getElementById('toggle-btn');
- if (!isRunning) {
+ if (!wIsRunning) {
// 시작
- startTime = Date.now() - elapsedTime;
+ wStartTime = Date.now() - wElapsedTime;
stopwatchInterval = setInterval(updateStopwatch, 10);
- isRunning = true;
+ wIsRunning = true;
// 버튼 상태 변경
toggleBtn.textContent = '정지';
@@ -510,8 +638,8 @@
} else {
// 정지
clearInterval(stopwatchInterval);
- elapsedTime = Date.now() - startTime;
- isRunning = false;
+ wElapsedTime = Date.now() - wStartTime;
+ wIsRunning = false;
// 버튼 상태 변경
toggleBtn.textContent = '시작';
@@ -521,7 +649,7 @@
updateStopwatchDisplay(); // 스톱워치 표시 갱신
// 스톱워치와 동일한 값을 입력 필드에 설정
- const exactSeconds = Math.floor((elapsedTime % 60000) / 1000) + ((elapsedTime % 1000) / 1000);
+ const exactSeconds = Math.floor((wElapsedTime % 60000) / 1000) + ((wElapsedTime % 1000) / 1000);
document.getElementById('time').value = exactSeconds.toFixed(3);
calculateSpeed();
@@ -531,8 +659,8 @@
// 스톱워치 초기화 함수
function resetStopwatch() {
clearInterval(stopwatchInterval);
- elapsedTime = 0;
- isRunning = false;
+ wElapsedTime = 0;
+ wIsRunning = false;
updateStopwatchDisplay();
// 버튼 상태 변경
@@ -543,15 +671,15 @@
// 스톱워치 표시 업데이트 함수
function updateStopwatch() {
- elapsedTime = Date.now() - startTime;
+ wElapsedTime = Date.now() - wStartTime;
updateStopwatchDisplay();
}
// 스톱워치 시간 표시 형식 변환 함수
function updateStopwatchDisplay() {
- const minutes = Math.floor(elapsedTime / 60000);
- const seconds = Math.floor((elapsedTime % 60000) / 1000);
- const milliseconds = elapsedTime % 1000;
+ const minutes = Math.floor(wElapsedTime / 60000);
+ const seconds = Math.floor((wElapsedTime % 60000) / 1000);
+ const milliseconds = wElapsedTime % 1000;
const display = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${milliseconds.toString().padStart(3, '0')}`;
document.getElementById('stopwatch-display').textContent = display;
@@ -581,6 +709,136 @@
resetStopwatch();
});
});
+ // --- 탭 전환 기능 개선 ---
+ const tabStopwatch = document.getElementById('tab-stopwatch');
+ const tabVideo = document.getElementById('tab-video');
+ const tabContentStopwatch = document.getElementById('tab-content-stopwatch');
+ const tabContentVideo = document.getElementById('tab-content-video');
+ function activateTab(tab) {
+ [tabStopwatch, tabVideo].forEach(btn => btn.classList.remove('active'));
+ [tabContentStopwatch, tabContentVideo].forEach(c => c.classList.remove('active'));
+ if(tab === 'stopwatch') {
+ tabStopwatch.classList.add('active');
+ tabContentStopwatch.classList.add('active');
+ } else {
+ tabVideo.classList.add('active');
+ tabContentVideo.classList.add('active');
+ }
+ }
+ tabStopwatch.onclick = () => activateTab('stopwatch');
+ tabVideo.onclick = () => activateTab('video');
+ // 최초 로드 시
+ activateTab('stopwatch');
+
+ // --- 영상 업로드/촬영 & 구간(프레임) 선택 기능 (시커바 기반) ---
+ const videoUpload = document.getElementById('video-upload');
+ const videoPreview = document.getElementById('video-preview');
+ const fileNameLabel = document.getElementById('file-name-label');
+
+ const setStartBtn = document.getElementById('set-start-btn');
+ const setEndBtn = document.getElementById('set-end-btn');
+ const resetFrameBtn = document.getElementById('reset-frame-btn');
+ let mediaRecorder, stream = null;
+ let videoFile = null;
+ let fps = 30;
+ let duration = 0;
+ // mp4box.js를 이용한 FPS 추출 함수 (Promise 반환)
+function getFPSfromMP4(file) {
+ return new Promise((resolve) => {
+ if (!file || !file.type.includes('mp4')) {
+ resolve(30);
+ return;
+ }
+ const fileReader = new FileReader();
+ fileReader.onload = function(e) {
+ const arrayBuffer = e.target.result;
+ // mp4box.js는 appendBuffer의 인자로 fileStart 속성이 반드시 필요함
+ const mp4boxFile = MP4Box.createFile();
+ mp4boxFile.onReady = function(info) {
+ let fps = 30;
+ try {
+ const videoTrack = info.tracks.find(t => t.video);
+ if (videoTrack && videoTrack.video && videoTrack.video.frame_rate) {
+ fps = videoTrack.video.frame_rate;
+ }
+ } catch (e) { fps = 30; }
+ resolve(fps || 30);
+ };
+ arrayBuffer.fileStart = 0;
+ mp4boxFile.appendBuffer(arrayBuffer, 0);
+ mp4boxFile.flush();
+ };
+ fileReader.readAsArrayBuffer(file);
+ });
+}
+ // startTime, endTime은 오직 여기서만 선언
+ let startTime = null;
+ let endTime = null;
+ const DIST_M = 18.288;
+
+ // 파일 업로드/촬영
+ videoUpload.onchange = function(e) {
+ if (!e.target.files || !e.target.files[0]) {
+ return;
+ }
+ videoFile = e.target.files[0];
+ videoPreview.src = URL.createObjectURL(videoFile);
+ videoPreview.classList.add('active');
+ videoPreview.onloadedmetadata = async function() {
+ duration = videoPreview.duration;
+ fps = await getFPSfromMP4(videoFile);
+ startTime = null;
+ endTime = null;
+ };
+ };
+
+ // 비디오 컨트롤(내장 시커바) 기반 시작/종료 지점 선택
+ setStartBtn.onclick = function() {
+ if (duration === 0) return;
+ startTime = videoPreview.currentTime;
+ setStartBtn.textContent = `시작 지점: ${startTime.toFixed(3)}초`;
+ if (endTime !== null && endTime <= startTime) {
+ endTime = null;
+ setEndBtn.textContent = '종료 지점 선택';
+ }
+ updateSection();
+ };
+ setEndBtn.onclick = function() {
+ if (duration === 0) return;
+ if (startTime === null) {
+ alert('먼저 시작 지점을 선택하세요.');
+ return;
+ }
+ endTime = videoPreview.currentTime;
+ if (endTime <= startTime) {
+ alert('종료 지점은 시작 지점보다 뒤여야 합니다.');
+ endTime = null;
+ return;
+ }
+ setEndBtn.textContent = `종료 지점: ${endTime.toFixed(3)}초`;
+ updateSection();
+ };
+
+ // 구간 초기화 버튼 클릭 시 버튼 텍스트도 초기화
+ resetFrameBtn.onclick = function() {
+ startTime = null;
+ endTime = null;
+ setStartBtn.textContent = '시작 지점 선택';
+ setEndBtn.textContent = '종료 지점 선택';
+ document.getElementById('distance-meters').value = DIST_M;
+ document.getElementById('distance-feet').value = perRound(DIST_M * 3.2808399, 2);
+ document.getElementById('time').value = '2.000';
+ calculateSpeed();
+ };
+
+ // 구간 시간 및 속도 계산
+ function updateSection() {
+ if (startTime !== null && endTime !== null) {
+ const dt = endTime - startTime;
+ document.getElementById('time').value = dt.toFixed(3);
+ calculateSpeed();
+ }
+ }