soiz1 commited on
Commit
687452b
·
1 Parent(s): 60ca6fc

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +295 -247
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>音声動画プレイヤー</title>
7
  <style>
8
  body {
9
  font-family: 'Arial', sans-serif;
@@ -50,7 +50,7 @@
50
  }
51
 
52
  .video-controls {
53
- background-color: rgba(0, 0, 0, 0.7);
54
  padding: 10px;
55
  border-radius: 0 0 5px 5px;
56
  display: flex;
@@ -78,34 +78,35 @@
78
  .progress-time {
79
  position: absolute;
80
  top: -25px;
81
- background-color: rgba(0, 0, 0, 0.8);
82
- padding: 2px 5px;
 
83
  border-radius: 3px;
84
  font-size: 12px;
85
  display: none;
 
86
  }
87
 
88
- .controls-row {
89
- display: flex;
90
- justify-content: space-between;
91
- align-items: center;
92
- }
93
-
94
- .left-controls, .right-controls {
95
  display: flex;
96
  align-items: center;
97
- gap: 10px;
98
  }
99
 
100
  .control-button {
101
  background: none;
102
  border: none;
103
  color: #e6f1ff;
104
- font-size: 16px;
105
  cursor: pointer;
106
  padding: 5px;
107
- border-radius: 3px;
108
- transition: all 0.2s;
 
 
 
 
 
109
  }
110
 
111
  .control-button:hover {
@@ -114,29 +115,40 @@
114
 
115
  .time-display {
116
  font-size: 14px;
117
- color: #e6f1ff;
118
- font-family: monospace;
119
  }
120
 
121
  .volume-control {
122
  display: flex;
123
  align-items: center;
124
  gap: 5px;
 
 
 
 
 
 
 
 
 
 
125
  }
126
 
127
  .volume-slider {
128
  width: 80px;
129
- height: 5px;
130
  -webkit-appearance: none;
131
  background: #1e2a47;
132
- border-radius: 5px;
133
  outline: none;
134
  opacity: 0;
135
- transition: opacity 0.2s;
136
  }
137
 
138
  .volume-control:hover .volume-slider {
139
  opacity: 1;
 
140
  }
141
 
142
  .volume-slider::-webkit-slider-thumb {
@@ -148,6 +160,50 @@
148
  cursor: pointer;
149
  }
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  .audio-controls {
152
  display: flex;
153
  flex-direction: column;
@@ -166,7 +222,7 @@
166
  color: #64ffda;
167
  }
168
 
169
- .audio-volume-slider {
170
  flex-grow: 1;
171
  height: 8px;
172
  -webkit-appearance: none;
@@ -175,7 +231,7 @@
175
  outline: none;
176
  }
177
 
178
- .audio-volume-slider::-webkit-slider-thumb {
179
  -webkit-appearance: none;
180
  width: 18px;
181
  height: 18px;
@@ -226,37 +282,10 @@
226
  background: linear-gradient(90deg, transparent, #64ffda, transparent);
227
  margin: 20px 0;
228
  }
229
-
230
- .fullscreen {
231
- position: fixed;
232
- top: 0;
233
- left: 0;
234
- width: 100%;
235
- height: 100%;
236
- background-color: black;
237
- z-index: 1000;
238
- display: flex;
239
- justify-content: center;
240
- align-items: center;
241
- }
242
-
243
- .fullscreen video {
244
- width: 100%;
245
- height: 100%;
246
- max-width: 100%;
247
- max-height: 100%;
248
- }
249
-
250
- .fullscreen .video-container {
251
- width: 100%;
252
- height: 100%;
253
- max-width: 100%;
254
- max-height: 100%;
255
- }
256
  </style>
257
  </head>
258
  <body>
259
- <h1>音声動画プレイヤー</h1>
260
 
261
  <div class="container">
262
  <div class="video-container">
@@ -268,18 +297,27 @@
268
  <div class="progress-bar" id="progress-bar"></div>
269
  <div class="progress-time" id="progress-time">00:00</div>
270
  </div>
271
- <div class="controls-row">
272
- <div class="left-controls">
273
- <button class="control-button" id="play-pause-btn">▶</button>
274
- <span class="time-display" id="time-display">00:00 / 00:00</span>
 
 
275
  </div>
276
- <div class="right-controls">
277
- <div class="volume-control">
278
- <button class="control-button" id="volume-btn">🔊</button>
279
- <input type="range" class="volume-slider" id="video-volume" min="0" max="1" step="0.01" value="1">
 
 
 
 
 
 
 
280
  </div>
281
- <button class="control-button" id="fullscreen-btn">⛶</button>
282
  </div>
 
283
  </div>
284
  </div>
285
  </div>
@@ -300,19 +338,6 @@
300
  <label for="loop">ループ再生:</label>
301
  <input type="checkbox" id="loop">
302
  </div>
303
- <div class="setting-item">
304
- <label for="playback-rate">再生速度:</label>
305
- <select id="playback-rate">
306
- <option value="0.25">0.25x</option>
307
- <option value="0.5">0.5x</option>
308
- <option value="0.75">0.75x</option>
309
- <option value="1" selected>1x</option>
310
- <option value="1.25">1.25x</option>
311
- <option value="1.5">1.5x</option>
312
- <option value="2">2x</option>
313
- <option value="3">3x</option>
314
- </select>
315
- </div>
316
  <div class="setting-item">
317
  <label for="global-volume">全体音量係数:</label>
318
  <input type="range" id="global-volume" min="0" max="3" step="0.1" value="1">
@@ -326,27 +351,27 @@
326
  <h2>音声コントロール</h2>
327
  <div class="audio-item">
328
  <label>p.mp3</label>
329
- <input type="range" class="audio-volume-slider" data-audio="p" min="0" max="1" step="0.01" value="1">
330
  <span class="volume-value">1</span>
331
  </div>
332
  <div class="audio-item">
333
  <label>a.mp3</label>
334
- <input type="range" class="audio-volume-slider" data-audio="a" min="0" max="1" step="0.01" value="1">
335
  <span class="volume-value">1</span>
336
  </div>
337
  <div class="audio-item">
338
  <label>t.mp3</label>
339
- <input type="range" class="audio-volume-slider" data-audio="t" min="0" max="1" step="0.01" value="1">
340
  <span class="volume-value">1</span>
341
  </div>
342
  <div class="audio-item">
343
  <label>s.mp3</label>
344
- <input type="range" class="audio-volume-slider" data-audio="s" min="0" max="1" step="0.01" value="1">
345
  <span class="volume-value">1</span>
346
  </div>
347
  <div class="audio-item">
348
  <label>k.mp3</label>
349
- <input type="range" class="audio-volume-slider" data-audio="k" min="0" max="1" step="0.01" value="1">
350
  <span class="volume-value">1</span>
351
  </div>
352
  </div>
@@ -365,24 +390,24 @@
365
  // 要素を取得
366
  const video = document.getElementById('video');
367
  const playPauseBtn = document.getElementById('play-pause-btn');
368
- const fullscreenBtn = document.getElementById('fullscreen-btn');
369
  const progressContainer = document.getElementById('progress-container');
370
  const progressBar = document.getElementById('progress-bar');
371
  const progressTime = document.getElementById('progress-time');
372
- const timeDisplay = document.getElementById('time-display');
373
  const volumeBtn = document.getElementById('volume-btn');
374
- const videoVolumeSlider = document.getElementById('video-volume');
375
-
 
 
376
  const playBtn = document.getElementById('play-btn');
377
  const pauseBtn = document.getElementById('pause-btn');
378
  const stopBtn = document.getElementById('stop-btn');
379
  const startTimeInput = document.getElementById('start-time');
380
  const endTimeInput = document.getElementById('end-time');
381
  const loopCheckbox = document.getElementById('loop');
382
- const playbackRateSelect = document.getElementById('playback-rate');
383
  const globalVolumeSlider = document.getElementById('global-volume');
384
  const globalVolumeValue = document.getElementById('global-volume-value');
385
- const volumeSliders = document.querySelectorAll('.audio-volume-slider');
386
  const volumeValues = document.querySelectorAll('.volume-value');
387
 
388
  // 音声オブジェクトを作成
@@ -397,7 +422,7 @@
397
  // 初期化
398
  let videoDuration = 0;
399
  let isPlaying = false;
400
- let isFullscreen = false;
401
  let lastVolume = 1;
402
 
403
  // 動画のメタデータが読み込まれたら
@@ -412,19 +437,137 @@
412
  // 時間表示を更新
413
  function updateTimeDisplay() {
414
  const currentTime = video.currentTime;
415
- const duration = video.duration;
 
 
 
 
 
416
 
417
- timeDisplay.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
418
- progressBar.style.width = `${(currentTime / duration) * 100}%`;
 
 
 
 
 
419
  }
420
 
421
- // 時間をフォーマット (00:00形式)
422
- function formatTime(seconds) {
423
- const mins = Math.floor(seconds / 60);
424
- const secs = Math.floor(seconds % 60);
425
- return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  }
427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
  // 音声ファイルを読み込む
429
  function loadAudioFiles() {
430
  audioFiles.forEach(file => {
@@ -440,22 +583,56 @@
440
  });
441
  }
442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  // 再生関数
444
  function playMedia() {
445
- if (isPlaying) return;
446
-
447
  const startTime = parseFloat(startTimeInput.value) || 0;
448
  let endTime = parseFloat(endTimeInput.value) || videoDuration;
449
- const loop = loopCheckbox.checked;
450
- const playbackRate = parseFloat(playbackRateSelect.value);
451
- const globalVolume = parseFloat(globalVolumeSlider.value);
452
 
453
  // 終了時間が動画の長さを超えないように
454
  endTime = Math.min(endTime, videoDuration);
455
 
456
  // 動画を設定
457
  video.currentTime = startTime;
458
- video.playbackRate = playbackRate;
459
  video.muted = true;
460
 
461
  // 音声を再生
@@ -465,49 +642,29 @@
465
  if (audioSources[file]) {
466
  audioSources[file].stop();
467
  }
468
-
469
- const source = audioContext.createBufferSource();
470
- source.buffer = audioBuffers[file];
471
-
472
- // ボリュームスライダーの値を取得
473
- const volumeSlider = document.querySelector(`.audio-volume-slider[data-audio="${file}"]`);
474
- const volume = parseFloat(volumeSlider.value) * globalVolume;
475
-
476
- // ゲインノードを設定
477
- gainNodes[file].gain.value = volume;
478
-
479
- // 接続
480
- source.connect(gainNodes[file]);
481
- gainNodes[file].connect(audioContext.destination);
482
-
483
- // 再生
484
- source.start(0, startTime, endTime - startTime);
485
-
486
- // ループ設定
487
- source.loop = loop;
488
- if (loop) {
489
- source.loopStart = startTime;
490
- source.loopEnd = endTime;
491
- }
492
-
493
- audioSources[file] = source;
494
  }
495
  });
496
 
497
  // 動画を再生
498
  video.play();
499
  isPlaying = true;
 
500
  playPauseBtn.textContent = '⏸';
501
 
502
  // 終了時間に達したら停止
503
  video.ontimeupdate = function() {
504
- updateTimeDisplay();
505
-
506
  if (video.currentTime >= endTime) {
507
- if (!loop) {
508
  stopMedia();
509
  } else {
510
  video.currentTime = startTime;
 
 
 
 
 
 
511
  }
512
  }
513
  };
@@ -526,131 +683,25 @@
526
  });
