soiz1's picture
Update index.html
e61a60b
raw
history blame
15.5 kB
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<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;
}
.controls {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 20px;
}
.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;
}
input[type="range"] {
flex-grow: 1;
height: 8px;
-webkit-appearance: none;
background: #1e2a47;
border-radius: 5px;
outline: none;
}
input[type="range"]::-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;
}
input[type="number"], input[type="checkbox"], select {
background-color: #112240;
border: 1px solid #64ffda;
color: #e6f1ff;
padding: 5px;
border-radius: 3px;
}
button {
background-color: #0a192f;
color: #64ffda;
border: 1px solid #64ffda;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s;
}
button:hover {
background-color: #64ffda;
color: #0a192f;
}
.buttons {
display: flex;
gap: 10px;
justify-content: center;
}
.tech-decoration {
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, #64ffda, transparent);
margin: 20px 0;
}
</style>
</head>
<body>
<h1>音声動画プレイヤー</h1>
<div class="container">
<div class="video-container">
<video id="video" muted>
<source src="v.mp4" type="video/mp4">
</video>
</div>
<div class="tech-decoration"></div>
<div class="settings">
<h2>設定</h2>
<div class="setting-item">
<label for="start-time">再生開始秒数:</label>
<input type="number" id="start-time" min="0" value="0" step="0.1">
</div>
<div class="setting-item">
<label for="end-time">再生終了秒数:</label>
<input type="number" id="end-time" min="0" value="0" step="0.1">
</div>
<div class="setting-item">
<label for="loop">ループ再生:</label>
<input type="checkbox" id="loop">
</div>
<div class="setting-item">
<label for="playback-rate">再生速度:</label>
<select id="playback-rate">
<option value="0.25">0.25x</option>
<option value="0.5">0.5x</option>
<option value="0.75">0.75x</option>
<option value="1" selected>1x</option>
<option value="1.25">1.25x</option>
<option value="1.5">1.5x</option>
<option value="2">2x</option>
<option value="3">3x</option>
</select>
</div>
<div class="setting-item">
<label for="global-volume">全体音量係数:</label>
<input type="range" id="global-volume" min="0" max="3" step="0.1" value="1">
<span id="global-volume-value">1</span>
</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="volume-slider" data-audio="p" min="0" max="1" step="0.01" value="1">
<span class="volume-value">1</span>
</div>
<div class="audio-item">
<label>a.mp3</label>
<input type="range" class="volume-slider" data-audio="a" min="0" max="1" step="0.01" value="1">
<span class="volume-value">1</span>
</div>
<div class="audio-item">
<label>t.mp3</label>
<input type="range" class="volume-slider" data-audio="t" min="0" max="1" step="0.01" value="1">
<span class="volume-value">1</span>
</div>
<div class="audio-item">
<label>s.mp3</label>
<input type="range" class="volume-slider" data-audio="s" min="0" max="1" step="0.01" value="1">
<span class="volume-value">1</span>
</div>
<div class="audio-item">
<label>k.mp3</label>
<input type="range" class="volume-slider" data-audio="k" min="0" max="1" step="0.01" value="1">
<span class="volume-value">1</span>
</div>
</div>
<div class="tech-decoration"></div>
<div class="buttons">
<button id="play-btn">再生</button>
<button id="pause-btn">一時停止</button>
<button id="stop-btn">停止</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 要素を取得
const video = document.getElementById('video');
const playBtn = document.getElementById('play-btn');
const pauseBtn = document.getElementById('pause-btn');
const stopBtn = document.getElementById('stop-btn');
const startTimeInput = document.getElementById('start-time');
const endTimeInput = document.getElementById('end-time');
const loopCheckbox = document.getElementById('loop');
const playbackRateSelect = document.getElementById('playback-rate');
const globalVolumeSlider = document.getElementById('global-volume');
const globalVolumeValue = document.getElementById('global-volume-value');
const volumeSliders = document.querySelectorAll('.volume-slider');
const volumeValues = document.querySelectorAll('.volume-value');
// 音声オブジェクトを作成
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const audioBuffers = {};
const audioSources = {};
const gainNodes = {};
// 音声ファイル名の配列
const audioFiles = ['p', 'a', 't', 's', 'k'];
// 初期化
let videoDuration = 0;
let isPlaying = false;
// 動画のメタデータが読み込まれたら
video.addEventListener('loadedmetadata', function() {
videoDuration = video.duration;
endTimeInput.value = videoDuration.toFixed(1);
endTimeInput.max = videoDuration;
startTimeInput.max = videoDuration - 0.1;
});
// 音声ファイルを読み込む
function loadAudioFiles() {
audioFiles.forEach(file => {
fetch(`${file}.mp3`)
.then(response => response.arrayBuffer())
.then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
.then(audioBuffer => {
audioBuffers[file] = audioBuffer;
gainNodes[file] = audioContext.createGain();
gainNodes[file].gain.value = 1;
})
.catch(error => console.error(`Error loading ${file}.mp3:`, error));
});
}
// 再生関数
function playMedia() {
if (isPlaying) return;
const startTime = parseFloat(startTimeInput.value) || 0;
let endTime = parseFloat(endTimeInput.value) || videoDuration;
const loop = loopCheckbox.checked;
const playbackRate = parseFloat(playbackRateSelect.value);
const globalVolume = parseFloat(globalVolumeSlider.value);
// 終了時間が動画の長さを超えないように
endTime = Math.min(endTime, videoDuration);
// 動画を設定
video.currentTime = startTime;
video.playbackRate = playbackRate;
video.muted = true;
// 音声を再生
audioFiles.forEach(file => {
if (audioBuffers[file]) {
// 既存のソースがあれば停止
if (audioSources[file]) {
audioSources[file].stop();
}
const source = audioContext.createBufferSource();
source.buffer = audioBuffers[file];
// ボリュームスライダーの値を取得
const volumeSlider = document.querySelector(`.volume-slider[data-audio="${file}"]`);
const volume = parseFloat(volumeSlider.value) * globalVolume;
// ゲインノードを設定
gainNodes[file].gain.value = volume;
// 接続
source.connect(gainNodes[file]);
gainNodes[file].connect(audioContext.destination);
// 再生
source.start(0, startTime, endTime - startTime);
// ループ設定
source.loop = loop;
if (loop) {
source.loopStart = startTime;
source.loopEnd = endTime;
}
audioSources[file] = source;
}
});
// 動画を再生
video.play();
isPlaying = true;
// 終了時間に達したら停止
video.ontimeupdate = function() {
if (video.currentTime >= endTime) {
if (!loop) {
stopMedia();
} else {
video.currentTime = startTime;
}
}
};
}
// 一時停止関数
function pauseMedia() {
if (!isPlaying) return;
video.pause();
audioFiles.forEach(file => {
if (audioSources[file]) {
audioSources[file].stop();
audioSources[file] = null;
}
});
isPlaying = false;
}
// 停止関数
function stopMedia() {
pauseMedia();
video.currentTime = parseFloat(startTimeInput.value) || 0;
}
// イベントリスナーを設定
playBtn.addEventListener('click', playMedia);
pauseBtn.addEventListener('click', pauseMedia);
stopBtn.addEventListener('click', stopMedia);
// ボリュームスライダーのイベント
volumeSliders.forEach((slider, index) => {
slider.addEventListener('input', function() {
const value = parseFloat(this.value);
volumeValues[index].textContent = value.toFixed(2);
if (isPlaying && audioSources[this.dataset.audio] && gainNodes[this.dataset.audio]) {
const globalVolume = parseFloat(globalVolumeSlider.value);
gainNodes[this.dataset.audio].gain.value = value * globalVolume;
}
});
});
// 全体音量スライダーのイベント
globalVolumeSlider.addEventListener('input', function() {
const value = parseFloat(this.value);
globalVolumeValue.textContent = value.toFixed(1);
if (isPlaying) {
audioFiles.forEach(file => {
if (gainNodes[file]) {
const volumeSlider = document.querySelector(`.volume-slider[data-audio="${file}"]`);
const volume = parseFloat(volumeSlider.value) * value;
gainNodes[file].gain.value = volume;
}
});
}
});
// 初期化
loadAudioFiles();
});
</script>
</body>
</html>