Update index.html
Browse files- index.html +107 -67
index.html
CHANGED
@@ -47,6 +47,7 @@
|
|
47 |
border-radius: 5px;
|
48 |
background-color: #000;
|
49 |
display: block;
|
|
|
50 |
}
|
51 |
|
52 |
.video-controls {
|
@@ -252,6 +253,37 @@
|
|
252 |
color: #ccd6f6;
|
253 |
}
|
254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
input[type="number"], input[type="checkbox"], select {
|
256 |
background-color: #112240;
|
257 |
border: 1px solid #64ffda;
|
@@ -260,12 +292,6 @@
|
|
260 |
border-radius: 3px;
|
261 |
}
|
262 |
|
263 |
-
.buttons {
|
264 |
-
display: flex;
|
265 |
-
gap: 10px;
|
266 |
-
justify-content: center;
|
267 |
-
}
|
268 |
-
|
269 |
.tech-decoration {
|
270 |
width: 100%;
|
271 |
height: 2px;
|
@@ -316,8 +342,8 @@
|
|
316 |
<input type="range" class="volume-slider" id="volume-slider" min="0" max="1" step="0.01" value="1">
|
317 |
</div>
|
318 |
<div class="speed-control">
|
319 |
-
<span class="speed-value" id="speed-value">
|
320 |
-
<input type="range" class="speed-slider" id="speed-slider" min="0.25" max="3" step="0.
|
321 |
</div>
|
322 |
<button class="control-button fullscreen-button" id="fullscreen-btn">⛶</button>
|
323 |
</div>
|
@@ -341,14 +367,18 @@
|
|
341 |
<input type="checkbox" id="loop">
|
342 |
</div>
|
343 |
<div class="setting-item">
|
344 |
-
<
|
345 |
-
|
346 |
-
|
|
|
|
|
347 |
</div>
|
348 |
<div class="setting-item">
|
349 |
-
<
|
350 |
-
|
351 |
-
|
|
|
|
|
352 |
</div>
|
353 |
</div>
|
354 |
|
@@ -359,37 +389,29 @@
|
|
359 |
<div class="audio-item">
|
360 |
<label>p.mp3</label>
|
361 |
<input type="range" class="audio-slider" data-audio="p" min="0" max="1" step="0.01" value="1">
|
362 |
-
<span class="volume-value">1</span>
|
363 |
</div>
|
364 |
<div class="audio-item">
|
365 |
<label>a.mp3</label>
|
366 |
<input type="range" class="audio-slider" data-audio="a" min="0" max="1" step="0.01" value="1">
|
367 |
-
<span class="volume-value">1</span>
|
368 |
</div>
|
369 |
<div class="audio-item">
|
370 |
<label>t.mp3</label>
|
371 |
<input type="range" class="audio-slider" data-audio="t" min="0" max="1" step="0.01" value="1">
|
372 |
-
<span class="volume-value">1</span>
|
373 |
</div>
|
374 |
<div class="audio-item">
|
375 |
<label>s.mp3</label>
|
376 |
<input type="range" class="audio-slider" data-audio="s" min="0" max="1" step="0.01" value="1">
|
377 |
-
<span class="volume-value">1</span>
|
378 |
</div>
|
379 |
<div class="audio-item">
|
380 |
<label>k.mp3</label>
|
381 |
<input type="range" class="audio-slider" data-audio="k" min="0" max="1" step="0.01" value="1">
|
382 |
-
<span class="volume-value">1</span>
|
383 |
</div>
|
384 |
</div>
|
385 |
-
|
386 |
-
<div class="tech-decoration"></div>
|
387 |
-
|
388 |
-
<div class="buttons">
|
389 |
-
<button id="play-btn">再生</button>
|
390 |
-
<button id="pause-btn">一時停止</button>
|
391 |
-
<button id="stop-btn">停止</button>
|
392 |
-
</div>
|
393 |
</div>
|
394 |
|
395 |
<script>
|
@@ -406,12 +428,9 @@
|
|
406 |
const volumeSlider = document.getElementById('volume-slider');
|
407 |
const speedSlider = document.getElementById('speed-slider');
|
408 |
const speedValue = document.getElementById('speed-value');
|
409 |
-
const
|
410 |
-
const
|
411 |
const fullscreenBtn = document.getElementById('fullscreen-btn');
|
412 |
-
const playBtn = document.getElementById('play-btn');
|
413 |
-
const pauseBtn = document.getElementById('pause-btn');
|
414 |
-
const stopBtn = document.getElementById('stop-btn');
|
415 |
const startTimeInput = document.getElementById('start-time');
|
416 |
const endTimeInput = document.getElementById('end-time');
|
417 |
const loopCheckbox = document.getElementById('loop');
|
@@ -425,6 +444,7 @@
|
|
425 |
const audioBuffers = {};
|
426 |
const audioSources = {};
|
427 |
const gainNodes = {};
|
|
|
428 |
|
429 |
// 音声ファイル名の配列
|
430 |
const audioFiles = ['p', 'a', 't', 's', 'k'];
|
@@ -434,6 +454,7 @@
|
|
434 |
let isPlaying = false;
|
435 |
let isVideoPlaying = false;
|
436 |
let lastVolume = 1;
|
|
|
437 |
|
438 |
// 動画のメタデータが読み込まれたら
|
439 |
video.addEventListener('loadedmetadata', function() {
|
@@ -466,6 +487,16 @@
|
|
466 |
// 動画の時間更新イベント
|
467 |
video.addEventListener('timeupdate', updateTimeDisplay);
|
468 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
469 |
// プログレスバークリックでシーク
|
470 |
progressContainer.addEventListener('click', function(e) {
|
471 |
if (!video.duration) return;
|
@@ -474,16 +505,30 @@
|
|
474 |
const pos = (e.clientX - rect.left) / rect.width;
|
475 |
const seekTime = pos * video.duration;
|
476 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
477 |
video.currentTime = seekTime;
|
478 |
|
479 |
// 音声も同期
|
480 |
audioFiles.forEach(file => {
|
481 |
if (audioSources[file]) {
|
482 |
audioSources[file].stop();
|
483 |
-
|
|
|
|
|
484 |
}
|
485 |
});
|
486 |
-
}
|
487 |
|
488 |
// プログレスバー上でマウス移動時に時間を表示
|
489 |
progressContainer.addEventListener('mousemove', function(e) {
|
@@ -505,6 +550,15 @@
|
|
505 |
progressTime.style.display = 'none';
|
506 |
});
|
507 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
508 |
// 再生/一時停止ボタン
|
509 |
playPauseBtn.addEventListener('click', function() {
|
510 |
if (isVideoPlaying) {
|
@@ -548,24 +602,26 @@
|
|
548 |
// 再生速度スライダー (動画プレイヤー)
|
549 |
speedSlider.addEventListener('input', function() {
|
550 |
const speed = parseFloat(this.value);
|
551 |
-
speedValue.textContent =
|
|
|
|
|
552 |
updatePlaybackRate(speed);
|
553 |
});
|
554 |
|
555 |
// 再生速度スライダー (設定メニュー)
|
556 |
-
|
557 |
const speed = parseFloat(this.value);
|
558 |
-
|
559 |
speedSlider.value = speed;
|
560 |
-
speedValue.textContent =
|
561 |
updatePlaybackRate(speed);
|
562 |
});
|
563 |
|
564 |
-
// 再生速度を更新
|
565 |
function updatePlaybackRate(speed) {
|
566 |
video.playbackRate = speed;
|
567 |
|
568 |
-
// 音声の再生速度も更新
|
569 |
audioFiles.forEach(file => {
|
570 |
if (audioSources[file]) {
|
571 |
audioSources[file].playbackRate.value = speed;
|
@@ -596,7 +652,7 @@
|
|
596 |
video.controls = false;
|
597 |
} else {
|
598 |
// 通常表示時の処理
|
599 |
-
video.controls = false;
|
600 |
}
|
601 |
}
|
602 |
|
@@ -639,7 +695,11 @@
|
|
639 |
const endTime = parseFloat(endTimeInput.value) || duration;
|
640 |
const loop = loopCheckbox.checked;
|
641 |
|
642 |
-
|
|
|
|
|
|
|
|
|
643 |
|
644 |
// ループ設定
|
645 |
source.loop = loop;
|
@@ -649,7 +709,7 @@
|
|
649 |
source.loopEnd = endTime;
|
650 |
}
|
651 |
|
652 |
-
// 再生速度を同期
|
653 |
source.playbackRate.value = video.playbackRate;
|
654 |
|
655 |
audioSources[file] = source;
|
@@ -657,8 +717,9 @@
|
|
657 |
|
658 |
// 再生関数
|
659 |
function playMedia() {
|
660 |
-
const startTime = parseFloat(startTimeInput.value) || 0;
|
661 |
let endTime = parseFloat(endTimeInput.value) || videoDuration;
|
|
|
662 |
|
663 |
// 終了時間が動画の長さを超えないように
|
664 |
endTime = Math.min(endTime, videoDuration);
|
@@ -683,23 +744,6 @@
|
|
683 |
isPlaying = true;
|
684 |
isVideoPlaying = true;
|
685 |
playPauseBtn.textContent = '⏸';
|
686 |
-
|
687 |
-
// 終了時間に達したら停止
|
688 |
-
video.ontimeupdate = function() {
|
689 |
-
if (video.currentTime >= endTime) {
|
690 |
-
if (!loopCheckbox.checked) {
|
691 |
-
stopMedia();
|
692 |
-
} else {
|
693 |
-
video.currentTime = startTime;
|
694 |
-
audioFiles.forEach(file => {
|
695 |
-
if (audioSources[file]) {
|
696 |
-
audioSources[file].stop();
|
697 |
-
playAudio(file, startTime);
|
698 |
-
}
|
699 |
-
});
|
700 |
-
}
|
701 |
-
}
|
702 |
-
};
|
703 |
}
|
704 |
|
705 |
// 一時停止関数
|
@@ -717,6 +761,7 @@
|
|
717 |
isPlaying = false;
|
718 |
isVideoPlaying = false;
|
719 |
playPauseBtn.textContent = '▶';
|
|
|
720 |
}
|
721 |
|
722 |
// 停止関数
|
@@ -727,11 +772,6 @@
|
|
727 |
updateTimeDisplay();
|
728 |
}
|
729 |
|
730 |
-
// イベントリスナーを設定
|
731 |
-
playBtn.addEventListener('click', playMedia);
|
732 |
-
pauseBtn.addEventListener('click', pauseMedia);
|
733 |
-
stopBtn.addEventListener('click', stopMedia);
|
734 |
-
|
735 |
// ボリュームスライダーのイベント
|
736 |
audioSliders.forEach((slider, index) => {
|
737 |
slider.addEventListener('input', function() {
|
@@ -765,7 +805,7 @@
|
|
765 |
loadAudioFiles();
|
766 |
updateVolumeIcon();
|
767 |
volumeSlider.value = video.volume;
|
768 |
-
video.controls = false;
|
769 |
});
|
770 |
</script>
|
771 |
</body>
|
|
|
47 |
border-radius: 5px;
|
48 |
background-color: #000;
|
49 |
display: block;
|
50 |
+
cursor: pointer;
|
51 |
}
|
52 |
|
53 |
.video-controls {
|
|
|
253 |
color: #ccd6f6;
|
254 |
}
|
255 |
|
256 |
+
.global-volume-container, .playback-speed-container {
|
257 |
+
display: flex;
|
258 |
+
align-items: center;
|
259 |
+
gap: 10px;
|
260 |
+
width: 100%;
|
261 |
+
}
|
262 |
+
|
263 |
+
.global-volume-slider, .playback-speed-slider {
|
264 |
+
flex-grow: 1;
|
265 |
+
height: 8px;
|
266 |
+
-webkit-appearance: none;
|
267 |
+
background: #1e2a47;
|
268 |
+
border-radius: 5px;
|
269 |
+
outline: none;
|
270 |
+
}
|
271 |
+
|
272 |
+
.global-volume-slider::-webkit-slider-thumb,
|
273 |
+
.playback-speed-slider::-webkit-slider-thumb {
|
274 |
+
-webkit-appearance: none;
|
275 |
+
width: 16px;
|
276 |
+
height: 16px;
|
277 |
+
background: #64ffda;
|
278 |
+
border-radius: 50%;
|
279 |
+
cursor: pointer;
|
280 |
+
}
|
281 |
+
|
282 |
+
.slider-value {
|
283 |
+
min-width: 40px;
|
284 |
+
text-align: right;
|
285 |
+
}
|
286 |
+
|
287 |
input[type="number"], input[type="checkbox"], select {
|
288 |
background-color: #112240;
|
289 |
border: 1px solid #64ffda;
|
|
|
292 |
border-radius: 3px;
|
293 |
}
|
294 |
|
|
|
|
|
|
|
|
|
|
|
|
|
295 |
.tech-decoration {
|
296 |
width: 100%;
|
297 |
height: 2px;
|
|
|
342 |
<input type="range" class="volume-slider" id="volume-slider" min="0" max="1" step="0.01" value="1">
|
343 |
</div>
|
344 |
<div class="speed-control">
|
345 |
+
<span class="speed-value" id="speed-value">1.00x</span>
|
346 |
+
<input type="range" class="speed-slider" id="speed-slider" min="0.25" max="3" step="0.05" value="1">
|
347 |
</div>
|
348 |
<button class="control-button fullscreen-button" id="fullscreen-btn">⛶</button>
|
349 |
</div>
|
|
|
367 |
<input type="checkbox" id="loop">
|
368 |
</div>
|
369 |
<div class="setting-item">
|
370 |
+
<div class="global-volume-container">
|
371 |
+
<label>全体音量係数:</label>
|
372 |
+
<input type="range" class="global-volume-slider" id="global-volume" min="0" max="3" step="0.1" value="1">
|
373 |
+
<span class="slider-value" id="global-volume-value">1.0</span>
|
374 |
+
</div>
|
375 |
</div>
|
376 |
<div class="setting-item">
|
377 |
+
<div class="playback-speed-container">
|
378 |
+
<label>再生速度:</label>
|
379 |
+
<input type="range" class="playback-speed-slider" id="playback-speed" min="0.25" max="3" step="0.05" value="1">
|
380 |
+
<span class="slider-value" id="playback-speed-value">1.00x</span>
|
381 |
+
</div>
|
382 |
</div>
|
383 |
</div>
|
384 |
|
|
|
389 |
<div class="audio-item">
|
390 |
<label>p.mp3</label>
|
391 |
<input type="range" class="audio-slider" data-audio="p" min="0" max="1" step="0.01" value="1">
|
392 |
+
<span class="slider-value volume-value">1.00</span>
|
393 |
</div>
|
394 |
<div class="audio-item">
|
395 |
<label>a.mp3</label>
|
396 |
<input type="range" class="audio-slider" data-audio="a" min="0" max="1" step="0.01" value="1">
|
397 |
+
<span class="slider-value volume-value">1.00</span>
|
398 |
</div>
|
399 |
<div class="audio-item">
|
400 |
<label>t.mp3</label>
|
401 |
<input type="range" class="audio-slider" data-audio="t" min="0" max="1" step="0.01" value="1">
|
402 |
+
<span class="slider-value volume-value">1.00</span>
|
403 |
</div>
|
404 |
<div class="audio-item">
|
405 |
<label>s.mp3</label>
|
406 |
<input type="range" class="audio-slider" data-audio="s" min="0" max="1" step="0.01" value="1">
|
407 |
+
<span class="slider-value volume-value">1.00</span>
|
408 |
</div>
|
409 |
<div class="audio-item">
|
410 |
<label>k.mp3</label>
|
411 |
<input type="range" class="audio-slider" data-audio="k" min="0" max="1" step="0.01" value="1">
|
412 |
+
<span class="slider-value volume-value">1.00</span>
|
413 |
</div>
|
414 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
415 |
</div>
|
416 |
|
417 |
<script>
|
|
|
428 |
const volumeSlider = document.getElementById('volume-slider');
|
429 |
const speedSlider = document.getElementById('speed-slider');
|
430 |
const speedValue = document.getElementById('speed-value');
|
431 |
+
const playbackSpeedSlider = document.getElementById('playback-speed');
|
432 |
+
const playbackSpeedValue = document.getElementById('playback-speed-value');
|
433 |
const fullscreenBtn = document.getElementById('fullscreen-btn');
|
|
|
|
|
|
|
434 |
const startTimeInput = document.getElementById('start-time');
|
435 |
const endTimeInput = document.getElementById('end-time');
|
436 |
const loopCheckbox = document.getElementById('loop');
|
|
|
444 |
const audioBuffers = {};
|
445 |
const audioSources = {};
|
446 |
const gainNodes = {};
|
447 |
+
const pitchShiftNodes = {};
|
448 |
|
449 |
// 音声ファイル名の配列
|
450 |
const audioFiles = ['p', 'a', 't', 's', 'k'];
|
|
|
454 |
let isPlaying = false;
|
455 |
let isVideoPlaying = false;
|
456 |
let lastVolume = 1;
|
457 |
+
let shouldPlayFromStartTime = false;
|
458 |
|
459 |
// 動画のメタデータが読み込まれたら
|
460 |
video.addEventListener('loadedmetadata', function() {
|
|
|
487 |
// 動画の時間更新イベント
|
488 |
video.addEventListener('timeupdate', updateTimeDisplay);
|
489 |
|
490 |
+
// 動画終了時の処理
|
491 |
+
video.addEventListener('ended', function() {
|
492 |
+
const startTime = parseFloat(startTimeInput.value) || 0;
|
493 |
+
if (!loopCheckbox.checked) {
|
494 |
+
stopMedia();
|
495 |
+
video.currentTime = startTime;
|
496 |
+
updateTimeDisplay();
|
497 |
+
}
|
498 |
+
});
|
499 |
+
|
500 |
// プログレスバークリックでシーク
|
501 |
progressContainer.addEventListener('click', function(e) {
|
502 |
if (!video.duration) return;
|
|
|
505 |
const pos = (e.clientX - rect.left) / rect.width;
|
506 |
const seekTime = pos * video.duration;
|
507 |
|
508 |
+
seekMedia(seekTime);
|
509 |
+
});
|
510 |
+
|
511 |
+
// 指定した時間にシーク
|
512 |
+
function seekMedia(time) {
|
513 |
+
const duration = video.duration || videoDuration;
|
514 |
+
const startTime = parseFloat(startTimeInput.value) || 0;
|
515 |
+
const endTime = parseFloat(endTimeInput.value) || duration;
|
516 |
+
|
517 |
+
// 範囲内に制限
|
518 |
+
const seekTime = Math.max(startTime, Math.min(time, endTime));
|
519 |
+
|
520 |
video.currentTime = seekTime;
|
521 |
|
522 |
// 音声も同期
|
523 |
audioFiles.forEach(file => {
|
524 |
if (audioSources[file]) {
|
525 |
audioSources[file].stop();
|
526 |
+
if (isPlaying) {
|
527 |
+
playAudio(file, seekTime);
|
528 |
+
}
|
529 |
}
|
530 |
});
|
531 |
+
}
|
532 |
|
533 |
// プログレスバー上でマウス移動時に時間を表示
|
534 |
progressContainer.addEventListener('mousemove', function(e) {
|
|
|
550 |
progressTime.style.display = 'none';
|
551 |
});
|
552 |
|
553 |
+
// 動画クリックで再生/一時停止
|
554 |
+
video.addEventListener('click', function() {
|
555 |
+
if (isVideoPlaying) {
|
556 |
+
pauseMedia();
|
557 |
+
} else {
|
558 |
+
playMedia();
|
559 |
+
}
|
560 |
+
});
|
561 |
+
|
562 |
// 再生/一時停止ボタン
|
563 |
playPauseBtn.addEventListener('click', function() {
|
564 |
if (isVideoPlaying) {
|
|
|
602 |
// 再生速度スライダー (動画プレイヤー)
|
603 |
speedSlider.addEventListener('input', function() {
|
604 |
const speed = parseFloat(this.value);
|
605 |
+
speedValue.textContent = speed.toFixed(2) + 'x';
|
606 |
+
playbackSpeedSlider.value = speed;
|
607 |
+
playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
|
608 |
updatePlaybackRate(speed);
|
609 |
});
|
610 |
|
611 |
// 再生速度スライダー (設定メニュー)
|
612 |
+
playbackSpeedSlider.addEventListener('input', function() {
|
613 |
const speed = parseFloat(this.value);
|
614 |
+
playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
|
615 |
speedSlider.value = speed;
|
616 |
+
speedValue.textContent = speed.toFixed(2) + 'x';
|
617 |
updatePlaybackRate(speed);
|
618 |
});
|
619 |
|
620 |
+
// 再生速度を更新 (音の高さを維持)
|
621 |
function updatePlaybackRate(speed) {
|
622 |
video.playbackRate = speed;
|
623 |
|
624 |
+
// 音声の再生速度も更新 (音の高さは維持)
|
625 |
audioFiles.forEach(file => {
|
626 |
if (audioSources[file]) {
|
627 |
audioSources[file].playbackRate.value = speed;
|
|
|
652 |
video.controls = false;
|
653 |
} else {
|
654 |
// 通常表示時の処理
|
655 |
+
video.controls = false;
|
656 |
}
|
657 |
}
|
658 |
|
|
|
695 |
const endTime = parseFloat(endTimeInput.value) || duration;
|
696 |
const loop = loopCheckbox.checked;
|
697 |
|
698 |
+
// 再生範囲を計算
|
699 |
+
const playStartTime = Math.max(startTime, parseFloat(startTimeInput.value) || 0);
|
700 |
+
const playDuration = Math.min(endTime, duration) - playStartTime;
|
701 |
+
|
702 |
+
source.start(0, playStartTime, playDuration);
|
703 |
|
704 |
// ループ設定
|
705 |
source.loop = loop;
|
|
|
709 |
source.loopEnd = endTime;
|
710 |
}
|
711 |
|
712 |
+
// 再生速度を同期 (音の高さは維持)
|
713 |
source.playbackRate.value = video.playbackRate;
|
714 |
|
715 |
audioSources[file] = source;
|
|
|
717 |
|
718 |
// 再生関数
|
719 |
function playMedia() {
|
720 |
+
const startTime = shouldPlayFromStartTime ? (parseFloat(startTimeInput.value) || 0) : video.currentTime;
|
721 |
let endTime = parseFloat(endTimeInput.value) || videoDuration;
|
722 |
+
shouldPlayFromStartTime = false;
|
723 |
|
724 |
// 終了時間が動画の長さを超えないように
|
725 |
endTime = Math.min(endTime, videoDuration);
|
|
|
744 |
isPlaying = true;
|
745 |
isVideoPlaying = true;
|
746 |
playPauseBtn.textContent = '⏸';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
747 |
}
|
748 |
|
749 |
// 一時停止関数
|
|
|
761 |
isPlaying = false;
|
762 |
isVideoPlaying = false;
|
763 |
playPauseBtn.textContent = '▶';
|
764 |
+
shouldPlayFromStartTime = true;
|
765 |
}
|
766 |
|
767 |
// 停止関数
|
|
|
772 |
updateTimeDisplay();
|
773 |
}
|
774 |
|
|
|
|
|
|
|
|
|
|
|
775 |
// ボリュームスライダーのイベント
|
776 |
audioSliders.forEach((slider, index) => {
|
777 |
slider.addEventListener('input', function() {
|
|
|
805 |
loadAudioFiles();
|
806 |
updateVolumeIcon();
|
807 |
volumeSlider.value = video.volume;
|
808 |
+
video.controls = false;
|
809 |
});
|
810 |
</script>
|
811 |
</body>
|