Spaces:
Running
Running
<html lang="ja"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>動画連続再生プレイヤー</title> | |
<style> | |
:root { | |
--primary-color: #00a8ff; | |
--secondary-color: #0097e6; | |
--dark-color: #192a56; | |
--light-color: #dcdde1; | |
--accent-color: #00d2d3; | |
--error-color: #e84118; | |
} | |
body { | |
font-family: 'Arial', sans-serif; | |
background-color: #1e272e; | |
color: var(--light-color); | |
margin: 0; | |
padding: 20px; | |
overflow-x: hidden; | |
} | |
.container { | |
display: flex; | |
flex-direction: column; | |
gap: 20px; | |
max-width: 1200px; | |
margin: 0 auto; | |
} | |
h1 { | |
color: var(--primary-color); | |
text-align: center; | |
text-shadow: 0 0 10px rgba(0, 168, 255, 0.5); | |
font-size: 2.5rem; | |
margin-bottom: 20px; | |
} | |
.main-content { | |
display: flex; | |
gap: 20px; | |
} | |
.video-tiles { | |
flex: 1; | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
gap: 15px; | |
} | |
.video-tile { | |
background: linear-gradient(145deg, #2f3640, #1e272e); | |
border: 1px solid var(--primary-color); | |
border-radius: 8px; | |
padding: 15px; | |
box-shadow: 0 0 15px rgba(0, 168, 255, 0.2); | |
position: relative; | |
transition: all 0.3s ease; | |
} | |
.video-tile:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 5px 20px rgba(0, 168, 255, 0.4); | |
} | |
.video-tile-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 10px; | |
} | |
.video-tile-title { | |
font-weight: bold; | |
color: var(--primary-color); | |
} | |
.video-tile-remove { | |
background-color: var(--error-color); | |
color: white; | |
border: none; | |
border-radius: 50%; | |
width: 24px; | |
height: 24px; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
transition: all 0.2s ease; | |
} | |
.video-tile-remove:hover { | |
transform: scale(1.1); | |
} | |
.video-preview { | |
width: 100%; | |
height: 150px; | |
background-color: #000; | |
margin-bottom: 10px; | |
border: 1px solid var(--secondary-color); | |
} | |
.video-preview video { | |
width: 100%; | |
height: 100%; | |
object-fit: contain; | |
} | |
.control-group { | |
margin-bottom: 10px; | |
} | |
.control-group label { | |
display: block; | |
margin-bottom: 5px; | |
color: var(--accent-color); | |
font-size: 0.9rem; | |
} | |
.control-group input[type="number"], | |
.control-group input[type="range"], | |
.control-group select { | |
width: 100%; | |
padding: 5px; | |
background-color: #2f3640; | |
border: 1px solid var(--primary-color); | |
border-radius: 4px; | |
color: white; | |
} | |
.control-group input[type="number"] { | |
width: calc(100% - 12px); | |
} | |
.add-video-btn { | |
background-color: var(--primary-color); | |
color: white; | |
border: none; | |
border-radius: 50%; | |
width: 50px; | |
height: 50px; | |
font-size: 1.5rem; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
margin: 20px auto; | |
transition: all 0.3s ease; | |
box-shadow: 0 0 15px rgba(0, 168, 255, 0.3); | |
} | |
.add-video-btn:hover { | |
background-color: var(--secondary-color); | |
transform: scale(1.1); | |
} | |
.global-controls { | |
display: flex; | |
justify-content: center; | |
gap: 15px; | |
margin-top: 20px; | |
} | |
.global-btn { | |
padding: 10px 15px; | |
background-color: var(--dark-color); | |
color: white; | |
border: 1px solid var(--primary-color); | |
border-radius: 4px; | |
cursor: pointer; | |
transition: all 0.2s ease; | |
} | |
.global-btn:hover { | |
background-color: var(--primary-color); | |
} | |
.loop-checkbox { | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
} | |
.loop-checkbox input { | |
width: auto; | |
} | |
/* フルスクリーン動画表示 */ | |
.fullscreen-video { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: black; | |
z-index: 1000; | |
display: none; | |
} | |
.fullscreen-video video { | |
width: 100%; | |
height: 100%; | |
object-fit: contain; | |
} | |
.close-fullscreen { | |
position: absolute; | |
top: 20px; | |
right: 20px; | |
background-color: var(--error-color); | |
color: white; | |
border: none; | |
border-radius: 50%; | |
width: 40px; | |
height: 40px; | |
font-size: 1.2rem; | |
cursor: pointer; | |
z-index: 1001; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
.video-controls { | |
position: absolute; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
display: flex; | |
align-items: center; | |
gap: 15px; | |
background-color: rgba(30, 39, 46, 0.8); | |
padding: 10px 20px; | |
border-radius: 8px; | |
z-index: 1001; | |
} | |
.control-btn { | |
background: none; | |
border: none; | |
color: white; | |
font-size: 1.5rem; | |
cursor: pointer; | |
transition: all 0.2s ease; | |
} | |
.control-btn:hover { | |
color: var(--primary-color); | |
} | |
.volume-control, .speed-control { | |
display: flex; | |
align-items: center; | |
gap: 5px; | |
} | |
.volume-control input, .speed-control input { | |
width: 80px; | |
} | |
/* ドラッグ可能なタイルのスタイル */ | |
.video-tile.dragging { | |
opacity: 0.5; | |
border: 2px dashed var(--accent-color); | |
} | |
/* コンテキストメニュー */ | |
.context-menu { | |
position: absolute; | |
background-color: #2f3640; | |
border: 1px solid var(--primary-color); | |
border-radius: 4px; | |
padding: 5px 0; | |
z-index: 1002; | |
display: none; | |
} | |
.context-menu-item { | |
padding: 8px 15px; | |
cursor: pointer; | |
} | |
.context-menu-item:hover { | |
background-color: var(--dark-color); | |
color: var(--primary-color); | |
} | |
/* サイバー風の装飾 */ | |
.cyber-decoration { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
pointer-events: none; | |
z-index: -1; | |
opacity: 0.1; | |
background: | |
linear-gradient(90deg, transparent 49%, var(--primary-color) 49%, var(--primary-color) 51%, transparent 51%), | |
linear-gradient(transparent 49%, var(--primary-color) 49%, var(--primary-color) 51%, transparent 51%); | |
background-size: 50px 50px; | |
} | |
/* プログレスバー */ | |
.progress-container { | |
position: absolute; | |
bottom: 80px; | |
left: 50%; | |
transform: translateX(-50%); | |
width: 80%; | |
height: 5px; | |
background-color: rgba(255, 255, 255, 0.2); | |
border-radius: 2px; | |
z-index: 1001; | |
cursor: pointer; | |
} | |
.progress-bar { | |
height: 100%; | |
background-color: var(--primary-color); | |
border-radius: 2px; | |
width: 0%; | |
} | |
.thumbnails { | |
position: absolute; | |
bottom: 90px; | |
left: 0; | |
background-color: rgba(30, 39, 46, 0.9); | |
border: 1px solid var(--primary-color); | |
border-radius: 4px; | |
padding: 5px; | |
display: none; | |
z-index: 1002; | |
} | |
.thumbnails img { | |
width: 160px; | |
height: 90px; | |
display: block; | |
} | |
/* 動画選択オプション */ | |
.video-options-container { | |
background: linear-gradient(145deg, #2f3640, #1e272e); | |
border: 1px solid var(--primary-color); | |
border-radius: 8px; | |
padding: 15px; | |
margin-bottom: 20px; | |
} | |
.video-type-selector { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 15px; | |
margin-bottom: 20px; | |
justify-content: center; | |
} | |
.video-type-selector div { | |
display: flex; | |
align-items: center; | |
gap: 5px; | |
} | |
.video-options { | |
display: grid; | |
grid-template-columns: repeat(2, 1fr); | |
gap: 15px; | |
margin-bottom: 20px; | |
} | |
.video-options div { | |
display: flex; | |
align-items: center; | |
gap: 5px; | |
} | |
.invalid-selection { | |
color: var(--error-color); | |
margin-top: 10px; | |
display: none; | |
} | |
.modal { | |
display: none; | |
position: fixed; | |
z-index: 1002; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0,0,0,0.8); | |
} | |
.modal-content { | |
background-color: #2f3640; | |
margin: 5% auto; | |
padding: 25px; | |
border: 2px solid var(--primary-color); | |
width: 90%; | |
max-width: 500px; | |
border-radius: 10px; | |
box-shadow: 0 0 20px rgba(0, 168, 255, 0.5); | |
} | |
.close-modal { | |
color: #aaa; | |
float: right; | |
font-size: 28px; | |
font-weight: bold; | |
cursor: pointer; | |
} | |
.change-video-btn { | |
background-color: var(--primary-color); | |
color: white; | |
border: none; | |
border-radius: 4px; | |
padding: 5px 10px; | |
margin-right: 5px; | |
cursor: pointer; | |
} | |
.modal .invalid-selection { | |
color: var(--error-color); | |
margin: 10px 0; | |
padding: 5px; | |
background-color: rgba(232, 65, 24, 0.2); | |
border-radius: 4px; | |
text-align: center; | |
} | |
/* 新しいPiPボタンスタイル */ | |
.pip-btn { | |
background-color: var(--primary-color); | |
color: white; | |
border: none; | |
border-radius: 4px; | |
padding: 10px 15px; | |
cursor: pointer; | |
transition: all 0.2s ease; | |
margin-left: 10px; | |
} | |
.pip-btn:hover { | |
background-color: var(--secondary-color); | |
} | |
/* 再生/一時停止ボタン */ | |
.play-pause-btn { | |
font-size: 1.8rem; | |
background: none; | |
border: none; | |
color: white; | |
cursor: pointer; | |
transition: all 0.2s ease; | |
} | |
.play-pause-btn:hover { | |
color: var(--primary-color); | |
} | |
/* フルスクリーンコントロールの改善 */ | |
.fullscreen-controls { | |
position: absolute; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
display: flex; | |
align-items: center; | |
gap: 15px; | |
background-color: rgba(30, 39, 46, 0.8); | |
padding: 15px 25px; | |
border-radius: 8px; | |
z-index: 1001; | |
width: 80%; | |
max-width: 800px; | |
justify-content: center; | |
} | |
.fullscreen-controls-left, .fullscreen-controls-center, .fullscreen-controls-right { | |
display: flex; | |
align-items: center; | |
gap: 15px; | |
} | |
.fullscreen-controls-center { | |
flex: 1; | |
justify-content: center; | |
} | |
.time-display { | |
font-size: 0.9rem; | |
color: white; | |
min-width: 80px; | |
text-align: center; | |
} | |
#saveVideoSettings { | |
background-color: var(--primary-color); | |
color: white; | |
border: none; | |
padding: 10px 20px; | |
border-radius: 4px; | |
cursor: pointer; | |
display: block; | |
margin: 20px auto 0; | |
font-size: 1rem; | |
transition: all 0.2s ease; | |
} | |
#saveVideoSettings:hover { | |
background-color: var(--secondary-color); | |
} | |
.modal h3 { | |
color: var(--primary-color); | |
margin-top: 0; | |
text-align: center; | |
font-size: 1.5rem; | |
} | |
/* チェックボックスと順序変更ボタンのスタイルを追加 */ | |
.video-tile-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 10px; | |
gap: 10px; | |
} | |
.video-tile-title { | |
font-weight: bold; | |
color: var(--primary-color); | |
flex-grow: 1; | |
} | |
.video-tile-checkbox { | |
margin-right: 10px; | |
} | |
.video-order-controls { | |
display: flex; | |
gap: 5px; | |
} | |
.order-btn { | |
background-color: var(--dark-color); | |
color: white; | |
border: 1px solid var(--primary-color); | |
border-radius: 4px; | |
width: 24px; | |
height: 24px; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
.order-btn:hover { | |
background-color: var(--primary-color); | |
} | |
/* カスタム動画アップロードのスタイル */ | |
#customVideoUpload { | |
margin-top: 10px; | |
padding: 10px; | |
background-color: rgba(0, 0, 0, 0.1); | |
border-radius: 4px; | |
} | |
#customVideoFile { | |
margin-top: 5px; | |
width: 100%; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="cyber-decoration"></div> | |
<div class="container"> | |
<h1>動画連続再生プレイヤー</h1> | |
<!-- モーダルダイアログ --> | |
<div class="modal" id="videoModal"> | |
<div class="modal-content"> | |
<span class="close-modal">×</span> | |
<h3>動画設定</h3> | |
<!-- 動画タイプセレクターにカスタムオプションを追加 --> | |
<div class="video-type-selector"> | |
<div> | |
<input type="radio" id="gangnamStyle" name="videoType" value="k" checked> | |
<label for="gangnamStyle">カンナムスタイル</label> | |
</div> | |
<div> | |
<input type="radio" id="drillStyle" name="videoType" value="m"> | |
<label for="drillStyle">槇原ドリル</label> | |
</div> | |
<div> | |
<input type="radio" id="yellStyle" name="videoType" value="e"> | |
<label for="yellStyle">エール</label> | |
</div> | |
<div> | |
<input type="radio" id="customStyle" name="videoType" value="c"> | |
<label for="customStyle">カスタム動画</label> | |
</div> | |
</div> | |
<!-- カスタム動画ファイルアップロード用の要素を追加 --> | |
<div id="customVideoUpload" style="display: none;"> | |
<label for="customVideoFile">カスタム動画ファイル:</label> | |
<input type="file" id="customVideoFile" accept="video/*"> | |
</div> | |
<div class="video-options"> | |
<div> | |
<input type="checkbox" id="modalBackOption" name="modalBackOption"> | |
<label for="modalBackOption">白背景(背景削除)</label> | |
</div> | |
<div> | |
<input type="checkbox" id="modalStickOption" name="modalStickOption"> | |
<label for="modalStickOption">棒人間</label> | |
</div> | |
<div> | |
<input type="checkbox" id="modalHumanOption" name="modalHumanOption" checked> | |
<label for="modalHumanOption">人間</label> | |
</div> | |
<div> | |
<input type="checkbox" id="modalFlipMode" name="modalFlipMode"> | |
<label for="modalFlipMode">左右反転</label> | |
</div> | |
</div> | |
<button id="saveVideoSettings">保存</button> | |
</div> | |
</div> | |
<!-- グローバル設定 --> | |
<div class="video-options-container"> | |
<h3>グローバル設定 (新規追加時に適用)</h3> | |
<!-- 動画タイプセレクターにカスタムオプションを追加 --> | |
<div class="video-type-selector"> | |
<div> | |
<input type="radio" id="gangnamStyle" name="videoType" value="k" checked> | |
<label for="gangnamStyle">カンナムスタイル</label> | |
</div> | |
<div> | |
<input type="radio" id="drillStyle" name="videoType" value="m"> | |
<label for="drillStyle">槇原ドリル</label> | |
</div> | |
<div> | |
<input type="radio" id="yellStyle" name="videoType" value="e"> | |
<label for="yellStyle">エール</label> | |
</div> | |
<div> | |
<input type="radio" id="customStyle" name="videoType" value="c"> | |
<label for="customStyle">カスタム動画</label> | |
</div> | |
</div> | |
<!-- カスタム動画ファイルアップロード用の要素を追加 --> | |
<div id="customVideoUpload" style="display: none;"> | |
<label for="customVideoFile">カスタム動画ファイル:</label> | |
<input type="file" id="customVideoFile" accept="video/*"> | |
</div> | |
<div class="video-options"> | |
<div> | |
<input type="checkbox" id="backOption" name="backOption"> | |
<label for="backOption">白背景(背景削除))</label> | |
</div> | |
<div> | |
<input type="checkbox" id="stickOption" name="stickOption"> | |
<label for="stickOption">棒人間</label> | |
</div> | |
<div> | |
<input type="checkbox" id="humanOption" name="humanOption" checked> | |
<label for="humanOption">人間</label> | |
</div> | |
<div> | |
<input type="checkbox" id="flipMode" name="flipMode"> | |
<label for="flipMode">左右反転</label> | |
</div> | |
</div> | |
</div> | |
<div class="main-content"> | |
<div class="video-tiles" id="videoTiles"> | |
<!-- 動画タイルがここに追加されます --> | |
</div> | |
</div> | |
<button class="add-video-btn" id="addVideoBtn">+</button> | |
<div class="global-controls"> | |
<button class="global-btn" id="startBtn">再生開始</button> | |
<button class="global-btn" id="muteBtn">消音</button> | |
<button class="global-btn" id="hideVideoBtn">消画</button> | |
<button class="pip-btn" id="pipBtn">PiPモード</button> | |
<div class="loop-checkbox"> | |
<input type="checkbox" id="loopCheckbox"> | |
<label for="loopCheckbox">ループ再生</label> | |
</div> | |
</div> | |
</div> | |
<!-- フルスクリーン動画表示 --> | |
<div class="fullscreen-video" id="fullscreenVideo"> | |
<video id="mainVideo"></video> | |
<button class="close-fullscreen" id="closeFullscreen">×</button> | |
<div class="progress-container" id="progressContainer"> | |
<div class="progress-bar" id="progressBar"></div> | |
</div> | |
<div class="thumbnails" id="thumbnails"></div> | |
<!-- 改善されたコントロールパネル --> | |
<div class="fullscreen-controls"> | |
<div class="fullscreen-controls-left"> | |
<button class="play-pause-btn" id="playPauseBtn">⏸</button> | |
<span class="time-display" id="currentTime">0:00</span> | |
<span>/</span> | |
<span class="time-display" id="durationTime">0:00</span> | |
</div> | |
<div class="fullscreen-controls-center"> | |
<button class="control-btn" id="prevBtn">◀</button> | |
<button class="control-btn" id="restartBtn">↺</button> | |
<button class="control-btn" id="nextBtn">▶</button> | |
</div> | |
<div class="fullscreen-controls-right"> | |
<div class="volume-control"> | |
<span>🔊</span> | |
<input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="1"> | |
</div> | |
<div class="speed-control"> | |
<span>⏱️</span> | |
<input type="range" id="speedSlider" min="0.5" max="2" step="0.1" value="1"> | |
<input type="number" id="speedInput" min="0.5" max="2" step="0.1" value="1" style="width: 50px;"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- サムネイル取得用の非表示動画要素 --> | |
<video id="thumbnailVideo" style="display: none;"></video> | |
<script> | |
// 許可された動画リスト | |
const allowedVideos = [ | |
'/videos/k-back-human.mp4', | |
'/videos/k-back-stick-human.mp4', | |
'/videos/k-back-stick.mp4', | |
'/videos/k-human.mp4', | |
'/videos/k-stick-human.mp4', | |
'/videos/m-back-human.mp4', | |
'/videos/m-back-stick-human.mp4', | |
'/videos/m-back-stick.mp4', | |
'/videos/m-human.mp4', | |
'/videos/m-stick-human.mp4', | |
'/videos/e-back-human.mp4', | |
'/videos/e-back-stick-human.mp4', | |
'/videos/e-back-stick.mp4', | |
'/videos/e-human.mp4', | |
'/videos/e-stick-human.mp4' | |
]; | |
// グローバル変数 | |
let videoTiles = []; | |
let currentVideoIndex = 0; | |
let isPlayingSequence = false; | |
let isLoopEnabled = false; | |
let isMuted = false; | |
let isVideoHidden = false; | |
let currentEditingTileId = null; | |
let isPlaying = false; | |
let customVideos = {}; | |
// DOM要素 | |
const videoTilesContainer = document.getElementById('videoTiles'); | |
const addVideoBtn = document.getElementById('addVideoBtn'); | |
const fullscreenVideo = document.getElementById('fullscreenVideo'); | |
const mainVideo = document.getElementById('mainVideo'); | |
const closeFullscreen = document.getElementById('closeFullscreen'); | |
const startBtn = document.getElementById('startBtn'); | |
const muteBtn = document.getElementById('muteBtn'); | |
const hideVideoBtn = document.getElementById('hideVideoBtn'); | |
const pipBtn = document.getElementById('pipBtn'); | |
const loopCheckbox = document.getElementById('loopCheckbox'); | |
const prevBtn = document.getElementById('prevBtn'); | |
const nextBtn = document.getElementById('nextBtn'); | |
const restartBtn = document.getElementById('restartBtn'); | |
const playPauseBtn = document.getElementById('playPauseBtn'); | |
const volumeSlider = document.getElementById('volumeSlider'); | |
const speedSlider = document.getElementById('speedSlider'); | |
const progressContainer = document.getElementById('progressContainer'); | |
const progressBar = document.getElementById('progressBar'); | |
const thumbnails = document.getElementById('thumbnails'); | |
const currentTimeDisplay = document.getElementById('currentTime'); | |
const durationTimeDisplay = document.getElementById('durationTime'); | |
const videoModal = document.getElementById('videoModal'); | |
const modalGangnamStyle = document.getElementById('modalGangnamStyle'); | |
const modalDrillStyle = document.getElementById('modalDrillStyle'); | |
const modalYellStyle = document.getElementById('modalYellStyle'); | |
const modalBackOption = document.getElementById('modalBackOption'); | |
const modalStickOption = document.getElementById('modalStickOption'); | |
const modalHumanOption = document.getElementById('modalHumanOption'); | |
const modalFlipMode = document.getElementById('modalFlipMode'); | |
const saveVideoSettingsBtn = document.getElementById('saveVideoSettings'); | |
const customVideoUpload = document.getElementById('customVideoUpload'); | |
const customVideoFile = document.getElementById('customVideoFile'); | |
const gangnamStyle = document.getElementById('gangnamStyle'); | |
const drillStyle = document.getElementById('drillStyle'); | |
const yellStyle = document.getElementById('yellStyle'); | |
const customStyle = document.getElementById('customStyle'); | |
if ('serviceWorker' in navigator) { | |
window.addEventListener('load', async () => { | |
try { | |
const registration = await navigator.serviceWorker.register('/sw.js', { | |
updateViaCache: 'none' // キャッシュ無視して最新を取る | |
}); | |
console.log('✅ Service Worker registered'); | |
// Service Worker がページ制御を開始するまで待つ(重要!) | |
await navigator.serviceWorker.ready; | |
console.log('✅ Service Worker is active and controlling the page'); | |
// 更新を強制的にチェック(でも失敗しても静かにスキップ) | |
registration.update().catch(err => { | |
alert('ServiceWorker update check failed:', err); | |
}); | |
// 新しい SW が見つかったら、状態変化を監視 | |
registration.addEventListener('updatefound', () => { | |
const newWorker = registration.installing; | |
if (newWorker) { | |
newWorker.addEventListener('statechange', () => { | |
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { | |
console.log('🔁 New Service Worker installed, reloading...'); | |
window.location.reload(); // ユーザーがオフラインなら必要 | |
} | |
}); | |
} | |
}); | |
} catch (error) { | |
console.error('❌ ServiceWorker registration failed:', error); | |
} | |
}); | |
} | |
// 初期化 | |
document.addEventListener('DOMContentLoaded', () => { | |
loadCustomVideos(); | |
loadVideoTiles(); | |
// モーダルイベント | |
document.querySelector('.close-modal').addEventListener('click', () => { | |
videoModal.style.display = 'none'; | |
}); | |
saveVideoSettingsBtn.addEventListener('click', saveVideoSettings); | |
// 動画タイプ変更時のイベント | |
gangnamStyle.addEventListener('change', updateVideoType); | |
drillStyle.addEventListener('change', updateVideoType); | |
yellStyle.addEventListener('change', updateVideoType); | |
customStyle.addEventListener('change', updateVideoType); | |
// 動画変更ボタンクリック | |
document.addEventListener('click', (e) => { | |
if (e.target.classList.contains('change-video-btn')) { | |
currentEditingTileId = e.target.dataset.tileId; | |
openVideoModal(currentEditingTileId); | |
} | |
if (e.target.classList.contains('video-tile-remove')) { | |
removeVideoTile(e.target.dataset.tileId); | |
} | |
if (e.target.classList.contains('order-up-btn')) { | |
moveTileUp(e.target.dataset.tileId); | |
} | |
if (e.target.classList.contains('order-down-btn')) { | |
moveTileDown(e.target.dataset.tileId); | |
} | |
}); | |
// スライダーと入力ボックスの同期 | |
speedSlider.addEventListener('input', function() { | |
speedInput.value = this.value; | |
updatePlaybackSpeed(); | |
}); | |
speedInput.addEventListener('change', function() { | |
speedSlider.value = this.value; | |
updatePlaybackSpeed(); | |
}); | |
// グローバルコントロール | |
addVideoBtn.addEventListener('click', addVideoTile); | |
startBtn.addEventListener('click', startVideoSequence); | |
muteBtn.addEventListener('click', toggleMute); | |
hideVideoBtn.addEventListener('click', toggleVideoHide); | |
pipBtn.addEventListener('click', togglePipMode); | |
closeFullscreen.addEventListener('click', closeFullscreenVideo); | |
loopCheckbox.addEventListener('change', () => { | |
isLoopEnabled = loopCheckbox.checked; | |
saveVideoTiles(); | |
}); | |
// フルスクリーンコントロール | |
prevBtn.addEventListener('click', playPreviousVideo); | |
nextBtn.addEventListener('click', playNextVideo); | |
restartBtn.addEventListener('click', restartCurrentVideo); | |
playPauseBtn.addEventListener('click', togglePlayPause); | |
volumeSlider.addEventListener('input', updateVolume); | |
speedSlider.addEventListener('input', updatePlaybackSpeed); | |
// プログレスバー | |
progressContainer.addEventListener('mousemove', showThumbnail); | |
progressContainer.addEventListener('mouseout', hideThumbnail); | |
progressContainer.addEventListener('click', seekVideo); | |
// プログレスバー更新開始 | |
mainVideo.addEventListener('play', () => { | |
isPlaying = true; | |
playPauseBtn.textContent = '⏸'; | |
updateProgressBar(); | |
updateTimeDisplay(); | |
}); | |
mainVideo.addEventListener('pause', () => { | |
isPlaying = false; | |
playPauseBtn.textContent = '▶'; | |
}); | |
mainVideo.addEventListener('timeupdate', updateTimeDisplay); | |
mainVideo.addEventListener('loadedmetadata', updateDurationDisplay); | |
// カスタム動画ファイルアップロード | |
customVideoFile.addEventListener('change', handleCustomVideoUpload); | |
}); | |
// 動画タイプが変更された時の処理 | |
function updateVideoType() { | |
if (customStyle.checked) { | |
customVideoUpload.style.display = 'block'; | |
} else { | |
customVideoUpload.style.display = 'none'; | |
} | |
} | |
// カスタム動画をアップロード | |
function handleCustomVideoUpload(e) { | |
const file = e.target.files[0]; | |
if (!file) return; | |
const reader = new FileReader(); | |
reader.onload = function(event) { | |
const videoId = 'custom-' + Date.now(); | |
customVideos[videoId] = { | |
name: file.name, | |
data: event.target.result | |
}; | |
saveCustomVideos(); | |
}; | |
reader.readAsDataURL(file); | |
} | |
// カスタム動画を保存 | |
function saveCustomVideos() { | |
localStorage.setItem('customVideos', JSON.stringify(customVideos)); | |
} | |
// カスタム動画を読み込み | |
function loadCustomVideos() { | |
const savedVideos = localStorage.getItem('customVideos'); | |
if (savedVideos) { | |
customVideos = JSON.parse(savedVideos); | |
} | |
} | |
// 動画タイルを追加 | |
function addVideoTile() { | |
const tileId = Date.now(); | |
const tile = document.createElement('div'); | |
tile.className = 'video-tile'; | |
tile.id = `tile-${tileId}`; | |
tile.draggable = true; | |
// グローバル設定を取得 | |
const videoType = document.querySelector('input[name="videoType"]:checked').value; | |
const back = document.getElementById('backOption').checked; | |
const stick = document.getElementById('stickOption').checked; | |
const human = document.getElementById('humanOption').checked; | |
const flip = document.getElementById('flipMode').checked; | |
tile.innerHTML = ` | |
<div class="video-tile-header"> | |
<input type="checkbox" class="video-tile-checkbox" id="enable-${tileId}" checked> | |
<span class="video-tile-title">動画 ${videoTiles.length + 1}</span> | |
<div class="video-order-controls"> | |
<button class="order-btn order-up-btn" data-tile-id="${tileId}">◁</button> | |
<button class="order-btn order-down-btn" data-tile-id="${tileId}">▷</button> | |
</div> | |
<div> | |
<button class="change-video-btn" data-tile-id="${tileId}">動画を変更</button> | |
<button class="video-tile-remove" data-tile-id="${tileId}">×</button> | |
</div> | |
</div> | |
<div class="video-preview"> | |
<video id="preview-${tileId}" muted></video> | |
</div> | |
<div class="control-group"> | |
<label for="startTime-${tileId}">再生開始秒数</label> | |
<input type="number" id="startTime-${tileId}" value="0" min="0" step="0.1"> | |
</div> | |
<div class="control-group"> | |
<label for="endTime-${tileId}">再生停止秒数</label> | |
<input type="number" id="endTime-${tileId}" value="" min="0" step="0.1" placeholder="未設定で動画最後まで"> | |
</div> | |
<div class="control-group"> | |
<label for="fadeIn-${tileId}">フェードイン秒数</label> | |
<input type="number" id="fadeIn-${tileId}" value="0" min="0" step="0.1"> | |
</div> | |
<div class="control-group"> | |
<label for="fadeOut-${tileId}">フェードアウト秒数</label> | |
<input type="number" id="fadeOut-${tileId}" value="0" min="0" step="0.1"> | |
</div> | |
<div class="control-group"> | |
<label for="waitTime-${tileId}">次の動画までの待機秒数</label> | |
<input type="number" id="waitTime-${tileId}" value="1" min="0" step="0.1"> | |
</div> | |
<div class="control-group"> | |
<label for="playbackRate-${tileId}">再生速度</label> | |
<input type="number" id="playbackRate-${tileId}" value="1" min="0.1" max="4" step="0.1"> | |
</div> | |
<div class="control-group"> | |
<label for="volume-${tileId}">音量 (0-1)</label> | |
<input type="number" id="volume-${tileId}" value="1" min="0" max="1" step="0.1"> | |
</div> | |
<input type="hidden" id="videoType-${tileId}" value="${videoType}"> | |
<input type="hidden" id="backOption-${tileId}" value="${back}"> | |
<input type="hidden" id="stickOption-${tileId}" value="${stick}"> | |
<input type="hidden" id="humanOption-${tileId}" value="${human}"> | |
<input type="hidden" id="flipMode-${tileId}" value="${flip}"> | |
<input type="hidden" id="customVideoId-${tileId}" value=""> | |
`; | |
videoTilesContainer.appendChild(tile); | |
videoTiles.push(tileId); | |
// プレビューの更新 | |
updateVideoPreview(tileId); | |
// 入力変更時のプレビュー更新 | |
const inputs = tile.querySelectorAll('input'); | |
inputs.forEach(input => { | |
if (!input.type.includes('hidden')) { | |
input.addEventListener('change', () => { | |
updateVideoPreview(tileId); | |
saveVideoTiles(); | |
}); | |
input.addEventListener('input', () => updateVideoPreview(tileId)); | |
} | |
}); | |
// チェックボックスのイベント | |
document.getElementById(`enable-${tileId}`).addEventListener('change', saveVideoTiles); | |
saveVideoTiles(); | |
return tileId; | |
} | |
// タイルを上に移動 | |
function moveTileUp(tileId) { | |
const index = videoTiles.indexOf(parseInt(tileId)); | |
if (index > 0) { | |
const temp = videoTiles[index]; | |
videoTiles[index] = videoTiles[index - 1]; | |
videoTiles[index - 1] = temp; | |
const tileElement = document.getElementById(`tile-${tileId}`); | |
const prevTileElement = document.getElementById(`tile-${videoTiles[index]}`); | |
tileElement.parentNode.insertBefore(tileElement, prevTileElement); | |
updateTileTitles(); | |
saveVideoTiles(); | |
} | |
} | |
// タイルを下に移動 | |
function moveTileDown(tileId) { | |
const index = videoTiles.indexOf(parseInt(tileId)); | |
if (index < videoTiles.length - 1) { | |
const temp = videoTiles[index]; | |
videoTiles[index] = videoTiles[index + 1]; | |
videoTiles[index + 1] = temp; | |
const tileElement = document.getElementById(`tile-${tileId}`); | |
const nextTileElement = document.getElementById(`tile-${videoTiles[index]}`); | |
nextTileElement.parentNode.insertBefore(nextTileElement, tileElement); | |
updateTileTitles(); | |
saveVideoTiles(); | |
} | |
} | |
// モーダルを開く | |
function openVideoModal(tileId) { | |
// 現在の設定をモーダルに反映 | |
modalGangnamStyle.checked = document.getElementById(`videoType-${tileId}`).value === 'k'; | |
modalDrillStyle.checked = document.getElementById(`videoType-${tileId}`).value === 'm'; | |
modalYellStyle.checked = document.getElementById(`videoType-${tileId}`).value === 'e'; | |
modalBackOption.checked = document.getElementById(`backOption-${tileId}`).value === 'true'; | |
modalStickOption.checked = document.getElementById(`stickOption-${tileId}`).value === 'true'; | |
modalHumanOption.checked = document.getElementById(`humanOption-${tileId}`).value === 'true'; | |
modalFlipMode.checked = document.getElementById(`flipMode-${tileId}`).value === 'true'; | |
videoModal.style.display = 'block'; | |
} | |
// 動画設定を保存 | |
function saveVideoSettings() { | |
if (!currentEditingTileId) return; | |
// モーダルの設定を取得 | |
const videoType = document.querySelector('input[name="modalVideoType"]:checked').value; | |
const back = modalBackOption.checked; | |
const stick = modalStickOption.checked; | |
const human = modalHumanOption.checked; | |
document.getElementById(`videoType-${currentEditingTileId}`).value = videoType; | |
document.getElementById(`backOption-${currentEditingTileId}`).value = back; | |
document.getElementById(`stickOption-${currentEditingTileId}`).value = stick; | |
document.getElementById(`humanOption-${currentEditingTileId}`).value = human; | |
document.getElementById(`flipMode-${currentEditingTileId}`).value = modalFlipMode.checked; | |
updateVideoPreview(currentEditingTileId); | |
videoModal.style.display = 'none'; | |
saveVideoTiles(); | |
} | |
// 動画プレビューを更新 | |
function updateVideoPreview(tileId) { | |
const videoUrl = getVideoUrl(tileId); | |
const previewVideo = document.getElementById(`preview-${tileId}`); | |
if (!previewVideo) return; | |
if (previewVideo.src !== videoUrl) { | |
previewVideo.src = videoUrl; | |
} | |
const startTime = parseFloat(document.getElementById(`startTime-${tileId}`).value) || 0; | |
const endTime = parseFloat(document.getElementById(`endTime-${tileId}`).value); | |
const playbackRate = parseFloat(document.getElementById(`playbackRate-${tileId}`).value) || 1; | |
const volume = parseFloat(document.getElementById(`volume-${tileId}`).value) || 1; | |
const flip = document.getElementById(`flipMode-${tileId}`).value === 'true'; | |
previewVideo.currentTime = startTime; | |
previewVideo.playbackRate = playbackRate; | |
previewVideo.volume = volume; | |
previewVideo.style.transform = flip ? 'scaleX(-1)' : 'scaleX(1)'; | |
previewVideo.controls = true; | |
if (endTime && !isNaN(endTime)) { | |
previewVideo.addEventListener('timeupdate', function onTimeUpdate() { | |
if (this.currentTime >= endTime) { | |
this.pause(); | |
this.removeEventListener('timeupdate', onTimeUpdate); | |
} | |
}); | |
} | |
previewVideo.play().catch(e => console.log("プレビュー自動再生がブロックされました:", e)); | |
} | |
// 動画URLを取得 | |
function getVideoUrl(tileId) { | |
const videoType = document.getElementById(`videoType-${tileId}`).value; | |
// カスタム動画の場合 | |
if (videoType === 'c') { | |
const customVideoId = document.getElementById(`customVideoId-${tileId}`).value; | |
if (customVideoId && customVideos[customVideoId]) { | |
return customVideos[customVideoId].data; | |
} | |
return null; | |
} | |
// 通常の動画の場合 | |
const back = document.getElementById(`backOption-${tileId}`).value === 'true'; | |
const stick = document.getElementById(`stickOption-${tileId}`).value === 'true'; | |
const human = document.getElementById(`humanOption-${tileId}`).value === 'true'; | |
let parts = [videoType]; | |
if (back) parts.push('back'); | |
if (stick) parts.push('stick'); | |
if (human) parts.push('human'); | |
// 何も選択されていない場合は人間をデフォルトで追加 | |
if (parts.length === 1) parts.push('human'); | |
const fileName = parts.join('-') + '.mp4'; | |
const videoUrl = `/videos/${fileName}`; | |
return allowedVideos.includes(videoUrl) ? videoUrl : null; | |
} | |
// 動画タイルを削除 | |
function removeVideoTile(tileId) { | |
if (videoTiles.length <= 1) { | |
alert("少なくとも1つの動画タイルが必要です"); | |
return; | |
} | |
const tileIndex = videoTiles.indexOf(parseInt(tileId)); | |
if (tileIndex !== -1) { | |
videoTiles.splice(tileIndex, 1); | |
const tileElement = document.getElementById(`tile-${tileId}`); | |
tileElement.remove(); | |
// タイル番号を更新 | |
updateTileTitles(); | |
saveVideoTiles(); | |
} | |
} | |
// タイルのタイトルを更新 | |
function updateTileTitles() { | |
const tiles = document.querySelectorAll('.video-tile'); | |
tiles.forEach((tile, index) => { | |
const title = tile.querySelector('.video-tile-title'); | |
if (title) { | |
title.textContent = `動画 ${index + 1}`; | |
} | |
}); | |
} | |
// データを保存 | |
function saveVideoTiles() { | |
const tilesData = videoTiles.map(tileId => { | |
return { | |
id: tileId, | |
videoType: document.getElementById(`videoType-${tileId}`).value, | |
backOption: document.getElementById(`backOption-${tileId}`).value, | |
stickOption: document.getElementById(`stickOption-${tileId}`).value, | |
humanOption: document.getElementById(`humanOption-${tileId}`).value, | |
flipMode: document.getElementById(`flipMode-${tileId}`).value, | |
startTime: document.getElementById(`startTime-${tileId}`).value, | |
endTime: document.getElementById(`endTime-${tileId}`).value, | |
fadeIn: document.getElementById(`fadeIn-${tileId}`).value, | |
fadeOut: document.getElementById(`fadeOut-${tileId}`).value, | |
waitTime: document.getElementById(`waitTime-${tileId}`).value, | |
playbackRate: document.getElementById(`playbackRate-${tileId}`).value, | |
volume: document.getElementById(`volume-${tileId}`).value, | |
enabled: document.getElementById(`enable-${tileId}`).checked, | |
customVideoId: document.getElementById(`customVideoId-${tileId}`).value | |
}; | |
}); | |
localStorage.setItem('videoPlayerData', JSON.stringify({ | |
tiles: tilesData, | |
loop: isLoopEnabled, | |
customVideos: customVideos | |
})); | |
} | |
// データを読み込み | |
function loadVideoTiles() { | |
const defaultData = {"tiles":[{"id":1747349402295,"videoType":"k","backOption":"false","stickOption":"true","humanOption":"true","flipMode":"false","startTime":"45","endTime":"","fadeIn":"10","fadeOut":"0","waitTime":"1","playbackRate":"1","volume":"1","enabled":true},{"id":1747349402296,"videoType":"m","backOption":"true","stickOption":"true","humanOption":"true","flipMode":"false","startTime":"0","endTime":"","fadeIn":"0","fadeOut":"0","waitTime":"1","playbackRate":"1","volume":"1","enabled":true}],"loop":false}; | |
const savedData = localStorage.getItem('videoPlayerData'); | |
const data = savedData ? JSON.parse(savedData) : defaultData; | |
isLoopEnabled = data.loop; | |
loopCheckbox.checked = isLoopEnabled; | |
if (data.customVideos) { | |
customVideos = data.customVideos; | |
} | |
// 保存されたタイルを再作成 | |
videoTilesContainer.innerHTML = ''; | |
videoTiles = []; | |
data.tiles.forEach(tileData => { | |
const tileId = addVideoTile(); | |
// 各設定を復元 | |
document.getElementById(`startTime-${tileId}`).value = tileData.startTime || 0; | |
document.getElementById(`endTime-${tileId}`).value = tileData.endTime || ''; | |
document.getElementById(`fadeIn-${tileId}`).value = tileData.fadeIn || 0; | |
document.getElementById(`fadeOut-${tileId}`).value = tileData.fadeOut || 0; | |
document.getElementById(`waitTime-${tileId}`).value = tileData.waitTime || 1; | |
document.getElementById(`playbackRate-${tileId}`).value = tileData.playbackRate || 1; | |
document.getElementById(`volume-${tileId}`).value = tileData.volume || 1; | |
document.getElementById(`enable-${tileId}`).checked = tileData.enabled !== false; | |
// 動画オプションを復元 | |
document.getElementById(`videoType-${tileId}`).value = tileData.videoType || 'k'; | |
document.getElementById(`backOption-${tileId}`).value = tileData.backOption || 'false'; | |
document.getElementById(`stickOption-${tileId}`).value = tileData.stickOption || 'false'; | |
document.getElementById(`humanOption-${tileId}`).value = tileData.humanOption || 'true'; | |
document.getElementById(`flipMode-${tileId}`).value = tileData.flipMode || 'false'; | |
// カスタム動画IDを復元 | |
if (tileData.customVideoId) { | |
document.getElementById(`customVideoId-${tileId}`).value = tileData.customVideoId; | |
} | |
// プレビューを更新 | |
updateVideoPreview(tileId); | |
}); | |
} | |
// 動画シーケンスを開始 | |
function startVideoSequence() { | |
if (videoTiles.length === 0) { | |
alert("再生する動画がありません"); | |
return; | |
} | |
// 有効な動画だけをフィルタリング | |
const enabledTiles = videoTiles.filter(tileId => { | |
return document.getElementById(`enable-${tileId}`).checked; | |
}); | |
if (enabledTiles.length === 0) { | |
alert("有効な動画がありません"); | |
return; | |
} | |
isPlayingSequence = true; | |
currentVideoIndex = 0; | |
// フルスクリーン表示 | |
fullscreenVideo.style.display = 'block'; | |
document.body.style.overflow = 'hidden'; | |
stopAllPreviews(); | |
// 最初の動画を再生 | |
playVideoByIndex(currentVideoIndex, enabledTiles); | |
} | |
// 全てのプレビュー動画を一時停止 | |
function stopAllPreviews() { | |
const previewVideos = document.querySelectorAll('.video-preview video'); | |
previewVideos.forEach(video => { | |
video.pause(); | |
video.currentTime = 0; | |
video.controls = false; | |
}); | |
} | |
// 指定したインデックスの動画を再生 | |
function playVideoByIndex(index, enabledTiles = null) { | |
if (!enabledTiles) { | |
enabledTiles = videoTiles.filter(tileId => { | |
return document.getElementById(`enable-${tileId}`).checked; | |
}); | |
} | |
if (index < 0 || index >= enabledTiles.length) { | |
if (isLoopEnabled) { | |
index = 0; | |
currentVideoIndex = 0; | |
} else { | |
closeFullscreenVideo(); | |
return; | |
} | |
} | |
const tileId = enabledTiles[index]; | |
const videoUrl = getVideoUrl(tileId); | |
if (!videoUrl) { | |
alert("無効な動画設定です。動画を確認してください。"); | |
return; | |
} | |
const startTime = parseFloat(document.getElementById(`startTime-${tileId}`).value) || 0; | |
const endTime = parseFloat(document.getElementById(`endTime-${tileId}`).value); | |
const fadeIn = parseFloat(document.getElementById(`fadeIn-${tileId}`).value) || 0; | |
const fadeOut = parseFloat(document.getElementById(`fadeOut-${tileId}`).value) || 0; | |
const waitTime = parseFloat(document.getElementById(`waitTime-${tileId}`).value) || 1; | |
const playbackRate = parseFloat(document.getElementById(`playbackRate-${tileId}`).value) || 1; | |
const volume = parseFloat(document.getElementById(`volume-${tileId}`).value) || 1; | |
const flip = document.getElementById(`flipMode-${tileId}`).value === 'true'; | |
// 既存のイベントリスナーをクリア | |
mainVideo.onloadedmetadata = null; | |
mainVideo.onended = null; | |
mainVideo.ontimeupdate = null; | |
// メインビデオを設定 | |
mainVideo.src = videoUrl; | |
mainVideo.currentTime = startTime; | |
mainVideo.playbackRate = playbackRate; | |
mainVideo.volume = 0; // 最初は音量0(フェードインのために) | |
mainVideo.loop = false; | |
mainVideo.style.transform = flip ? 'scaleX(-1)' : 'scaleX(1)'; | |
mainVideo.style.opacity = '1'; // 常に表示 | |
// フェードイン処理 | |
if (fadeIn > 0) { | |
const targetVolume = isMuted ? 0 : volume; | |
const fadeInInterval = 50; // ミリ秒 | |
const steps = fadeIn * 1000 / fadeInInterval; | |
let currentStep = 0; | |
const fadeInIntervalId = setInterval(() => { | |
currentStep++; | |
const progress = currentStep / steps; | |
mainVideo.volume = targetVolume * progress; | |
if (currentStep >= steps) { | |
clearInterval(fadeInIntervalId); | |
mainVideo.volume = targetVolume; | |
} | |
}, fadeInInterval); | |
} else { | |
mainVideo.volume = isMuted ? 0 : volume; | |
} | |
mainVideo.onloadedmetadata = () => { | |
thumbnailVideo.src = videoUrl; | |
// 終了時間が設定されている場合 | |
if (endTime && !isNaN(endTime)) { | |
mainVideo.addEventListener('timeupdate', function onTimeUpdate() { | |
const remainingTime = endTime - this.currentTime; | |
// フェードアウト処理 | |
if (remainingTime <= fadeOut && fadeOut > 0) { | |
const fadeOutProgress = remainingTime / fadeOut; | |
const targetVolume = isMuted ? 0 : volume; | |
mainVideo.volume = targetVolume * fadeOutProgress; | |
} | |
if (this.currentTime >= endTime) { | |
this.pause(); | |
this.removeEventListener('timeupdate', onTimeUpdate); | |
setTimeout(() => { | |
currentVideoIndex++; | |
playVideoByIndex(currentVideoIndex, enabledTiles); | |
}, waitTime * 1000); | |
} | |
}); | |
} else { | |
// 終了時間が設定されていない場合 | |
mainVideo.onended = () => { | |
setTimeout(() => { | |
currentVideoIndex++; | |
playVideoByIndex(currentVideoIndex, enabledTiles); | |
}, waitTime * 1000); | |
}; | |
} | |
mainVideo.play().catch(e => console.log("動画再生がブロックされました:", e)); | |
}; | |
} | |
// フルスクリーン動画を閉じる | |
function closeFullscreenVideo() { | |
fullscreenVideo.style.display = 'none'; | |
document.body.style.overflow = 'auto'; | |
mainVideo.pause(); | |
isPlayingSequence = false; | |
isPlaying = false; | |
playPauseBtn.textContent = '▶'; | |
} | |
// 前の動画を再生 | |
function playPreviousVideo() { | |
if (!isPlayingSequence) return; | |
const enabledTiles = videoTiles.filter(tileId => { | |
return document.getElementById(`enable-${tileId}`).checked; | |
}); | |
currentVideoIndex--; | |
if (currentVideoIndex < 0) { | |
currentVideoIndex = enabledTiles.length - 1; | |
} | |
playVideoByIndex(currentVideoIndex, enabledTiles); | |
} | |
// 次の動画を再生 | |
function playNextVideo() { | |
if (!isPlayingSequence) return; | |
const enabledTiles = videoTiles.filter(tileId => { | |
return document.getElementById(`enable-${tileId}`).checked; | |
}); | |
currentVideoIndex++; | |
playVideoByIndex(currentVideoIndex, enabledTiles); | |
} | |
// 現在の動画を最初から再生 | |
function restartCurrentVideo() { | |
if (!isPlayingSequence) return; | |
const enabledTiles = videoTiles.filter(tileId => { | |
return document.getElementById(`enable-${tileId}`).checked; | |
}); | |
playVideoByIndex(currentVideoIndex, enabledTiles); | |
} | |
// 再生/一時停止を切り替え | |
function togglePlayPause() { | |
if (!isPlayingSequence) return; | |
if (isPlaying) { | |
mainVideo.pause(); | |
} else { | |
mainVideo.play(); | |
} | |
} | |
// 音量を更新 | |
function updateVolume() { | |
mainVideo.volume = volumeSlider.value; | |
isMuted = mainVideo.volume === 0; | |
muteBtn.textContent = isMuted ? "音を戻す" : "消音"; | |
} | |
// 再生速度を更新 | |
function updatePlaybackSpeed() { | |
mainVideo.playbackRate = speedSlider.value; | |
} | |
// ミュートを切り替え | |
function toggleMute() { | |
isMuted = !isMuted; | |
mainVideo.volume = isMuted ? 0 : volumeSlider.value; | |
muteBtn.textContent = isMuted ? "音を戻す" : "消音"; | |
} | |
// PiPモードを切り替え | |
function togglePipMode() {} | |
// ビデオ表示を切り替え | |
function toggleVideoHide() { | |
isVideoHidden = !isVideoHidden; | |
mainVideo.style.display = isVideoHidden ? 'none' : 'block'; | |
hideVideoBtn.textContent = isVideoHidden ? "表示" : "消画"; | |
} | |
// プログレスバーの更新 | |
function updateProgressBar() { | |
if (!isPlayingSequence) return; | |
const duration = mainVideo.duration || 1; | |
const currentTime = mainVideo.currentTime || 0; | |
const progressPercent = (currentTime / duration) * 100; | |
progressBar.style.width = `${progressPercent}%`; | |
requestAnimationFrame(updateProgressBar); | |
} | |
// 時間表示を更新 | |
function updateTimeDisplay() { | |
currentTimeDisplay.textContent = formatTime(mainVideo.currentTime || 0); | |
} | |
// 動画の長さを表示 | |
function updateDurationDisplay() { | |
durationTimeDisplay.textContent = formatTime(mainVideo.duration || 0); | |
} | |
// サムネイルを表示 | |
function showThumbnail(e) { | |
if (!isPlayingSequence) return; | |
const rect = progressContainer.getBoundingClientRect(); | |
const pos = (e.clientX - rect.left) / rect.width; | |
const duration = mainVideo.duration || 1; | |
const time = pos * duration; | |
// サムネイルビデオを設定 | |
thumbnailVideo.currentTime = time; | |
// サムネイルを表示 | |
thumbnails.style.display = 'block'; | |
thumbnails.style.left = `${e.clientX - 80}px`; | |
// キャンバスを使用してサムネイルを生成 | |
const canvas = document.createElement('canvas'); | |
canvas.width = 160; | |
canvas.height = 90; | |
const ctx = canvas.getContext('2d'); | |
ctx.drawImage(thumbnailVideo, 0, 0, canvas.width, canvas.height); | |
thumbnails.innerHTML = ''; | |
thumbnails.appendChild(canvas); | |
// 時間表示を追加 | |
const timeLabel = document.createElement('div'); | |
timeLabel.textContent = formatTime(time); | |
thumbnails.appendChild(timeLabel); | |
} | |
// サムネイルを非表示 | |
function hideThumbnail() { | |
thumbnails.style.display = 'none'; | |
} | |
// 時間をフォーマット | |
function formatTime(seconds) { | |
const mins = Math.floor(seconds / 60); | |
const secs = Math.floor(seconds % 60); | |
return `${mins}:${secs < 10 ? '0' : ''}${secs}`; | |
} | |
// ビデオをシーク | |
function seekVideo(e) { | |
if (!isPlayingSequence) return; | |
const rect = progressContainer.getBoundingClientRect(); | |
const pos = (e.clientX - rect.left) / rect.width; | |
const duration = mainVideo.duration || 1; | |
mainVideo.currentTime = pos * duration; | |
} | |
</script> | |
</body> | |
</html> |