|
<!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> |
|
|
|
.tech-background { |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
z-index: -2; |
|
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e); |
|
overflow: hidden; |
|
} |
|
|
|
.circuit-line { |
|
position: absolute; |
|
background: rgba(0, 255, 255, 0.1); |
|
box-shadow: 0 0 10px rgba(0, 255, 255, 0.3); |
|
} |
|
|
|
.grid-dot { |
|
position: absolute; |
|
width: 2px; |
|
height: 2px; |
|
background: rgba(0, 255, 255, 0.3); |
|
border-radius: 50%; |
|
} |
|
|
|
.hexagon { |
|
position: absolute; |
|
width: 40px; |
|
height: 23px; |
|
background: rgba(0, 255, 255, 0.05); |
|
border: 1px solid rgba(0, 255, 255, 0.1); |
|
box-shadow: 0 0 5px rgba(0, 255, 255, 0.2); |
|
} |
|
|
|
.hexagon:before, .hexagon:after { |
|
content: ""; |
|
position: absolute; |
|
width: 0; |
|
border-left: 20px solid transparent; |
|
border-right: 20px solid transparent; |
|
} |
|
|
|
.hexagon:before { |
|
bottom: 100%; |
|
border-bottom: 11.5px solid rgba(0, 255, 255, 0.05); |
|
} |
|
|
|
.hexagon:after { |
|
top: 100%; |
|
width: 0; |
|
border-top: 11.5px solid rgba(0, 255, 255, 0.05); |
|
} |
|
|
|
.pulse { |
|
position: absolute; |
|
width: 10px; |
|
height: 10px; |
|
background: rgba(0, 255, 255, 0.7); |
|
border-radius: 50%; |
|
box-shadow: 0 0 10px 5px rgba(0, 255, 255, 0.5); |
|
animation: pulse 2s infinite; |
|
} |
|
|
|
@keyframes pulse { |
|
0% { transform: scale(0.8); opacity: 0.7; } |
|
50% { transform: scale(1.2); opacity: 1; } |
|
100% { transform: scale(0.8); opacity: 0.7; } |
|
} |
|
|
|
@keyframes float { |
|
0% { transform: translateY(0) rotate(0deg); } |
|
50% { transform: translateY(-20px) rotate(5deg); } |
|
100% { transform: translateY(0) rotate(0deg); } |
|
} |
|
|
|
|
|
body { |
|
font-family: 'Arial', sans-serif; |
|
background-color: rgba(10, 25, 47, 0.8); |
|
color: #e6f1ff; |
|
margin: 0; |
|
padding: 20px; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
min-height: 100vh; |
|
} |
|
|
|
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: rgba(17, 34, 64, 0.5); |
|
border-radius: 10px; |
|
padding: 20px; |
|
box-shadow: 0 0 20px rgba(100, 255, 218, 0.2); |
|
backdrop-filter: blur(5px); |
|
border: 1px solid rgba(100, 255, 218, 0.1); |
|
} |
|
|
|
.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; |
|
background-image: linear-gradient(#64ffda, #64ffda); |
|
background-size: 100% 100%; |
|
background-repeat: no-repeat; |
|
} |
|
|
|
.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; |
|
background-image: linear-gradient(#64ffda, #64ffda); |
|
background-size: 100% 100%; |
|
background-repeat: no-repeat; |
|
} |
|
|
|
.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; |
|
background-image: linear-gradient(#64ffda, #64ffda); |
|
background-size: 100% 100%; |
|
background-repeat: no-repeat; |
|
} |
|
|
|
.audio-slider::-webkit-slider-thumb { |
|
-webkit-appearance: none; |
|
width: 18px; |
|
height: 18px; |
|
background: #64ffda; |
|
border-radius: 50%; |
|
cursor: pointer; |
|
} |
|
|
|
.settings { |
|
background-color: rbga(30, 42, 71, 0.3); |
|
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; |
|
background-image: linear-gradient(#64ffda, #64ffda); |
|
background-size: 100% 100%; |
|
background-repeat: no-repeat; |
|
} |
|
|
|
.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); |
|
} |
|
|
|
|
|
.combine-button { |
|
background-color: #64ffda; |
|
color: #0a192f; |
|
border: none; |
|
padding: 10px 20px; |
|
border-radius: 5px; |
|
font-size: 16px; |
|
cursor: pointer; |
|
margin-top: 20px; |
|
transition: all 0.3s; |
|
font-weight: bold; |
|
} |
|
|
|
.combine-button:hover { |
|
background-color: #52e0c4; |
|
transform: translateY(-2px); |
|
box-shadow: 0 5px 15px rgba(100, 255, 218, 0.4); |
|
} |
|
|
|
.combine-button:disabled { |
|
background-color: #3a5a78; |
|
cursor: not-allowed; |
|
transform: none; |
|
box-shadow: none; |
|
} |
|
|
|
|
|
.combine-status { |
|
margin-top: 10px; |
|
color: #64ffda; |
|
font-size: 14px; |
|
height: 20px; |
|
} |
|
|
|
|
|
.preview-section { |
|
margin-top: 20px; |
|
padding: 15px; |
|
background-color: rgba(17, 34, 64, 0.7); |
|
border-radius: 5px; |
|
display: none; |
|
} |
|
|
|
.preview-section h3 { |
|
margin-top: 0; |
|
color: #64ffda; |
|
border-bottom: 1px solid #64ffda; |
|
padding-bottom: 5px; |
|
} |
|
|
|
|
|
.disabled-overlay { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background-color: rgba(10, 25, 47, 0.7); |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
z-index: 10; |
|
border-radius: 5px; |
|
} |
|
|
|
.disabled-message { |
|
background-color: rgba(30, 42, 71, 0.9); |
|
padding: 20px; |
|
border-radius: 5px; |
|
text-align: center; |
|
max-width: 80%; |
|
} |
|
|
|
.disabled-message p { |
|
margin-bottom: 15px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
|
|
<div class="tech-background" id="techBg"></div> |
|
|
|
|
|
<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"> |
|
|
|
<div class="disabled-overlay" id="disabledOverlay"> |
|
<div class="disabled-message"> |
|
<p>音声の合成が完了していません</p> |
|
<p>下の音声コントロールで各パートの音量を調整し、「合成」ボタンを押してください</p> |
|
</div> |
|
</div> |
|
|
|
<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" disabled>▶</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" disabled>🔊</button> |
|
<input type="range" class="volume-slider" id="volume-slider" min="0" max="1" step="0.01" value="1" disabled> |
|
</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.5" max="2" step="0.01" value="1" disabled> |
|
</div> |
|
<button class="control-button fullscreen-button" id="fullscreen-btn" disabled>⛶</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.01" disabled> |
|
<button class="time-set-button" id="set-start-time" disabled>現在の秒数に設定</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.01" disabled> |
|
<button class="time-set-button" id="set-end-time" disabled>現在の秒数に設定</button> |
|
</div> |
|
</div> |
|
<div class="setting-item"> |
|
<label for="loop">ループ再生:</label> |
|
<input type="checkbox" id="loop" disabled> |
|
</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" disabled> |
|
<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" disabled> |
|
<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>ピアノ</label> |
|
<input type="range" class="audio-slider" data-audio="p" min="0" max="1" step="0.01" value="0"> |
|
<span class="slider-value volume-value">0.00</span> |
|
</div> |
|
<div class="audio-item"> |
|
<label>ソプラノ</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>アルト</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>テノール</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>全体(非推奨)</label> |
|
<input type="range" class="audio-slider" data-audio="k" min="0" max="1" step="0.01" value="0"> |
|
<span class="slider-value volume-value">0.00</span> |
|
</div> |
|
|
|
|
|
<button class="combine-button" id="combine-button">音声を合成</button> |
|
<div class="combine-status" id="combine-status"></div> |
|
</div> |
|
|
|
|
|
<div class="preview-section" id="preview-section"> |
|
<h3>プレビュー</h3> |
|
<p>合成された音声をプレビューできます。再生ボタンをクリックして確認してください。</p> |
|
<button class="control-button" id="preview-button">▶</button> |
|
<span id="preview-time">00:00 / 00:00</span> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
function createTechBackground() { |
|
const bg = document.getElementById('techBg'); |
|
|
|
|
|
for (let i = 0; i < 200; i++) { |
|
const line = document.createElement('div'); |
|
line.className = 'circuit-line'; |
|
|
|
const isHorizontal = Math.random() > 0.5; |
|
if (isHorizontal) { |
|
line.style.width = `${Math.random() * 300 + 100}px`; |
|
line.style.height = '1px'; |
|
} else { |
|
line.style.width = '1px'; |
|
line.style.height = `${Math.random() * 300 + 100}px`; |
|
} |
|
|
|
line.style.left = `${Math.random() * 100}%`; |
|
line.style.top = `${Math.random() * 100}%`; |
|
line.style.opacity = Math.random() * 0.5 + 0.1; |
|
|
|
bg.appendChild(line); |
|
} |
|
|
|
|
|
for (let i = 0; i < 200; i++) { |
|
const dot = document.createElement('div'); |
|
dot.className = 'grid-dot'; |
|
dot.style.left = `${Math.random() * 100}%`; |
|
dot.style.top = `${Math.random() * 100}%`; |
|
bg.appendChild(dot); |
|
} |
|
|
|
|
|
for (let i = 0; i < 15; i++) { |
|
const hex = document.createElement('div'); |
|
hex.className = 'hexagon'; |
|
hex.style.left = `${Math.random() * 100}%`; |
|
hex.style.top = `${Math.random() * 100}%`; |
|
hex.style.transform = `rotate(${Math.random() * 360}deg)`; |
|
hex.style.animation = `float ${Math.random() * 10 + 5}s infinite ease-in-out`; |
|
bg.appendChild(hex); |
|
} |
|
|
|
|
|
for (let i = 0; i < 8; i++) { |
|
const pulse = document.createElement('div'); |
|
pulse.className = 'pulse'; |
|
pulse.style.left = `${Math.random() * 100}%`; |
|
pulse.style.top = `${Math.random() * 100}%`; |
|
pulse.style.animationDelay = `${Math.random() * 2}s`; |
|
bg.appendChild(pulse); |
|
} |
|
} |
|
|
|
createTechBackground(); |
|
|
|
|
|
let loadingCount = 0; |
|
let totalToLoad = 6; |
|
let lastUpdateTime = 0; |
|
const updateInterval = 1; |
|
|
|
|
|
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 (el.preservesPitch !== undefined) { |
|
el.preservesPitch = value; |
|
} else if (el.webkitPreservesPitch !== undefined) { |
|
el.webkitPreservesPitch = value; |
|
} else if (el.mozPreservesPitch !== undefined) { |
|
el.mozPreservesPitch = value; |
|
} |
|
|
|
|
|
if (el.playbackRate !== undefined) { |
|
const rate = el.playbackRate; |
|
|
|
el.playbackRate = 1.0; |
|
el.playbackRate = rate; |
|
} |
|
} 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 lastVolume = 1; |
|
let currentPlaybackRate = 1; |
|
let isFullscreen = false; |
|
|
|
|
|
video.addEventListener('loadedmetadata', function() { |
|
try { |
|
videoDuration = video.duration; |
|
endTimeInput.value = videoDuration.toFixed(2); |
|
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(); |
|
} catch (error) { |
|
handleError(error, '再生/一時停止操作中にエラーが発生しました'); |
|
} |
|
}); |
|
|
|
|
|
function updateTimeDisplay() { |
|
const now = performance.now(); |
|
if (now - lastUpdateTime < updateInterval && !isFullscreen) return; |
|
lastUpdateTime = now; |
|
|
|
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}%`; |
|
} catch (error) { |
|
handleError(error, '時間表示更新中にエラーが発生しました'); |
|
} |
|
} |
|
|
|
|
|
function togglePlayPause() { |
|
if (isPlaying) { |
|
pauseMedia(); |
|
} else { |
|
playMedia(); |
|
} |
|
} |
|
|
|
|
|
function playMedia() { |
|
try { |
|
const duration = video.duration || videoDuration; |
|
const startTime = parseFloat(startTimeInput.value) || 0; |
|
const endTime = parseFloat(endTimeInput.value) || duration; |
|
|
|
|
|
if (video.currentTime >= endTime) { |
|
video.currentTime = startTime; |
|
} |
|
|
|
|
|
video.play().then(() => { |
|
isPlaying = true; |
|
playPauseBtn.textContent = '⏸'; |
|
|
|
|
|
audioFiles.forEach(file => { |
|
if (audioElements[file]) { |
|
audioElements[file].currentTime = video.currentTime; |
|
audioElements[file].play().catch(e => { |
|
if (e.name !== 'AbortError') { |
|
console.error(`音声再生エラー (${file}.mp3):`, e); |
|
} |
|
}); |
|
} |
|
}); |
|
}).catch(error => { |
|
console.error('動画再生エラー:', error); |
|
}); |
|
} catch (error) { |
|
console.error('メディア再生エラー:', error); |
|
} |
|
} |
|
|
|
|
|
function pauseMedia() { |
|
try { |
|
video.pause(); |
|
isPlaying = false; |
|
playPauseBtn.textContent = '▶'; |
|
|
|
|
|
audioFiles.forEach(file => { |
|
if (audioElements[file]) { |
|
audioElements[file].pause(); |
|
} |
|
}); |
|
} catch (error) { |
|
console.error('メディア一時停止エラー:', error); |
|
} |
|
} |
|
|
|
|
|
video.addEventListener('timeupdate', function() { |
|
const duration = video.duration || videoDuration; |
|
const endTime = parseFloat(endTimeInput.value) || duration; |
|
|
|
|
|
if (video.currentTime >= endTime && endTime > 0) { |
|
if (loopCheckbox.checked) { |
|
const startTime = parseFloat(startTimeInput.value) || 0; |
|
video.currentTime = startTime; |
|
|
|
|
|
audioFiles.forEach(file => { |
|
if (audioElements[file]) { |
|
audioElements[file].currentTime = startTime; |
|
if (isPlaying) { |
|
audioElements[file].play().catch(e => { |
|
if (e.name !== 'AbortError') { |
|
console.error(`音声再生エラー (${file}.mp3):`, e); |
|
} |
|
}); |
|
} |
|
} |
|
}); |
|
} else { |
|
pauseMedia(); |
|
video.currentTime = endTime; |
|
} |
|
} |
|
|
|
updateTimeDisplay(); |
|
}); |
|
|
|
|
|
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(); |
|
}); |
|
|
|
|
|
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]) { |
|
|
|
setPreservesPitch(audioElements[file], true); |
|
audioElements[file].playbackRate = speed; |
|
|
|
|
|
setTimeout(() => { |
|
setPreservesPitch(audioElements[file], true); |
|
}, 100); |
|
} |
|
}); |
|
} catch (error) { |
|
handleError(error, '再生速度更新中にエラーが発生しました'); |
|
} |
|
} |
|
|
|
|
|
fullscreenBtn.addEventListener('click', function() { |
|
try { |
|
if (!isFullscreen) { |
|
if (videoContainer.requestFullscreen) { |
|
videoContainer.requestFullscreen(); |
|
} else if (videoContainer.webkitRequestFullscreen) { |
|
videoContainer.webkitRequestFullscreen(); |
|
} else if (videoContainer.msRequestFullscreen) { |
|
videoContainer.msRequestFullscreen(); |
|
} |
|
} else { |
|
if (document.exitFullscreen) { |
|
document.exitFullscreen(); |
|
} else if (document.webkitExitFullscreen) { |
|
document.webkitExitFullscreen(); |
|
} else if (document.msExitFullscreen) { |
|
document.msExitFullscreen(); |
|
} |
|
} |
|
} catch (error) { |
|
handleError(error, '全画面表示中にエラーが発生しました'); |
|
} |
|
}); |
|
|
|
|
|
document.addEventListener('fullscreenchange', handleFullscreenChange); |
|
document.addEventListener('webkitfullscreenchange', handleFullscreenChange); |
|
document.addEventListener('msfullscreenchange', handleFullscreenChange); |
|
|
|
function handleFullscreenChange() { |
|
isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement); |
|
fullscreenBtn.textContent = isFullscreen ? '⛶' : '⛶'; |
|
|
|
if (isFullscreen) { |
|
|
|
video.controls = false; |
|
} else { |
|
|
|
video.controls = false; |
|
} |
|
} |
|
|
|
|
|
document.addEventListener('keydown', function(e) { |
|
if (e.key === 'Escape' && isFullscreen) { |
|
if (document.exitFullscreen) { |
|
document.exitFullscreen(); |
|
} else if (document.webkitExitFullscreen) { |
|
document.webkitExitFullscreen(); |
|
} else if (document.msExitFullscreen) { |
|
document.msExitFullscreen(); |
|
} |
|
} |
|
}); |
|
|
|
|
|
function loadAudioFiles() { |
|
audioFiles.forEach(file => { |
|
try { |
|
const audio = new Audio(`${file}.mp3`); |
|
audio.preload = 'auto'; |
|
audio.loop = false; |
|
|
|
|
|
setPreservesPitch(audio, true); |
|
|
|
|
|
audio.volume = 0.5; |
|
|
|
audioElements[file] = audio; |
|
|
|
|
|
const onLoaded = function() { |
|
console.log(`${file}.mp3 loaded`); |
|
setPreservesPitch(audio, true); |
|
checkLoadingComplete(); |
|
audio.removeEventListener('loadedmetadata', onLoaded); |
|
}; |
|
|
|
audio.addEventListener('loadedmetadata', onLoaded); |
|
|
|
|
|
const onError = function() { |
|
handleError(audio.error, `音声ファイル読み込み中にエラーが発生しました (${file}.mp3)`); |
|
checkLoadingComplete(); |
|
audio.removeEventListener('error', onError); |
|
}; |
|
|
|
audio.addEventListener('error', onError); |
|
|
|
|
|
audio.load().catch(e => { |
|
console.error(`音声ファイル読み込みエラー (${file}.mp3):`, e); |
|
}); |
|
} catch (error) { |
|
handleError(error, `音声ファイル初期化中にエラーが発生しました (${file}.mp3)`); |
|
checkLoadingComplete(); |
|
} |
|
}); |
|
} |
|
|
|
|
|
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; |
|
const calculatedVolume = value * globalVolume; |
|
audioElements[this.dataset.audio].volume = calculatedVolume; |
|
|
|
|
|
updateSliderBackgrounds(); |
|
} |
|
} catch (error) { |
|
handleError(error, '音声ボリューム設定中にエラーが発生しました'); |
|
} |
|
}); |
|
|
|
|
|
slider.value = 1; |
|
volumeValues[index].textContent = '1.00'; |
|
}); |
|
|
|
|
|
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}"]`); |
|
if (volumeSlider) { |
|
const sliderValue = parseFloat(volumeSlider.value); |
|
const calculatedVolume = sliderValue * (value / 10); |
|
audioElements[file].volume = calculatedVolume; |
|
} |
|
} |
|
}); |
|
|
|
|
|
updateSliderBackgrounds(); |
|
} catch (error) { |
|
handleError(error, '全体音量設定中にエラーが発生しました'); |
|
} |
|
}); |
|
|
|
|
|
globalVolumeSlider.value = 5; |
|
globalVolumeValue.textContent = '5.0'; |
|
|
|
|
|
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(2); |
|
} catch (error) { |
|
handleError(error, '開始時間設定中にエラーが発生しました'); |
|
} |
|
}); |
|
|
|
|
|
setEndTimeBtn.addEventListener('click', function() { |
|
try { |
|
endTimeInput.value = video.currentTime.toFixed(2); |
|
} catch (error) { |
|
handleError(error, '終了時間設定中にエラーが発生しました'); |
|
} |
|
}); |
|
|
|
|
|
function updateSliderBackgrounds() { |
|
|
|
const volumePercent = volumeSlider.value * 100; |
|
volumeSlider.style.backgroundSize = `${volumePercent}% 100%`; |
|
|
|
|
|
const speedPercent = (speedSlider.value - speedSlider.min) / (speedSlider.max - speedSlider.min) * 100; |
|
speedSlider.style.backgroundSize = `${speedPercent}% 100%`; |
|
|
|
|
|
const globalVolumePercent = (globalVolumeSlider.value - globalVolumeSlider.min) / (globalVolumeSlider.max - globalVolumeSlider.min) * 100; |
|
globalVolumeSlider.style.backgroundSize = `${globalVolumePercent}% 100%`; |
|
|
|
|
|
const playbackSpeedPercent = (playbackSpeedSlider.value - playbackSpeedSlider.min) / (playbackSpeedSlider.max - playbackSpeedSlider.min) * 100; |
|
playbackSpeedSlider.style.backgroundSize = `${playbackSpeedPercent}% 100%`; |
|
|
|
|
|
audioSliders.forEach(slider => { |
|
const percent = slider.value * 100; |
|
slider.style.backgroundSize = `${percent}% 100%`; |
|
}); |
|
} |
|
|
|
|
|
function initSliderBackgrounds() { |
|
|
|
const sliders = [ |
|
volumeSlider, |
|
speedSlider, |
|
globalVolumeSlider, |
|
playbackSpeedSlider, |
|
...audioSliders |
|
]; |
|
|
|
sliders.forEach(slider => { |
|
if (slider) { |
|
slider.style.backgroundImage = 'linear-gradient(#64ffda, #64ffda)'; |
|
slider.style.backgroundRepeat = 'no-repeat'; |
|
} |
|
}); |
|
|
|
updateSliderBackgrounds(); |
|
} |
|
|
|
|
|
const allSliders = [ |
|
volumeSlider, |
|
speedSlider, |
|
globalVolumeSlider, |
|
playbackSpeedSlider, |
|
...audioSliders |
|
]; |
|
|
|
allSliders.forEach(slider => { |
|
if (slider) { |
|
slider.addEventListener('input', updateSliderBackgrounds); |
|
} |
|
}); |
|
|
|
|
|
loadAudioFiles(); |
|
updateVolumeIcon(); |
|
volumeSlider.value = video.volume; |
|
video.controls = false; |
|
initSliderBackgrounds(); |
|
|
|
|
|
setTimeout(() => { |
|
audioFiles.forEach(file => { |
|
if (audioElements[file]) { |
|
const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`); |
|
if (volumeSlider) { |
|
const value = parseFloat(volumeSlider.value); |
|
const globalVolume = parseFloat(globalVolumeSlider.value) / 10; |
|
audioElements[file].volume = value * globalVolume; |
|
} |
|
} |
|
}); |
|
}, 1000); |
|
}); |
|
</script> |
|
</body> |
|
</html> |