soiz1's picture
Update index.html
60ca6fc
raw
history blame
25.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;
display: block;
}
.video-controls {
background-color: rgba(0, 0, 0, 0.7);
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;
background-color: rgba(0, 0, 0, 0.8);
padding: 2px 5px;
border-radius: 3px;
font-size: 12px;
display: none;
}
.controls-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.left-controls, .right-controls {
display: flex;
align-items: center;
gap: 10px;
}
.control-button {
background: none;
border: none;
color: #e6f1ff;
font-size: 16px;
cursor: pointer;
padding: 5px;
border-radius: 3px;
transition: all 0.2s;
}
.control-button:hover {
background-color: rgba(100, 255, 218, 0.2);
}
.time-display {
font-size: 14px;
color: #e6f1ff;
font-family: monospace;
}
.volume-control {
display: flex;
align-items: center;
gap: 5px;
}
.volume-slider {
width: 80px;
height: 5px;
-webkit-appearance: none;
background: #1e2a47;
border-radius: 5px;
outline: none;
opacity: 0;
transition: opacity 0.2s;
}
.volume-control:hover .volume-slider {
opacity: 1;
}
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
background: #64ffda;
border-radius: 50%;
cursor: pointer;
}
.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-volume-slider {
flex-grow: 1;
height: 8px;
-webkit-appearance: none;
background: #1e2a47;
border-radius: 5px;
outline: none;
}
.audio-volume-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;
}
input[type="number"], input[type="checkbox"], select {
background-color: #112240;
border: 1px solid #64ffda;
color: #e6f1ff;
padding: 5px;
border-radius: 3px;
}
.buttons {
display: flex;
gap: 10px;
justify-content: center;
}
.tech-decoration {
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, #64ffda, transparent);
margin: 20px 0;
}
.fullscreen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: black;
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.fullscreen video {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
}
.fullscreen .video-container {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
}
</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 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="controls-row">
<div class="left-controls">
<button class="control-button" id="play-pause-btn"></button>
<span class="time-display" id="time-display">00:00 / 00:00</span>
</div>
<div class="right-controls">
<div class="volume-control">
<button class="control-button" id="volume-btn">🔊</button>
<input type="range" class="volume-slider" id="video-volume" min="0" max="1" step="0.01" value="1">
</div>
<button class="control-button" id="fullscreen-btn"></button>
</div>
</div>
</div>
</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="audio-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="audio-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="audio-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="audio-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="audio-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 playPauseBtn = document.getElementById('play-pause-btn');
const fullscreenBtn = document.getElementById('fullscreen-btn');
const progressContainer = document.getElementById('progress-container');
const progressBar = document.getElementById('progress-bar');
const progressTime = document.getElementById('progress-time');
const timeDisplay = document.getElementById('time-display');
const volumeBtn = document.getElementById('volume-btn');
const videoVolumeSlider = document.getElementById('video-volume');
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('.audio-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;
let isFullscreen = false;
let lastVolume = 1;
// 動画のメタデータが読み込まれたら
video.addEventListener('loadedmetadata', function() {
videoDuration = video.duration;
endTimeInput.value = videoDuration.toFixed(1);
endTimeInput.max = videoDuration;
startTimeInput.max = videoDuration - 0.1;
updateTimeDisplay();
});
// 時間表示を更新
function updateTimeDisplay() {
const currentTime = video.currentTime;
const duration = video.duration;
timeDisplay.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
progressBar.style.width = `${(currentTime / duration) * 100}%`;
}
// 時間をフォーマット (00:00形式)
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
// 音声ファイルを読み込む
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(`.audio-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;
playPauseBtn.textContent = '⏸';
// 終了時間に達したら停止
video.ontimeupdate = function() {
updateTimeDisplay();
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;
playPauseBtn.textContent = '▶';
}
// 停止関数
function stopMedia() {
pauseMedia();
video.currentTime = parseFloat(startTimeInput.value) || 0;
updateTimeDisplay();
}
// 再生/一時停止ボタンの切り替え
playPauseBtn.addEventListener('click', function() {
if (isPlaying) {
pauseMedia();
} else {
playMedia();
}
});
// プログレスバークリックでシーク
progressContainer.addEventListener('click', function(e) {
if (!videoDuration) return;
const rect = this.getBoundingClientRect();
const pos = (e.clientX - rect.left) / rect.width;
const seekTime = pos * videoDuration;
video.currentTime = seekTime;
if (!isPlaying) {
updateTimeDisplay();
}
});
// プログレスバー上でマウス移動時に時間を表示
progressContainer.addEventListener('mousemove', function(e) {
if (!videoDuration) return;
const rect = this.getBoundingClientRect();
const pos = (e.clientX - rect.left) / rect.width;
const seekTime = pos * videoDuration;
progressTime.textContent = formatTime(seekTime);
progressTime.style.left = `${e.clientX - rect.left}px`;
progressTime.style.display = 'block';
});
progressContainer.addEventListener('mouseout', function() {
progressTime.style.display = 'none';
});
// ボリュームコントロール
volumeBtn.addEventListener('click', function() {
if (video.volume > 0) {
lastVolume = video.volume;
video.volume = 0;
videoVolumeSlider.value = 0;
volumeBtn.textContent = '🔇';
} else {
video.volume = lastVolume;
videoVolumeSlider.value = lastVolume;
volumeBtn.textContent = lastVolume > 0.5 ? '🔊' : '🔉';
}
});
videoVolumeSlider.addEventListener('input', function() {
const volume = parseFloat(this.value);
video.volume = volume;
lastVolume = volume;
if (volume === 0) {
volumeBtn.textContent = '🔇';
} else if (volume > 0.5) {
volumeBtn.textContent = '🔊';
} else {
volumeBtn.textContent = '🔉';
}
});
// フルスクリーン
fullscreenBtn.addEventListener('click', function() {
if (!isFullscreen) {
enterFullscreen();
} else {
exitFullscreen();
}
});
function enterFullscreen() {
const elem = video.parentElement;
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) {
elem.msRequestFullscreen();
}
isFullscreen = true;
}
function exitFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
isFullscreen = false;
}
// フルスクリーン状態の変更を監視
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('msfullscreenchange', handleFullscreenChange);
function handleFullscreenChange() {
isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement);
}
// ボリュームスライダーのイベント
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(`.audio-volume-slider[data-audio="${file}"]`);
const volume = parseFloat(volumeSlider.value) * value;
gainNodes[file].gain.value = volume;
}
});
}
});
// 再生ボタン
playBtn.addEventListener('click', playMedia);
pauseBtn.addEventListener('click', pauseMedia);
stopBtn.addEventListener('click', stopMedia);
// 初期化
loadAudioFiles();
});
</script>
</body>
</html>