527
 
528
  isPlaying = false;
 
529
  playPauseBtn.textContent = '▶';
530
  }
531
 
532
  // 停止関数
533
  function stopMedia() {
534
  pauseMedia();
535
- video.currentTime = parseFloat(startTimeInput.value) || 0;
 
536
  updateTimeDisplay();
537
  }
538
 
539
- // 再生/一時停止ボタンの切り替え
540
- playPauseBtn.addEventListener('click', function() {
541
- if (isPlaying) {
542
- pauseMedia();
543
- } else {
544
- playMedia();
545
- }
546
- });
547
-
548
- // プログレスバークリックでシーク
549
- progressContainer.addEventListener('click', function(e) {
550
- if (!videoDuration) return;
551
-
552
- const rect = this.getBoundingClientRect();
553
- const pos = (e.clientX - rect.left) / rect.width;
554
- const seekTime = pos * videoDuration;
555
-
556
- video.currentTime = seekTime;
557
-
558
- if (!isPlaying) {
559
- updateTimeDisplay();
560
- }
561
- });
562
-
563
- // プログレスバー上でマウス移動時に時間を表示
564
- progressContainer.addEventListener('mousemove', function(e) {
565
- if (!videoDuration) return;
566
-
567
- const rect = this.getBoundingClientRect();
568
- const pos = (e.clientX - rect.left) / rect.width;
569
- const seekTime = pos * videoDuration;
570
-
571
- progressTime.textContent = formatTime(seekTime);
572
- progressTime.style.left = `${e.clientX - rect.left}px`;
573
- progressTime.style.display = 'block';
574
- });
575
-
576
- progressContainer.addEventListener('mouseout', function() {
577
- progressTime.style.display = 'none';
578
- });
579
-
580
- // ボリュームコントロール
581
- volumeBtn.addEventListener('click', function() {
582
- if (video.volume > 0) {
583
- lastVolume = video.volume;
584
- video.volume = 0;
585
- videoVolumeSlider.value = 0;
586
- volumeBtn.textContent = '🔇';
587
- } else {
588
- video.volume = lastVolume;
589
- videoVolumeSlider.value = lastVolume;
590
- volumeBtn.textContent = lastVolume > 0.5 ? '🔊' : '🔉';
591
- }
592
- });
593
-
594
- videoVolumeSlider.addEventListener('input', function() {
595
- const volume = parseFloat(this.value);
596
- video.volume = volume;
597
- lastVolume = volume;
598
-
599
- if (volume === 0) {
600
- volumeBtn.textContent = '🔇';
601
- } else if (volume > 0.5) {
602
- volumeBtn.textContent = '🔊';
603
- } else {
604
- volumeBtn.textContent = '🔉';
605
- }
606
- });
607
-
608
- // フルスクリーン
609
- fullscreenBtn.addEventListener('click', function() {
610
- if (!isFullscreen) {
611
- enterFullscreen();
612
- } else {
613
- exitFullscreen();
614
- }
615
- });
616
-
617
- function enterFullscreen() {
618
- const elem = video.parentElement;
619
-
620
- if (elem.requestFullscreen) {
621
- elem.requestFullscreen();
622
- } else if (elem.webkitRequestFullscreen) {
623
- elem.webkitRequestFullscreen();
624
- } else if (elem.msRequestFullscreen) {
625
- elem.msRequestFullscreen();
626
- }
627
-
628
- isFullscreen = true;
629
- }
630
-
631
- function exitFullscreen() {
632
- if (document.exitFullscreen) {
633
- document.exitFullscreen();
634
- } else if (document.webkitExitFullscreen) {
635
- document.webkitExitFullscreen();
636
- } else if (document.msExitFullscreen) {
637
- document.msExitFullscreen();
638
- }
639
-
640
- isFullscreen = false;
641
- }
642
-
643
- // フルスクリーン状態の変更を監視
644
- document.addEventListener('fullscreenchange', handleFullscreenChange);
645
- document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
646
- document.addEventListener('msfullscreenchange', handleFullscreenChange);
647
-
648
- function handleFullscreenChange() {
649
- isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement);
650
- }
651
 
