sonygod commited on
Commit
a068a69
·
1 Parent(s): e82d4dc

添加语音播放

Browse files
Files changed (1) hide show
  1. youtube_sub.js +172 -12
youtube_sub.js CHANGED
@@ -23,6 +23,11 @@ GM_addStyle(`
23
  (function () {
24
  'use strict';
25
 
 
 
 
 
 
26
  // 增强的样式定义,添加过渡效果
27
  const styles = `
28
  .subtitle-manager {
@@ -322,7 +327,7 @@ GM_addStyle(`
322
  opacity: 1;
323
  }
324
 
325
- .word-definition-popup {
326
  position: fixed;
327
  right: -400px;
328
  top: 50%;
@@ -336,6 +341,8 @@ GM_addStyle(`
336
  z-index: 10000;
337
  max-height: 80vh;
338
  overflow-y: auto;
 
 
339
  }
340
 
341
  .word-definition-popup.show {
@@ -343,9 +350,33 @@ GM_addStyle(`
343
  }
344
 
345
  .word-definition-popup .markdown {
346
- font-size: 14px;
 
347
  line-height: 1.6;
348
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
 
350
  .mixed-text-container {
351
  display: flex;
@@ -375,6 +406,69 @@ GM_addStyle(`
375
  border-top: 1px solid rgba(255,255,255,0.1);
376
  padding-top: 8px;
377
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  `;
379
 
380
  // Add popup HTML
@@ -422,8 +516,44 @@ GM_addStyle(`
422
  this.container = null;
423
  this.currentTime = 0;
424
  this.isCollapsed = false;
 
425
  this.setupUI();
426
  this.setupEventListeners();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  }
428
 
429
  checkCaptionSources(player) {
@@ -806,13 +936,15 @@ GM_addStyle(`
806
  wordBtn.onclick = async (e) => {
807
  e.stopPropagation();
808
  console.log(`You click "${word.text.trim()}" word`);
809
-
810
  let popup = document.querySelector('.word-definition-popup');
811
  if (!popup) {
812
  // Create popup container
813
  popup = document.createElement('div');
814
  popup.className = 'word-definition-popup';
815
 
 
 
816
  // Create markdown container
817
  const markdownDiv = document.createElement('div');
818
  markdownDiv.className = 'markdown';
@@ -826,6 +958,30 @@ GM_addStyle(`
826
  popup.classList.remove('show');
827
  });
828
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
829
 
830
  // Ensure markdown container exists
831
  let markdownContainer = popup.querySelector('.markdown');
@@ -968,15 +1124,15 @@ GM_addStyle(`
968
  this.subtitles.english?.length || 0,
969
  this.subtitles.chinese?.length || 0
970
  );
971
-
972
  for (let i = 0; i < maxLength; i++) {
973
  const line = document.createElement('div');
974
  line.className = 'subtitle-line mixed-line';
975
-
976
  // Text container
977
  const textContainer = document.createElement('div');
978
  textContainer.className = 'mixed-text-container';
979
-
980
  // Chinese text with word buttons
981
  const zhText = document.createElement('div');
982
  zhText.className = 'zh-text';
@@ -995,7 +1151,7 @@ GM_addStyle(`
995
  } else {
996
  zhText.textContent = '翻译中...';
997
  }
998
-
999
  // English text with word buttons
1000
  const enText = document.createElement('div');
1001
  enText.className = 'en-text';
@@ -1012,23 +1168,23 @@ GM_addStyle(`
1012
  }
1013
  });
1014
  }
1015
-
1016
  textContainer.appendChild(zhText);
1017
  textContainer.appendChild(enText);
1018
  line.appendChild(textContainer);
1019
-
1020
  // Add timestamp
1021
  const timestamp = document.createElement('span');
1022
  timestamp.className = 'timestamp';
1023
- timestamp.textContent = this.subtitles.english[i] ?
1024
  this.formatTime(this.subtitles.english[i].startTime) : '';
1025
  line.appendChild(timestamp);
1026
-
1027
  if (this.subtitles.english[i]) {
1028
  line.dataset.time = this.subtitles.english[i].startTime;
1029
  line.dataset.index = i;
1030
  }
1031
-
1032
  container.appendChild(line);
1033
  }
1034
  }
@@ -1591,6 +1747,10 @@ GM_addStyle(`
1591
  debug.log('Current URL:', window.location.href);
1592
  debug.log('Document ready state:', document.readyState);
1593
 
 
 
 
 
1594
  if (!window.location.pathname.includes('/watch')) {
1595
  debug.log('Not a video page, skipping');
1596
  return;
 
23
  (function () {
24
  'use strict';
25
 
26
+ // Singleton check
27
+ if (window.subtitleManagerInstance) {
28
+ window.subtitleManagerInstance.cleanup();
29
+ }
30
+
31
  // 增强的样式定义,添加过渡效果
32
  const styles = `
33
  .subtitle-manager {
 
327
  opacity: 1;
328
  }
329
 
330
+ .word-definition-popup {
331
  position: fixed;
332
  right: -400px;
333
  top: 50%;
 
341
  z-index: 10000;
342
  max-height: 80vh;
343
  overflow-y: auto;
344
+ font-size: 15px;
345
+ line-height: 1.6;
346
  }
347
 
348
  .word-definition-popup.show {
 
350
  }
351
 
352
  .word-definition-popup .markdown {
353
+ padding-top: 50px;
354
+ font-size: 15px;
355
  line-height: 1.6;
356
  }
357
+ .word-definition-popup .markdown p {
358
+ margin: 12px 0;
359
+ }
360
+
361
+ .word-definition-popup .markdown strong {
362
+ color: #4CAF50;
363
+ font-weight: 600;
364
+ }
365
+
366
+ .word-definition-popup .markdown em {
367
+ color: #FFC107;
368
+ font-style: normal;
369
+ }
370
+
371
+ .word-definition-popup[data-theme="light"] {
372
+ background: rgba(255, 255, 255, 0.95);
373
+ color: #333;
374
+ }
375
+
376
+ .word-definition-popup[data-theme="light"] .speaker-btn {
377
+ background: rgba(0, 0, 0, 0.1);
378
+ color: #333;
379
+ }
380
 
381
  .mixed-text-container {
382
  display: flex;
 
406
  border-top: 1px solid rgba(255,255,255,0.1);
407
  padding-top: 8px;
408
  }
409
+
410
+
411
+ @media (max-width: 768px), (orientation: portrait) {
412
+ .subtitle-manager {
413
+ position: relative;
414
+ right: auto;
415
+ top: auto;
416
+ width: 100%;
417
+ height: 50vh;
418
+ margin-top: 10px;
419
+ margin-top: 400px; /* Move down below video */
420
+ background: rgba(33, 33, 33, 0.98); /* Slightly more opaque for mobile */
421
+ }
422
+
423
+ .subtitle-manager.collapsed {
424
+ transform: translateY(calc(100% - 40px));
425
+ margin-top: 400px; /* Keep margin when collapsed */
426
+ }
427
+
428
+ .subtitle-toggle {
429
+ top: -30px;
430
+ left: 10px;
431
+ transform: rotate(90deg);
432
+ }
433
+
434
+ .theme-toggle {
435
+ top: -30px;
436
+ right: 10px;
437
+ }
438
+
439
+ .subtitle-line {
440
+ width: calc(100% - 30px);
441
+ max-width: none;
442
+ }
443
+ }
444
+
445
+
446
+ .word-definition-popup .speaker-btn {
447
+ position: absolute;
448
+ top: 15px;
449
+ right: 15px;
450
+ width: 36px;
451
+ height: 36px;
452
+ background: rgba(255, 255, 255, 0.1);
453
+ border: none;
454
+ border-radius: 50%;
455
+ color: white;
456
+ cursor: pointer;
457
+ font-size: 20px;
458
+ display: flex;
459
+ align-items: center;
460
+ justify-content: center;
461
+ transition: all 0.2s ease;
462
+ }
463
+
464
+ .word-definition-popup .speaker-btn:hover {
465
+ background: rgba(255, 255, 255, 0.1);
466
+ transform: scale(1.1);
467
+ }
468
+
469
+ .word-definition-popup[data-theme="light"] .speaker-btn {
470
+ color: #333;
471
+ }
472
  `;
473
 
474
  // Add popup HTML
 
516
  this.container = null;
517
  this.currentTime = 0;
518
  this.isCollapsed = false;
519
+ this.isMobile = window.innerWidth <= 768 || window.matchMedia('(orientation: portrait)').matches;
520
  this.setupUI();
521
  this.setupEventListeners();
522
+ window.subtitleManagerInstance = this;
523
+ }
524
+ cleanup() {
525
+ // Remove existing instance
526
+ if (this.container) {
527
+ this.container.remove();
528
+ }
529
+
530
+ // Clear any event listeners
531
+ window.removeEventListener('resize', this.resizeHandler);
532
+ }
533
+
534
+ setupResizeHandler() {
535
+ this.resizeHandler = () => {
536
+ const isMobile = window.innerWidth <= 768 || window.matchMedia('(orientation: portrait)').matches;
537
+ if (this.isMobile !== isMobile) {
538
+ this.isMobile = isMobile;
539
+ this.updateLayout();
540
+ }
541
+ };
542
+
543
+ window.addEventListener('resize', this.resizeHandler);
544
+ }
545
+
546
+ updateLayout() {
547
+ if (this.isMobile) {
548
+ // Insert after video player
549
+ const player = document.querySelector('.html5-video-player');
550
+ if (player?.parentNode) {
551
+ player.parentNode.insertBefore(this.container, player.nextSibling);
552
+ }
553
+ } else {
554
+ // Move back to body for desktop view
555
+ document.body.appendChild(this.container);
556
+ }
557
  }
558
 
559
  checkCaptionSources(player) {
 
936
  wordBtn.onclick = async (e) => {
937
  e.stopPropagation();
938
  console.log(`You click "${word.text.trim()}" word`);
939
+ const wordText = word.text.trim();
940
  let popup = document.querySelector('.word-definition-popup');
941
  if (!popup) {
942
  // Create popup container
943
  popup = document.createElement('div');
944
  popup.className = 'word-definition-popup';
945
 
946
+
947
+
948
  // Create markdown container
949
  const markdownDiv = document.createElement('div');
950
  markdownDiv.className = 'markdown';
 
958
  popup.classList.remove('show');
959
  });
960
  }
961
+ // Update speaker button for current word
962
+ let speakerBtn = popup.querySelector('.speaker-btn');
963
+ if (speakerBtn) {
964
+ speakerBtn.remove();
965
+ }
966
+
967
+ // Create new speaker button with current word
968
+ speakerBtn = document.createElement('button');
969
+ speakerBtn.className = 'speaker-btn';
970
+ speakerBtn.textContent = '🔊';
971
+ speakerBtn.onclick = (e) => {
972
+ e.stopPropagation();
973
+ try {
974
+ const utterance = new SpeechSynthesisUtterance(wordText); // Use current wordText
975
+ utterance.lang = 'en-US';
976
+ utterance.rate = 0.9;
977
+ speechSynthesis.speak(utterance);
978
+ } catch (error) {
979
+ console.error('TTS failed:', error);
980
+ }
981
+ };
982
+
983
+ // Insert new speaker button at the start
984
+ popup.insertBefore(speakerBtn, popup.firstChild);
985
 
986
  // Ensure markdown container exists
987
  let markdownContainer = popup.querySelector('.markdown');
 
1124
  this.subtitles.english?.length || 0,
1125
  this.subtitles.chinese?.length || 0
1126
  );
1127
+
1128
  for (let i = 0; i < maxLength; i++) {
1129
  const line = document.createElement('div');
1130
  line.className = 'subtitle-line mixed-line';
1131
+
1132
  // Text container
1133
  const textContainer = document.createElement('div');
1134
  textContainer.className = 'mixed-text-container';
1135
+
1136
  // Chinese text with word buttons
1137
  const zhText = document.createElement('div');
1138
  zhText.className = 'zh-text';
 
1151
  } else {
1152
  zhText.textContent = '翻译中...';
1153
  }
1154
+
1155
  // English text with word buttons
1156
  const enText = document.createElement('div');
1157
  enText.className = 'en-text';
 
1168
  }
1169
  });
1170
  }
1171
+
1172
  textContainer.appendChild(zhText);
1173
  textContainer.appendChild(enText);
1174
  line.appendChild(textContainer);
1175
+
1176
  // Add timestamp
1177
  const timestamp = document.createElement('span');
1178
  timestamp.className = 'timestamp';
1179
+ timestamp.textContent = this.subtitles.english[i] ?
1180
  this.formatTime(this.subtitles.english[i].startTime) : '';
1181
  line.appendChild(timestamp);
1182
+
1183
  if (this.subtitles.english[i]) {
1184
  line.dataset.time = this.subtitles.english[i].startTime;
1185
  line.dataset.index = i;
1186
  }
1187
+
1188
  container.appendChild(line);
1189
  }
1190
  }
 
1747
  debug.log('Current URL:', window.location.href);
1748
  debug.log('Document ready state:', document.readyState);
1749
 
1750
+ // Cleanup existing instance
1751
+ if (window.subtitleManagerInstance) {
1752
+ window.subtitleManagerInstance.cleanup();
1753
+ }
1754
  if (!window.location.pathname.includes('/watch')) {
1755
  debug.log('Not a video page, skipping');
1756
  return;