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>
|