| function createControls(root, video, host) |
| { |
| return new Controller(root, video, host); |
| }; |
| |
| function Controller(root, video, host) |
| { |
| this.video = video; |
| this.root = root; |
| this.host = host; |
| this.controls = {}; |
| this.listeners = {}; |
| this.isLive = false; |
| this.statusHidden = true; |
| this.hasWirelessPlaybackTargets = false; |
| this.canToggleShowControlsButton = false; |
| this.isListeningForPlaybackTargetAvailabilityEvent = false; |
| this.currentTargetIsWireless = false; |
| this.wirelessPlaybackDisabled = false; |
| this.isVolumeSliderActive = false; |
| this.currentDisplayWidth = 0; |
| this._scrubbing = false; |
| this._pageScaleFactor = 1; |
| |
| this.addVideoListeners(); |
| this.createBase(); |
| this.createControls(); |
| this.createTimeClones(); |
| this.updateBase(); |
| this.updateControls(); |
| this.updateDuration(); |
| this.updateProgress(); |
| this.updateTime(); |
| this.updateReadyState(); |
| this.updatePlaying(); |
| this.updateThumbnail(); |
| this.updateCaptionButton(); |
| this.updateCaptionContainer(); |
| this.updateFullscreenButtons(); |
| this.updateVolume(); |
| this.updateHasAudio(); |
| this.updateHasVideo(); |
| this.updateWirelessTargetAvailable(); |
| this.updateWirelessPlaybackStatus(); |
| this.updatePictureInPicturePlaceholder(); |
| this.scheduleUpdateLayoutForDisplayedWidth(); |
| |
| this.listenFor(this.root, 'resize', this.handleRootResize); |
| }; |
| |
| /* Enums */ |
| Controller.InlineControls = 0; |
| Controller.FullScreenControls = 1; |
| |
| Controller.PlayAfterSeeking = 0; |
| Controller.PauseAfterSeeking = 1; |
| |
| /* Globals */ |
| Controller.gSimulateWirelessPlaybackTarget = false; // Used for testing when there are no wireless targets. |
| Controller.gSimulatePictureInPictureAvailable = false; // Used for testing when picture-in-picture is not available. |
| |
| Controller.prototype = { |
| |
| /* Constants */ |
| HandledVideoEvents: { |
| loadstart: 'handleLoadStart', |
| error: 'handleError', |
| abort: 'handleAbort', |
| suspend: 'handleSuspend', |
| stalled: 'handleStalled', |
| waiting: 'handleWaiting', |
| emptied: 'handleReadyStateChange', |
| loadedmetadata: 'handleReadyStateChange', |
| loadeddata: 'handleReadyStateChange', |
| canplay: 'handleReadyStateChange', |
| canplaythrough: 'handleReadyStateChange', |
| timeupdate: 'handleTimeUpdate', |
| durationchange: 'handleDurationChange', |
| playing: 'handlePlay', |
| pause: 'handlePause', |
| progress: 'handleProgress', |
| volumechange: 'handleVolumeChange', |
| webkitfullscreenchange: 'handleFullscreenChange', |
| webkitbeginfullscreen: 'handleFullscreenChange', |
| webkitendfullscreen: 'handleFullscreenChange', |
| }, |
| PlaceholderPollingDelay: 33, |
| HideControlsDelay: 4 * 1000, |
| RewindAmount: 30, |
| MaximumSeekRate: 8, |
| SeekDelay: 1500, |
| ClassNames: { |
| active: 'active', |
| dropped: 'dropped', |
| exit: 'exit', |
| failed: 'failed', |
| hidden: 'hidden', |
| hiding: 'hiding', |
| threeDigitTime: 'three-digit-time', |
| fourDigitTime: 'four-digit-time', |
| fiveDigitTime: 'five-digit-time', |
| sixDigitTime: 'six-digit-time', |
| list: 'list', |
| muteBox: 'mute-box', |
| muted: 'muted', |
| paused: 'paused', |
| pictureInPicture: 'picture-in-picture', |
| playing: 'playing', |
| returnFromPictureInPicture: 'return-from-picture-in-picture', |
| selected: 'selected', |
| show: 'show', |
| small: 'small', |
| thumbnail: 'thumbnail', |
| thumbnailImage: 'thumbnail-image', |
| thumbnailTrack: 'thumbnail-track', |
| volumeBox: 'volume-box', |
| noVideo: 'no-video', |
| down: 'down', |
| out: 'out', |
| pictureInPictureButton: 'picture-in-picture-button', |
| placeholderShowing: 'placeholder-showing', |
| usesLTRUserInterfaceLayoutDirection: 'uses-ltr-user-interface-layout-direction', |
| appleTV: 'appletv', |
| }, |
| KeyCodes: { |
| enter: 13, |
| escape: 27, |
| space: 32, |
| pageUp: 33, |
| pageDown: 34, |
| end: 35, |
| home: 36, |
| left: 37, |
| up: 38, |
| right: 39, |
| down: 40 |
| }, |
| MinimumTimelineWidth: 80, |
| ButtonWidth: 32, |
| |
| extend: function(child) |
| { |
| // This function doesn't actually do what we want it to. In particular it |
| // is not copying the getters and setters to the child class, since they are |
| // not enumerable. What we should do is use ES6 classes, or assign the __proto__ |
| // directly. |
| // FIXME: Use ES6 classes. |
| |
| for (var property in this) { |
| if (!child.hasOwnProperty(property)) |
| child[property] = this[property]; |
| } |
| }, |
| |
| get idiom() |
| { |
| return "apple"; |
| }, |
| |
| UIString: function(developmentString, replaceString, replacementString) |
| { |
| var localized = UIStringTable[developmentString]; |
| if (replaceString && replacementString) |
| return localized.replace(replaceString, replacementString); |
| |
| if (localized) |
| return localized; |
| |
| console.error("Localization for string \"" + developmentString + "\" not found."); |
| return "LOCALIZED STRING NOT FOUND"; |
| }, |
| |
| listenFor: function(element, eventName, handler, useCapture) |
| { |
| if (typeof useCapture === 'undefined') |
| useCapture = false; |
| |
| if (!(this.listeners[eventName] instanceof Array)) |
| this.listeners[eventName] = []; |
| this.listeners[eventName].push({element:element, handler:handler, useCapture:useCapture}); |
| element.addEventListener(eventName, this, useCapture); |
| }, |
| |
| stopListeningFor: function(element, eventName, handler, useCapture) |
| { |
| if (typeof useCapture === 'undefined') |
| useCapture = false; |
| |
| if (!(this.listeners[eventName] instanceof Array)) |
| return; |
| |
| this.listeners[eventName] = this.listeners[eventName].filter(function(entry) { |
| return !(entry.element === element && entry.handler === handler && entry.useCapture === useCapture); |
| }); |
| element.removeEventListener(eventName, this, useCapture); |
| }, |
| |
| addVideoListeners: function() |
| { |
| for (var name in this.HandledVideoEvents) { |
| this.listenFor(this.video, name, this.HandledVideoEvents[name]); |
| }; |
| |
| /* text tracks */ |
| this.listenFor(this.video.textTracks, 'change', this.handleTextTrackChange); |
| this.listenFor(this.video.textTracks, 'addtrack', this.handleTextTrackAdd); |
| this.listenFor(this.video.textTracks, 'removetrack', this.handleTextTrackRemove); |
| |
| /* audio tracks */ |
| this.listenFor(this.video.audioTracks, 'change', this.handleAudioTrackChange); |
| this.listenFor(this.video.audioTracks, 'addtrack', this.handleAudioTrackAdd); |
| this.listenFor(this.video.audioTracks, 'removetrack', this.handleAudioTrackRemove); |
| |
| /* video tracks */ |
| this.listenFor(this.video.videoTracks, 'change', this.updateHasVideo); |
| this.listenFor(this.video.videoTracks, 'addtrack', this.updateHasVideo); |
| this.listenFor(this.video.videoTracks, 'removetrack', this.updateHasVideo); |
| |
| /* controls attribute */ |
| this.controlsObserver = new MutationObserver(this.handleControlsChange.bind(this)); |
| this.controlsObserver.observe(this.video, { attributes: true, attributeFilter: ['controls'] }); |
| |
| this.listenFor(this.video, 'webkitcurrentplaybacktargetiswirelesschanged', this.handleWirelessPlaybackChange); |
| |
| if ('webkitPresentationMode' in this.video) |
| this.listenFor(this.video, 'webkitpresentationmodechanged', this.handlePresentationModeChange); |
| }, |
| |
| removeVideoListeners: function() |
| { |
| for (var name in this.HandledVideoEvents) { |
| this.stopListeningFor(this.video, name, this.HandledVideoEvents[name]); |
| }; |
| |
| /* text tracks */ |
| this.stopListeningFor(this.video.textTracks, 'change', this.handleTextTrackChange); |
| this.stopListeningFor(this.video.textTracks, 'addtrack', this.handleTextTrackAdd); |
| this.stopListeningFor(this.video.textTracks, 'removetrack', this.handleTextTrackRemove); |
| |
| /* audio tracks */ |
| this.stopListeningFor(this.video.audioTracks, 'change', this.handleAudioTrackChange); |
| this.stopListeningFor(this.video.audioTracks, 'addtrack', this.handleAudioTrackAdd); |
| this.stopListeningFor(this.video.audioTracks, 'removetrack', this.handleAudioTrackRemove); |
| |
| /* video tracks */ |
| this.stopListeningFor(this.video.videoTracks, 'change', this.updateHasVideo); |
| this.stopListeningFor(this.video.videoTracks, 'addtrack', this.updateHasVideo); |
| this.stopListeningFor(this.video.videoTracks, 'removetrack', this.updateHasVideo); |
| |
| /* controls attribute */ |
| this.controlsObserver.disconnect(); |
| delete(this.controlsObserver); |
| |
| this.stopListeningFor(this.video, 'webkitcurrentplaybacktargetiswirelesschanged', this.handleWirelessPlaybackChange); |
| this.setShouldListenForPlaybackTargetAvailabilityEvent(false); |
| |
| if ('webkitPresentationMode' in this.video) |
| this.stopListeningFor(this.video, 'webkitpresentationmodechanged', this.handlePresentationModeChange); |
| }, |
| |
| handleEvent: function(event) |
| { |
| var preventDefault = false; |
| |
| try { |
| if (event.target === this.video) { |
| var handlerName = this.HandledVideoEvents[event.type]; |
| var handler = this[handlerName]; |
| if (handler && handler instanceof Function) |
| handler.call(this, event); |
| } |
| |
| if (!(this.listeners[event.type] instanceof Array)) |
| return; |
| |
| this.listeners[event.type].forEach(function(entry) { |
| if (entry.element === event.currentTarget && entry.handler instanceof Function) |
| preventDefault |= entry.handler.call(this, event); |
| }, this); |
| } catch(e) { |
| if (window.console) |
| console.error(e); |
| } |
| |
| if (preventDefault) { |
| event.stopPropagation(); |
| event.preventDefault(); |
| } |
| }, |
| |
| createBase: function() |
| { |
| var base = this.base = document.createElement('div'); |
| base.setAttribute('pseudo', '-webkit-media-controls'); |
| this.listenFor(base, 'mousemove', this.handleWrapperMouseMove); |
| this.listenFor(this.video, 'mouseout', this.handleWrapperMouseOut); |
| if (this.host.textTrackContainer) |
| base.appendChild(this.host.textTrackContainer); |
| }, |
| |
| shouldHaveAnyUI: function() |
| { |
| return this.shouldHaveControls() || (this.video.textTracks && this.video.textTracks.length) || this.currentPlaybackTargetIsWireless(); |
| }, |
| |
| shouldShowControls: function() |
| { |
| if (!this.isAudio() && !this.host.allowsInlineMediaPlayback) |
| return true; |
| |
| return this.video.controls || this.isFullScreen(); |
| }, |
| |
| shouldHaveControls: function() |
| { |
| return this.shouldShowControls() || this.isFullScreen() || this.presentationMode() === 'picture-in-picture' || this.currentPlaybackTargetIsWireless(); |
| }, |
| |
| |
| setNeedsTimelineMetricsUpdate: function() |
| { |
| this.timelineMetricsNeedsUpdate = true; |
| }, |
| |
| scheduleUpdateLayoutForDisplayedWidth: function() |
| { |
| setTimeout(this.updateLayoutForDisplayedWidth.bind(this), 0); |
| }, |
| |
| updateTimelineMetricsIfNeeded: function() |
| { |
| if (this.timelineMetricsNeedsUpdate && !this.controlsAreHidden()) { |
| this.timelineLeft = this.controls.timeline.offsetLeft; |
| this.timelineWidth = this.controls.timeline.offsetWidth; |
| this.timelineHeight = this.controls.timeline.offsetHeight; |
| this.timelineMetricsNeedsUpdate = false; |
| } |
| }, |
| |
| updateBase: function() |
| { |
| if (this.shouldHaveAnyUI()) { |
| if (!this.base.parentNode) { |
| this.root.appendChild(this.base); |
| } |
| } else { |
| if (this.base.parentNode) { |
| this.base.parentNode.removeChild(this.base); |
| } |
| } |
| }, |
| |
| createControls: function() |
| { |
| var panel = this.controls.panel = document.createElement('div'); |
| panel.setAttribute('pseudo', '-webkit-media-controls-panel'); |
| panel.setAttribute('aria-label', (this.isAudio() ? this.UIString('Audio Playback') : this.UIString('Video Playback'))); |
| panel.setAttribute('role', 'toolbar'); |
| this.listenFor(panel, 'mousedown', this.handlePanelMouseDown); |
| this.listenFor(panel, 'transitionend', this.handlePanelTransitionEnd); |
| this.listenFor(panel, 'click', this.handlePanelClick); |
| this.listenFor(panel, 'dblclick', this.handlePanelClick); |
| this.listenFor(panel, 'dragstart', this.handlePanelDragStart); |
| |
| var panelBackgroundContainer = this.controls.panelBackgroundContainer = document.createElement('div'); |
| panelBackgroundContainer.setAttribute('pseudo', '-webkit-media-controls-panel-background-container'); |
| |
| var panelTint = this.controls.panelTint = document.createElement('div'); |
| panelTint.setAttribute('pseudo', '-webkit-media-controls-panel-tint'); |
| this.listenFor(panelTint, 'mousedown', this.handlePanelMouseDown); |
| this.listenFor(panelTint, 'transitionend', this.handlePanelTransitionEnd); |
| this.listenFor(panelTint, 'click', this.handlePanelClick); |
| this.listenFor(panelTint, 'dblclick', this.handlePanelClick); |
| this.listenFor(panelTint, 'dragstart', this.handlePanelDragStart); |
| |
| var panelBackground = this.controls.panelBackground = document.createElement('div'); |
| panelBackground.setAttribute('pseudo', '-webkit-media-controls-panel-background'); |
| |
| var rewindButton = this.controls.rewindButton = document.createElement('button'); |
| rewindButton.setAttribute('pseudo', '-webkit-media-controls-rewind-button'); |
| rewindButton.setAttribute('aria-label', this.UIString('Rewind ##sec## Seconds', '##sec##', this.RewindAmount)); |
| this.listenFor(rewindButton, 'click', this.handleRewindButtonClicked); |
| |
| var seekBackButton = this.controls.seekBackButton = document.createElement('button'); |
| seekBackButton.setAttribute('pseudo', '-webkit-media-controls-seek-back-button'); |
| seekBackButton.setAttribute('aria-label', this.UIString('Rewind')); |
| this.listenFor(seekBackButton, 'mousedown', this.handleSeekBackMouseDown); |
| this.listenFor(seekBackButton, 'mouseup', this.handleSeekBackMouseUp); |
| |
| var seekForwardButton = this.controls.seekForwardButton = document.createElement('button'); |
| seekForwardButton.setAttribute('pseudo', '-webkit-media-controls-seek-forward-button'); |
| seekForwardButton.setAttribute('aria-label', this.UIString('Fast Forward')); |
| this.listenFor(seekForwardButton, 'mousedown', this.handleSeekForwardMouseDown); |
| this.listenFor(seekForwardButton, 'mouseup', this.handleSeekForwardMouseUp); |
| |
| var playButton = this.controls.playButton = document.createElement('button'); |
| playButton.setAttribute('pseudo', '-webkit-media-controls-play-button'); |
| playButton.setAttribute('aria-label', this.UIString('Play')); |
| this.listenFor(playButton, 'click', this.handlePlayButtonClicked); |
| |
| var statusDisplay = this.controls.statusDisplay = document.createElement('div'); |
| statusDisplay.setAttribute('pseudo', '-webkit-media-controls-status-display'); |
| statusDisplay.classList.add(this.ClassNames.hidden); |
| |
| var timelineBox = this.controls.timelineBox = document.createElement('div'); |
| timelineBox.setAttribute('pseudo', '-webkit-media-controls-timeline-container'); |
| |
| var currentTime = this.controls.currentTime = document.createElement('div'); |
| currentTime.setAttribute('pseudo', '-webkit-media-controls-current-time-display'); |
| currentTime.setAttribute('aria-label', this.UIString('Elapsed')); |
| currentTime.setAttribute('role', 'timer'); |
| |
| var timeline = this.controls.timeline = document.createElement('input'); |
| timeline.setAttribute('pseudo', '-webkit-media-controls-timeline'); |
| timeline.setAttribute('aria-label', this.UIString('Duration')); |
| timeline.type = 'range'; |
| timeline.value = 0; |
| this.listenFor(timeline, 'input', this.handleTimelineInput); |
| this.listenFor(timeline, 'change', this.handleTimelineChange); |
| this.listenFor(timeline, 'mouseover', this.handleTimelineMouseOver); |
| this.listenFor(timeline, 'mouseout', this.handleTimelineMouseOut); |
| this.listenFor(timeline, 'mousemove', this.handleTimelineMouseMove); |
| this.listenFor(timeline, 'mousedown', this.handleTimelineMouseDown); |
| this.listenFor(timeline, 'mouseup', this.handleTimelineMouseUp); |
| this.listenFor(timeline, 'keydown', this.handleTimelineKeyDown); |
| timeline.step = .01; |
| |
| this.timelineContextName = "_webkit-media-controls-timeline-" + this.host.generateUUID(); |
| timeline.style.backgroundImage = '-webkit-canvas(' + this.timelineContextName + ')'; |
| |
| var thumbnailTrack = this.controls.thumbnailTrack = document.createElement('div'); |
| thumbnailTrack.classList.add(this.ClassNames.thumbnailTrack); |
| |
| var thumbnail = this.controls.thumbnail = document.createElement('div'); |
| thumbnail.classList.add(this.ClassNames.thumbnail); |
| |
| var thumbnailImage = this.controls.thumbnailImage = document.createElement('img'); |
| thumbnailImage.classList.add(this.ClassNames.thumbnailImage); |
| |
| var remainingTime = this.controls.remainingTime = document.createElement('div'); |
| remainingTime.setAttribute('pseudo', '-webkit-media-controls-time-remaining-display'); |
| remainingTime.setAttribute('aria-label', this.UIString('Remaining')); |
| remainingTime.setAttribute('role', 'timer'); |
| |
| var muteBox = this.controls.muteBox = document.createElement('div'); |
| muteBox.classList.add(this.ClassNames.muteBox); |
| this.listenFor(muteBox, 'mouseover', this.handleMuteBoxOver); |
| |
| var muteButton = this.controls.muteButton = document.createElement('button'); |
| muteButton.setAttribute('pseudo', '-webkit-media-controls-mute-button'); |
| muteButton.setAttribute('aria-label', this.UIString('Mute')); |
| // Make the mute button a checkbox since it only has on/off states. |
| muteButton.setAttribute('role', 'checkbox'); |
| this.listenFor(muteButton, 'click', this.handleMuteButtonClicked); |
| |
| var minButton = this.controls.minButton = document.createElement('button'); |
| minButton.setAttribute('pseudo', '-webkit-media-controls-volume-min-button'); |
| minButton.setAttribute('aria-label', this.UIString('Minimum Volume')); |
| this.listenFor(minButton, 'click', this.handleMinButtonClicked); |
| |
| var maxButton = this.controls.maxButton = document.createElement('button'); |
| maxButton.setAttribute('pseudo', '-webkit-media-controls-volume-max-button'); |
| maxButton.setAttribute('aria-label', this.UIString('Maximum Volume')); |
| this.listenFor(maxButton, 'click', this.handleMaxButtonClicked); |
| |
| var volumeBox = this.controls.volumeBox = document.createElement('div'); |
| volumeBox.setAttribute('pseudo', '-webkit-media-controls-volume-slider-container'); |
| volumeBox.classList.add(this.ClassNames.volumeBox); |
| |
| var volumeBoxBackground = this.controls.volumeBoxBackground = document.createElement('div'); |
| volumeBoxBackground.setAttribute('pseudo', '-webkit-media-controls-volume-slider-container-background'); |
| |
| var volumeBoxTint = this.controls.volumeBoxTint = document.createElement('div'); |
| volumeBoxTint.setAttribute('pseudo', '-webkit-media-controls-volume-slider-container-tint'); |
| |
| var volume = this.controls.volume = document.createElement('input'); |
| volume.setAttribute('pseudo', '-webkit-media-controls-volume-slider'); |
| volume.setAttribute('aria-label', this.UIString('Volume')); |
| volume.type = 'range'; |
| volume.min = 0; |
| volume.max = 1; |
| volume.step = .05; |
| this.listenFor(volume, 'input', this.handleVolumeSliderInput); |
| this.listenFor(volume, 'change', this.handleVolumeSliderChange); |
| this.listenFor(volume, 'mousedown', this.handleVolumeSliderMouseDown); |
| this.listenFor(volume, 'mouseup', this.handleVolumeSliderMouseUp); |
| |
| this.volumeContextName = "_webkit-media-controls-volume-" + this.host.generateUUID(); |
| volume.style.backgroundImage = '-webkit-canvas(' + this.volumeContextName + ')'; |
| |
| var captionButton = this.controls.captionButton = document.createElement('button'); |
| captionButton.setAttribute('pseudo', '-webkit-media-controls-toggle-closed-captions-button'); |
| captionButton.setAttribute('aria-label', this.UIString('Captions')); |
| captionButton.setAttribute('aria-haspopup', 'true'); |
| captionButton.setAttribute('aria-owns', 'audioAndTextTrackMenu'); |
| this.listenFor(captionButton, 'click', this.handleCaptionButtonClicked); |
| |
| var fullscreenButton = this.controls.fullscreenButton = document.createElement('button'); |
| fullscreenButton.setAttribute('pseudo', '-webkit-media-controls-fullscreen-button'); |
| fullscreenButton.setAttribute('aria-label', this.UIString('Display Full Screen')); |
| this.listenFor(fullscreenButton, 'click', this.handleFullscreenButtonClicked); |
| |
| var pictureInPictureButton = this.controls.pictureInPictureButton = document.createElement('button'); |
| pictureInPictureButton.setAttribute('pseudo', '-webkit-media-controls-picture-in-picture-button'); |
| pictureInPictureButton.setAttribute('aria-label', this.UIString('Display Picture in Picture')); |
| pictureInPictureButton.classList.add(this.ClassNames.pictureInPictureButton); |
| this.listenFor(pictureInPictureButton, 'click', this.handlePictureInPictureButtonClicked); |
| |
| var inlinePlaybackPlaceholder = this.controls.inlinePlaybackPlaceholder = document.createElement('div'); |
| inlinePlaybackPlaceholder.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-status'); |
| inlinePlaybackPlaceholder.setAttribute('aria-label', this.UIString('Video Playback Placeholder')); |
| this.listenFor(inlinePlaybackPlaceholder, 'click', this.handlePlaceholderClick); |
| this.listenFor(inlinePlaybackPlaceholder, 'dblclick', this.handlePlaceholderClick); |
| if (!Controller.gSimulatePictureInPictureAvailable) |
| inlinePlaybackPlaceholder.classList.add(this.ClassNames.hidden); |
| |
| var inlinePlaybackPlaceholderText = this.controls.inlinePlaybackPlaceholderText = document.createElement('div'); |
| inlinePlaybackPlaceholderText.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-text'); |
| |
| var inlinePlaybackPlaceholderTextTop = this.controls.inlinePlaybackPlaceholderTextTop = document.createElement('p'); |
| inlinePlaybackPlaceholderTextTop.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-text-top'); |
| |
| var inlinePlaybackPlaceholderTextBottom = this.controls.inlinePlaybackPlaceholderTextBottom = document.createElement('p'); |
| inlinePlaybackPlaceholderTextBottom.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-text-bottom'); |
| |
| var wirelessTargetPicker = this.controls.wirelessTargetPicker = document.createElement('button'); |
| wirelessTargetPicker.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-picker-button'); |
| wirelessTargetPicker.setAttribute('aria-label', this.UIString('Choose Wireless Display')); |
| this.listenFor(wirelessTargetPicker, 'click', this.handleWirelessPickerButtonClicked); |
| |
| // Show controls button is an accessibility workaround since the controls are now removed from the DOM. http://webkit.org/b/145684 |
| var showControlsButton = this.showControlsButton = document.createElement('button'); |
| showControlsButton.setAttribute('pseudo', '-webkit-media-show-controls'); |
| this.showShowControlsButton(false); |
| showControlsButton.setAttribute('aria-label', this.UIString('Show Controls')); |
| this.listenFor(showControlsButton, 'click', this.handleShowControlsClick); |
| this.base.appendChild(showControlsButton); |
| |
| if (!Controller.gSimulateWirelessPlaybackTarget) |
| wirelessTargetPicker.classList.add(this.ClassNames.hidden); |
| }, |
| |
| createTimeClones: function() |
| { |
| var currentTimeClone = this.currentTimeClone = document.createElement('div'); |
| currentTimeClone.setAttribute('pseudo', '-webkit-media-controls-current-time-display'); |
| currentTimeClone.setAttribute('aria-hidden', 'true'); |
| currentTimeClone.classList.add('clone'); |
| this.base.appendChild(currentTimeClone); |
| |
| var remainingTimeClone = this.remainingTimeClone = document.createElement('div'); |
| remainingTimeClone.setAttribute('pseudo', '-webkit-media-controls-time-remaining-display'); |
| remainingTimeClone.setAttribute('aria-hidden', 'true'); |
| remainingTimeClone.classList.add('clone'); |
| this.base.appendChild(remainingTimeClone); |
| }, |
| |
| setControlsType: function(type) |
| { |
| if (type === this.controlsType) |
| return; |
| this.controlsType = type; |
| |
| this.reconnectControls(); |
| this.updateShouldListenForPlaybackTargetAvailabilityEvent(); |
| }, |
| |
| setIsLive: function(live) |
| { |
| if (live === this.isLive) |
| return; |
| this.isLive = live; |
| |
| this.updateStatusDisplay(); |
| |
| this.reconnectControls(); |
| }, |
| |
| reconnectControls: function() |
| { |
| this.disconnectControls(); |
| |
| if (this.controlsType === Controller.InlineControls) |
| this.configureInlineControls(); |
| else if (this.controlsType == Controller.FullScreenControls) |
| this.configureFullScreenControls(); |
| if (this.shouldHaveControls() || this.currentPlaybackTargetIsWireless()) |
| this.addControls(); |
| }, |
| |
| disconnectControls: function(event) |
| { |
| for (var item in this.controls) { |
| var control = this.controls[item]; |
| if (control && control.parentNode) |
| control.parentNode.removeChild(control); |
| } |
| }, |
| |
| configureInlineControls: function() |
| { |
| this.controls.inlinePlaybackPlaceholder.appendChild(this.controls.inlinePlaybackPlaceholderText); |
| this.controls.inlinePlaybackPlaceholderText.appendChild(this.controls.inlinePlaybackPlaceholderTextTop); |
| this.controls.inlinePlaybackPlaceholderText.appendChild(this.controls.inlinePlaybackPlaceholderTextBottom); |
| this.controls.panel.appendChild(this.controls.panelBackgroundContainer); |
| this.controls.panelBackgroundContainer.appendChild(this.controls.panelBackground); |
| this.controls.panelBackgroundContainer.appendChild(this.controls.panelTint); |
| this.controls.panel.appendChild(this.controls.playButton); |
| if (!this.isLive) |
| this.controls.panel.appendChild(this.controls.rewindButton); |
| this.controls.panel.appendChild(this.controls.statusDisplay); |
| if (!this.isLive) { |
| this.controls.panel.appendChild(this.controls.timelineBox); |
| this.controls.timelineBox.appendChild(this.controls.currentTime); |
| this.controls.timelineBox.appendChild(this.controls.thumbnailTrack); |
| this.controls.thumbnailTrack.appendChild(this.controls.timeline); |
| this.controls.thumbnailTrack.appendChild(this.controls.thumbnail); |
| this.controls.thumbnail.appendChild(this.controls.thumbnailImage); |
| this.controls.timelineBox.appendChild(this.controls.remainingTime); |
| } |
| this.controls.panel.appendChild(this.controls.muteBox); |
| this.controls.muteBox.appendChild(this.controls.volumeBox); |
| this.controls.volumeBox.appendChild(this.controls.volumeBoxBackground); |
| this.controls.volumeBox.appendChild(this.controls.volumeBoxTint); |
| this.controls.volumeBox.appendChild(this.controls.volume); |
| this.controls.muteBox.appendChild(this.controls.muteButton); |
| this.controls.panel.appendChild(this.controls.wirelessTargetPicker); |
| this.controls.panel.appendChild(this.controls.captionButton); |
| if (!this.isAudio()) { |
| this.updatePictureInPictureButton(); |
| this.controls.panel.appendChild(this.controls.fullscreenButton); |
| } |
| |
| this.controls.panel.style.removeProperty('left'); |
| this.controls.panel.style.removeProperty('top'); |
| this.controls.panel.style.removeProperty('bottom'); |
| }, |
| |
| configureFullScreenControls: function() |
| { |
| this.controls.inlinePlaybackPlaceholder.appendChild(this.controls.inlinePlaybackPlaceholderText); |
| this.controls.inlinePlaybackPlaceholderText.appendChild(this.controls.inlinePlaybackPlaceholderTextTop); |
| this.controls.inlinePlaybackPlaceholderText.appendChild(this.controls.inlinePlaybackPlaceholderTextBottom); |
| this.controls.panel.appendChild(this.controls.panelBackground); |
| this.controls.panel.appendChild(this.controls.panelTint); |
| this.controls.panel.appendChild(this.controls.volumeBox); |
| this.controls.volumeBox.appendChild(this.controls.minButton); |
| this.controls.volumeBox.appendChild(this.controls.volume); |
| this.controls.volumeBox.appendChild(this.controls.maxButton); |
| this.controls.panel.appendChild(this.controls.seekBackButton); |
| this.controls.panel.appendChild(this.controls.playButton); |
| this.controls.panel.appendChild(this.controls.seekForwardButton); |
| this.controls.panel.appendChild(this.controls.wirelessTargetPicker); |
| this.controls.panel.appendChild(this.controls.captionButton); |
| if (!this.isAudio()) { |
| this.updatePictureInPictureButton(); |
| this.controls.panel.appendChild(this.controls.fullscreenButton); |
| } |
| if (!this.isLive) { |
| this.controls.panel.appendChild(this.controls.timelineBox); |
| this.controls.timelineBox.appendChild(this.controls.currentTime); |
| this.controls.timelineBox.appendChild(this.controls.thumbnailTrack); |
| this.controls.thumbnailTrack.appendChild(this.controls.timeline); |
| this.controls.thumbnailTrack.appendChild(this.controls.thumbnail); |
| this.controls.thumbnail.appendChild(this.controls.thumbnailImage); |
| this.controls.timelineBox.appendChild(this.controls.remainingTime); |
| } else |
| this.controls.panel.appendChild(this.controls.statusDisplay); |
| }, |
| |
| updateControls: function() |
| { |
| if (this.isFullScreen()) |
| this.setControlsType(Controller.FullScreenControls); |
| else |
| this.setControlsType(Controller.InlineControls); |
| |
| this.setNeedsUpdateForDisplayedWidth(); |
| this.updateLayoutForDisplayedWidth(); |
| this.setNeedsTimelineMetricsUpdate(); |
| |
| if (this.shouldShowControls()) { |
| this.controls.panel.classList.add(this.ClassNames.show); |
| this.controls.panel.classList.remove(this.ClassNames.hidden); |
| this.resetHideControlsTimer(); |
| this.showShowControlsButton(false); |
| } else { |
| this.controls.panel.classList.remove(this.ClassNames.show); |
| this.controls.panel.classList.add(this.ClassNames.hidden); |
| this.showShowControlsButton(true); |
| } |
| }, |
| |
| isPlayable: function() |
| { |
| return this.video.readyState > HTMLMediaElement.HAVE_NOTHING && !this.video.error; |
| }, |
| |
| updateStatusDisplay: function(event) |
| { |
| this.updateShouldListenForPlaybackTargetAvailabilityEvent(); |
| if (this.video.error !== null) |
| this.controls.statusDisplay.innerText = this.UIString('Error'); |
| else if (this.isLive && this.video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) |
| this.controls.statusDisplay.innerText = this.UIString('Live Broadcast'); |
| else if (!this.isPlayable() && this.video.networkState === HTMLMediaElement.NETWORK_LOADING) |
| this.controls.statusDisplay.innerText = this.UIString('Loading'); |
| else |
| this.controls.statusDisplay.innerText = ''; |
| |
| this.setStatusHidden(!this.isLive && this.isPlayable()); |
| }, |
| |
| handleLoadStart: function(event) |
| { |
| this.updateStatusDisplay(); |
| this.updateProgress(); |
| }, |
| |
| handleError: function(event) |
| { |
| this.updateStatusDisplay(); |
| }, |
| |
| handleAbort: function(event) |
| { |
| this.updateStatusDisplay(); |
| }, |
| |
| handleSuspend: function(event) |
| { |
| this.updateStatusDisplay(); |
| }, |
| |
| handleStalled: function(event) |
| { |
| this.updateStatusDisplay(); |
| this.updateProgress(); |
| }, |
| |
| handleWaiting: function(event) |
| { |
| this.updateStatusDisplay(); |
| }, |
| |
| handleReadyStateChange: function(event) |
| { |
| this.updateReadyState(); |
| this.updateDuration(); |
| this.updateCaptionButton(); |
| this.updateCaptionContainer(); |
| this.updateFullscreenButtons(); |
| this.updateWirelessTargetAvailable(); |
| this.updateWirelessTargetPickerButton(); |
| this.updateProgress(); |
| this.updateControls(); |
| }, |
| |
| handleTimeUpdate: function(event) |
| { |
| if (!this.scrubbing) { |
| this.updateTime(); |
| this.updateProgress(); |
| } |
| this.drawTimelineBackground(); |
| }, |
| |
| handleDurationChange: function(event) |
| { |
| this.updateDuration(); |
| this.updateTime(); |
| this.updateProgress(); |
| }, |
| |
| handlePlay: function(event) |
| { |
| this.setPlaying(true); |
| }, |
| |
| handlePause: function(event) |
| { |
| this.setPlaying(false); |
| }, |
| |
| handleProgress: function(event) |
| { |
| this.updateProgress(); |
| }, |
| |
| handleVolumeChange: function(event) |
| { |
| this.updateVolume(); |
| }, |
| |
| handleTextTrackChange: function(event) |
| { |
| this.updateCaptionContainer(); |
| }, |
| |
| handleTextTrackAdd: function(event) |
| { |
| var track = event.track; |
| |
| if (this.trackHasThumbnails(track) && track.mode === 'disabled') |
| track.mode = 'hidden'; |
| |
| this.updateThumbnail(); |
| this.updateCaptionButton(); |
| this.updateCaptionContainer(); |
| }, |
| |
| handleTextTrackRemove: function(event) |
| { |
| this.updateThumbnail(); |
| this.updateCaptionButton(); |
| this.updateCaptionContainer(); |
| }, |
| |
| handleAudioTrackChange: function(event) |
| { |
| this.updateHasAudio(); |
| }, |
| |
| handleAudioTrackAdd: function(event) |
| { |
| this.updateHasAudio(); |
| this.updateCaptionButton(); |
| }, |
| |
| handleAudioTrackRemove: function(event) |
| { |
| this.updateHasAudio(); |
| this.updateCaptionButton(); |
| }, |
| |
| presentationMode: function() { |
| if ('webkitPresentationMode' in this.video) |
| return this.video.webkitPresentationMode; |
| |
| if (this.isFullScreen()) |
| return 'fullscreen'; |
| |
| return 'inline'; |
| }, |
| |
| isFullScreen: function() |
| { |
| if (!this.video.webkitDisplayingFullscreen) |
| return false; |
| |
| if ('webkitPresentationMode' in this.video && this.video.webkitPresentationMode === 'picture-in-picture') |
| return false; |
| |
| return true; |
| }, |
| |
| updatePictureInPictureButton: function() |
| { |
| var shouldShowPictureInPictureButton = (Controller.gSimulatePictureInPictureAvailable || ('webkitSupportsPresentationMode' in this.video && this.video.webkitSupportsPresentationMode('picture-in-picture'))) && this.hasVideo(); |
| if (shouldShowPictureInPictureButton) { |
| if (!this.controls.pictureInPictureButton.parentElement) { |
| if (this.controls.fullscreenButton.parentElement == this.controls.panel) |
| this.controls.panel.insertBefore(this.controls.pictureInPictureButton, this.controls.fullscreenButton); |
| else |
| this.controls.panel.appendChild(this.controls.pictureInPictureButton); |
| } |
| this.controls.pictureInPictureButton.classList.remove(this.ClassNames.hidden); |
| } else |
| this.controls.pictureInPictureButton.classList.add(this.ClassNames.hidden); |
| }, |
| |
| timelineStepFromVideoDuration: function() |
| { |
| var step; |
| var duration = this.video.duration; |
| if (duration <= 10) |
| step = .5; |
| else if (duration <= 60) |
| step = 1; |
| else if (duration <= 600) |
| step = 10; |
| else if (duration <= 3600) |
| step = 30; |
| else |
| step = 60; |
| |
| return step; |
| }, |
| |
| incrementTimelineValue: function() |
| { |
| var value = this.video.currentTime + this.timelineStepFromVideoDuration(); |
| return value > this.video.duration ? this.video.duration : value; |
| }, |
| |
| decrementTimelineValue: function() |
| { |
| var value = this.video.currentTime - this.timelineStepFromVideoDuration(); |
| return value < 0 ? 0 : value; |
| }, |
| |
| showInlinePlaybackPlaceholderWhenSafe: function() { |
| if (this.presentationMode() != 'picture-in-picture') |
| return; |
| |
| if (!this.host.isVideoLayerInline) { |
| this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.hidden); |
| this.base.classList.add(this.ClassNames.placeholderShowing); |
| } else |
| setTimeout(this.showInlinePlaybackPlaceholderWhenSafe.bind(this), this.PlaceholderPollingDelay); |
| }, |
| |
| shouldReturnVideoLayerToInline: function() |
| { |
| var presentationMode = this.presentationMode(); |
| return presentationMode === 'inline' || presentationMode === 'fullscreen'; |
| }, |
| |
| updatePictureInPicturePlaceholder: function() |
| { |
| var presentationMode = this.presentationMode(); |
| |
| switch (presentationMode) { |
| case 'inline': |
| this.controls.panel.classList.remove(this.ClassNames.pictureInPicture); |
| this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.hidden); |
| this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.pictureInPicture); |
| this.controls.inlinePlaybackPlaceholderTextTop.classList.remove(this.ClassNames.pictureInPicture); |
| this.controls.inlinePlaybackPlaceholderTextBottom.classList.remove(this.ClassNames.pictureInPicture); |
| this.base.classList.remove(this.ClassNames.placeholderShowing); |
| |
| this.controls.pictureInPictureButton.classList.remove(this.ClassNames.returnFromPictureInPicture); |
| break; |
| case 'picture-in-picture': |
| this.controls.panel.classList.add(this.ClassNames.pictureInPicture); |
| this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.pictureInPicture); |
| this.showInlinePlaybackPlaceholderWhenSafe(); |
| |
| this.controls.inlinePlaybackPlaceholderTextTop.innerText = this.UIString('This video is playing in picture in picture.'); |
| this.controls.inlinePlaybackPlaceholderTextTop.classList.add(this.ClassNames.pictureInPicture); |
| this.controls.inlinePlaybackPlaceholderTextBottom.innerText = ""; |
| this.controls.inlinePlaybackPlaceholderTextBottom.classList.add(this.ClassNames.pictureInPicture); |
| |
| this.controls.pictureInPictureButton.classList.add(this.ClassNames.returnFromPictureInPicture); |
| break; |
| default: |
| this.controls.panel.classList.remove(this.ClassNames.pictureInPicture); |
| this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.pictureInPicture); |
| this.controls.inlinePlaybackPlaceholderTextTop.classList.remove(this.ClassNames.pictureInPicture); |
| this.controls.inlinePlaybackPlaceholderTextBottom.classList.remove(this.ClassNames.pictureInPicture); |
| |
| this.controls.pictureInPictureButton.classList.remove(this.ClassNames.returnFromPictureInPicture); |
| break; |
| } |
| }, |
| |
| handlePresentationModeChange: function(event) |
| { |
| this.updatePictureInPicturePlaceholder(); |
| this.updateControls(); |
| this.updateCaptionContainer(); |
| this.resetHideControlsTimer(); |
| if (this.presentationMode() != 'fullscreen' && this.video.paused && this.controlsAreHidden()) |
| this.showControls(); |
| this.host.setPreparedToReturnVideoLayerToInline(this.shouldReturnVideoLayerToInline()); |
| }, |
| |
| handleFullscreenChange: function(event) |
| { |
| this.updateBase(); |
| this.updateControls(); |
| this.updateFullscreenButtons(); |
| this.updateWirelessPlaybackStatus(); |
| |
| if (this.isFullScreen()) { |
| this.controls.fullscreenButton.classList.add(this.ClassNames.exit); |
| this.controls.fullscreenButton.setAttribute('aria-label', this.UIString('Exit Full Screen')); |
| this.host.enteredFullscreen(); |
| } else { |
| this.controls.fullscreenButton.classList.remove(this.ClassNames.exit); |
| this.controls.fullscreenButton.setAttribute('aria-label', this.UIString('Display Full Screen')); |
| this.host.exitedFullscreen(); |
| } |
| |
| if ('webkitPresentationMode' in this.video) |
| this.handlePresentationModeChange(event); |
| }, |
| |
| handleShowControlsClick: function(event) |
| { |
| if (!this.video.controls && !this.isFullScreen()) |
| return; |
| |
| if (this.controlsAreHidden()) |
| this.showControls(true); |
| }, |
| |
| handleWrapperMouseMove: function(event) |
| { |
| if (!this.video.controls && !this.isFullScreen()) |
| return; |
| |
| if (this.controlsAreHidden()) |
| this.showControls(); |
| this.resetHideControlsTimer(); |
| |
| if (!this.isDragging) |
| return; |
| var delta = new WebKitPoint(event.clientX - this.initialDragLocation.x, event.clientY - this.initialDragLocation.y); |
| this.controls.panel.style.left = this.initialOffset.x + delta.x + 'px'; |
| this.controls.panel.style.top = this.initialOffset.y + delta.y + 'px'; |
| event.stopPropagation() |
| }, |
| |
| handleWrapperMouseOut: function(event) |
| { |
| this.hideControls(); |
| this.clearHideControlsTimer(); |
| }, |
| |
| handleWrapperMouseUp: function(event) |
| { |
| this.isDragging = false; |
| this.stopListeningFor(this.base, 'mouseup', 'handleWrapperMouseUp', true); |
| }, |
| |
| handlePanelMouseDown: function(event) |
| { |
| if (event.target != this.controls.panelTint && event.target != this.controls.inlinePlaybackPlaceholder) |
| return; |
| |
| if (!this.isFullScreen()) |
| return; |
| |
| this.listenFor(this.base, 'mouseup', this.handleWrapperMouseUp, true); |
| this.isDragging = true; |
| this.initialDragLocation = new WebKitPoint(event.clientX, event.clientY); |
| this.initialOffset = new WebKitPoint( |
| parseInt(this.controls.panel.style.left) | 0, |
| parseInt(this.controls.panel.style.top) | 0 |
| ); |
| }, |
| |
| handlePanelTransitionEnd: function(event) |
| { |
| var opacity = window.getComputedStyle(this.controls.panel).opacity; |
| if (!parseInt(opacity) && !this.controlsAlwaysVisible() && (this.video.controls || this.isFullScreen())) { |
| this.base.removeChild(this.controls.inlinePlaybackPlaceholder); |
| this.base.removeChild(this.controls.panel); |
| } |
| }, |
| |
| handlePanelClick: function(event) |
| { |
| // Prevent clicks in the panel from playing or pausing the video in a MediaDocument. |
| event.preventDefault(); |
| }, |
| |
| handlePanelDragStart: function(event) |
| { |
| // Prevent drags in the panel from triggering a drag event on the <video> element. |
| event.preventDefault(); |
| }, |
| |
| handlePlaceholderClick: function(event) |
| { |
| // Prevent clicks in the placeholder from playing or pausing the video in a MediaDocument. |
| event.preventDefault(); |
| }, |
| |
| handleRewindButtonClicked: function(event) |
| { |
| var newTime = Math.max( |
| this.video.currentTime - this.RewindAmount, |
| this.video.seekable.start(0)); |
| this.video.currentTime = newTime; |
| return true; |
| }, |
| |
| canPlay: function() |
| { |
| return this.video.paused || this.video.ended || this.video.readyState < HTMLMediaElement.HAVE_METADATA; |
| }, |
| |
| handlePlayButtonClicked: function(event) |
| { |
| if (this.canPlay()) { |
| this.canToggleShowControlsButton = true; |
| this.video.play(); |
| } else |
| this.video.pause(); |
| return true; |
| }, |
| |
| handleTimelineInput: function(event) |
| { |
| if (this.scrubbing) |
| this.video.pause(); |
| |
| this.video.fastSeek(this.controls.timeline.value); |
| this.updateControlsWhileScrubbing(); |
| }, |
| |
| handleTimelineChange: function(event) |
| { |
| this.video.currentTime = this.controls.timeline.value; |
| this.updateProgress(); |
| }, |
| |
| handleTimelineDown: function(event) |
| { |
| this.controls.thumbnail.classList.add(this.ClassNames.show); |
| }, |
| |
| handleTimelineUp: function(event) |
| { |
| this.controls.thumbnail.classList.remove(this.ClassNames.show); |
| }, |
| |
| handleTimelineMouseOver: function(event) |
| { |
| this.controls.thumbnail.classList.add(this.ClassNames.show); |
| }, |
| |
| handleTimelineMouseOut: function(event) |
| { |
| this.controls.thumbnail.classList.remove(this.ClassNames.show); |
| }, |
| |
| handleTimelineMouseMove: function(event) |
| { |
| if (this.controls.thumbnail.classList.contains(this.ClassNames.hidden)) |
| return; |
| |
| this.updateTimelineMetricsIfNeeded(); |
| this.controls.thumbnail.classList.add(this.ClassNames.show); |
| var localPoint = webkitConvertPointFromPageToNode(this.controls.timeline, new WebKitPoint(event.clientX, event.clientY)); |
| var percent = (localPoint.x - this.timelineLeft) / this.timelineWidth; |
| percent = Math.max(Math.min(1, percent), 0); |
| this.controls.thumbnail.style.left = percent * 100 + '%'; |
| |
| var thumbnailTime = percent * this.video.duration; |
| for (var i = 0; i < this.video.textTracks.length; ++i) { |
| var track = this.video.textTracks[i]; |
| if (!this.trackHasThumbnails(track)) |
| continue; |
| |
| if (!track.cues) |
| continue; |
| |
| for (var j = 0; j < track.cues.length; ++j) { |
| var cue = track.cues[j]; |
| if (thumbnailTime >= cue.startTime && thumbnailTime < cue.endTime) { |
| this.controls.thumbnailImage.src = cue.text; |
| return; |
| } |
| } |
| } |
| }, |
| |
| handleTimelineMouseDown: function(event) |
| { |
| this.scrubbing = true; |
| }, |
| |
| handleTimelineMouseUp: function(event) |
| { |
| this.scrubbing = false; |
| }, |
| |
| handleTimelineKeyDown: function(event) |
| { |
| if (event.keyCode == this.KeyCodes.left) |
| this.controls.timeline.value = this.decrementTimelineValue(); |
| else if (event.keyCode == this.KeyCodes.right) |
| this.controls.timeline.value = this.incrementTimelineValue(); |
| }, |
| |
| handleMuteButtonClicked: function(event) |
| { |
| this.video.muted = !this.video.muted; |
| if (this.video.muted) |
| this.controls.muteButton.setAttribute('aria-checked', 'true'); |
| else |
| this.controls.muteButton.setAttribute('aria-checked', 'false'); |
| this.drawVolumeBackground(); |
| return true; |
| }, |
| |
| handleMuteBoxOver: function(event) |
| { |
| this.drawVolumeBackground(); |
| }, |
| |
| handleMinButtonClicked: function(event) |
| { |
| if (this.video.muted) { |
| this.video.muted = false; |
| this.controls.muteButton.setAttribute('aria-checked', 'false'); |
| } |
| this.video.volume = 0; |
| return true; |
| }, |
| |
| handleMaxButtonClicked: function(event) |
| { |
| if (this.video.muted) { |
| this.video.muted = false; |
| this.controls.muteButton.setAttribute('aria-checked', 'false'); |
| } |
| this.video.volume = 1; |
| }, |
| |
| updateVideoVolume: function() |
| { |
| if (this.video.muted) { |
| this.video.muted = false; |
| this.controls.muteButton.setAttribute('aria-checked', 'false'); |
| } |
| this.video.volume = this.controls.volume.value; |
| this.controls.volume.setAttribute('aria-valuetext', `${parseInt(this.controls.volume.value * 100)}%`); |
| }, |
| |
| handleVolumeSliderInput: function(event) |
| { |
| this.updateVideoVolume(); |
| this.drawVolumeBackground(); |
| }, |
| |
| handleVolumeSliderChange: function(event) |
| { |
| this.updateVideoVolume(); |
| }, |
| |
| handleVolumeSliderMouseDown: function(event) |
| { |
| this.isVolumeSliderActive = true; |
| this.drawVolumeBackground(); |
| }, |
| |
| handleVolumeSliderMouseUp: function(event) |
| { |
| this.isVolumeSliderActive = false; |
| this.drawVolumeBackground(); |
| }, |
| |
| handleCaptionButtonClicked: function(event) |
| { |
| if (this.captionMenu) |
| this.destroyCaptionMenu(); |
| else |
| this.buildCaptionMenu(); |
| return true; |
| }, |
| |
| hasVideo: function() |
| { |
| return this.video.videoTracks && this.video.videoTracks.length; |
| }, |
| |
| updateFullscreenButtons: function() |
| { |
| var shouldBeHidden = !this.video.webkitSupportsFullscreen || !this.hasVideo(); |
| this.controls.fullscreenButton.classList.toggle(this.ClassNames.hidden, shouldBeHidden && !this.isFullScreen()); |
| this.updatePictureInPictureButton(); |
| this.setNeedsUpdateForDisplayedWidth(); |
| this.updateLayoutForDisplayedWidth(); |
| }, |
| |
| handleFullscreenButtonClicked: function(event) |
| { |
| if (this.isFullScreen()) |
| this.video.webkitExitFullscreen(); |
| else |
| this.video.webkitEnterFullscreen(); |
| return true; |
| }, |
| |
| updateWirelessTargetPickerButton: function() { |
| var wirelessTargetPickerColor; |
| if (this.controls.wirelessTargetPicker.classList.contains('playing')) |
| wirelessTargetPickerColor = "-apple-wireless-playback-target-active"; |
| else |
| wirelessTargetPickerColor = "rgba(255,255,255,0.45)"; |
| if (window.devicePixelRatio == 2) |
| this.controls.wirelessTargetPicker.style.backgroundImage = "url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 15' stroke='" + wirelessTargetPickerColor + "'><defs> <clipPath fill-rule='evenodd' id='cut-hole'><path d='M 0,0.5 L 16,0.5 L 16,15.5 L 0,15.5 z M 0,14.5 L 16,14.5 L 8,5 z'/></clipPath></defs><rect fill='none' clip-path='url(#cut-hole)' x='0.5' y='2' width='15' height='8'/><path stroke='none' fill='" + wirelessTargetPickerColor +"' d='M 3.5,13.25 L 12.5,13.25 L 8,8 z'/></svg>\")"; |
| else |
| this.controls.wirelessTargetPicker.style.backgroundImage = "url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 15' stroke='" + wirelessTargetPickerColor + "'><defs> <clipPath fill-rule='evenodd' id='cut-hole'><path d='M 0,1 L 16,1 L 16,16 L 0,16 z M 0,15 L 16,15 L 8,5.5 z'/></clipPath></defs><rect fill='none' clip-path='url(#cut-hole)' x='0.5' y='2.5' width='15' height='8'/><path stroke='none' fill='" + wirelessTargetPickerColor +"' d='M 2.75,14 L 13.25,14 L 8,8.75 z'/></svg>\")"; |
| }, |
| |
| handleControlsChange: function() |
| { |
| try { |
| this.updateBase(); |
| |
| if (this.shouldHaveControls() && !this.hasControls()) |
| this.addControls(); |
| else if (!this.shouldHaveControls() && this.hasControls()) |
| this.removeControls(); |
| } catch(e) { |
| if (window.console) |
| console.error(e); |
| } |
| }, |
| |
| nextRate: function() |
| { |
| return Math.min(this.MaximumSeekRate, Math.abs(this.video.playbackRate * 2)); |
| }, |
| |
| handleSeekBackMouseDown: function(event) |
| { |
| this.actionAfterSeeking = (this.canPlay() ? Controller.PauseAfterSeeking : Controller.PlayAfterSeeking); |
| this.video.play(); |
| this.video.playbackRate = this.nextRate() * -1; |
| this.seekInterval = setInterval(this.seekBackFaster.bind(this), this.SeekDelay); |
| }, |
| |
| seekBackFaster: function() |
| { |
| this.video.playbackRate = this.nextRate() * -1; |
| }, |
| |
| handleSeekBackMouseUp: function(event) |
| { |
| this.video.playbackRate = this.video.defaultPlaybackRate; |
| if (this.actionAfterSeeking === Controller.PauseAfterSeeking) |
| this.video.pause(); |
| else if (this.actionAfterSeeking === Controller.PlayAfterSeeking) |
| this.video.play(); |
| if (this.seekInterval) |
| clearInterval(this.seekInterval); |
| }, |
| |
| handleSeekForwardMouseDown: function(event) |
| { |
| this.actionAfterSeeking = (this.canPlay() ? Controller.PauseAfterSeeking : Controller.PlayAfterSeeking); |
| this.video.play(); |
| this.video.playbackRate = this.nextRate(); |
| this.seekInterval = setInterval(this.seekForwardFaster.bind(this), this.SeekDelay); |
| }, |
| |
| seekForwardFaster: function() |
| { |
| this.video.playbackRate = this.nextRate(); |
| }, |
| |
| handleSeekForwardMouseUp: function(event) |
| { |
| this.video.playbackRate = this.video.defaultPlaybackRate; |
| if (this.actionAfterSeeking === Controller.PauseAfterSeeking) |
| this.video.pause(); |
| else if (this.actionAfterSeeking === Controller.PlayAfterSeeking) |
| this.video.play(); |
| if (this.seekInterval) |
| clearInterval(this.seekInterval); |
| }, |
| |
| updateDuration: function() |
| { |
| var duration = this.video.duration; |
| this.controls.timeline.min = 0; |
| this.controls.timeline.max = duration; |
| |
| this.setIsLive(duration === Number.POSITIVE_INFINITY); |
| |
| var timeControls = [this.controls.currentTime, this.controls.remainingTime, this.currentTimeClone, this.remainingTimeClone]; |
| |
| function removeTimeClass(className) { |
| for (let element of timeControls) |
| element.classList.remove(className); |
| } |
| |
| function addTimeClass(className) { |
| for (let element of timeControls) |
| element.classList.add(className); |
| } |
| |
| // Reset existing style. |
| removeTimeClass(this.ClassNames.threeDigitTime); |
| removeTimeClass(this.ClassNames.fourDigitTime); |
| removeTimeClass(this.ClassNames.fiveDigitTime); |
| removeTimeClass(this.ClassNames.sixDigitTime); |
| |
| if (duration >= 60*60*10) |
| addTimeClass(this.ClassNames.sixDigitTime); |
| else if (duration >= 60*60) |
| addTimeClass(this.ClassNames.fiveDigitTime); |
| else if (duration >= 60*10) |
| addTimeClass(this.ClassNames.fourDigitTime); |
| else |
| addTimeClass(this.ClassNames.threeDigitTime); |
| }, |
| |
| progressFillStyle: function(context) |
| { |
| var height = this.timelineHeight; |
| var gradient = context.createLinearGradient(0, 0, 0, height); |
| gradient.addColorStop(0, 'rgb(2, 2, 2)'); |
| gradient.addColorStop(1, 'rgb(23, 23, 23)'); |
| return gradient; |
| }, |
| |
| updateProgress: function() |
| { |
| this.updateTimelineMetricsIfNeeded(); |
| this.drawTimelineBackground(); |
| }, |
| |
| addRoundedRect: function(ctx, x, y, width, height, radius) { |
| ctx.moveTo(x + radius, y); |
| ctx.arcTo(x + width, y, x + width, y + radius, radius); |
| ctx.lineTo(x + width, y + height - radius); |
| ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius); |
| ctx.lineTo(x + radius, y + height); |
| ctx.arcTo(x, y + height, x, y + height - radius, radius); |
| ctx.lineTo(x, y + radius); |
| ctx.arcTo(x, y, x + radius, y, radius); |
| }, |
| |
| drawTimelineBackground: function() { |
| var dpr = window.devicePixelRatio; |
| var width = this.timelineWidth * dpr; |
| var height = this.timelineHeight * dpr; |
| |
| if (!width || !height) |
| return; |
| |
| var played = this.controls.timeline.value / this.controls.timeline.max; |
| var buffered = 0; |
| for (var i = 0, end = this.video.buffered.length; i < end; ++i) |
| buffered = Math.max(this.video.buffered.end(i), buffered); |
| |
| buffered /= this.video.duration; |
| |
| var ctx = document.getCSSCanvasContext('2d', this.timelineContextName, width, height); |
| |
| width /= dpr; |
| height /= dpr; |
| |
| ctx.save(); |
| ctx.scale(dpr, dpr); |
| ctx.clearRect(0, 0, width, height); |
| |
| var timelineHeight = 3; |
| var trackHeight = 1; |
| var scrubberWidth = 3; |
| var scrubberHeight = 15; |
| var borderSize = 2; |
| var scrubberPosition = Math.max(0, Math.min(width - scrubberWidth, Math.round(width * played))); |
| |
| // Draw buffered section. |
| ctx.save(); |
| if (this.isAudio()) |
| ctx.fillStyle = "rgb(71, 71, 71)"; |
| else |
| ctx.fillStyle = "rgb(30, 30, 30)"; |
| ctx.fillRect(1, 8, Math.round(width * buffered) - borderSize, trackHeight); |
| ctx.restore(); |
| |
| // Draw timeline border. |
| ctx.save(); |
| ctx.beginPath(); |
| this.addRoundedRect(ctx, scrubberPosition, 7, width - scrubberPosition, timelineHeight, timelineHeight / 2.0); |
| this.addRoundedRect(ctx, scrubberPosition + 1, 8, width - scrubberPosition - borderSize , trackHeight, trackHeight / 2.0); |
| ctx.closePath(); |
| ctx.clip("evenodd"); |
| if (this.isAudio()) |
| ctx.fillStyle = "rgb(71, 71, 71)"; |
| else |
| ctx.fillStyle = "rgb(30, 30, 30)"; |
| ctx.fillRect(0, 0, width, height); |
| ctx.restore(); |
| |
| // Draw played section. |
| ctx.save(); |
| ctx.beginPath(); |
| this.addRoundedRect(ctx, 0, 7, width, timelineHeight, timelineHeight / 2.0); |
| ctx.closePath(); |
| ctx.clip(); |
| if (this.isAudio()) |
| ctx.fillStyle = "rgb(116, 116, 116)"; |
| else |
| ctx.fillStyle = "rgb(75, 75, 75)"; |
| ctx.fillRect(0, 0, width * played, height); |
| ctx.restore(); |
| |
| // Draw the scrubber. |
| ctx.save(); |
| ctx.clearRect(scrubberPosition - 1, 0, scrubberWidth + borderSize, height, 0); |
| ctx.beginPath(); |
| this.addRoundedRect(ctx, scrubberPosition, 1, scrubberWidth, scrubberHeight, 1); |
| ctx.closePath(); |
| ctx.clip(); |
| if (this.isAudio()) |
| ctx.fillStyle = "rgb(181, 181, 181)"; |
| else |
| ctx.fillStyle = "rgb(140, 140, 140)"; |
| ctx.fillRect(0, 0, width, height); |
| ctx.restore(); |
| |
| ctx.restore(); |
| }, |
| |
| drawVolumeBackground: function() { |
| var dpr = window.devicePixelRatio; |
| var width = this.controls.volume.offsetWidth * dpr; |
| var height = this.controls.volume.offsetHeight * dpr; |
| |
| if (!width || !height) |
| return; |
| |
| var ctx = document.getCSSCanvasContext('2d', this.volumeContextName, width, height); |
| |
| width /= dpr; |
| height /= dpr; |
| |
| ctx.save(); |
| ctx.scale(dpr, dpr); |
| ctx.clearRect(0, 0, width, height); |
| |
| var seekerPosition = this.controls.volume.value; |
| var trackHeight = 1; |
| var timelineHeight = 3; |
| var scrubberRadius = 3.5; |
| var scrubberDiameter = 2 * scrubberRadius; |
| var borderSize = 2; |
| |
| var scrubberPosition = Math.round(seekerPosition * (width - scrubberDiameter - borderSize)); |
| |
| |
| // Draw portion of volume under slider thumb. |
| ctx.save(); |
| ctx.beginPath(); |
| this.addRoundedRect(ctx, 0, 3, scrubberPosition + 2, timelineHeight, timelineHeight / 2.0); |
| ctx.closePath(); |
| ctx.clip(); |
| ctx.fillStyle = "rgb(75, 75, 75)"; |
| ctx.fillRect(0, 0, width, height); |
| ctx.restore(); |
| |
| // Draw portion of volume above slider thumb. |
| ctx.save(); |
| ctx.beginPath(); |
| this.addRoundedRect(ctx, scrubberPosition, 3, width - scrubberPosition, timelineHeight, timelineHeight / 2.0); |
| ctx.closePath(); |
| ctx.clip(); |
| ctx.fillStyle = "rgb(30, 30, 30)"; |
| ctx.fillRect(0, 0, width, height); |
| ctx.restore(); |
| |
| // Clear a hole in the slider for the scrubber. |
| ctx.save(); |
| ctx.beginPath(); |
| this.addRoundedRect(ctx, scrubberPosition, 0, scrubberDiameter + borderSize, height, (scrubberDiameter + borderSize) / 2.0); |
| ctx.closePath(); |
| ctx.clip(); |
| ctx.clearRect(0, 0, width, height); |
| ctx.restore(); |
| |
| // Draw scrubber. |
| ctx.save(); |
| ctx.beginPath(); |
| this.addRoundedRect(ctx, scrubberPosition + 1, 1, scrubberDiameter, scrubberDiameter, scrubberRadius); |
| ctx.closePath(); |
| ctx.clip(); |
| if (this.isVolumeSliderActive) |
| ctx.fillStyle = "white"; |
| else |
| ctx.fillStyle = "rgb(140, 140, 140)"; |
| ctx.fillRect(0, 0, width, height); |
| ctx.restore(); |
| |
| ctx.restore(); |
| }, |
| |
| formatTimeDescription: function(time) |
| { |
| if (isNaN(time)) |
| time = 0; |
| var absTime = Math.abs(time); |
| var intSeconds = Math.floor(absTime % 60).toFixed(0); |
| var intMinutes = Math.floor((absTime / 60) % 60).toFixed(0); |
| var intHours = Math.floor(absTime / (60 * 60)).toFixed(0); |
| |
| var secondString = intSeconds == 1 ? 'Second' : 'Seconds'; |
| var minuteString = intMinutes == 1 ? 'Minute' : 'Minutes'; |
| var hourString = intHours == 1 ? 'Hour' : 'Hours'; |
| if (intHours > 0) |
| return `${intHours} ${this.UIString(hourString)}, ${intMinutes} ${this.UIString(minuteString)}, ${intSeconds} ${this.UIString(secondString)}`; |
| if (intMinutes > 0) |
| return `${intMinutes} ${this.UIString(minuteString)}, ${intSeconds} ${this.UIString(secondString)}`; |
| return `${intSeconds} ${this.UIString(secondString)}`; |
| }, |
| |
| formatTime: function(time) |
| { |
| if (isNaN(time)) |
| time = 0; |
| var absTime = Math.abs(time); |
| var intSeconds = Math.floor(absTime % 60).toFixed(0); |
| var intMinutes = Math.floor((absTime / 60) % 60).toFixed(0); |
| var intHours = Math.floor(absTime / (60 * 60)).toFixed(0); |
| var sign = time < 0 ? '-' : String(); |
| |
| if (intHours > 0) |
| return sign + intHours + ':' + String('00' + intMinutes).slice(-2) + ":" + String('00' + intSeconds).slice(-2); |
| |
| return sign + String('00' + intMinutes).slice(-2) + ":" + String('00' + intSeconds).slice(-2) |
| }, |
| |
| updatePlaying: function() |
| { |
| this.setPlaying(!this.canPlay()); |
| }, |
| |
| setPlaying: function(isPlaying) |
| { |
| if (!this.video.controls && !this.isFullScreen()) |
| return; |
| |
| if (this.isPlaying === isPlaying) |
| return; |
| this.isPlaying = isPlaying; |
| |
| if (!isPlaying) { |
| this.controls.panel.classList.add(this.ClassNames.paused); |
| if (this.controls.panelBackground) |
| this.controls.panelBackground.classList.add(this.ClassNames.paused); |
| this.controls.playButton.classList.add(this.ClassNames.paused); |
| this.controls.playButton.setAttribute('aria-label', this.UIString('Play')); |
| this.showControls(); |
| } else { |
| this.controls.panel.classList.remove(this.ClassNames.paused); |
| if (this.controls.panelBackground) |
| this.controls.panelBackground.classList.remove(this.ClassNames.paused); |
| this.controls.playButton.classList.remove(this.ClassNames.paused); |
| this.controls.playButton.setAttribute('aria-label', this.UIString('Pause')); |
| this.resetHideControlsTimer(); |
| this.canToggleShowControlsButton = true; |
| } |
| }, |
| |
| updateForShowingControls: function() |
| { |
| this.updateLayoutForDisplayedWidth(); |
| this.setNeedsTimelineMetricsUpdate(); |
| this.updateTime(); |
| this.updateProgress(); |
| this.drawVolumeBackground(); |
| this.drawTimelineBackground(); |
| this.controls.panel.classList.add(this.ClassNames.show); |
| this.controls.panel.classList.remove(this.ClassNames.hidden); |
| if (this.controls.panelBackground) { |
| this.controls.panelBackground.classList.add(this.ClassNames.show); |
| this.controls.panelBackground.classList.remove(this.ClassNames.hidden); |
| } |
| }, |
| |
| showShowControlsButton: function (shouldShow) { |
| this.showControlsButton.hidden = !shouldShow; |
| if (shouldShow && this.shouldHaveControls()) |
| this.showControlsButton.focus(); |
| }, |
| |
| showControls: function(focusControls) |
| { |
| this.updateShouldListenForPlaybackTargetAvailabilityEvent(); |
| if (!this.video.controls && !this.isFullScreen()) |
| return; |
| |
| this.updateForShowingControls(); |
| if (this.shouldHaveControls() && !this.controls.panel.parentElement) { |
| this.base.appendChild(this.controls.inlinePlaybackPlaceholder); |
| this.base.appendChild(this.controls.panel); |
| if (focusControls) |
| this.controls.playButton.focus(); |
| } |
| this.showShowControlsButton(false); |
| }, |
| |
| hideControls: function() |
| { |
| if (this.controlsAlwaysVisible()) |
| return; |
| |
| this.clearHideControlsTimer(); |
| this.updateShouldListenForPlaybackTargetAvailabilityEvent(); |
| this.controls.panel.classList.remove(this.ClassNames.show); |
| if (this.controls.panelBackground) |
| this.controls.panelBackground.classList.remove(this.ClassNames.show); |
| this.showShowControlsButton(this.isPlayable() && this.isPlaying && this.canToggleShowControlsButton); |
| }, |
| |
| setNeedsUpdateForDisplayedWidth: function() |
| { |
| this.currentDisplayWidth = 0; |
| }, |
| |
| scheduleUpdateLayoutForDisplayedWidth: function() |
| { |
| setTimeout(this.updateLayoutForDisplayedWidth.bind(this), 0); |
| }, |
| |
| isControlVisible: function(control) |
| { |
| if (!control) |
| return false; |
| if (!this.root.contains(control)) |
| return false; |
| return !control.classList.contains(this.ClassNames.hidden) |
| }, |
| |
| updateLayoutForDisplayedWidth: function() |
| { |
| if (!this.controls || !this.controls.panel) |
| return; |
| |
| var visibleWidth = this.controls.panel.getBoundingClientRect().width; |
| if (this._pageScaleFactor > 1) |
| visibleWidth *= this._pageScaleFactor; |
| |
| if (visibleWidth <= 0 || visibleWidth == this.currentDisplayWidth) |
| return; |
| |
| this.currentDisplayWidth = visibleWidth; |
| |
| // Filter all the buttons which are not explicitly hidden. |
| var buttons = [this.controls.playButton, this.controls.rewindButton, this.controls.captionButton, |
| this.controls.fullscreenButton, this.controls.pictureInPictureButton, |
| this.controls.wirelessTargetPicker, this.controls.muteBox]; |
| var visibleButtons = buttons.filter(this.isControlVisible, this); |
| |
| // This tells us how much room we need in order to display every visible button. |
| var visibleButtonWidth = this.ButtonWidth * visibleButtons.length; |
| |
| var currentTimeWidth = this.currentTimeClone.getBoundingClientRect().width; |
| var remainingTimeWidth = this.remainingTimeClone.getBoundingClientRect().width; |
| |
| // Check if there is enough room for the scrubber. |
| var shouldDropTimeline = (visibleWidth - visibleButtonWidth - currentTimeWidth - remainingTimeWidth) < this.MinimumTimelineWidth; |
| this.controls.timeline.classList.toggle(this.ClassNames.dropped, shouldDropTimeline); |
| this.controls.currentTime.classList.toggle(this.ClassNames.dropped, shouldDropTimeline); |
| this.controls.thumbnailTrack.classList.toggle(this.ClassNames.dropped, shouldDropTimeline); |
| this.controls.remainingTime.classList.toggle(this.ClassNames.dropped, shouldDropTimeline); |
| |
| // Then controls in the following order: |
| var removeOrder = [this.controls.wirelessTargetPicker, this.controls.pictureInPictureButton, |
| this.controls.captionButton, this.controls.muteBox, this.controls.rewindButton, |
| this.controls.fullscreenButton]; |
| removeOrder.forEach(function(control) { |
| var shouldDropControl = visibleWidth < visibleButtonWidth && this.isControlVisible(control); |
| control.classList.toggle(this.ClassNames.dropped, shouldDropControl); |
| if (shouldDropControl) |
| visibleButtonWidth -= this.ButtonWidth; |
| }, this); |
| }, |
| |
| controlsAlwaysVisible: function() |
| { |
| if (this.presentationMode() === 'picture-in-picture') |
| return true; |
| |
| return this.isAudio() || this.currentPlaybackTargetIsWireless() || this.scrubbing; |
| }, |
| |
| controlsAreHidden: function() |
| { |
| return !this.controlsAlwaysVisible() && !this.controls.panel.classList.contains(this.ClassNames.show) && !this.controls.panel.parentElement; |
| }, |
| |
| removeControls: function() |
| { |
| if (this.controls.panel.parentNode) |
| this.controls.panel.parentNode.removeChild(this.controls.panel); |
| this.destroyCaptionMenu(); |
| }, |
| |
| addControls: function() |
| { |
| this.base.appendChild(this.controls.inlinePlaybackPlaceholder); |
| this.base.appendChild(this.controls.panel); |
| this.updateControls(); |
| }, |
| |
| hasControls: function() |
| { |
| return this.controls.panel.parentElement; |
| }, |
| |
| updateTime: function() |
| { |
| var currentTime = this.video.currentTime; |
| var timeRemaining = currentTime - this.video.duration; |
| this.currentTimeClone.innerText = this.controls.currentTime.innerText = this.formatTime(currentTime); |
| this.controls.currentTime.setAttribute('aria-label', `${this.UIString('Elapsed')} ${this.formatTimeDescription(currentTime)}`); |
| this.controls.timeline.value = this.video.currentTime; |
| this.remainingTimeClone.innerText = this.controls.remainingTime.innerText = this.formatTime(timeRemaining); |
| this.controls.remainingTime.setAttribute('aria-label', `${this.UIString('Remaining')} ${this.formatTimeDescription(timeRemaining)}`); |
| |
| // Mark the timeline value in percentage format in accessibility. |
| var timeElapsedPercent = currentTime / this.video.duration; |
| timeElapsedPercent = Math.max(Math.min(1, timeElapsedPercent), 0); |
| this.controls.timeline.setAttribute('aria-valuetext', `${parseInt(timeElapsedPercent * 100)}%`); |
| }, |
| |
| updateControlsWhileScrubbing: function() |
| { |
| if (!this.scrubbing) |
| return; |
| |
| var currentTime = (this.controls.timeline.value / this.controls.timeline.max) * this.video.duration; |
| var timeRemaining = currentTime - this.video.duration; |
| this.currentTimeClone.innerText = this.controls.currentTime.innerText = this.formatTime(currentTime); |
| this.remainingTimeClone.innerText = this.controls.remainingTime.innerText = this.formatTime(timeRemaining); |
| this.drawTimelineBackground(); |
| }, |
| |
| updateReadyState: function() |
| { |
| this.updateStatusDisplay(); |
| }, |
| |
| setStatusHidden: function(hidden) |
| { |
| if (this.statusHidden === hidden) |
| return; |
| |
| this.statusHidden = hidden; |
| |
| if (hidden) { |
| this.controls.statusDisplay.classList.add(this.ClassNames.hidden); |
| this.controls.currentTime.classList.remove(this.ClassNames.hidden); |
| this.controls.timeline.classList.remove(this.ClassNames.hidden); |
| this.controls.remainingTime.classList.remove(this.ClassNames.hidden); |
| this.setNeedsTimelineMetricsUpdate(); |
| this.showControls(); |
| } else { |
| this.controls.statusDisplay.classList.remove(this.ClassNames.hidden); |
| this.controls.currentTime.classList.add(this.ClassNames.hidden); |
| this.controls.timeline.classList.add(this.ClassNames.hidden); |
| this.controls.remainingTime.classList.add(this.ClassNames.hidden); |
| this.hideControls(); |
| } |
| this.updateWirelessTargetAvailable(); |
| }, |
| |
| trackHasThumbnails: function(track) |
| { |
| return track.kind === 'thumbnails' || (track.kind === 'metadata' && track.label === 'thumbnails'); |
| }, |
| |
| updateThumbnail: function() |
| { |
| for (var i = 0; i < this.video.textTracks.length; ++i) { |
| var track = this.video.textTracks[i]; |
| if (this.trackHasThumbnails(track)) { |
| this.controls.thumbnail.classList.remove(this.ClassNames.hidden); |
| return; |
| } |
| } |
| |
| this.controls.thumbnail.classList.add(this.ClassNames.hidden); |
| }, |
| |
| updateCaptionButton: function() |
| { |
| var audioTracks = this.host.sortedTrackListForMenu(this.video.audioTracks); |
| var textTracks = this.host.sortedTrackListForMenu(this.video.textTracks); |
| |
| if ((textTracks && textTracks.length) || (audioTracks && audioTracks.length > 1)) |
| this.controls.captionButton.classList.remove(this.ClassNames.hidden); |
| else |
| this.controls.captionButton.classList.add(this.ClassNames.hidden); |
| this.setNeedsUpdateForDisplayedWidth(); |
| this.updateLayoutForDisplayedWidth(); |
| }, |
| |
| updateCaptionContainer: function() |
| { |
| if (!this.host.textTrackContainer) |
| return; |
| |
| var hasClosedCaptions = this.video.webkitHasClosedCaptions; |
| var hasHiddenClass = this.host.textTrackContainer.classList.contains(this.ClassNames.hidden); |
| |
| if (hasClosedCaptions && hasHiddenClass) |
| this.host.textTrackContainer.classList.remove(this.ClassNames.hidden); |
| else if (!hasClosedCaptions && !hasHiddenClass) |
| this.host.textTrackContainer.classList.add(this.ClassNames.hidden); |
| |
| this.updateBase(); |
| this.host.updateTextTrackContainer(); |
| }, |
| |
| buildCaptionMenu: function() |
| { |
| var audioTracks = this.host.sortedTrackListForMenu(this.video.audioTracks); |
| var textTracks = this.host.sortedTrackListForMenu(this.video.textTracks); |
| |
| if ((!textTracks || !textTracks.length) && (!audioTracks || !audioTracks.length)) |
| return; |
| |
| this.captionMenu = document.createElement('div'); |
| this.captionMenu.setAttribute('pseudo', '-webkit-media-controls-closed-captions-container'); |
| this.captionMenu.setAttribute('id', 'audioAndTextTrackMenu'); |
| this.base.appendChild(this.captionMenu); |
| this.captionMenuItems = []; |
| |
| var offItem = this.host.captionMenuOffItem; |
| var automaticItem = this.host.captionMenuAutomaticItem; |
| var displayMode = this.host.captionDisplayMode; |
| |
| var list = document.createElement('div'); |
| this.captionMenu.appendChild(list); |
| list.classList.add(this.ClassNames.list); |
| |
| if (audioTracks && audioTracks.length > 1) { |
| var heading = document.createElement('h3'); |
| heading.id = 'webkitMediaControlsAudioTrackHeading'; // for AX menu label |
| list.appendChild(heading); |
| heading.innerText = this.UIString('Audio'); |
| |
| var ul = document.createElement('ul'); |
| ul.setAttribute('role', 'menu'); |
| ul.setAttribute('aria-labelledby', 'webkitMediaControlsAudioTrackHeading'); |
| list.appendChild(ul); |
| |
| for (var i = 0; i < audioTracks.length; ++i) { |
| var menuItem = document.createElement('li'); |
| menuItem.setAttribute('role', 'menuitemradio'); |
| menuItem.setAttribute('tabindex', '-1'); |
| this.captionMenuItems.push(menuItem); |
| this.listenFor(menuItem, 'click', this.audioTrackItemSelected); |
| this.listenFor(menuItem, 'keyup', this.handleAudioTrackItemKeyUp); |
| ul.appendChild(menuItem); |
| |
| var track = audioTracks[i]; |
| menuItem.innerText = this.host.displayNameForTrack(track); |
| menuItem.track = track; |
| |
| var itemCheckmark = document.createElement("img"); |
| itemCheckmark.classList.add("checkmark-container"); |
| menuItem.insertBefore(itemCheckmark, menuItem.firstChild); |
| |
| if (track.enabled) { |
| menuItem.classList.add(this.ClassNames.selected); |
| menuItem.setAttribute('tabindex', '0'); |
| menuItem.setAttribute('aria-checked', 'true'); |
| } |
| } |
| } |
| |
| if (textTracks && textTracks.length > 2) { |
| var heading = document.createElement('h3'); |
| heading.id = 'webkitMediaControlsClosedCaptionsHeading'; // for AX menu label |
| list.appendChild(heading); |
| heading.innerText = this.UIString('Subtitles'); |
| |
| var ul = document.createElement('ul'); |
| ul.setAttribute('role', 'menu'); |
| ul.setAttribute('aria-labelledby', 'webkitMediaControlsClosedCaptionsHeading'); |
| list.appendChild(ul); |
| |
| for (var i = 0; i < textTracks.length; ++i) { |
| var menuItem = document.createElement('li'); |
| menuItem.setAttribute('role', 'menuitemradio'); |
| menuItem.setAttribute('tabindex', '-1'); |
| this.captionMenuItems.push(menuItem); |
| this.listenFor(menuItem, 'click', this.captionItemSelected); |
| this.listenFor(menuItem, 'keyup', this.handleCaptionItemKeyUp); |
| ul.appendChild(menuItem); |
| |
| var track = textTracks[i]; |
| menuItem.innerText = this.host.displayNameForTrack(track); |
| menuItem.track = track; |
| |
| var itemCheckmark = document.createElement("img"); |
| itemCheckmark.classList.add("checkmark-container"); |
| menuItem.insertBefore(itemCheckmark, menuItem.firstChild); |
| |
| if (track === offItem) { |
| var offMenu = menuItem; |
| continue; |
| } |
| |
| if (track === automaticItem) { |
| if (displayMode === 'automatic') { |
| menuItem.classList.add(this.ClassNames.selected); |
| menuItem.setAttribute('tabindex', '0'); |
| menuItem.setAttribute('aria-checked', 'true'); |
| } |
| continue; |
| } |
| |
| if (displayMode != 'automatic' && track.mode === 'showing') { |
| var trackMenuItemSelected = true; |
| menuItem.classList.add(this.ClassNames.selected); |
| menuItem.setAttribute('tabindex', '0'); |
| menuItem.setAttribute('aria-checked', 'true'); |
| } |
| |
| } |
| |
| if (offMenu && (displayMode === 'forced-only' || displayMode === 'manual') && !trackMenuItemSelected) { |
| offMenu.classList.add(this.ClassNames.selected); |
| offMenu.setAttribute('tabindex', '0'); |
| offMenu.setAttribute('aria-checked', 'true'); |
| } |
| } |
| |
| // focus first selected menuitem |
| for (var i = 0, c = this.captionMenuItems.length; i < c; i++) { |
| var item = this.captionMenuItems[i]; |
| if (item.classList.contains(this.ClassNames.selected)) { |
| item.focus(); |
| break; |
| } |
| } |
| |
| }, |
| |
| captionItemSelected: function(event) |
| { |
| this.host.setSelectedTextTrack(event.target.track); |
| this.destroyCaptionMenu(); |
| }, |
| |
| focusSiblingCaptionItem: function(event) |
| { |
| var currentItem = event.target; |
| var pendingItem = false; |
| switch(event.keyCode) { |
| case this.KeyCodes.left: |
| case this.KeyCodes.up: |
| pendingItem = currentItem.previousSibling; |
| break; |
| case this.KeyCodes.right: |
| case this.KeyCodes.down: |
| pendingItem = currentItem.nextSibling; |
| break; |
| } |
| if (pendingItem) { |
| currentItem.setAttribute('tabindex', '-1'); |
| pendingItem.setAttribute('tabindex', '0'); |
| pendingItem.focus(); |
| } |
| }, |
| |
| handleCaptionItemKeyUp: function(event) |
| { |
| switch (event.keyCode) { |
| case this.KeyCodes.enter: |
| case this.KeyCodes.space: |
| this.captionItemSelected(event); |
| break; |
| case this.KeyCodes.escape: |
| this.destroyCaptionMenu(); |
| break; |
| case this.KeyCodes.left: |
| case this.KeyCodes.up: |
| case this.KeyCodes.right: |
| case this.KeyCodes.down: |
| this.focusSiblingCaptionItem(event); |
| break; |
| default: |
| return; |
| } |
| // handled |
| event.stopPropagation(); |
| event.preventDefault(); |
| }, |
| |
| audioTrackItemSelected: function(event) |
| { |
| for (var i = 0; i < this.video.audioTracks.length; ++i) { |
| var track = this.video.audioTracks[i]; |
| track.enabled = (track == event.target.track); |
| } |
| |
| this.destroyCaptionMenu(); |
| }, |
| |
| focusSiblingAudioTrackItem: function(event) |
| { |
| var currentItem = event.target; |
| var pendingItem = false; |
| switch(event.keyCode) { |
| case this.KeyCodes.left: |
| case this.KeyCodes.up: |
| pendingItem = currentItem.previousSibling; |
| break; |
| case this.KeyCodes.right: |
| case this.KeyCodes.down: |
| pendingItem = currentItem.nextSibling; |
| break; |
| } |
| if (pendingItem) { |
| currentItem.setAttribute('tabindex', '-1'); |
| pendingItem.setAttribute('tabindex', '0'); |
| pendingItem.focus(); |
| } |
| }, |
| |
| handleAudioTrackItemKeyUp: function(event) |
| { |
| switch (event.keyCode) { |
| case this.KeyCodes.enter: |
| case this.KeyCodes.space: |
| this.audioTrackItemSelected(event); |
| break; |
| case this.KeyCodes.escape: |
| this.destroyCaptionMenu(); |
| break; |
| case this.KeyCodes.left: |
| case this.KeyCodes.up: |
| case this.KeyCodes.right: |
| case this.KeyCodes.down: |
| this.focusSiblingAudioTrackItem(event); |
| break; |
| default: |
| return; |
| } |
| // handled |
| event.stopPropagation(); |
| event.preventDefault(); |
| }, |
| |
| destroyCaptionMenu: function() |
| { |
| if (!this.captionMenu) |
| return; |
| |
| this.captionMenuItems.forEach(function(item){ |
| this.stopListeningFor(item, 'click', this.captionItemSelected); |
| this.stopListeningFor(item, 'keyup', this.handleCaptionItemKeyUp); |
| }, this); |
| |
| // FKA and AX: focus the trigger before destroying the element with focus |
| if (this.controls.captionButton) |
| this.controls.captionButton.focus(); |
| |
| if (this.captionMenu.parentNode) |
| this.captionMenu.parentNode.removeChild(this.captionMenu); |
| delete this.captionMenu; |
| delete this.captionMenuItems; |
| }, |
| |
| updateHasAudio: function() |
| { |
| if (this.video.audioTracks.length && !this.currentPlaybackTargetIsWireless()) |
| this.controls.muteBox.classList.remove(this.ClassNames.hidden); |
| else |
| this.controls.muteBox.classList.add(this.ClassNames.hidden); |
| |
| this.setNeedsUpdateForDisplayedWidth(); |
| this.updateLayoutForDisplayedWidth(); |
| }, |
| |
| updateHasVideo: function() |
| { |
| this.controls.panel.classList.toggle(this.ClassNames.noVideo, !this.hasVideo()); |
| // The availability of the picture-in-picture button as well as the full-screen |
| // button depends no the value returned by hasVideo(), so make sure we invalidate |
| // the availability of both controls. |
| this.updateFullscreenButtons(); |
| }, |
| |
| updateVolume: function() |
| { |
| if (this.video.muted || !this.video.volume) { |
| this.controls.muteButton.classList.add(this.ClassNames.muted); |
| this.controls.volume.value = 0; |
| } else { |
| this.controls.muteButton.classList.remove(this.ClassNames.muted); |
| this.controls.volume.value = this.video.volume; |
| } |
| this.controls.volume.setAttribute('aria-valuetext', `${parseInt(this.controls.volume.value * 100)}%`); |
| this.drawVolumeBackground(); |
| }, |
| |
| isAudio: function() |
| { |
| return this.video instanceof HTMLAudioElement; |
| }, |
| |
| clearHideControlsTimer: function() |
| { |
| if (this.hideTimer) |
| clearTimeout(this.hideTimer); |
| this.hideTimer = null; |
| }, |
| |
| resetHideControlsTimer: function() |
| { |
| if (this.hideTimer) { |
| clearTimeout(this.hideTimer); |
| this.hideTimer = null; |
| } |
| |
| if (this.isPlaying) |
| this.hideTimer = setTimeout(this.hideControls.bind(this), this.HideControlsDelay); |
| }, |
| |
| handlePictureInPictureButtonClicked: function(event) { |
| if (!('webkitSetPresentationMode' in this.video)) |
| return; |
| |
| if (this.presentationMode() === 'picture-in-picture') |
| this.video.webkitSetPresentationMode('inline'); |
| else |
| this.video.webkitSetPresentationMode('picture-in-picture'); |
| }, |
| |
| currentPlaybackTargetIsWireless: function() { |
| if (Controller.gSimulateWirelessPlaybackTarget) |
| return true; |
| |
| if (!this.currentTargetIsWireless || this.wirelessPlaybackDisabled) |
| return false; |
| |
| return true; |
| }, |
| |
| updateShouldListenForPlaybackTargetAvailabilityEvent: function() { |
| var shouldListen = true; |
| if (this.video.error) |
| shouldListen = false; |
| if (!this.isAudio() && !this.video.paused && this.controlsAreHidden()) |
| shouldListen = false; |
| if (document.hidden) |
| shouldListen = false; |
| |
| this.setShouldListenForPlaybackTargetAvailabilityEvent(shouldListen); |
| }, |
| |
| updateWirelessPlaybackStatus: function() { |
| if (this.currentPlaybackTargetIsWireless()) { |
| var deviceName = ""; |
| var deviceType = ""; |
| var type = this.host.externalDeviceType; |
| if (type == "airplay") { |
| deviceType = this.UIString('##WIRELESS_PLAYBACK_DEVICE_TYPE##'); |
| deviceName = this.UIString('##WIRELESS_PLAYBACK_DEVICE_NAME##', '##DEVICE_NAME##', this.host.externalDeviceDisplayName || "Apple TV"); |
| } else if (type == "tvout") { |
| deviceType = this.UIString('##TVOUT_DEVICE_TYPE##'); |
| deviceName = this.UIString('##TVOUT_DEVICE_NAME##'); |
| } |
| |
| this.controls.inlinePlaybackPlaceholderTextTop.innerText = deviceType; |
| this.controls.inlinePlaybackPlaceholderTextBottom.innerText = deviceName; |
| this.controls.inlinePlaybackPlaceholder.setAttribute('aria-label', deviceType + ", " + deviceName); |
| this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.appleTV); |
| this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.hidden); |
| this.controls.wirelessTargetPicker.classList.add(this.ClassNames.playing); |
| if (!this.isFullScreen() && (this.video.offsetWidth <= 250 || this.video.offsetHeight <= 200)) { |
| this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.small); |
| this.controls.inlinePlaybackPlaceholderTextTop.classList.add(this.ClassNames.small); |
| this.controls.inlinePlaybackPlaceholderTextBottom.classList.add(this.ClassNames.small); |
| } else { |
| this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.small); |
| this.controls.inlinePlaybackPlaceholderTextTop.classList.remove(this.ClassNames.small); |
| this.controls.inlinePlaybackPlaceholderTextBottom.classList.remove(this.ClassNames.small); |
| } |
| this.controls.volumeBox.classList.add(this.ClassNames.hidden); |
| this.controls.muteBox.classList.add(this.ClassNames.hidden); |
| this.updateBase(); |
| this.showControls(); |
| } else { |
| this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.hidden); |
| this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.appleTV); |
| this.controls.wirelessTargetPicker.classList.remove(this.ClassNames.playing); |
| this.controls.volumeBox.classList.remove(this.ClassNames.hidden); |
| this.controls.muteBox.classList.remove(this.ClassNames.hidden); |
| } |
| this.setNeedsUpdateForDisplayedWidth(); |
| this.updateLayoutForDisplayedWidth(); |
| this.reconnectControls(); |
| this.updateWirelessTargetPickerButton(); |
| }, |
| |
| updateWirelessTargetAvailable: function() { |
| this.currentTargetIsWireless = this.video.webkitCurrentPlaybackTargetIsWireless; |
| this.wirelessPlaybackDisabled = this.video.webkitWirelessVideoPlaybackDisabled; |
| |
| var wirelessPlaybackTargetsAvailable = Controller.gSimulateWirelessPlaybackTarget || this.hasWirelessPlaybackTargets; |
| if (this.wirelessPlaybackDisabled) |
| wirelessPlaybackTargetsAvailable = false; |
| |
| if (wirelessPlaybackTargetsAvailable && this.isPlayable()) |
| this.controls.wirelessTargetPicker.classList.remove(this.ClassNames.hidden); |
| else |
| this.controls.wirelessTargetPicker.classList.add(this.ClassNames.hidden); |
| this.setNeedsUpdateForDisplayedWidth(); |
| this.updateLayoutForDisplayedWidth(); |
| }, |
| |
| handleWirelessPickerButtonClicked: function(event) |
| { |
| this.video.webkitShowPlaybackTargetPicker(); |
| return true; |
| }, |
| |
| handleWirelessPlaybackChange: function(event) { |
| this.updateWirelessTargetAvailable(); |
| this.updateWirelessPlaybackStatus(); |
| this.setNeedsTimelineMetricsUpdate(); |
| }, |
| |
| handleWirelessTargetAvailableChange: function(event) { |
| var wirelessPlaybackTargetsAvailable = event.availability == "available"; |
| if (this.hasWirelessPlaybackTargets === wirelessPlaybackTargetsAvailable) |
| return; |
| |
| this.hasWirelessPlaybackTargets = wirelessPlaybackTargetsAvailable; |
| this.updateWirelessTargetAvailable(); |
| this.setNeedsTimelineMetricsUpdate(); |
| }, |
| |
| setShouldListenForPlaybackTargetAvailabilityEvent: function(shouldListen) { |
| if (!window.WebKitPlaybackTargetAvailabilityEvent || this.isListeningForPlaybackTargetAvailabilityEvent == shouldListen) |
| return; |
| |
| if (shouldListen && this.video.error) |
| return; |
| |
| this.isListeningForPlaybackTargetAvailabilityEvent = shouldListen; |
| if (shouldListen) |
| this.listenFor(this.video, 'webkitplaybacktargetavailabilitychanged', this.handleWirelessTargetAvailableChange); |
| else |
| this.stopListeningFor(this.video, 'webkitplaybacktargetavailabilitychanged', this.handleWirelessTargetAvailableChange); |
| }, |
| |
| get scrubbing() |
| { |
| return this._scrubbing; |
| }, |
| |
| set scrubbing(flag) |
| { |
| if (this._scrubbing == flag) |
| return; |
| this._scrubbing = flag; |
| |
| if (this._scrubbing) |
| this.wasPlayingWhenScrubbingStarted = !this.video.paused; |
| else if (this.wasPlayingWhenScrubbingStarted && this.video.paused) { |
| this.video.play(); |
| this.resetHideControlsTimer(); |
| } |
| }, |
| |
| get pageScaleFactor() |
| { |
| return this._pageScaleFactor; |
| }, |
| |
| set pageScaleFactor(newScaleFactor) |
| { |
| if (this._pageScaleFactor === newScaleFactor) |
| return; |
| |
| this._pageScaleFactor = newScaleFactor; |
| }, |
| |
| set usesLTRUserInterfaceLayoutDirection(usesLTRUserInterfaceLayoutDirection) |
| { |
| this.controls.volumeBox.classList.toggle(this.ClassNames.usesLTRUserInterfaceLayoutDirection, usesLTRUserInterfaceLayoutDirection); |
| }, |
| |
| handleRootResize: function(event) |
| { |
| this.updateLayoutForDisplayedWidth(); |
| this.setNeedsTimelineMetricsUpdate(); |
| this.updateTimelineMetricsIfNeeded(); |
| this.drawTimelineBackground(); |
| }, |
| |
| getCurrentControlsStatus: function () |
| { |
| var result = { |
| idiom: this.idiom, |
| status: "ok" |
| }; |
| |
| var elements = [ |
| { |
| name: "Show Controls", |
| object: this.showControlsButton, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Status Display", |
| object: this.controls.statusDisplay, |
| styleValues: ["display"], |
| extraProperties: ["textContent"], |
| }, |
| { |
| name: "Play Button", |
| object: this.controls.playButton, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Rewind Button", |
| object: this.controls.rewindButton, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Timeline Box", |
| object: this.controls.timelineBox, |
| }, |
| { |
| name: "Mute Box", |
| object: this.controls.muteBox, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Fullscreen Button", |
| object: this.controls.fullscreenButton, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "AppleTV Device Picker", |
| object: this.controls.wirelessTargetPicker, |
| styleValues: ["display"], |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Picture-in-picture Button", |
| object: this.controls.pictureInPictureButton, |
| extraProperties: ["parentElement", "hidden"], |
| }, |
| { |
| name: "Caption Button", |
| object: this.controls.captionButton, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Timeline", |
| object: this.controls.timeline, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Current Time", |
| object: this.controls.currentTime, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Thumbnail Track", |
| object: this.controls.thumbnailTrack, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Time Remaining", |
| object: this.controls.remainingTime, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Track Menu", |
| object: this.captionMenu, |
| }, |
| { |
| name: "Inline playback placeholder", |
| object: this.controls.inlinePlaybackPlaceholder, |
| }, |
| { |
| name: "Media Controls Panel", |
| object: this.controls.panel, |
| extraProperties: ["hidden"], |
| }, |
| { |
| name: "Control Base Element", |
| object: this.base || null, |
| }, |
| ]; |
| |
| elements.forEach(function (element) { |
| var obj = element.object; |
| delete element.object; |
| |
| element.computedStyle = {}; |
| if (obj && element.styleValues) { |
| var computedStyle = window.getComputedStyle(obj); |
| element.styleValues.forEach(function (propertyName) { |
| element.computedStyle[propertyName] = computedStyle[propertyName]; |
| }); |
| delete element.styleValues; |
| } |
| |
| element.bounds = obj ? obj.getBoundingClientRect() : null; |
| element.className = obj ? obj.className : null; |
| element.ariaLabel = obj ? obj.getAttribute('aria-label') : null; |
| |
| if (element.extraProperties) { |
| element.extraProperties.forEach(function (property) { |
| element[property] = obj ? obj[property] : null; |
| }); |
| delete element.extraProperties; |
| } |
| |
| element.element = obj; |
| }); |
| |
| result.elements = elements; |
| |
| return JSON.stringify(result); |
| } |
| |
| }; |