@@ -10,11 +10,13 @@ const YT_SELECTORS = {
10
10
thumbnail : '.ytp-cued-thumbnail-overlay-image' ,
11
11
embedTitle : '.ytp-title-text' ,
12
12
queueHandle : 'ytd-playlist-panel-video-renderer' ,
13
- playbackControls : '.ytp-left-controls'
13
+ playbackControls : '.ytp-chrome-bottom' ,
14
+ overlays : '.videowall-endscreen, .ytp-upnext, .ytp-ce-element'
14
15
} ;
15
16
const ENABLE_LOGS = true ;
16
17
const logDebug = ( ...args ) => ENABLE_LOGS && console . log ( LOGTAG , ...args ) ;
17
18
const logError = ( ...args ) => ENABLE_LOGS && console . error ( LOGTAG , ...args ) ;
19
+ const CHROME_CONTROLS_MIN_WIDTH = 480 ;
18
20
19
21
class YoutubeExtension {
20
22
// We set a custom UA to force Youtube to display the most optimal
@@ -75,11 +77,27 @@ class YoutubeExtension {
75
77
return text . includes ( '360' ) ;
76
78
}
77
79
80
+ is360Video ( ) {
81
+ const targets = [
82
+ document . querySelector ( YT_SELECTORS . disclaimer ) ,
83
+ document . querySelector ( YT_SELECTORS . embedTitle )
84
+ ] ;
85
+ return targets . some ( ( node ) => node && this . is360 ( node . textContent ) ) ; ;
86
+ }
87
+
78
88
isStereo ( text ) {
79
89
const words = text . toLowerCase ( ) . split ( / \s + | \. / ) ;
80
90
return words . includes ( 'stereo' ) || words . includes ( '3d' ) || words . includes ( 'vr' ) ;
81
91
}
82
92
93
+ isStereoVideo ( ) {
94
+ const targets = [
95
+ document . querySelector ( YT_SELECTORS . disclaimer ) ,
96
+ document . querySelector ( YT_SELECTORS . embedTitle )
97
+ ] ;
98
+ return targets . some ( ( node ) => node && this . isStereo ( node . textContent ) ) ; ;
99
+ }
100
+
83
101
// Automatically select a video projection if needed
84
102
overrideVideoProjection ( ) {
85
103
if ( ! this . isWatchingPage ( ) ) {
@@ -94,14 +112,8 @@ class YoutubeExtension {
94
112
}
95
113
// There is no standard API to detect video projection yet.
96
114
// Try to infer it from the video disclaimer or title for now.
97
- const targets = [
98
- document . querySelector ( YT_SELECTORS . disclaimer ) ,
99
- document . querySelector ( YT_SELECTORS . embedTitle )
100
- ] ;
101
- const is360 = targets . some ( ( node ) => node && this . is360 ( node . textContent ) ) ;
102
- if ( is360 ) {
103
- const stereo = targets . some ( ( node ) => node && this . isStereo ( node . textContent ) ) ;
104
- qs . set ( 'mozVideoProjection' , stereo ? '360s_auto' : '360_auto' ) ;
115
+ if ( this . is360Video ( ) ) {
116
+ qs . set ( 'mozVideoProjection' , this . isStereoVideo ( ) ? '360s_auto' : '360_auto' ) ;
105
117
this . updateURL ( qs ) ;
106
118
this . updateVideoStyle ( ) ;
107
119
logDebug ( `Video projection set to: ${ qs . get ( VIDEO_PROJECTION_PARAM ) } ` ) ;
@@ -119,7 +131,7 @@ class YoutubeExtension {
119
131
}
120
132
121
133
overrideClick ( event ) {
122
- this . overrideVideoProjection ( ) ;
134
+ this . playerFixes ( ) ;
123
135
const player = this . getPlayer ( ) ;
124
136
if ( ! this . isWatchingPage ( ) || ! this . hasVideoProjection ( ) || document . fullscreenElement || ! player ) {
125
137
return ; // Only override click in the Youtube watching page for 360 videos.
@@ -146,7 +158,8 @@ class YoutubeExtension {
146
158
147
159
// Fix for the draggable items to continue being draggable when a context menu is displayed.
148
160
// https://github.com/MozillaReality/FirefoxReality/issues/2611
149
- fixQueueContextMenu ( ) {
161
+ queueContextMenuFix ( ) {
162
+ logDebug ( 'queueContextMenuFix' ) ;
150
163
const handles = document . querySelectorAll ( YT_SELECTORS . queueHandle ) ;
151
164
for ( var i = 0 ; i < handles . length ; i ++ ) {
152
165
handles [ i ] . removeEventListener ( 'contextmenu' , this . onContextMenu ) ;
@@ -169,9 +182,10 @@ class YoutubeExtension {
169
182
}
170
183
171
184
// Prevent the double click to reach the player to avoid double clicking
172
- // to trigger a playback forward event.
185
+ // to trigger a playback forward/backward event.
173
186
// https://github.com/MozillaReality/FirefoxReality/issues/2947
174
187
videoControlsForwardFix ( ) {
188
+ logDebug ( 'videoControlsForwardFix' ) ;
175
189
const playbackControls = document . querySelector ( YT_SELECTORS . playbackControls ) ;
176
190
playbackControls . removeEventListener ( "touchstart" , this . videoControlsOnTouchStart ) ;
177
191
playbackControls . addEventListener ( "touchstart" , this . videoControlsOnTouchStart ) ;
@@ -189,9 +203,28 @@ class YoutubeExtension {
189
203
return false ;
190
204
}
191
205
206
+ playerControlsMarginFix ( ) {
207
+ if ( youtube . isInFullscreen ( ) ) {
208
+ if ( window . innerHeight < CHROME_CONTROLS_MIN_WIDTH ) {
209
+ var of = CHROME_CONTROLS_MIN_WIDTH - window . innerHeight ;
210
+ console . log ( of )
211
+ document . querySelector ( ".ytp-chrome-bottom" ) . style . setProperty ( "margin-bottom" , `${ of } px` )
212
+ } else {
213
+ document . querySelector ( ".ytp-chrome-bottom" ) . style . removeProperty ( "margin-bottom" )
214
+ }
215
+
216
+ } else {
217
+ document . querySelector ( ".ytp-chrome-bottom" ) . style . removeProperty ( "margin-bottom" )
218
+ }
219
+ }
220
+
192
221
playerFixes ( ) {
193
- this . fixQueueContextMenu ( ) ;
222
+ this . overrideVideoProjection ( ) ;
223
+ this . overrideQualityRetry ( ) ;
224
+ this . hideOverlaysFix ( ) ;
225
+ this . queueContextMenuFix ( ) ;
194
226
this . videoControlsForwardFix ( ) ;
227
+ this . playerControlsMarginFix ( ) ;
195
228
}
196
229
197
230
// Runs the callback when the video is ready (has loaded the first frame).
@@ -252,13 +285,41 @@ class YoutubeExtension {
252
285
return video && video . readyState >= 2 ;
253
286
}
254
287
288
+ // Hide overlays when in immersive mode
289
+ // https://github.com/MozillaReality/FirefoxReality/issues/2673
290
+ hideOverlaysFix ( ) {
291
+ if ( youtube . is360Video ( ) || youtube . isStereoVideo ( ) ) {
292
+ logDebug ( 'hideOverlaysFix' ) ;
293
+ var overlays = document . querySelectorAll ( YT_SELECTORS . overlays ) ;
294
+ var observer = new MutationObserver ( ( mutations ) => {
295
+ if ( youtube . isInFullscreen ( ) ) {
296
+ for ( const mutation of mutations ) {
297
+ if ( mutation . target ) {
298
+ mutation . target . style . display = 'none' ;
299
+ }
300
+ }
301
+ }
302
+ } ) ;
303
+ for ( const overlay of overlays ) {
304
+ observer . observe ( overlay , { attributes : true } ) ;
305
+ }
306
+ }
307
+ }
308
+
309
+ isInFullscreen ( ) {
310
+ return ! ( ( document . fullScreenElement !== undefined && document . fullScreenElement === null ) ||
311
+ ( document . msFullscreenElement !== undefined && document . msFullscreenElement === null ) ||
312
+ ( document . mozFullScreen !== undefined && ! document . mozFullScreen ) ||
313
+ ( document . webkitIsFullScreen !== undefined && ! document . webkitIsFullScreen ) ) ;
314
+ }
315
+
255
316
// Utility function to retry tasks max n times until the execution is successful.
256
317
retry ( taskName , task , attempts = 10 , interval = 200 ) {
257
318
let succeeded = false ;
258
319
try {
259
320
succeeded = task ( ) ;
260
321
} catch ( ex ) {
261
- logError ( `Got exception runnning ${ taskName } task: ${ ex } ` ) ;
322
+ logError ( `Got exception running ${ taskName } task: ${ ex } ` ) ;
262
323
}
263
324
if ( succeeded ) {
264
325
logDebug ( `${ taskName } succeeded` ) ;
@@ -278,6 +339,8 @@ class YoutubeExtension {
278
339
window . history . replaceState ( { } , document . title , newUrl ) ;
279
340
logDebug ( `update URL to ${ newUrl } ` ) ;
280
341
}
342
+
343
+
281
344
}
282
345
283
346
logDebug ( `Initializing youtube extension in frame: ${ window . location . href } ` ) ;
@@ -286,18 +349,17 @@ youtube.overrideUA();
286
349
youtube . overrideViewport ( ) ;
287
350
window . addEventListener ( 'load' , ( ) => {
288
351
logDebug ( 'page load' ) ;
289
- youtube . overrideVideoProjection ( ) ;
290
- youtube . fixQueueContextMenu ( ) ;
291
352
// Wait until video has loaded the first frame to force quality change.
292
353
// This prevents the infinite spinner problem.
293
354
// See https://github.com/MozillaReality/FirefoxReality/issues/1433
294
355
if ( youtube . isWatchingPage ( ) ) {
295
- youtube . waitForVideoReady ( ( ) => youtube . overrideQualityRetry ( ) ) ;
356
+ youtube . waitForVideoReady ( ( ) => youtube . playerFixes ( ) ) ;
296
357
}
297
358
} ) ;
298
-
359
+ window . addEventListener ( "resize" , ( ) => youtube . playerControlsMarginFix ( ) ) ;
360
+ document . addEventListener ( "fullscreenchange" , ( ) => youtube . playerControlsMarginFix ( ) ) ;
299
361
window . addEventListener ( 'pushstate' , ( ) => youtube . overrideVideoProjection ( ) ) ;
300
362
window . addEventListener ( 'popstate' , ( ) => youtube . overrideVideoProjection ( ) ) ;
301
363
window . addEventListener ( 'click' , event => youtube . overrideClick ( event ) ) ;
302
- window . addEventListener ( 'mouseup' , event => youtube . playerFixes ( ) ) ;
303
- window . addEventListener ( "yt-navigate-start " , ( ) => youtube . playerFixes ( ) ) ;
364
+ window . addEventListener ( 'mouseup' , event => youtube . queueContextMenuFix ( ) ) ;
365
+ window . addEventListener ( "yt-navigate-finish " , ( ) => youtube . playerFixes ( ) ) ;
0 commit comments