Skip to content

Commit f337747

Browse files
authored
fix: focus the play toggle instead of the tech element on Edge to avoid a black frame with hardware-accelerated protected playback (#9217)
1 parent 1ce2b21 commit f337747

4 files changed

Lines changed: 113 additions & 5 deletions

File tree

src/js/big-play-button.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
import Button from './button.js';
55
import Component from './component.js';
6+
import * as browser from './utils/browser.js';
67
import {isPromise, silencePromise} from './utils/promise';
78

89
/**
@@ -45,21 +46,30 @@ class BigPlayButton extends Button {
4546
*/
4647
handleClick(event) {
4748
const playPromise = this.player_.play();
49+
const cb = this.player_.getChild('controlBar');
50+
const playToggle = cb && cb.getChild('playToggle');
4851

4952
// exit early if tapped or clicked via the mouse
5053
if (event.type === 'tap' || this.mouseused_ && 'clientX' in event && 'clientY' in event) {
5154
silencePromise(playPromise);
5255

53-
if (this.player_.tech(true)) {
56+
// On Microsoft Edge, moving focus to the <video> (tech) element as playback
57+
// starts prevents a protected (DRM/EME) video surface from being presented:
58+
// audio plays but the frame stays black until a later repaint or focus change.
59+
// Focus the control-bar play toggle instead of the tech on Edge, mirroring the
60+
// keyboard branch below. This is the pointer-path counterpart to the fix for
61+
// https://github.com/videojs/video.js/issues/6270 (#6318/#6508 redirected only
62+
// the keyboard path; the mouse/tap tech.focus() survived and regressed on
63+
// Chromium Edge with hardware-accelerated DRM).
64+
if (browser.IS_EDGE) {
65+
(playToggle || this.player_).focus();
66+
} else if (this.player_.tech(true)) {
5467
this.player_.tech(true).focus();
5568
}
5669

5770
return;
5871
}
5972

60-
const cb = this.player_.getChild('controlBar');
61-
const playToggle = cb && cb.getChild('playToggle');
62-
6373
if (!playToggle) {
6474
this.player_.tech(true).focus();
6575
return;

src/js/poster-image.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import ClickableComponent from './clickable-component.js';
55
import Component from './component.js';
66
import * as Dom from './utils/dom.js';
7+
import * as browser from './utils/browser.js';
78
import {silencePromise} from './utils/promise';
89

910
/** @import Player from './player' */
@@ -166,7 +167,17 @@ class PosterImage extends ClickableComponent {
166167
return;
167168
}
168169

169-
if (this.player_.tech(true)) {
170+
// On Microsoft Edge, moving focus to the <video> (tech) element as playback
171+
// starts prevents a protected (DRM/EME) video surface from being presented:
172+
// audio plays but the frame stays black until a later repaint or focus change.
173+
// Focus the control-bar play toggle instead of the tech on Edge. See
174+
// https://github.com/videojs/video.js/issues/6270.
175+
const cb = this.player_.getChild('controlBar');
176+
const playToggle = cb && cb.getChild('playToggle');
177+
178+
if (browser.IS_EDGE) {
179+
(playToggle || this.player_).focus();
180+
} else if (this.player_.tech(true)) {
170181
this.player_.tech(true).focus();
171182
}
172183

test/unit/big-play-button.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* eslint-env qunit */
2+
import TestHelpers from './test-helpers.js';
3+
import * as browser from '../../src/js/utils/browser.js';
4+
5+
QUnit.module('BigPlayButton', {
6+
beforeEach() {
7+
this.player = TestHelpers.makePlayer();
8+
this.bigPlayButton = this.player.getChild('BigPlayButton');
9+
this.playToggle = this.player.getChild('ControlBar').getChild('PlayToggle');
10+
this.tech = this.player.tech(true);
11+
12+
// Focus-only tests: avoid real play() promises/side effects.
13+
this.player.play = () => {};
14+
15+
// Count focus targets instead of relying on document.activeElement.
16+
this.techFocus = 0;
17+
this.toggleFocus = 0;
18+
this.tech.focus = () => {
19+
this.techFocus++;
20+
};
21+
this.playToggle.focus = () => {
22+
this.toggleFocus++;
23+
};
24+
25+
this.origEdge = browser.IS_EDGE;
26+
},
27+
afterEach() {
28+
browser.stub_IS_EDGE(this.origEdge);
29+
this.player.dispose();
30+
}
31+
});
32+
33+
QUnit.test('mouse/tap click focuses the tech on non-Edge browsers', function(assert) {
34+
browser.stub_IS_EDGE(false);
35+
36+
this.bigPlayButton.handleClick({type: 'tap'});
37+
38+
assert.strictEqual(this.techFocus, 1, 'the tech (video element) is focused');
39+
assert.strictEqual(this.toggleFocus, 0, 'the play toggle is not focused');
40+
});
41+
42+
// Regression guard for videojs/video.js#6270: focusing the <video> element as
43+
// playback starts on Edge leaves protected (DRM/EME) video as a black frame.
44+
QUnit.test('mouse/tap click focuses the play toggle, not the tech, on Edge', function(assert) {
45+
browser.stub_IS_EDGE(true);
46+
47+
this.bigPlayButton.handleClick({type: 'tap'});
48+
49+
assert.strictEqual(this.toggleFocus, 1, 'the play toggle is focused on Edge');
50+
assert.strictEqual(this.techFocus, 0, 'the tech is NOT focused on Edge');
51+
});

test/unit/poster.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-env qunit */
22
import PosterImage from '../../src/js/poster-image.js';
33
import TestHelpers from './test-helpers.js';
4+
import * as browser from '../../src/js/utils/browser.js';
45
import document from 'global/document';
56

67
QUnit.module('PosterImage', {
@@ -76,6 +77,41 @@ QUnit.test('should remove itself from the document flow when there is no poster'
7677
posterImage.dispose();
7778
});
7879

80+
// Regression guard for videojs/video.js#6270: on Edge, focusing the <video>
81+
// element when the poster is clicked leaves protected (DRM/EME) video black.
82+
QUnit.test('handleClick focuses the play toggle, not the tech, on Edge', function(assert) {
83+
const player = this.mockPlayer;
84+
85+
player.controls(true);
86+
player.play = () => {};
87+
88+
const tech = player.tech(true);
89+
const playToggle = player.getChild('ControlBar').getChild('PlayToggle');
90+
let techFocus = 0;
91+
let toggleFocus = 0;
92+
93+
tech.focus = () => {
94+
techFocus++;
95+
};
96+
playToggle.focus = () => {
97+
toggleFocus++;
98+
};
99+
100+
const origEdge = browser.IS_EDGE;
101+
102+
browser.stub_IS_EDGE(true);
103+
player.posterImage.handleClick({type: 'tap'});
104+
assert.strictEqual(toggleFocus, 1, 'play toggle focused on Edge');
105+
assert.strictEqual(techFocus, 0, 'tech not focused on Edge');
106+
107+
browser.stub_IS_EDGE(false);
108+
player.posterImage.handleClick({type: 'tap'});
109+
assert.strictEqual(techFocus, 1, 'tech focused on non-Edge');
110+
assert.strictEqual(toggleFocus, 1, 'play toggle not focused again on non-Edge');
111+
112+
browser.stub_IS_EDGE(origEdge);
113+
});
114+
79115
QUnit.test('should hide the poster in the appropriate player states', function(assert) {
80116
const posterImage = new PosterImage(this.mockPlayer);
81117
const playerDiv = document.createElement('div');

0 commit comments

Comments
 (0)