1-
21import { playbackManager } from '../playback/playbackmanager' ;
32import layoutManager from '../layoutManager' ;
43import template from './subtitlesync.template.html' ;
54import './subtitlesync.scss' ;
65
7- let player ;
8- let subtitleSyncSlider ;
9- let subtitleSyncTextField ;
10- let subtitleSyncCloseButton ;
11- let subtitleSyncContainer ;
12-
13- function init ( instance ) {
14- const parent = document . createElement ( 'div' ) ;
15- document . body . appendChild ( parent ) ;
16- parent . innerHTML = template ;
17-
18- subtitleSyncSlider = parent . querySelector ( '.subtitleSyncSlider' ) ;
19- subtitleSyncTextField = parent . querySelector ( '.subtitleSyncTextField' ) ;
20- subtitleSyncCloseButton = parent . querySelector ( '.subtitleSync-closeButton' ) ;
21- subtitleSyncContainer = parent . querySelector ( '.subtitleSyncContainer' ) ;
22-
23- if ( layoutManager . tv ) {
24- subtitleSyncSlider . classList . add ( 'focusable' ) ;
25- // HACK: Delay to give time for registered element attach (Firefox)
26- setTimeout ( function ( ) {
27- subtitleSyncSlider . enableKeyboardDragging ( ) ;
28- } , 0 ) ;
29- }
30-
31- subtitleSyncContainer . classList . add ( 'hide' ) ;
32-
33- subtitleSyncTextField . updateOffset = function ( offset ) {
34- this . textContent = offset + 's' ;
35- } ;
36-
37- subtitleSyncTextField . addEventListener ( 'click' , function ( ) {
38- // keep focus to prevent fade with osd
39- this . hasFocus = true ;
40- } ) ;
41-
42- subtitleSyncTextField . addEventListener ( 'keydown' , function ( event ) {
43- if ( event . key === 'Enter' ) {
44- // if input key is enter search for float pattern
45- let inputOffset = / [ - + ] ? \d + \. ? \d * / g. exec ( this . textContent ) ;
46- if ( inputOffset ) {
47- inputOffset = inputOffset [ 0 ] ;
48- inputOffset = parseFloat ( inputOffset ) ;
49-
50- subtitleSyncSlider . updateOffset ( inputOffset ) ;
51- } else {
52- this . textContent = ( playbackManager . getPlayerSubtitleOffset ( player ) || 0 ) + 's' ;
53- }
54- this . hasFocus = false ;
55- event . preventDefault ( ) ;
56- } else {
57- // keep focus to prevent fade with osd
58- this . hasFocus = true ;
59- if ( event . key . match ( / [ + - \d . s ] / ) === null ) {
60- event . preventDefault ( ) ;
61- }
62- }
6+ // Constants
7+ const DEFAULT_OFFSET = 0 ;
8+
9+ function createSliderBubbleHtml ( _ , value ) {
10+ return '<h1 class="sliderBubbleText">'
11+ + ( value > 0 ? '+' : '' ) + Number . parseFloat ( value ) + 's'
12+ + '</h1>' ;
13+ }
14+
15+ class OffsetController {
16+ constructor ( player , slider , textField ) {
17+ this . player = player ;
18+ this . slider = slider ;
19+ this . textField = textField ;
20+
21+ this . #initSlider( ) ;
22+ this . #initTextField( ) ;
23+
24+ // Set initial offset
25+ this . reset ( ) ;
26+ }
6327
64- // FIXME: TV layout will require special handling for navigation keys. But now field is not focusable
65- event . stopPropagation ( ) ;
66- } ) ;
28+ get currentOffset ( ) {
29+ return Number . parseFloat ( this . slider . value ) ;
30+ }
31+
32+ set currentOffset ( value ) {
33+ this . slider . value = value . toString ( ) ;
6734
68- subtitleSyncTextField . blur = function ( ) {
69- // prevent textfield to blur while element has focus
70- if ( ! this . hasFocus && this . prototype ) {
71- this . prototype . blur ( ) ;
35+ // rely on slider value trimming
36+ value = this . currentOffset ;
37+
38+ playbackManager . setSubtitleOffset ( value , this . player ) ;
39+ this . textField . updateOffset ( value ) ;
40+ }
41+
42+ #initSlider( ) {
43+ const slider = this . slider ;
44+
45+ if ( layoutManager . tv ) {
46+ slider . classList . add ( 'focusable' ) ;
47+ // Delay required for Firefox – wait for custom element to attach
48+ setTimeout ( ( ) => slider . enableKeyboardDragging ( ) , 0 ) ;
7249 }
73- } ;
7450
75- function updateSubtitleOffset ( ) {
76- const value = parseFloat ( subtitleSyncSlider . value ) ;
77- // set new offset
78- playbackManager . setSubtitleOffset ( value , player ) ;
79- // synchronize with textField value
80- subtitleSyncTextField . updateOffset ( value ) ;
51+ // When slider changes we assign the value to the currentOffset to trigger the setter
52+ slider . addEventListener ( 'change' , ( ) => {
53+ // eslint-disable-next-line no-self-assign
54+ this . currentOffset = this . currentOffset ;
55+ } ) ;
56+
57+ slider . getBubbleHtml = createSliderBubbleHtml ;
8158 }
8259
83- subtitleSyncSlider . updateOffset = function ( sliderValue ) {
84- // default value is 0s = 0ms
85- this . value = sliderValue === undefined ? 0 : sliderValue ;
60+ #initTextField( ) {
61+ const textField = this . textField ;
8662
87- updateSubtitleOffset ( ) ;
88- } ;
63+ textField . updateOffset = ( offset ) => {
64+ textField . textContent = offset + 's' ;
65+ } ;
66+
67+ textField . addEventListener ( 'click' , ( ) => {
68+ // keep focus to prevent fade with osd
69+ textField . hasFocus = true ;
70+ } ) ;
71+
72+ textField . addEventListener ( 'keydown' , ( event ) => {
73+ if ( event . key === 'Enter' ) {
74+ // if input key is enter search for float pattern
75+ let inputOffset = / [ - + ] ? \d + \. ? \d * / g. exec ( textField . textContent ) ;
76+ if ( inputOffset ) {
77+ inputOffset = Number . parseFloat ( inputOffset [ 0 ] ) ;
78+ this . currentOffset = inputOffset ;
79+ } else {
80+ textField . updateOffset ( playbackManager . getPlayerSubtitleOffset ( this . player ) || 0 ) ;
81+ }
82+ textField . hasFocus = false ;
83+ event . preventDefault ( ) ;
84+ } else {
85+ // keep focus to prevent fade with osd
86+ textField . hasFocus = true ;
87+ if ( event . key . match ( / [ + - \d . s ] / ) === null ) {
88+ event . preventDefault ( ) ;
89+ }
90+ }
8991
90- subtitleSyncSlider . addEventListener ( 'change' , ( ) => updateSubtitleOffset ( ) ) ;
92+ // TV layout will require special handling for navigation keys. But now field is not focusable
93+ event . stopPropagation ( ) ;
94+ } ) ;
9195
92- subtitleSyncSlider . getBubbleHtml = function ( _ , value ) {
93- return '<h1 class="sliderBubbleText">'
94- + ( value > 0 ? '+' : '' ) + parseFloat ( value ) + 's'
95- + '</h1>' ;
96- } ;
96+ // Preserve native blur while respecting the custom focus flag
97+ const originalBlur = textField . blur . bind ( textField ) ;
98+ textField . blur = function ( ) {
99+ if ( ! this . hasFocus ) {
100+ originalBlur ( ) ;
101+ }
102+ } ;
103+ }
97104
98- subtitleSyncCloseButton . addEventListener ( 'click' , function ( ) {
99- playbackManager . disableShowingSubtitleOffset ( player ) ;
100- SubtitleSync . prototype . toggle ( 'forceToHide' ) ;
101- } ) ;
105+ adjustOffset ( delta ) {
106+ this . currentOffset = this . currentOffset + delta ;
107+ }
102108
103- instance . element = parent ;
109+ reset ( ) {
110+ this . currentOffset = DEFAULT_OFFSET ;
111+ }
104112}
105113
106114class SubtitleSync {
107115 constructor ( currentPlayer ) {
108- player = currentPlayer ;
109- init ( this ) ;
116+ this . player = currentPlayer ;
117+ this . #initUI( ) ;
118+
119+ // Create the offset controller
120+ this . offsetController = new OffsetController (
121+ this . player ,
122+ this . subtitleSyncSlider ,
123+ this . subtitleSyncTextField
124+ ) ;
110125 }
111126
112127 destroy ( ) {
113- SubtitleSync . prototype . toggle ( 'forceToHide' ) ;
114- if ( player ) {
115- playbackManager . disableShowingSubtitleOffset ( player ) ;
116- playbackManager . setSubtitleOffset ( 0 , player ) ;
128+ this . toggle ( 'forceToHide' ) ;
129+ if ( this . player ) {
130+ playbackManager . disableShowingSubtitleOffset ( this . player ) ;
131+ this . offsetController . reset ( ) ;
117132 }
118- const elem = this . element ;
119- if ( elem ) {
120- elem . parentNode . removeChild ( elem ) ;
133+
134+ if ( this . element ) {
135+ this . element . remove ( ) ;
121136 this . element = null ;
122137 }
138+
139+ this . player = null ;
123140 }
124141
125142 toggle ( action ) {
@@ -128,43 +145,75 @@ class SubtitleSync {
128145 return ;
129146 }
130147
131- if ( player && playbackManager . supportSubtitleOffset ( player ) ) {
132- if ( ! action ) {
133- // if showing subtitle sync is enabled and if there is an external subtitle stream enabled
134- if ( playbackManager . isShowingSubtitleOffsetEnabled ( player ) && playbackManager . canHandleOffsetOnCurrentSubtitle ( player ) ) {
135- // if no subtitle offset is defined or element has focus (offset being defined)
136- if ( ! ( playbackManager . getPlayerSubtitleOffset ( player ) || subtitleSyncTextField . hasFocus ) ) {
137- // set default offset to '0' = 0ms
138- subtitleSyncSlider . value = '0' ;
139- subtitleSyncTextField . textContent = '0s' ;
140- playbackManager . setSubtitleOffset ( 0 , player ) ;
141- }
142- // show subtitle sync
143- subtitleSyncContainer . classList . remove ( 'hide' ) ;
144- return ;
145- }
146- } else if ( action === 'hide' && subtitleSyncTextField . hasFocus ) {
147- // do not hide if element has focus
148- return ;
149- }
148+ if ( ! this . player || ! playbackManager . supportSubtitleOffset ( this . player ) ) {
149+ return ;
150+ }
150151
151- subtitleSyncContainer . classList . add ( 'hide' ) ;
152+ if ( ! action ) {
153+ this . #tryShowSubtitleSync( ) ;
154+ } else if ( action === 'hide' && this . subtitleSyncTextField . hasFocus ) {
155+ // do not hide if element has focus
156+ return ;
157+ } else {
158+ this . subtitleSyncContainer . classList . add ( 'hide' ) ;
152159 }
153160 }
154161
155- update ( offset ) {
156- this . toggle ( ) ;
162+ #initUI( ) {
163+ const parent = document . createElement ( 'div' ) ;
164+ document . body . appendChild ( parent ) ;
165+ parent . innerHTML = template ;
166+
167+ // Store DOM elements
168+ this . element = parent ;
169+ this . subtitleSyncSlider = parent . querySelector ( '.subtitleSyncSlider' ) ;
170+ this . subtitleSyncTextField = parent . querySelector ( '.subtitleSyncTextField' ) ;
171+ this . subtitleSyncCloseButton = parent . querySelector ( '.subtitleSync-closeButton' ) ;
172+ this . subtitleSyncContainer = parent . querySelector ( '.subtitleSyncContainer' ) ;
157173
158- const value = parseFloat ( subtitleSyncSlider . value ) + offset ;
159- subtitleSyncSlider . updateOffset ( value ) ;
174+ this . #setupCloseButton( ) ;
175+
176+ // Initially hide the container
177+ this . subtitleSyncContainer . classList . add ( 'hide' ) ;
178+ }
179+
180+ #setupCloseButton( ) {
181+ this . subtitleSyncCloseButton . addEventListener ( 'click' , ( ) => {
182+ playbackManager . disableShowingSubtitleOffset ( this . player ) ;
183+ this . toggle ( 'forceToHide' ) ;
184+ } ) ;
185+ }
186+
187+ #tryShowSubtitleSync( ) {
188+ // if showing subtitle sync is enabled and if there is an external subtitle stream enabled
189+ if ( ! this . #canShowSubtitleSync( ) ) {
190+ this . subtitleSyncContainer . classList . add ( 'hide' ) ;
191+ return ;
192+ }
193+
194+ // If no subtitle offset is defined or element has focus (offset being defined)
195+ if ( ! ( playbackManager . getPlayerSubtitleOffset ( this . player ) || this . subtitleSyncTextField . hasFocus ) ) {
196+ // reset to default offset
197+ this . offsetController . reset ( ) ;
198+ }
199+
200+ // show subtitle sync
201+ this . subtitleSyncContainer . classList . remove ( 'hide' ) ;
202+ }
203+
204+ #canShowSubtitleSync( ) {
205+ return playbackManager . isShowingSubtitleOffsetEnabled ( this . player )
206+ && playbackManager . canHandleOffsetOnCurrentSubtitle ( this . player ) ;
160207 }
161208
162209 incrementOffset ( ) {
163- this . update ( + subtitleSyncSlider . step ) ;
210+ this . toggle ( ) ;
211+ this . offsetController . adjustOffset ( + this . subtitleSyncSlider . step ) ;
164212 }
165213
166214 decrementOffset ( ) {
167- this . update ( - subtitleSyncSlider . step ) ;
215+ this . toggle ( ) ;
216+ this . offsetController . adjustOffset ( - this . subtitleSyncSlider . step ) ;
168217 }
169218}
170219
0 commit comments