soiz1 commited on
Commit
f46e070
·
verified ·
1 Parent(s): db5f40a

Update sender.html

Browse files
Files changed (1) hide show
  1. sender.html +231 -3
sender.html CHANGED
@@ -865,7 +865,7 @@
865
  <hr>
866
 
867
  <h3>プレイヤーの使い方</h3>
868
- <p>「▶」や「⏸」ボタンで再生や一時停止ができます。「↺」で最初から再生できます。音量スライダーで音量を変更できます。スライダーで再生速度も変更できます。「⇲」で、動画を小さく表示し、他のタブに移動しながら見れるようにします。「⛶」で動画を全画面で表示できます。</p>
869
 
870
  <hr>
871
 
@@ -878,7 +878,6 @@
878
 
879
  <h3>高度な設定</h3>
880
  <ul>
881
- <li>消画モード:画面を消すことができます。</li>
882
  <li>全体音量係数:全体の音量を上げたり下げたりできます。10が最大です。</li>
883
  <li>再生速度:再生速度を変更できます。</li>
884
  <li>テンポ:テンポを入力すると、そのテンポになるように再生速度を自動調整します。</li>
@@ -908,6 +907,18 @@
908
 
909
  </script>
910
  <div class="audio-controls">
 
 
 
 
 
 
 
 
 
 
 
 
911
  <h2>音声コントロール</h2>
912
  <span>各パートの音の大きさを設定できます。自分のパートの音量を1、それ以外のパートの音量を0.3くらいにすると他のパートのタイミングを確認しながら練習できます。<br>
913
  ※「ピアノ」と「全体」は他のパートと音がずれるため、組み合わせて使用しないでください。</span>
@@ -1053,7 +1064,224 @@
1053
  <br>楽譜に記載された「♪=」の横のテンポ(時の旅人は約92、地球星歌は約66)を基準に計算します。</span>
1054
  </div>
1055
  </div>
1056
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1057
  <script>