652
  // ボリュームスライダーのイベント
653
- volumeSliders.forEach((slider, index) => {
654
  slider.addEventListener('input', function() {
655
  const value = parseFloat(this.value);
656
  volumeValues[index].textContent = value.toFixed(2);
@@ -670,7 +721,7 @@
670
  if (isPlaying) {
671
  audioFiles.forEach(file => {
672
  if (gainNodes[file]) {
673
- const volumeSlider = document.querySelector(`.audio-volume-slider[data-audio="${file}"]`);
674
  const volume = parseFloat(volumeSlider.value) * value;
675
  gainNodes[file].gain.value = volume;
676
  }
@@ -678,13 +729,10 @@
678
  }
679
  });
680
 
681
- // 再生ボタン
682
- playBtn.addEventListener('click', playMedia);
683
- pauseBtn.addEventListener('click', pauseMedia);
684
- stopBtn.addEventListener('click', stopMedia);
685
-
686
  // 初期化
687
  loadAudioFiles();
 
 
688
  });
689
  </script>
690
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>高度な音声動画プレイヤー</title>
7
  <style>
8
  body {
9
  font-family: 'Arial', sans-serif;
 
50
  }
51
 
52
  .video-controls {
53
+ background-color: rgba(17, 34, 64, 0.9);
54
  padding: 10px;
55
  border-radius: 0 0 5px 5px;
56
  display: flex;
 
78
  .progress-time {
79
  position: absolute;
80
  top: -25px;
81
+ transform: translateX(-50%);
82
+ background-color: rgba(30, 42, 71, 0.9);
83
+ padding: 3px 6px;
84
  border-radius: 3px;
85
  font-size: 12px;
86
  display: none;
87
+ white-space: nowrap;
88
  }
89
 
90
+ .main-controls {
 
 
 
 
 
 
91
  display: flex;
92
  align-items: center;
93
+ gap: 15px;
94
  }
95
 
96
  .control-button {
97
  background: none;
98
  border: none;
99
  color: #e6f1ff;
100
+ font-size: 18px;
101
  cursor: pointer;
102
  padding: 5px;
103
+ border-radius: 50%;
104
+ width: 36px;
105
+ height: 36px;
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ transition: background-color 0.3s;
110
  }
111
 
112
  .control-button:hover {
 
115
 
116
  .time-display {
117
  font-size: 14px;
118
+ color: #ccd6f6;
119
+ white-space: nowrap;
120
  }
121
 
122
  .volume-control {
123
  display: flex;
124
  align-items: center;
125
  gap: 5px;
126
+ margin-left: auto;
127
+ }
128
+
129
+ .volume-button {
130
+ background: none;
131
+ border: none;
132
+ color: #e6f1ff;
133
+ font-size: 18px;
134
+ cursor: pointer;
135
+ padding: 5px;
136
  }
137
 
138
  .volume-slider {
139
  width: 80px;
140
+ height: 6px;
141
  -webkit-appearance: none;
142
  background: #1e2a47;
143
+ border-radius: 3px;
144
  outline: none;
145
  opacity: 0;
146
+ transition: opacity 0.3s, width 0.3s;
147
  }
148
 
149
  .volume-control:hover .volume-slider {
150
  opacity: 1;
151
+ width: 100px;
152
  }
153
 
154
  .volume-slider::-webkit-slider-thumb {
 
160
  cursor: pointer;
161
  }
162
 
163
+ .speed-control {
164
+ position: relative;
165
+ }
166
+
167
+ .speed-button {
168
+ background: none;
169
+ border: none;
170
+ color: #e6f1ff;
171
+ font-size: 14px;
172
+ cursor: pointer;
173
+ padding: 5px 10px;
174
+ border-radius: 3px;
175
+ }
176
+
177
+ .speed-options {
178
+ position: absolute;
179
+ bottom: 100%;
180
+ left: 0;
181
+ background-color: #1e2a47;
182
+ border-radius: 5px;
183
+ padding: 5px 0;
184
+ display: none;
185
+ min-width: 80px;
186
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
187
+ }
188
+
189
+ .speed-options.show {
190
+ display: block;
191
+ }
192
+
193
+ .speed-option {
194
+ padding: 5px 10px;
195
+ cursor: pointer;
196
+ text-align: center;
197
+ }
198
+
199
+ .speed-option:hover {
200
+ background-color: rgba(100, 255, 218, 0.2);
201
+ }
202
+
203
+ .fullscreen-button {
204
+ margin-left: 10px;
205
+ }
206
+
207
  .audio-controls {
208
  display: flex;
209
  flex-direction: column;
 
222
  color: #64ffda;
223
  }
224
 
225
+ .audio-slider {
226
  flex-grow: 1;
227
  height: 8px;
228
  -webkit-appearance: none;
 
231
  outline: none;
232
  }
233
 
234
+ .audio-slider::-webkit-slider-thumb {
235
  -webkit-appearance: none;
236
  width: 18px;
237
  height: 18px;
 
282
  background: linear-gradient(90deg, transparent, #64ffda, transparent);
283
  margin: 20px 0;
284
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  </style>
286
  </head>
287
  <body>
288
+ <h1>高度な音声動画プレイヤー</h1>
289
 
290
  <div class="container">
291
  <div class="video-container">
 
297
  <div class="progress-bar" id="progress-bar"></div>
298
  <div class="progress-time" id="progress-time">00:00</div>
299
  </div>
300
+ <div class="main-controls">
301
+ <button class="control-button" id="play-pause-btn">▶</button>
302
+ <div class="time-display" id="time-display">00:00 / 00:00</div>
303
+ <div class="volume-control">
304
+ <button class="volume-button" id="volume-btn">🔊</button>
305
+ <input type="range" class="volume-slider" id="volume-slider" min="0" max="1" step="0.01" value="1">
306
  </div>
307
+ <div class="speed-control">
308
+ <button class="speed-button" id="speed-btn">1x</button>
309
+ <div class="speed-options" id="speed-options">
310
+ <div class="speed-option" data-speed="0.25">0.25x</div>
311
+ <div class="speed-option" data-speed="0.5">0.5x</div>
312
+ <div class="speed-option" data-speed="0.75">0.75x</div>
313
+ <div class="speed-option" data-speed="1">1x</div>
314
+ <div class="speed-option" data-speed="1.25">1.25x</div>
315
+ <div class="speed-option" data-speed="1.5">1.5x</div>
316
+ <div class="speed-option" data-speed="2">2x</div>
317
+ <div class="speed-option" data-speed="3">3x</div>
318
  </div>
 
319
  </div>
320
+ <button class="control-button fullscreen-button" id="fullscreen-btn">⛶</button>
321
  </div>
322
  </div>
323
  </div>
 
338
  <label for="loop">ループ再生:</label>
339
  <input type="checkbox" id="loop">
340
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  <div class="setting-item">
342
  <label for="global-volume">全体音量係数:</label>
343
  <input type="range" id="global-volume" min="0" max="3" step="0.1" value="1">
 
351
  <h2>音声コントロール</h2>
352
  <div class="audio-item">
353
  <label>p.mp3</label>
354
+ <input type="range" class="audio-slider" data-audio="p" min="0" max="1" step="0.01" value="1">
355
  <span class="volume-value">1</span>
356
  </div>
357
  <div class="audio-item">
358
  <label>a.mp3</label>
359
+ <input type="range" class="audio-slider" data-audio="a" min="0" max="1" step="0.01" value="1">
360
  <span class="volume-value">1</span>
361
  </div>
362
  <div class="audio-item">
363
  <label>t.mp3</label>
364
+ <input type="range" class="audio-slider" data-audio="t" min="0" max="1" step="0.01" value="1">
365
  <span class="volume-value">1</span>
366
  </div>
367
  <div class="audio-item">
368
  <label>s.mp3</label>
369
+ <input type="range" class="audio-slider" data-audio="s" min="0" max="1" step="0.01" value="1">
370
  <span class="volume-value">1</span>
371
  </div>
372
  <div class="audio-item">
373
  <label>k.mp3</label>
374
+ <input type="range" class="audio-slider" data-audio="k" min="0" max="1" step="0.01" value="1">
375
  <span class="volume-value">1</span>
376
  </div>
377
  </div>
 
390
  // 要素を取得
391
  const video = document.getElementById('video');
392
  const playPauseBtn = document.getElementById('play-pause-btn');
393
+ const timeDisplay = document.getElementById('time-display');
394
  const progressContainer = document.getElementById('progress-container');
395
  const progressBar = document.getElementById('progress-bar');
396
  const progressTime = document.getElementById('progress-time');
 
397
  const volumeBtn = document.getElementById('volume-btn');
398
+ const volumeSlider = document.getElementById('volume-slider');
399
+ const speedBtn = document.getElementById('speed-btn');
400
+ const speedOptions = document.getElementById('speed-options');
401
+ const fullscreenBtn = document.getElementById('fullscreen-btn');
402
  const playBtn = document.getElementById('play-btn');
403
  const pauseBtn = document.getElementById('pause-btn');
404
  const stopBtn = document.getElementById('stop-btn');
405
  const startTimeInput = document.getElementById('start-time');
406
  const endTimeInput = document.getElementById('end-time');
407
  const loopCheckbox = document.getElementById('loop');
 
408
  const globalVolumeSlider = document.getElementById('global-volume');
409
  const globalVolumeValue = document.getElementById('global-volume-value');
410
+ const audioSliders = document.querySelectorAll('.audio-slider');
411
  const volumeValues = document.querySelectorAll('.volume-value');
412
 
413
  // 音声オブジェクトを作成
 
422
  // 初期化
423
  let videoDuration = 0;
424
  let isPlaying = false;
425
+ let isVideoPlaying = false;
426
  let lastVolume = 1;
427
 
428
  // 動画のメタデータが読み込まれたら
 
437
  // 時間表示を更新
438
  function updateTimeDisplay() {
439
  const currentTime = video.currentTime;
440
+ const duration = video.duration || videoDuration;
441
+
442
+ const currentMinutes = Math.floor(currentTime / 60);
443
+ const currentSeconds = Math.floor(currentTime % 60);
444
+ const durationMinutes = Math.floor(duration / 60);
445
+ const durationSeconds = Math.floor(duration % 60);
446
 
447
+ timeDisplay.textContent =
448
+ `${String(currentMinutes).padStart(2, '0')}:${String(currentSeconds).padStart(2, '0')} / ` +
449
+ `${String(durationMinutes).padStart(2, '0')}:${String(durationSeconds).padStart(2, '0')}`;
450
+
451
+ // プログレスバーを更新
452
+ const progressPercent = (currentTime / duration) * 100;
453
+ progressBar.style.width = `${progressPercent}%`;
454
  }
455
 
456
+ // 動画の時間更新イベント
457
+ video.addEventListener('timeupdate', updateTimeDisplay);
458
+
459
+ // プログレスバークリックでシーク
460
+ progressContainer.addEventListener('click', function(e) {
461
+ if (!video.duration) return;
462
+
463
+ const rect = this.getBoundingClientRect();
464
+ const pos = (e.clientX - rect.left) / rect.width;
465
+ const seekTime = pos * video.duration;
466
+
467
+ video.currentTime = seekTime;
468
+
469
+ // 音声も同期
470
+ audioFiles.forEach(file => {
471
+ if (audioSources[file]) {
472
+ audioSources[file].stop();
473
+ playAudio(file, seekTime);
474
+ }
475
+ });
476
+ });
477
+
478
+ // プログレスバー上でマウス移動時に時間を表示
479
+ progressContainer.addEventListener('mousemove', function(e) {
480
+ if (!video.duration) return;
481
+
482
+ const rect = this.getBoundingClientRect();
483
+ const pos = (e.clientX - rect.left) / rect.width;
484
+ const hoverTime = pos * video.duration;
485
+
486
+ const minutes = Math.floor(hoverTime / 60);
487
+ const seconds = Math.floor(hoverTime % 60);
488
+
489
+ progressTime.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
490
+ progressTime.style.display = 'block';
491
+ progressTime.style.left = `${pos * 100}%`;
492
+ });
493
+
494
+ progressContainer.addEventListener('mouseleave', function() {
495
+ progressTime.style.display = 'none';
496
+ });
497
+
498
+ // 再生/一時停止ボタン
499
+ playPauseBtn.addEventListener('click', function() {
500
+ if (isVideoPlaying) {
501
+ pauseMedia();
502
+ } else {
503
+ playMedia();
504
+ }
505
+ });
506
+
507
+ // 音量コントロール
508
+ volumeSlider.addEventListener('input', function() {
509
+ video.volume = this.value;
510
+ lastVolume = this.value;
511
+ updateVolumeIcon();
512
+ });
513
+
514
+ // 音量ボタン
515
+ volumeBtn.addEventListener('click', function() {
516
+ if (video.volume > 0) {
517
+ lastVolume = video.volume;
518
+ video.volume = 0;
519
+ volumeSlider.value = 0;
520
+ } else {
521
+ video.volume = lastVolume;
522
+ volumeSlider.value = lastVolume;
523
+ }
524
+ updateVolumeIcon();
525
+ });
526
+
527
+ // 音量アイコンを更新
528
+ function updateVolumeIcon() {
529
+ if (video.volume === 0) {
530
+ volumeBtn.textContent = '🔇';
531
+ } else if (video.volume < 0.5) {
532
+ volumeBtn.textContent = '🔈';
533
+ } else {
534
+ volumeBtn.textContent = '🔊';
535
+ }
536
  }
537
 
538
+ // 再生速度コントロール
539
+ speedBtn.addEventListener('click', function() {
540
+ speedOptions.classList.toggle('show');
541
+ });
542
+
543
+ // 再生速度オプション選択
544
+ document.querySelectorAll('.speed-option').forEach(option => {
545
+ option.addEventListener('click', function() {
546
+ const speed = parseFloat(this.dataset.speed);
547
+ video.playbackRate = speed;
548
+ speedBtn.textContent = `${speed}x`;
549
+ speedOptions.classList.remove('show');
550
+
551
+ // 音声の再生速度も更新
552
+ audioFiles.forEach(file => {
553
+ if (audioSources[file]) {
554
+ audioSources[file].playbackRate.value = speed;
555
+ }
556
+ });
557
+ });
558
+ });
559
+
560
+ // 全画面ボタン
561
+ fullscreenBtn.addEventListener('click', function() {
562
+ if (video.requestFullscreen) {
563
+ video.requestFullscreen();
564
+ } else if (video.webkitRequestFullscreen) {
565
+ video.webkitRequestFullscreen();
566
+ } else if (video.msRequestFullscreen) {
567
+ video.msRequestFullscreen();
568
+ }
569
+ });
570
+
571
  // 音声ファイルを読み込む
572
  function loadAudioFiles() {
573
  audioFiles.forEach(file => {
 
583
  });
584
  }
585
 
586
+ // 音声を再生
587
+ function playAudio(file, startTime) {
588
+ if (!audioBuffers[file]) return;
589
+
590
+ const source = audioContext.createBufferSource();
591
+ source.buffer = audioBuffers[file];
592
+
593
+ // ボリュームスライダーの値を取得
594
+ const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
595
+ const volume = parseFloat(volumeSlider.value) * parseFloat(globalVolumeSlider.value);
596
+
597
+ // ゲインノードを設定
598
+ gainNodes[file].gain.value = volume;
599
+
600
+ // 接続
601
+ source.connect(gainNodes[file]);
602
+ gainNodes[file].connect(audioContext.destination);
603
+
604
+ // 再生
605
+ const currentTime = video.currentTime;
606
+ const duration = video.duration || videoDuration;
607
+ const endTime = parseFloat(endTimeInput.value) || duration;
608
+ const loop = loopCheckbox.checked;
609
+
610
+ source.start(0, currentTime, endTime - currentTime);
611
+
612
+ // ループ設定
613
+ source.loop = loop;
614
+ if (loop) {
615
+ const start = parseFloat(startTimeInput.value) || 0;
616
+ source.loopStart = start;
617
+ source.loopEnd = endTime;
618
+ }
619
+
620
+ // 再生速度を同期
621
+ source.playbackRate.value = video.playbackRate;
622
+
623
+ audioSources[file] = source;
624
+ }
625
+
626
  // 再生関数
627
  function playMedia() {
 
 
628
  const startTime = parseFloat(startTimeInput.value) || 0;
629
  let endTime = parseFloat(endTimeInput.value) || videoDuration;
 
 
 
630
 
631
  // 終了時間が動画の長さを超えないように
632
  endTime = Math.min(endTime, videoDuration);
633
 
634
  // 動画を設定
635
  video.currentTime = startTime;
 
636
  video.muted = true;
637
 
638
  // 音声を再生
 
642
  if (audioSources[file]) {
643
  audioSources[file].stop();
644
  }
645
+ playAudio(file, startTime);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
646
  }
647
  });
648
 
649
  // 動画を再生
650
  video.play();
651
  isPlaying = true;
652
+ isVideoPlaying = true;
653
  playPauseBtn.textContent = '⏸';
654
 
655
  // 終了時間に達したら停止
656
  video.ontimeupdate = function() {
 
 
657
  if (video.currentTime >= endTime) {
658
+ if (!loopCheckbox.checked) {
659
  stopMedia();
660
  } else {
661
  video.currentTime = startTime;
662
+ audioFiles.forEach(file => {
663
+ if (audioSources[file]) {
664
+ audioSources[file].stop();
665
+ playAudio(file, startTime);
666
+ }
667
+ });
668
  }
669
  }
670
  };
 
683
  });
684
 
685
  isPlaying = false;
686
+ isVideoPlaying = false;
687
  playPauseBtn.textContent = '▶';
688
  }
689
 
690
  // 停止関数
691
  function stopMedia() {
692
  pauseMedia();
693
+ const startTime = parseFloat(startTimeInput.value) || 0;
694
+ video.currentTime = startTime;
695
  updateTimeDisplay();
696
  }
697
 
698
+ // イベントリスナーを設定
699
+ playBtn.addEventListener('click', playMedia);
700
+ pauseBtn.addEventListener('click', pauseMedia);
701
+ stopBtn.addEventListener('click', stopMedia);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
702
 
703
  // ボリュームスライダーのイベント
704
+ audioSliders.forEach((slider, index) => {
705
  slider.addEventListener('input', function() {
706
  const value = parseFloat(this.value);
707
  volumeValues[index].textContent = value.toFixed(2);
 
721
  if (isPlaying) {
722
  audioFiles.forEach(file => {
723
  if (gainNodes[file]) {
724
+ const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
725
  const volume = parseFloat(volumeSlider.value) * value;
726
  gainNodes[file].gain.value = volume;
727
  }
 
729
  }
730
  });
731
 
 
 
 
 
 
732
  // 初期化
733
  loadAudioFiles();
734
+ updateVolumeIcon();
735
+ volumeSlider.value = video.volume;
736
  });
737
  </script>
738
  </body>