diff --git a/build-system/tasks/presubmit-checks.js b/build-system/tasks/presubmit-checks.js index e0c5678f2fc2f..baca378be4028 100644 --- a/build-system/tasks/presubmit-checks.js +++ b/build-system/tasks/presubmit-checks.js @@ -575,7 +575,7 @@ var forbiddenTerms = { 'src/event-helper.js', ], }, - '[A-Za-z]*Full[Ss]creen\\(': { + '([eE]xit|[eE]nter|[cC]ancel|[rR]equest)Full[Ss]creen\\(': { message: 'Use fullscreenEnter() and fullscreenExit() from dom.js instead.', whitelist: [ 'ads/google/imaVideo.js', diff --git a/extensions/amp-3q-player/0.1/amp-3q-player.js b/extensions/amp-3q-player/0.1/amp-3q-player.js index d40f3e37aeda6..357d152324c3f 100644 --- a/extensions/amp-3q-player/0.1/amp-3q-player.js +++ b/extensions/amp-3q-player/0.1/amp-3q-player.js @@ -17,7 +17,12 @@ import {isLayoutSizeDefined} from '../../../src/layout'; import {tryParseJson} from '../../../src/json'; import {user, dev} from '../../../src/log'; -import {removeElement, fullscreenEnter, fullscreenExit} from '../../../src/dom'; +import { + removeElement, + fullscreenEnter, + fullscreenExit, + isFullscreenElement, +} from '../../../src/dom'; import { installVideoManagerForDoc, } from '../../../src/service/video-manager-impl'; @@ -229,6 +234,11 @@ class Amp3QPlayer extends AMP.BaseElement { fullscreenExit(dev().assertElement(this.iframe_)); } + /** @override */ + isFullscreen() { + return isFullscreenElement(dev().assertElement(this.iframe_)); + } + /** @override */ getCurrentTime() { // Not supported. diff --git a/extensions/amp-brid-player/0.1/amp-brid-player.js b/extensions/amp-brid-player/0.1/amp-brid-player.js index 4ab43b8b85958..784b6b07b0939 100644 --- a/extensions/amp-brid-player/0.1/amp-brid-player.js +++ b/extensions/amp-brid-player/0.1/amp-brid-player.js @@ -22,7 +22,12 @@ import { import {VideoEvents} from '../../../src/video-interface'; import {Services} from '../../../src/services'; import {assertAbsoluteHttpOrHttpsUrl} from '../../../src/url'; -import {removeElement, fullscreenEnter, fullscreenExit} from '../../../src/dom'; +import { + removeElement, + fullscreenEnter, + fullscreenExit, + isFullscreenElement, +} from '../../../src/dom'; import {getData, listen} from '../../../src/event-helper'; /** @@ -300,6 +305,11 @@ class AmpBridPlayer extends AMP.BaseElement { fullscreenExit(dev().assertElement(this.iframe_)); } + /** @override */ + isFullscreen() { + return isFullscreenElement(dev().assertElement(this.iframe_)); + } + /** @override */ getCurrentTime() { // Not supported. diff --git a/extensions/amp-dailymotion/0.1/amp-dailymotion.js b/extensions/amp-dailymotion/0.1/amp-dailymotion.js index a8b77bd582998..a64425ae24a41 100644 --- a/extensions/amp-dailymotion/0.1/amp-dailymotion.js +++ b/extensions/amp-dailymotion/0.1/amp-dailymotion.js @@ -32,6 +32,7 @@ import { getDataParamsFromAttributes, fullscreenEnter, fullscreenExit, + isFullscreenElement, } from '../../../src/dom'; /** @@ -61,6 +62,7 @@ const DailymotionEvents = { // Other events VOLUMECHANGE: 'volumechange', STARTED_BUFFERING: 'progress', + FULLSCREEN_CHANGE: 'fullscreenchange', }; /** @@ -101,6 +103,9 @@ class AmpDailymotion extends AMP.BaseElement { /** @private {?Function} */ this.startedBufferingResolver_ = null; + /** @private {boolean} */ + this.isFullscreen_ = false; + } /** @@ -222,6 +227,9 @@ class AmpDailymotion extends AMP.BaseElement { case DailymotionEvents.STARTED_BUFFERING: this.startedBufferingResolver_(true); break; + case DailymotionEvents.FULLSCREEN_CHANGE: + this.isFullscreen_ = data['fullscreen'] == 'true'; + break; default: } @@ -365,6 +373,16 @@ class AmpDailymotion extends AMP.BaseElement { } } + /** @override */ + isFullscreen() { + const platform = Services.platformFor(this.win); + if (platform.isSafari() || platform.isIos()) { + return this.isFullscreen_; + } else { + return isFullscreenElement(dev().assertElement(this.iframe_)); + } + } + /** @override */ getCurrentTime() { // Not supported. diff --git a/extensions/amp-ima-video/0.1/amp-ima-video.js b/extensions/amp-ima-video/0.1/amp-ima-video.js index fa8422dbc58bc..4b7fa5ec4bb46 100644 --- a/extensions/amp-ima-video/0.1/amp-ima-video.js +++ b/extensions/amp-ima-video/0.1/amp-ima-video.js @@ -30,7 +30,12 @@ import { listen, } from '../../../src/event-helper'; import {dict} from '../../../src/utils/object'; -import {removeElement, fullscreenEnter, fullscreenExit} from '../../../src/dom'; +import { + removeElement, + fullscreenEnter, + fullscreenExit, + isFullscreenElement, +} from '../../../src/dom'; import {user, dev} from '../../../src/log'; import {VideoEvents} from '../../../src/video-interface'; import {Services} from '../../../src/services'; @@ -289,6 +294,13 @@ class AmpImaVideo extends AMP.BaseElement { fullscreenExit(dev().assertElement(this.iframe_)); } + /** @override */ + isFullscreen() { + // TODO(@aghassemi, #10597) Report fullscreen status of internal <video> + // element rather than iframe + return isFullscreenElement(dev().assertElement(this.iframe_)); + } + /** @override */ getCurrentTime() { // Not supported. diff --git a/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js b/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js index be937fc0e424a..893d2ac1b721b 100644 --- a/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js +++ b/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js @@ -22,7 +22,12 @@ import {user, dev} from '../../../src/log'; import { installVideoManagerForDoc, } from '../../../src/service/video-manager-impl'; -import {removeElement, fullscreenEnter, fullscreenExit} from '../../../src/dom'; +import { + removeElement, + fullscreenEnter, + fullscreenExit, + isFullscreenElement, +} from '../../../src/dom'; import {getData, listen} from '../../../src/event-helper'; import {isObject} from '../../../src/types'; import {VideoEvents} from '../../../src/video-interface'; @@ -255,6 +260,11 @@ class AmpNexxtvPlayer extends AMP.BaseElement { fullscreenExit(dev().assertElement(this.iframe_)); } + /** @override */ + isFullscreen() { + return isFullscreenElement(dev().assertElement(this.iframe_)); + } + /** @override */ getCurrentTime() { // Not supported. diff --git a/extensions/amp-ooyala-player/0.1/amp-ooyala-player.js b/extensions/amp-ooyala-player/0.1/amp-ooyala-player.js index 8396ebe06dc24..e7f454db29b6f 100644 --- a/extensions/amp-ooyala-player/0.1/amp-ooyala-player.js +++ b/extensions/amp-ooyala-player/0.1/amp-ooyala-player.js @@ -17,7 +17,12 @@ import {isLayoutSizeDefined} from '../../../src/layout'; import {tryParseJson} from '../../../src/json'; import {user, dev} from '../../../src/log'; -import {removeElement, fullscreenEnter, fullscreenExit} from '../../../src/dom'; +import { + removeElement, + fullscreenEnter, + fullscreenExit, + isFullscreenElement, +} from '../../../src/dom'; import { installVideoManagerForDoc, } from '../../../src/service/video-manager-impl'; @@ -240,6 +245,11 @@ class AmpOoyalaPlayer extends AMP.BaseElement { fullscreenExit(dev().assertElement(this.iframe_)); } + /** @override */ + isFullscreen() { + return isFullscreenElement(dev().assertElement(this.iframe_)); + } + /** @override */ getCurrentTime() { // Not supported. diff --git a/extensions/amp-video/0.1/amp-video.js b/extensions/amp-video/0.1/amp-video.js index 91676eec45a5a..c2c79383c6bdb 100644 --- a/extensions/amp-video/0.1/amp-video.js +++ b/extensions/amp-video/0.1/amp-video.js @@ -14,7 +14,12 @@ * limitations under the License. */ -import {elementByTag, fullscreenEnter, fullscreenExit} from '../../../src/dom'; +import { + elementByTag, + fullscreenEnter, + fullscreenExit, + isFullscreenElement, +} from '../../../src/dom'; import {listen} from '../../../src/event-helper'; import {isLayoutSizeDefined} from '../../../src/layout'; import {getMode} from '../../../src/mode'; @@ -288,6 +293,11 @@ class AmpVideo extends AMP.BaseElement { fullscreenExit(dev().assertElement(this.video_)); } + /** @override */ + isFullscreen() { + return isFullscreenElement(dev().assertElement(this.video_)); + } + /** @override */ getCurrentTime() { return this.video_.currentTime; diff --git a/extensions/amp-youtube/0.1/amp-youtube.js b/extensions/amp-youtube/0.1/amp-youtube.js index 6fba3f1d2f541..090060a031520 100644 --- a/extensions/amp-youtube/0.1/amp-youtube.js +++ b/extensions/amp-youtube/0.1/amp-youtube.js @@ -19,6 +19,7 @@ import { removeElement, fullscreenEnter, fullscreenExit, + isFullscreenElement, } from '../../../src/dom'; import {tryParseJson} from '../../../src/json'; import {getData, listen} from '../../../src/event-helper'; @@ -451,6 +452,10 @@ class AmpYoutube extends AMP.BaseElement { fullscreenExit(dev().assertElement(this.iframe_)); } + /** @override */ + isFullscreen() { + return isFullscreenElement(dev().assertElement(this.iframe_)); + } /** @override */ getCurrentTime() { diff --git a/src/dom.js b/src/dom.js index 53a9ea6f87aae..94a1c1f6fe01e 100644 --- a/src/dom.js +++ b/src/dom.js @@ -803,3 +803,27 @@ export function fullscreenExit(element) { return; } } + + +/** + * Replacement for `Document.fullscreenElement`. + * https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenElement + * @param {!Element} element + * @return {boolean} + */ +export function isFullscreenElement(element) { + const isFullscreen = element.webkitDisplayingFullscreen; + if (isFullscreen) { + return true; + } + if (element.ownerDocument) { + const fullscreenElement = element.ownerDocument.fullscreenElement + || element.ownerDocument.webkitFullscreenElement + || element.ownerDocument.mozFullScreenElement + || element.webkitCurrentFullScreenElement; + if (fullscreenElement == element) { + return true; + } + } + return false; +} diff --git a/src/service/video-manager-impl.js b/src/service/video-manager-impl.js index 0ae27c3c5b447..84cff3a668003 100644 --- a/src/service/video-manager-impl.js +++ b/src/service/video-manager-impl.js @@ -204,7 +204,7 @@ export class VideoManager { maybeInstallOrientationObserver_(entry) { // The orientation observer is only useful for automatically putting videos // in fullscreen. - if (!entry.video.element.hasAttribute(VideoAttributes.AUTOFULLSCREEN)) { + if (!entry.hasFullscreenOnLandscape) { return; } @@ -223,15 +223,16 @@ export class VideoManager { }; // Chrome apparently considers 'orientationchange' to be an untrusted // event, while 'change' on screen.orientation is considered a user - // interaction + // interaction. However on Chrome we still need to listen to + // 'orientationchange' to be able to exit fullscreen since 'change' does not + // fire when a video is in fullscreen. if (screen && 'orientation' in screen) { const orient = /** @type {!ScreenOrientation} */ (screen.orientation); listen(orient, 'change', handleOrientationChange.bind(this)); - } else { - // iOS Safari does not have screen.orientation but classifies - // 'orientationchange' as a user interaction. - listen(win, 'orientationchange', handleOrientationChange.bind(this)); } + // iOS Safari does not have screen.orientation but classifies + // 'orientationchange' as a user interaction. + listen(win, 'orientationchange', handleOrientationChange.bind(this)); } /** @@ -439,7 +440,12 @@ class VideoEntry { this.hasAutoplay = element.hasAttribute(VideoAttributes.AUTOPLAY); - this.hasAutoFs = element.hasAttribute(VideoAttributes.AUTOFULLSCREEN); + const fsOnLandscapeAttr = element.getAttribute( + VideoAttributes.FULLSCREEN_ON_LANDSCAPE + ); + + this.hasFullscreenOnLandscape = fsOnLandscapeAttr !== undefined + && (fsOnLandscapeAttr === '' || fsOnLandscapeAttr == 'always'); listenOncePromise(element, VideoEvents.LOAD) .then(() => this.videoLoaded()); @@ -543,11 +549,13 @@ class VideoEntry { * @private */ orientationChanged_(isLandscape) { - // Put the video in/out of fullscreen depending on orientation + // Put the video in/out of fullscreen depending on screen orientation if (!isLandscape && this.isFullscreenByOrientationChange_) { this.exitFullscreen_(); - } else if (this.getPlayingState() == PlayingStates.PLAYING_MANUAL - && this.isVisible_) { + } else if (isLandscape + && this.getPlayingState() == PlayingStates.PLAYING_MANUAL + && this.isVisible_ + && Services.viewerForDoc(this.ampdoc_).isVisible()) { this.enterFullscreen_(); } } @@ -557,8 +565,11 @@ class VideoEntry { * @private */ enterFullscreen_() { + if (this.video.isFullscreen() || this.isFullscreenByOrientationChange_) { + return; + } this.video.fullscreenEnter(); - this.isFullscreenByOrientationChange_ = true; + this.isFullscreenByOrientationChange_ = this.video.isFullscreen(); } /** @@ -566,6 +577,9 @@ class VideoEntry { * @private */ exitFullscreen_() { + if (!this.isFullscreenByOrientationChange_) { + return; + } this.video.fullscreenExit(); this.isFullscreenByOrientationChange_ = false; } diff --git a/src/video-interface.js b/src/video-interface.js index f8e56ee1c78ec..b9a9ce8fe6aeb 100644 --- a/src/video-interface.js +++ b/src/video-interface.js @@ -134,6 +134,12 @@ export class VideoInterface { */ fullscreenExit() {} + /** + * Returns whether the video is currently in fullscreen mode or not + * @return {boolean} + */ + isFullscreen() {} + /** * Automatically comes from {@link ./base-element.BaseElement} * @@ -184,7 +190,7 @@ export const VideoAttributes = { */ DOCK: 'dock', /** - * auto-fullscreen + * fullscreen-on-landscape * * If enabled, this automatically expands the currently visible video and * playing to fullscreen when the user changes the device's orientation to @@ -195,7 +201,7 @@ export const VideoAttributes = { * http://caniuse.com/#feat=screen-orientation * and http://caniuse.com/#feat=fullscreen */ - AUTOFULLSCREEN: 'auto-fullscreen', + FULLSCREEN_ON_LANDSCAPE: 'fullscreen-on-landscape', };