1058
  document.addEventListener('DOMContentLoaded', function() {
1059
  // 同期管理用の変数
 
865
  <hr>
866
 
867
  <h3>プレイヤーの使い方</h3>
868
+ <p>「▶」や「⏸」ボタンで再生や一時停止ができます。「↺」で最初から再生できます。音量スライダーで音量を変更できます。スライダーで再生速度も変更できます。</p>
869
 
870
  <hr>
871
 
 
878
 
879
  <h3>高度な設定</h3>
880
  <ul>
 
881
  <li>全体音量係数:全体の音量を上げたり下げたりできます。10が最大です。</li>
882
  <li>再生速度:再生速度を変更できます。</li>
883
  <li>テンポ:テンポを入力すると、そのテンポになるように再生速度を自動調整します。</li>
 
907
 
908
  </script>
909
  <div class="audio-controls">
910
+ <details>
911
+ <summary style="font-size: 1.2em;">オフライン設定</summary>
912
+ <div class="swsettings" id="swsettings">
913
+ <a>下のボタンを押すと、サービスワーカーでページをオフラインで使用できるようにします。<br>
914
+ ブラウザの仕様により、オフラインで開く際に<b>最初のみ二回再読み込みボタンを押さなければいけません。</b><br>
915
+ 不安定な機能で、ページの更新などができなくなるバグが発生する可能性があります。</a>
916
+ <br>
917
+ <button class="combine-button" id="sw-register-btn" disabled>登録を開始</button>
918
+ <div class="combine-status" id="sw-status">
919
+ </div>
920
+ </div>
921
+ </details><br>
922
  <h2>音声コントロール</h2>
923
  <span>各パートの音の大きさを設定できます。自分のパートの音量を1、それ以外のパートの音量を0.3くらいにすると他のパートのタイミングを確認しながら練習できます。<br>
924
  ※「ピアノ」と「全体」は他のパートと音がずれるため、組み合わせて使用しないでください。</span>
 
1064
  <br>楽譜に記載された「♪=」の横のテンポ(時の旅人は約92、地球星歌は約66)を基準に計算します。</span>
1065
  </div>
1066
  </div>
1067
+ <script>
1068
+ window.addEventListener('load', async () => {
1069
+ const statusElem = document.getElementById('sw-status');
1070
+ const registerBtn = document.getElementById('sw-register-btn');
1071
+ const UPDATE_URL = '/update.txt';
1072
+ const LS_SW_VERSION = 'sw_version';
1073
+ const LS_LAST_SEEN = 'last_seen_update_txt';
1074
+ const CHECK_INTERVAL_MS = 30000;
1075
+ const SKIP_WAITING_TIMEOUT_MS = 8000;
1076
+
1077
+ const log = (v) => { console.log(v); try { if(statusElem) statusElem.textContent = String(v); } catch (e) {} };
1078
+ const err = (m, e) => { console.error(m, e); try { if(statusElem) statusElem.textContent = m + (e?.message ? (' — ' + e.message) : ''); } catch (_) {} };
1079
+
1080
+ if(!('serviceWorker' in navigator)) {
1081
+ err('このブラウザは Service Worker に対応していません。');
1082
+ if(registerBtn) registerBtn.disabled = true;
1083
+ return;
1084
+ }
1085
+ if(registerBtn) registerBtn.disabled = false;
1086
+
1087
+ // CSS
1088
+ if(!document.getElementById('sw-update-style')) {
1089
+ const style = document.createElement('style');
1090
+ style.id = 'sw-update-style';
1091
+ style.textContent = `.sw-popup{position:fixed;top:20px;right:20px;background:#fff3cd;color:#856304;border:1px solid #ffeeba;border-radius:8px;padding:16px;z-index:10000;width:320px;font-family:sans-serif}
1092
+ .sw-popup .close{position:absolute;top:6px;right:8px;background:none;border:none;font-weight:bold;cursor:pointer}
1093
+ .sw-popup .primary{display:block;margin-top:12px;padding:8px;border:none;background:#ffc107;border-radius:4px;width:100%;cursor:pointer}
1094
+ .sw-popup .secondary{display:block;margin-top:8px;padding:6px;border:1px solid #ddd;background:#fff;border-radius:4px;width:100%;cursor:pointer}`;
1095
+ document.head.appendChild(style);
1096
+ }
1097
+
1098
+ // popup queue
1099
+ const queue = [];
1100
+ let showing = false;
1101
+
1102
+ function enqueue(type, payload = {}) { queue.push({ type, payload });
1103
+ showNext(); }
1104
+
1105
+ function showNext() { if(showing || !queue.length) return; const it = queue.shift();
1106
+ showing = true;
1107
+ show(it.type, it.payload).finally(() => { showing = false;
1108
+ setTimeout(showNext, 200); }); }
1109
+
1110
+ function show(type, payload) {
1111
+ return new Promise(resolve => {
1112
+ const pop = document.createElement('div');
1113
+ pop.className = 'sw-popup';
1114
+ pop.setAttribute('data-type', type);
1115
+ let title = '更新',
1116
+ body = '更新があります。';
1117
+ if(type === 'reload') { title = 'ページ更新が必要です';
1118
+ body = 'ページが新しくなりました。再読み込みしてください。'; } else if(type === 'sw-update') { title = 'Service Worker の更新';
1119
+ body = '新しい Service Worker が利用可能です。反映するには更新してください。'; }
1120
+ pop.innerHTML = `<button class="close" aria-label="閉じる">×</button><strong>${title}</strong><p style="margin:10px 0 0">${body}</p><div><button class="primary">${payload.primaryText|| (type==='sw-update'?'サービスワーカーを更新する':'再読み込み')}</button><button class="secondary">${payload.secondaryText||'後で'}</button></div>`;
1121
+ document.body.appendChild(pop);
1122
+ pop.querySelector('.close').addEventListener('click', () => { pop.remove();
1123
+ resolve(); });
1124
+ pop.querySelector('.secondary').addEventListener('click', () => { pop.remove();
1125
+ resolve(); });
1126
+
1127
+ const primary = pop.querySelector('.primary');
1128
+
1129
+ if(type === 'reload') {
1130
+ primary.addEventListener('click', () => location.reload());
1131
+ } else if(type === 'sw-update') {
1132
+ primary.addEventListener('click', async () => {
1133
+ primary.disabled = true;
1134
+ try {
1135
+ log('Service Worker 更新処理を開始します...');
1136
+ const reg = await navigator.serviceWorker.getRegistration();
1137
+ // まず、サーバに問い合わせて sw.js を更新取得してもらう(reg.update)
1138
+ if(reg) {
1139
+ try { await reg.update();
1140
+ log('reg.update() を呼び出しました。'); } catch (e) { console.warn('reg.update() エラー', e); }
1141
+ } else {
1142
+ log('registration が見つかりません。');
1143
+ }
1144
+
1145
+ // waiting または installing を監視して installed になったら skipWaiting を送る
1146
+ let handled = false;
1147
+ const attemptHandle = async () => {
1148
+ const r = await navigator.serviceWorker.getRegistration();
1149
+ if(!r) return;
1150
+ if(r.waiting) {
1151
+ log('waiting を発見: skipWaiting を送信します。');
1152
+ r.waiting.postMessage({ type: 'SKIP_WAITING' });
1153
+ handled = true;
1154
+ // controllerchange を待つ
1155
+ navigator.serviceWorker.addEventListener('controllerchange', function oncc() {
1156
+ navigator.serviceWorker.removeEventListener('controllerchange', oncc);
1157
+ (async () => {
1158
+ // activated されてから最新の update.txt を保存
1159
+ const latest = await fetchVersion();
1160
+ if(latest) {
1161
+ localStorage.setItem(LS_SW_VERSION, latest);
1162
+ log('sw_version を保存しました: ' + latest);
1163
+ }
1164
+ setTimeout(() => location.reload(), 400);
1165
+ })();
1166
+ });
1167
+ return;
1168
+ }
1169
+ if(r.installing) {
1170
+ log('installing を監視します...');
1171
+ r.installing.addEventListener('statechange', function onsc() {
1172
+ if(r.installing.state === 'installed') {
1173
+ r.installing.removeEventListener('statechange', onsc);
1174
+ if(r.waiting) {
1175
+ r.waiting.postMessage({ type: 'SKIP_WAITING' });
1176
+ }
1177
+ }
1178
+ });
1179
+ return;
1180
+ }
1181
+ };
1182
+
1183
+ await attemptHandle();
1184
+
1185
+ // タイムアウトしてもフォールバック
1186
+ setTimeout(async () => {
1187
+ if(!handled) {
1188
+ log('waiting の応答がありません。フォールバックで update.txt を保存してリロードします。');
1189
+ const latest = await fetchVersion();
1190
+ if(latest) {
1191
+ localStorage.setItem(LS_SW_VERSION, latest);
1192
+ log('sw_version を保存しました(フォールバック): ' + latest);
1193
+ }
1194
+ location.reload();
1195
+ }
1196
+ }, SKIP_WAITING_TIMEOUT_MS);
1197
+
1198
+ } catch (e) {
1199
+ err('sw-update 処理でエラー', e);
1200
+ const latest = await fetchVersion();
1201
+ if(latest) localStorage.setItem(LS_SW_VERSION, latest);
1202
+ location.reload();
1203
+ }
1204
+ });
1205
+ } else {
1206
+ primary.addEventListener('click', () => { pop.remove();
1207
+ resolve(); });
1208
+ }
1209
+ });
1210
+ }
1211
+
1212
+ // fetch update.txt
1213
+ async function fetchVersion() {
1214
+ try {
1215
+ const r = await fetch(UPDATE_URL + '?t=' + Date.now(), { cache: 'no-store' });
1216
+ if(r.ok) return (await r.text()).trim();
1217
+ } catch (e) { console.warn('update.txt fetch failed', e); }
1218
+ return null;
1219
+ }
1220
+
1221
+ // メインロジック
1222
+ let lastSeen = localStorage.getItem(LS_LAST_SEEN) || null;
1223
+
1224
+ async function check() {
1225
+ const latest = await fetchVersion();
1226
+ if(!latest) return;
1227
+ // ページの「最後に見たupdate.txt」と違えば reload を提示
1228
+ if(lastSeen && latest !== lastSeen) {
1229
+ enqueue('reload');
1230
+ }
1231
+ lastSeen = latest;
1232
+ localStorage.setItem(LS_LAST_SEEN, latest);
1233
+
1234
+ // sw_version と最新が違えば sw-update を提示(controller に依存しない)
1235
+ const swv = localStorage.getItem(LS_SW_VERSION);
1236
+ if(swv && swv !== latest) {
1237
+ enqueue('sw-update');
1238
+ }
1239
+ }
1240
+
1241
+ // 初回
1242
+ (async () => {
1243
+ const latest = await fetchVersion();
1244
+ if(latest) {
1245
+ if(!lastSeen) {
1246
+ lastSeen = latest;
1247
+ localStorage.setItem(LS_LAST_SEEN, latest);
1248
+ }
1249
+ const swv = localStorage.getItem(LS_SW_VERSION);
1250
+ if(swv && swv !== latest) enqueue('sw-update');
1251
+ }
1252
+ setInterval(check, CHECK_INTERVAL_MS);
1253
+ })();
1254
+
1255
+ // register ボタンを押したら sw を登録して sw_version を保存
1256
+ if(registerBtn) {
1257
+ registerBtn.addEventListener('click', async () => {
1258
+ registerBtn.disabled = true;
1259
+ try {
1260
+ log('既存の Service Worker を unregister...');
1261
+ const regs = await navigator.serviceWorker.getRegistrations();
1262
+ for(const r of regs) await r.unregister();
1263
+ log('registering...');
1264
+ await navigator.serviceWorker.register('/sw.js');
1265
+ const newv = await fetchVersion();
1266
+ if(newv) {
1267
+ localStorage.setItem(LS_SW_VERSION, newv);
1268
+ log('sw_version を保存: ' + newv);
1269
+ } else {
1270
+ log('update.txt を取得できませんでし��(sw_version 保存不可)。');
1271
+ }
1272
+ registerBtn.textContent = 'ページをリロード';
1273
+ registerBtn.disabled = false;
1274
+ registerBtn.onclick = () => location.reload();
1275
+ } catch (e) {
1276
+ err('登録失敗', e);
1277
+ registerBtn.disabled = false;
1278
+ }
1279
+ });
1280
+ }
1281
+
1282
+ });
1283
+
1284
+ </script>
1285
  <script>
1286
  document.addEventListener('DOMContentLoaded', function() {
1287
  // 同期管理用の変数