|
1 | 1 | import {Localize} from '../../modules/Localize.mjs'; |
2 | 2 | import {EventEmitter} from '../../modules/eventemitter.mjs'; |
3 | 3 | import {WebVTT} from '../../modules/vtt.mjs'; |
| 4 | +import {AlertPolyfill} from '../../utils/AlertPolyfill.mjs'; |
4 | 5 | import {WebUtils} from '../../utils/WebUtils.mjs'; |
5 | 6 | import {DOMElements} from '../DOMElements.mjs'; |
6 | 7 |
|
@@ -31,27 +32,132 @@ export class SubtitleSyncer extends EventEmitter { |
31 | 32 | // track line is grabbable |
32 | 33 | let isGrabbingTrack = false; |
33 | 34 | let grabStartTrack = 0; |
| 35 | + let grabbedCue = null; |
| 36 | + let grabbedEdge = null; |
| 37 | + |
34 | 38 |
|
35 | 39 | this.ui.timelineTrack.addEventListener('mousedown', (e) => { |
36 | 40 | isGrabbingTrack = true; |
37 | 41 | grabStartTrack = e.clientX; |
| 42 | + if (window.subEditMode && !e.shiftKey) { |
| 43 | + const grabbed = this.trackElements.find((el) => { |
| 44 | + const rect = el.element.getBoundingClientRect(); |
| 45 | + return e.clientX >= rect.left && e.clientX <= rect.right; |
| 46 | + }) || null; |
| 47 | + // check if grabbing right edge to resize cue |
| 48 | + if (grabbed) { |
| 49 | + const rect = grabbed.element.getBoundingClientRect(); |
| 50 | + if (e.clientX >= rect.right - 5 && e.clientX <= rect.right + 5) { |
| 51 | + grabbedEdge = 'right'; |
| 52 | + } else { |
| 53 | + grabbedEdge = null; |
| 54 | + } |
| 55 | + |
| 56 | + grabbedCue = grabbed.cue; |
| 57 | + } else { |
| 58 | + grabbedCue = null; |
| 59 | + } |
| 60 | + } else { |
| 61 | + grabbedCue = null; |
| 62 | + grabbedEdge = null; |
| 63 | + } |
38 | 64 | }); |
39 | 65 |
|
40 | | - DOMElements.playerContainer.addEventListener('mouseup', () => { |
41 | | - isGrabbingTrack = false; |
| 66 | + // double click to edit |
| 67 | + this.ui.timelineTrack.addEventListener('dblclick', (e) => { |
| 68 | + if (!window.subEditMode) return; |
| 69 | + const time = this.client.interfaceController.fineTimeControls.mousePositionToTime(e.clientX); |
| 70 | + const cue = this.trackToSync.cues.find((c) => { |
| 71 | + return time >= c.startTime && time <= c.endTime; |
| 72 | + }); |
| 73 | + if (cue) { |
| 74 | + AlertPolyfill.prompt('Edit subtitle text', cue.text).then((newText) => { |
| 75 | + if (newText) { |
| 76 | + cue.text = newText; |
| 77 | + cue.dom2 = null; // reset cached DOM tree so it will be regenerated with new text |
| 78 | + cue.dom = null; // reset cached DOM tree so it will be regenerated with new text |
| 79 | + // update element |
| 80 | + const el = this.trackElements.find((el) => el.cue === cue); |
| 81 | + if (el) { |
| 82 | + el.element.replaceChildren(); |
| 83 | + if (!cue.dom2) { |
| 84 | + cue.dom2 = WebVTT.convertCueToDOMTree(window, cue.text); |
| 85 | + } |
| 86 | + el.element.appendChild(cue.dom2); |
| 87 | + el.element.title = cue.text; |
| 88 | + } |
| 89 | + } else { |
| 90 | + // if text is empty, remove cue |
| 91 | + const index = this.trackToSync.cues.indexOf(cue); |
| 92 | + if (index !== -1) { |
| 93 | + this.trackToSync.cues.splice(index, 1); |
| 94 | + } |
| 95 | + } |
| 96 | + }); |
| 97 | + } else { |
| 98 | + // create new cue at this time with default duration of 2 seconds |
| 99 | + const newCue = new VTTCue(time, time + 2, 'New subtitle'); |
| 100 | + this.trackToSync.cues.push(newCue); |
| 101 | + // sort cues by start time |
| 102 | + this.trackToSync.cues.sort((a, b) => a.startTime - b.startTime); |
| 103 | + this.client.interfaceController.subtitlesManager.renderSubtitles(); |
| 104 | + AlertPolyfill.prompt('Edit subtitle text', newCue.text).then((newText) => { |
| 105 | + if (newText) { |
| 106 | + newCue.text = newText; |
| 107 | + newCue.dom2 = null; // reset cached DOM tree so it will be regenerated with new text |
| 108 | + newCue.dom = null; // reset cached DOM tree so it will be regenerated with new text |
| 109 | + const el = this.trackElements.find((el) => el.cue === newCue); |
| 110 | + if (el) { |
| 111 | + el.element.replaceChildren(); |
| 112 | + if (!newCue.dom2) { |
| 113 | + newCue.dom2 = WebVTT.convertCueToDOMTree(window, newCue.text); |
| 114 | + } |
| 115 | + el.element.appendChild(newCue.dom2); |
| 116 | + el.element.title = newCue.text; |
| 117 | + } |
| 118 | + this.client.interfaceController.subtitlesManager.renderSubtitles(); |
| 119 | + } |
| 120 | + }); |
| 121 | + } |
42 | 122 | }); |
43 | 123 |
|
44 | | - DOMElements.playerContainer.addEventListener('mouseleave', () => { |
| 124 | + const clearGrabbing = () => { |
| 125 | + // if cue was grabbed then resort |
| 126 | + if (grabbedCue) { |
| 127 | + this.trackToSync.cues.sort((a, b) => a.startTime - b.startTime); |
| 128 | + } |
| 129 | + |
45 | 130 | isGrabbingTrack = false; |
46 | | - }); |
| 131 | + grabbedCue = null; |
| 132 | + grabbedEdge = null; |
| 133 | + }; |
| 134 | + |
| 135 | + DOMElements.playerContainer.addEventListener('mouseup', clearGrabbing); |
| 136 | + |
| 137 | + DOMElements.playerContainer.addEventListener('mouseleave', clearGrabbing); |
47 | 138 |
|
48 | 139 | DOMElements.playerContainer.addEventListener('mousemove', (e) => { |
49 | 140 | if (!this.client.player) return; |
50 | 141 | const video = this.client.player.getVideo(); |
51 | 142 | if (isGrabbingTrack) { |
52 | 143 | const delta = e.clientX - grabStartTrack; |
53 | 144 | grabStartTrack = e.clientX; |
54 | | - this.trackToSync.shift(delta / this.ui.timelineTrack.clientWidth * video.duration); |
| 145 | + const amount = delta / this.ui.timelineTrack.clientWidth * video.duration; |
| 146 | + if (grabbedCue) { |
| 147 | + if (grabbedEdge === 'right') { |
| 148 | + grabbedCue.endTime += amount; |
| 149 | + // update element |
| 150 | + const el = this.trackElements.find((el) => el.cue === grabbedCue); |
| 151 | + if (el) { |
| 152 | + el.element.style.width = (grabbedCue.endTime - grabbedCue.startTime) / video.duration * 100 + '%'; |
| 153 | + } |
| 154 | + } else { |
| 155 | + this.trackToSync.shiftAfter(grabbedCue, amount); |
| 156 | + } |
| 157 | + } else { |
| 158 | + if (window.subEditMode) return; |
| 159 | + this.trackToSync.shift(amount); |
| 160 | + } |
55 | 161 | this.client.interfaceController.subtitlesManager.renderSubtitles(); |
56 | 162 | } |
57 | 163 | }); |
|
0 commit comments