soiz1's picture
Update index.html
48e2a13
raw
history blame
42.9 kB
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<script src="https://soiz1-eruda3.hf.space/eruda.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高度な音声動画プレイヤー</title>
<style>
/* 既存のスタイルはそのまま保持 */
body {
font-family: 'Arial', sans-serif;
background-color: #0a192f;
color: #e6f1ff;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
h1 {
color: #64ffda;
text-align: center;
margin-bottom: 30px;
border-bottom: 1px solid #64ffda;
padding-bottom: 10px;
width: 100%;
}
.container {
display: flex;
flex-direction: column;
width: 100%;
max-width: 800px;
background-color: #112240;
border-radius: 10px;
padding: 20px;
box-shadow: 0 0 20px rgba(100, 255, 218, 0.2);
}
.video-container {
position: relative;
width: 100%;
margin-bottom: 20px;
}
video {
width: 100%;
border-radius: 5px;
background-color: #000;
display: block;
cursor: pointer;
}
.video-controls {
background-color: rgba(17, 34, 64, 0.9);
padding: 10px;
border-radius: 0 0 5px 5px;
display: flex;
flex-direction: column;
gap: 10px;
}
.progress-container {
width: 100%;
height: 10px;
background-color: #1e2a47;
border-radius: 5px;
cursor: pointer;
position: relative;
}
.progress-bar {
height: 100%;
background-color: #64ffda;
border-radius: 5px;
width: 0%;
position: relative;
}
.progress-time {
position: absolute;
top: -25px;
transform: translateX(-50%);
background-color: rgba(30, 42, 71, 0.9);
padding: 3px 6px;
border-radius: 3px;
font-size: 12px;
display: none;
white-space: nowrap;
}
.main-controls {
display: flex;
align-items: center;
gap: 15px;
}
.control-button {
background: none;
border: none;
color: #e6f1ff;
font-size: 18px;
cursor: pointer;
padding: 5px;
border-radius: 50%;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s;
}
.control-button:hover {
background-color: rgba(100, 255, 218, 0.2);
}
.time-display {
font-size: 14px;
color: #ccd6f6;
white-space: nowrap;
}
.volume-control {
display: flex;
align-items: center;
gap: 5px;
margin-left: auto;
}
.volume-button {
background: none;
border: none;
color: #e6f1ff;
font-size: 18px;
cursor: pointer;
padding: 5px;
}
.volume-slider {
width: 80px;
height: 6px;
-webkit-appearance: none;
background: #1e2a47;
border-radius: 3px;
outline: none;
opacity: 0;
transition: opacity 0.3s, width 0.3s;
}
.volume-control:hover .volume-slider {
opacity: 1;
width: 100px;
}
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
background: #64ffda;
border-radius: 50%;
cursor: pointer;
}
.speed-control {
display: flex;
align-items: center;
gap: 5px;
}
.speed-slider {
width: 80px;
height: 6px;
-webkit-appearance: none;
background: #1e2a47;
border-radius: 3px;
outline: none;
}
.speed-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
background: #64ffda;
border-radius: 50%;
cursor: pointer;
}
.speed-value {
font-size: 14px;
min-width: 30px;
text-align: center;
}
.fullscreen-button {
margin-left: 10px;
}
.audio-controls {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 15px;
}
.audio-item {
display: flex;
align-items: center;
gap: 10px;
}
.audio-item label {
min-width: 50px;
color: #64ffda;
}
.audio-slider {
flex-grow: 1;
height: 8px;
-webkit-appearance: none;
background: #1e2a47;
border-radius: 5px;
outline: none;
}
.audio-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
background: #64ffda;
border-radius: 50%;
cursor: pointer;
}
.settings {
background-color: #1e2a47;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.setting-item:last-child {
margin-bottom: 0;
}
.setting-item label {
color: #ccd6f6;
}
.global-volume-container, .playback-speed-container {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
}
.global-volume-slider, .playback-speed-slider {
flex-grow: 1;
height: 8px;
-webkit-appearance: none;
background: #1e2a47;
border-radius: 5px;
outline: none;
}
.global-volume-slider::-webkit-slider-thumb,
.playback-speed-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: #64ffda;
border-radius: 50%;
cursor: pointer;
}
.slider-value {
min-width: 40px;
text-align: right;
}
input[type="number"], input[type="checkbox"], select {
background-color: #112240;
border: 1px solid #64ffda;
color: #e6f1ff;
padding: 5px;
border-radius: 3px;
}
.tech-decoration {
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, #64ffda, transparent);
margin: 20px 0;
}
/* 全画面時のスタイル */
.video-container:-webkit-full-screen {
width: 100%;
height: 100%;
background-color: black;
}
.video-container:-webkit-full-screen video {
width: 100%;
height: 100%;
}
.video-container:-webkit-full-screen .video-controls {
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100%;
border-radius: 0;
}
/* ローディングアニメーション */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
transition: opacity 1s ease-out;
}
.spinner-box {
width: 300px;
height: 300px;
display: flex;
justify-content: center;
align-items: center;
background-color: transparent;
}
/* 軌道スタイル */
.leo {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
}
.blue-orbit {
width: 165px;
height: 165px;
border: 1px solid #91daffa5;
animation: spin3D 3s linear .2s infinite;
}
.green-orbit {
width: 120px;
height: 120px;
border: 1px solid #91ffbfa5;
animation: spin3D 2s linear 0s infinite;
}
.red-orbit {
width: 90px;
height: 90px;
border: 1px solid #ffca91a5;
animation: spin3D 1s linear 0s infinite;
}
.white-orbit {
width: 60px;
height: 60px;
border: 2px solid #ffffff;
animation: spin3D 10s linear 0s infinite;
}
.w1 {
transform: rotate3D(1, 1, 1, 90deg);
}
.w2 {
transform: rotate3D(1, 2, .5, 90deg);
}
.w3 {
transform: rotate3D(.5, 1, 2, 90deg);
}
/* キーフレームアニメーション */
@keyframes spin3D {
from {
transform: rotate3d(.5,.5,.5, 360deg);
}
to {
transform: rotate3d(0,0,0, 0deg);
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.time-set-button {
background-color: #112240;
border: 1px solid #64ffda;
color: #e6f1ff;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
margin-left: 5px;
transition: background-color 0.3s;
}
.time-set-button:hover {
background-color: rgba(100, 255, 218, 0.2);
}
</style>
</head>
<body>
<!-- ローディングオーバーレイ -->
<div class="loading-overlay" id="loadingOverlay">
<div class="spinner-box">
<div class="blue-orbit leo"></div>
<div class="green-orbit leo"></div>
<div class="red-orbit leo"></div>
<div class="white-orbit w1 leo"></div>
<div class="white-orbit w2 leo"></div>
<div class="white-orbit w3 leo"></div>
</div>
</div>
<h1>高度な音声動画プレイヤー</h1>
<div class="container">
<div class="video-container" id="video-container">
<video id="video" muted>
<source src="v.mp4" type="video/mp4">
</video>
<div class="video-controls">
<div class="progress-container" id="progress-container">
<div class="progress-bar" id="progress-bar"></div>
<div class="progress-time" id="progress-time">00:00</div>
</div>
<div class="main-controls">
<button class="control-button" id="play-pause-btn"></button>
<div class="time-display" id="time-display">00:00.00 / 00:00.00</div>
<div class="volume-control">
<button class="volume-button" id="volume-btn">🔊</button>
<input type="range" class="volume-slider" id="volume-slider" min="0" max="1" step="0.01" value="1">
</div>
<div class="speed-control">
<span class="speed-value" id="speed-value">1.00x</span>
<input type="range" class="speed-slider" id="speed-slider" min="0.25" max="3" step="0.05" value="1">
</div>
<button class="control-button fullscreen-button" id="fullscreen-btn"></button>
</div>
</div>
</div>
<div class="tech-decoration"></div>
<div class="settings">
<h2>設定</h2>
<div class="setting-item">
<label for="start-time">再生開始秒数:</label>
<div>
<input type="number" id="start-time" min="0" value="0" step="0.1">
<button class="time-set-button" id="set-start-time">現在の秒数に設定</button>
</div>
</div>
<div class="setting-item">
<label for="end-time">再生終了秒数:</label>
<div>
<input type="number" id="end-time" min="0" value="0" step="0.1">
<button class="time-set-button" id="set-end-time">現在の秒数に設定</button>
</div>
</div>
<div class="setting-item">
<label for="loop">ループ再生:</label>
<input type="checkbox" id="loop">
</div>
<div class="setting-item">
<div class="global-volume-container">
<label>全体音量係数:</label>
<input type="range" class="global-volume-slider" id="global-volume" min="0" max="10" step="0.01" value="0.5">
<span class="slider-value" id="global-volume-value">0.5</span>
</div>
</div>
<div class="setting-item">
<div class="playback-speed-container">
<label>再生速度:</label>
<input type="range" class="playback-speed-slider" id="playback-speed" min="0.5" max="2" step="0.01" value="1">
<span class="slider-value" id="playback-speed-value">1.00x</span>
</div>
</div>
</div>
<div class="tech-decoration"></div>
<div class="audio-controls">
<h2>音声コントロール</h2>
<div class="audio-item">
<label>p.mp3</label>
<input type="range" class="audio-slider" data-audio="p" min="0" max="1" step="0.01" value="1">
<span class="slider-value volume-value">1.00</span>
</div>
<div class="audio-item">
<label>a.mp3</label>
<input type="range" class="audio-slider" data-audio="a" min="0" max="1" step="0.01" value="1">
<span class="slider-value volume-value">1.00</span>
</div>
<div class="audio-item">
<label>t.mp3</label>
<input type="range" class="audio-slider" data-audio="t" min="0" max="1" step="0.01" value="1">
<span class="slider-value volume-value">1.00</span>
</div>
<div class="audio-item">
<label>s.mp3</label>
<input type="range" class="audio-slider" data-audio="s" min="0" max="1" step="0.01" value="1">
<span class="slider-value volume-value">1.00</span>
</div>
<div class="audio-item">
<label>k.mp3</label>
<input type="range" class="audio-slider" data-audio="k" min="0" max="1" step="0.01" value="1">
<span class="slider-value volume-value">1.00</span>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// ローディング状態を管理
let loadingCount = 0;
let totalToLoad = 6; // 動画 + 5音声ファイル
// ローディング完了チェック
function checkLoadingComplete() {
loadingCount++;
if (loadingCount >= totalToLoad) {
setTimeout(function() {
const loadingOverlay = document.getElementById('loadingOverlay');
loadingOverlay.style.opacity = '0';
setTimeout(function() {
loadingOverlay.style.display = 'none';
}, 1000);
}, 500);
}
}
// エラーハンドリング関数
function handleError(error, message) {
console.error(message, error);
window.alert(`${message}\n\nエラー詳細: ${error.message}`);
}
// ピッチ設定関数
function setPreservesPitch(el, value) {
try {
if ('preservesPitch' in el) {
el.preservesPitch = value; // モダンブラウザ
} else if ('webkitPreservesPitch' in el) {
el.webkitPreservesPitch = value; // 古い WebKit (Chrome <86 など)
} else if ('mozPreservesPitch' in el) {
el.mozPreservesPitch = value; // 古い Gecko (Firefox ≤100)
}
} catch (error) {
handleError(error, 'ピッチ設定中にエラーが発生しました');
}
}
// 要素を取得
const video = document.getElementById('video');
const videoContainer = document.getElementById('video-container');
const playPauseBtn = document.getElementById('play-pause-btn');
const timeDisplay = document.getElementById('time-display');
const progressContainer = document.getElementById('progress-container');
const progressBar = document.getElementById('progress-bar');
const progressTime = document.getElementById('progress-time');
const volumeBtn = document.getElementById('volume-btn');
const volumeSlider = document.getElementById('volume-slider');
const speedSlider = document.getElementById('speed-slider');
const speedValue = document.getElementById('speed-value');
const playbackSpeedSlider = document.getElementById('playback-speed');
const playbackSpeedValue = document.getElementById('playback-speed-value');
const fullscreenBtn = document.getElementById('fullscreen-btn');
const startTimeInput = document.getElementById('start-time');
const endTimeInput = document.getElementById('end-time');
const loopCheckbox = document.getElementById('loop');
const globalVolumeSlider = document.getElementById('global-volume');
const globalVolumeValue = document.getElementById('global-volume-value');
const audioSliders = document.querySelectorAll('.audio-slider');
const volumeValues = document.querySelectorAll('.volume-value');
const setStartTimeBtn = document.getElementById('set-start-time');
const setEndTimeBtn = document.getElementById('set-end-time');
// 音声オブジェクトを作成
const audioElements = {};
// 音声ファイル名の配列
const audioFiles = ['p', 'a', 't', 's', 'k'];
// 初期化
let videoDuration = 0;
let isPlaying = false;
let isVideoPlaying = false;
let lastVolume = 1;
let currentPlaybackRate = 1;
// 動画のメタデータが読み込まれたら
video.addEventListener('loadedmetadata', function() {
try {
videoDuration = video.duration;
endTimeInput.value = videoDuration.toFixed(1);
endTimeInput.max = videoDuration;
startTimeInput.max = videoDuration - 0.1;
updateTimeDisplay();
checkLoadingComplete();
} catch (error) {
handleError(error, '動画メタデータ読み込み中にエラーが発生しました');
}
});
// 動画エラー処理
video.addEventListener('error', function() {
handleError(video.error, '動画読み込み中にエラーが発生しました');
});
// 再生ボタンクリック
playPauseBtn.addEventListener('click', function() {
try {
// 現在の秒数が終了秒数を超えている場合は開始位置に戻す
const endTime = parseFloat(endTimeInput.value) || videoDuration;
if (video.currentTime >= endTime) {
const startTime = parseFloat(startTimeInput.value) || 0;
seekMedia(startTime);
}
togglePlayPause();
// 音声要素の再生を許可
audioFiles.forEach(file => {
if (audioElements[file]) {
audioElements[file].play().catch(e => {
if (e.name !== 'AbortError') {
handleError(e, `音声再生中にエラーが発生しました (${file}.mp3)`);
}
});
}
});
} catch (error) {
handleError(error, '再生/一時停止操作中にエラーが発生しました');
}
});
// 時間表示を更新
function updateTimeDisplay() {
try {
const currentTime = video.currentTime;
const duration = video.duration || videoDuration;
const currentMinutes = Math.floor(currentTime / 60);
const currentSeconds = Math.floor(currentTime % 60);
const currentMilliseconds = Math.floor((currentTime % 1) * 100);
const durationMinutes = Math.floor(duration / 60);
const durationSeconds = Math.floor(duration % 60);
const durationMilliseconds = Math.floor((duration % 1) * 100);
timeDisplay.textContent =
`${String(currentMinutes).padStart(2, '0')}:${String(currentSeconds).padStart(2, '0')}.${String(currentMilliseconds).padStart(2, '0')} / ` +
`${String(durationMinutes).padStart(2, '0')}:${String(durationSeconds).padStart(2, '0')}.${String(durationMilliseconds).padStart(2, '0')}`;
// プログレスバーを更新
const progressPercent = (currentTime / duration) * 100;
progressBar.style.width = `${progressPercent}%`;
// 現在の秒数が終了秒数を超えている場合
const endTime = parseFloat(endTimeInput.value) || duration;
if (currentTime >= endTime && endTime > 0) {
if (loopCheckbox.checked) {
const startTime = parseFloat(startTimeInput.value) || 0;
seekMedia(startTime);
if (!isPlaying) {
playMedia();
}
} else {
pauseMedia();
}
}
} catch (error) {
handleError(error, '時間表示更新中にエラーが発生しました');
}
}
// 動画の時間更新イベント
video.addEventListener('timeupdate', function() {
updateTimeDisplay();
});
// 動画終了時の処理
video.addEventListener('ended', function() {
try {
if (!loopCheckbox.checked) {
const startTime = parseFloat(startTimeInput.value) || 0;
seekMedia(startTime);
pauseMedia();
}
} catch (error) {
handleError(error, '動画終了処理中にエラーが発生しました');
}
});
// プログレスバークリックでシーク
progressContainer.addEventListener('click', function(e) {
try {
if (!video.duration) return;
const rect = this.getBoundingClientRect();
const pos = (e.clientX - rect.left) / rect.width;
const seekTime = pos * video.duration;
seekMedia(seekTime);
} catch (error) {
handleError(error, 'シーク操作中にエラーが発生しました');
}
});
// 指定した時間にシーク
function seekMedia(time) {
try {
const duration = video.duration || videoDuration;
const startTime = parseFloat(startTimeInput.value) || 0;
const endTime = parseFloat(endTimeInput.value) || duration;
// 範囲内に制限
const seekTime = Math.max(startTime, Math.min(time, endTime));
video.currentTime = seekTime;
// 音声も同期
audioFiles.forEach(file => {
if (audioElements[file]) {
audioElements[file].currentTime = seekTime;
}
});
} catch (error) {
handleError(error, 'メディアシーク中にエラーが発生しました');
}
}
// プログレスバー上でマウス移動時に時間を表示
progressContainer.addEventListener('mousemove', function(e) {
try {
if (!video.duration) return;
const rect = this.getBoundingClientRect();
const pos = (e.clientX - rect.left) / rect.width;
const hoverTime = pos * video.duration;
const minutes = Math.floor(hoverTime / 60);
const seconds = Math.floor(hoverTime % 60);
const milliseconds = Math.floor((hoverTime % 1) * 100);
progressTime.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(milliseconds).padStart(2, '0')}`;
progressTime.style.display = 'block';
progressTime.style.left = `${pos * 100}%`;
} catch (error) {
handleError(error, 'プログレスバー操作中にエラーが発生しました');
}
});
progressContainer.addEventListener('mouseleave', function() {
progressTime.style.display = 'none';
});
// 動画クリックで再生/一時停止
video.addEventListener('click', function() {
togglePlayPause();
});
// 再生/一時停止をトグル
function togglePlayPause() {
if (isVideoPlaying) {
pauseMedia();
} else {
playMedia();
}
}
// 音量コントロール
volumeSlider.addEventListener('input', function() {
try {
video.volume = this.value;
lastVolume = this.value;
updateVolumeIcon();
} catch (error) {
handleError(error, '音量設定中にエラーが発生しました');
}
});
// 音量ボタン
volumeBtn.addEventListener('click', function() {
try {
if (video.volume > 0) {
lastVolume = video.volume;
video.volume = 0;
volumeSlider.value = 0;
} else {
video.volume = lastVolume;
volumeSlider.value = lastVolume;
}
updateVolumeIcon();
} catch (error) {
handleError(error, '音量切り替え中にエラーが発生しました');
}
});
// 音量アイコンを更新
function updateVolumeIcon() {
if (video.volume === 0) {
volumeBtn.textContent = '🔇';
} else if (video.volume < 0.5) {
volumeBtn.textContent = '🔈';
} else {
volumeBtn.textContent = '🔊';
}
}
// 再生速度スライダー (動画プレイヤー)
speedSlider.addEventListener('input', function() {
try {
const speed = parseFloat(this.value);
speedValue.textContent = speed.toFixed(2) + 'x';
playbackSpeedSlider.value = speed;
playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
updatePlaybackRate(speed);
} catch (error) {
handleError(error, '再生速度設定中にエラーが発生しました');
}
});
// 再生速度スライダー (設定メニュー)
playbackSpeedSlider.addEventListener('input', function() {
try {
const speed = parseFloat(this.value);
playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
speedSlider.value = speed;
speedValue.textContent = speed.toFixed(2) + 'x';
updatePlaybackRate(speed);
} catch (error) {
handleError(error, '再生速度設定中にエラーが発生しました');
}
});
function updatePlaybackRate(speed) {
try {
currentPlaybackRate = speed;
video.playbackRate = speed;
// 音声の再生速度を変更
audioFiles.forEach(file => {
if (audioElements[file]) {
audioElements[file].playbackRate = speed;
setPreservesPitch(audioElements[file], true); // ピッチを保持
}
});
} catch (error) {
handleError(error, '再生速度更新中にエラーが発生しました');
}
}
// 全画面ボタン
fullscreenBtn.addEventListener('click', function() {
try {
if (videoContainer.requestFullscreen) {
videoContainer.requestFullscreen();
} else if (videoContainer.webkitRequestFullscreen) {
videoContainer.webkitRequestFullscreen();
} else if (videoContainer.msRequestFullscreen) {
videoContainer.msRequestFullscreen();
}
} catch (error) {
handleError(error, '全画面表示中にエラーが発生しました');
}
});
// 全画面変更イベント
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('msfullscreenchange', handleFullscreenChange);
function handleFullscreenChange() {
const isFullscreen = document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement;
if (isFullscreen) {
// 全画面時の処理
video.controls = false;
} else {
// 通常表示時の処理
video.controls = false;
}
}
// 音声ファイルをロード
function loadAudioFiles() {
audioFiles.forEach(file => {
try {
const audio = new Audio(`${file}.mp3`);
audio.preload = 'auto';
setPreservesPitch(audio, true); // ピッチを保持
audio.loop = false;
audioElements[file] = audio;
// 音声が読み込まれたら
audio.addEventListener('loadedmetadata', function() {
console.log(`${file}.mp3 loaded`);
checkLoadingComplete();
});
// エラー処理
audio.addEventListener('error', function() {
handleError(audio.error, `音声ファイル読み込み中にエラーが発生しました (${file}.mp3)`);
checkLoadingComplete(); // エラー時もカウント
});
} catch (error) {
handleError(error, `音声ファイル初期化中にエラーが発生しました (${file}.mp3)`);
checkLoadingComplete(); // エラー時もカウント
}
});
}
function playAudio(file, startTime) {
if (!audioElements[file]) return;
try {
const audio = audioElements[file];
const duration = video.duration || videoDuration;
const endTime = parseFloat(endTimeInput.value) || duration;
// ボリューム設定
const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
const volume = parseFloat(volumeSlider.value) * parseFloat(globalVolumeSlider.value/10);
audio.volume = volume;
// 再生速度設定
audio.playbackRate = currentPlaybackRate;
setPreservesPitch(audio, true);
// 再生時間計算
const playStartTime = Math.max(startTime, parseFloat(startTimeInput.value) || 0);
const playEndTime = Math.min(endTime, duration);
audio.currentTime = playStartTime;
// 再生
audio.play().catch(e => {
if (e.name !== 'AbortError') {
handleError(e, `音声再生中にエラーが発生しました (${file}.mp3)`);
}
});
// ループ設定
audio.loop = loopCheckbox.checked;
if (!loopCheckbox.checked) {
// ループ再生でない場合、終了時間に達したら停止
setTimeout(() => {
if (audio.currentTime >= playEndTime) {
audio.pause();
}
}, (playEndTime - playStartTime) * 1000);
}
} catch (error) {
handleError(error, `音声再生中にエラーが発生しました (${file}.mp3)`);
}
}
// 再生関数
function playMedia() {
try {
const startTime = video.currentTime;
const endTime = parseFloat(endTimeInput.value) || videoDuration;
// 終了時間が動画の長さを超えないように
const actualEndTime = Math.min(endTime, videoDuration);
// 現在位置が終了時間を超えている場合、開始位置に戻る
if (startTime >= actualEndTime) {
video.currentTime = parseFloat(startTimeInput.value) || 0;
}
// 動画を再生
video.play();
isPlaying = true;
isVideoPlaying = true;
playPauseBtn.textContent = '⏸';
// 音声を再生
audioFiles.forEach(file => {
playAudio(file, video.currentTime);
});
} catch (error) {
handleError(error, 'メディア再生中にエラーが発生しました');
}
}
// 一時停止関数
function pauseMedia() {
if (!isPlaying) return;
try {
video.pause();
audioFiles.forEach(file => {
if (audioElements[file]) {
audioElements[file].pause();
}
});
isPlaying = false;
isVideoPlaying = false;
playPauseBtn.textContent = '▶';
} catch (error) {
handleError(error, 'メディア一時停止中にエラーが発生しました');
}
}
// ボリュームスライダーのイベント
audioSliders.forEach((slider, index) => {
slider.addEventListener('input', function() {
try {
const value = parseFloat(this.value);
volumeValues[index].textContent = value.toFixed(2);
if (audioElements[this.dataset.audio]) {
const globalVolume = parseFloat(globalVolumeSlider.value/10);
audioElements[this.dataset.audio].volume = value * globalVolume;
}
} catch (error) {
handleError(error, '音声ボリューム設定中にエラーが発生しました');
}
});
});
// 全体音量スライダーのイベント
globalVolumeSlider.addEventListener('input', function() {
try {
const value = parseFloat(this.value);
globalVolumeValue.textContent = value.toFixed(1);
audioFiles.forEach(file => {
if (audioElements[file]) {
const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
const volume = parseFloat(volumeSlider.value) * value;
audioElements[file].volume = volume;
}
});
} catch (error) {
handleError(error, '全体音量設定中にエラーが発生しました');
}
});
// ループ設定変更時
loopCheckbox.addEventListener('change', function() {
try {
audioFiles.forEach(file => {
if (audioElements[file]) {
audioElements[file].loop = this.checked;
}
});
} catch (error) {
handleError(error, 'ループ設定変更中にエラーが発生しました');
}
});
// 現在の秒数を開始時間に設定
setStartTimeBtn.addEventListener('click', function() {
try {
startTimeInput.value = video.currentTime.toFixed(1);
} catch (error) {
handleError(error, '開始時間設定中にエラーが発生しました');
}
});
// 現在の秒数を終了時間に設定
setEndTimeBtn.addEventListener('click', function() {
try {
endTimeInput.value = video.currentTime.toFixed(1);
} catch (error) {
handleError(error, '終了時間設定中にエラーが発生しました');
}
});
// 初期化
loadAudioFiles();
updateVolumeIcon();
volumeSlider.value = video.volume;
video.controls = false;
});
</script>
</body>
</html>