UNPKG

@enact/sandstone

Version:

Large-screen/TV support library for Enact, containing a variety of UI components.

1,186 lines (1,176 loc) 84.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "Video", { enumerable: true, get: function get() { return _Video["default"]; } }); exports["default"] = exports.VideoPlayerBase = exports.VideoPlayer = void 0; var _ApiDecorator = _interopRequireDefault(require("@enact/core/internal/ApiDecorator")); var _dispatcher = require("@enact/core/dispatcher"); var _util = require("@enact/core/util"); var _handle = require("@enact/core/handle"); var _keymap = require("@enact/core/keymap"); var _platform = require("@enact/core/platform"); var _propTypes = _interopRequireDefault(require("@enact/core/internal/prop-types")); var _I18nDecorator = require("@enact/i18n/I18nDecorator"); var _util2 = require("@enact/i18n/util"); var _spotlight = require("@enact/spotlight"); var _SpotlightContainerDecorator = require("@enact/spotlight/SpotlightContainerDecorator"); var _Spottable = require("@enact/spotlight/Spottable"); var _Announce = _interopRequireDefault(require("@enact/ui/AnnounceDecorator/Announce")); var _ComponentOverride = _interopRequireDefault(require("@enact/ui/ComponentOverride")); var _FloatingLayer = require("@enact/ui/FloatingLayer"); var _FloatingLayerDecorator = require("@enact/ui/FloatingLayer/FloatingLayerDecorator"); var _ForwardRef = _interopRequireDefault(require("@enact/ui/ForwardRef")); var _Media = _interopRequireDefault(require("@enact/ui/Media")); var _Slottable = _interopRequireDefault(require("@enact/ui/Slottable")); var _Touchable = _interopRequireDefault(require("@enact/ui/Touchable")); var _DurationFmt = _interopRequireDefault(require("ilib/lib/DurationFmt")); var _equals = _interopRequireDefault(require("ramda/src/equals")); var _propTypes2 = _interopRequireDefault(require("prop-types")); var _react = require("react"); var _$L = _interopRequireDefault(require("../internal/$L")); var _Button = _interopRequireDefault(require("../Button")); var _Skinnable = _interopRequireDefault(require("../Skinnable")); var _Spinner = _interopRequireDefault(require("../Spinner")); var _MediaPlayer = require("../MediaPlayer"); var _Overlay = _interopRequireDefault(require("./Overlay")); var _MediaTitle = _interopRequireDefault(require("./MediaTitle")); var _FeedbackContent = _interopRequireDefault(require("./FeedbackContent")); var _FeedbackTooltip = _interopRequireDefault(require("./FeedbackTooltip")); var _Video = _interopRequireDefault(require("./Video")); var _VideoPlayerModule = _interopRequireDefault(require("./VideoPlayer.module.css")); var _jsxRuntime = require("react/jsx-runtime"); var _class; var _excluded = ["playerRef"], _excluded2 = ["backButtonAriaLabel", "className", "disabled", "infoComponents", "initialJumpDelay", "jumpDelay", "loading", "locale", "mediaControlsComponent", "no5WayJump", "noAutoPlay", "noMiniFeedback", "noSlider", "noSpinner", "selection", "spotlightDisabled", "spotlightId", "style", "thumbnailComponent", "thumbnailSrc", "title", "videoComponent"]; /** * Provides Sandstone-themed video player components. * * @module sandstone/VideoPlayer * @exports Video * @exports VideoPlayer * @exports VideoPlayerBase */ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } var isEnter = (0, _keymap.is)('enter'); var isLeft = (0, _keymap.is)('left'); var isRight = (0, _keymap.is)('right'); var jumpBackKeyCode = 37; var jumpForwardKeyCode = 39; var controlsHandleAboveSelectionKeys = [13, 16777221, jumpBackKeyCode, jumpForwardKeyCode]; var getControlsHandleAboveHoldConfig = function getControlsHandleAboveHoldConfig(_ref) { var frequency = _ref.frequency, time = _ref.time; return { events: [{ name: 'hold', time: time }], frequency: frequency }; }; var shouldJump = function shouldJump(_ref2, _ref3) { var disabled = _ref2.disabled, no5WayJump = _ref2.no5WayJump; var mediaControlsVisible = _ref3.mediaControlsVisible, sourceUnavailable = _ref3.sourceUnavailable; return !no5WayJump && !mediaControlsVisible && !(disabled || sourceUnavailable); }; var calcNumberValueOfPlaybackRate = function calcNumberValueOfPlaybackRate(rate) { var pbArray = String(rate).split('/'); return pbArray.length > 1 ? parseInt(pbArray[0]) / parseInt(pbArray[1]) : parseFloat(rate); }; var RootComponent = function RootComponent(_ref4) { var playerRef = _ref4.playerRef, rest = _objectWithoutProperties(_ref4, _excluded); return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", _objectSpread({ ref: playerRef }, rest)); }; RootComponent.propTypes = { /* * Called with the reference to the mediaControls node. * * @type {Object|Function} * @public */ playerRef: _propTypes["default"].ref }; var SpottableDiv = (0, _Touchable["default"])((0, _Spottable.Spottable)('div')); var RootContainer = (0, _ForwardRef["default"])({ prop: 'playerRef' }, (0, _SpotlightContainerDecorator.SpotlightContainerDecorator)({ enterTo: 'default-element', defaultElement: [".".concat(_VideoPlayerModule["default"].controlsHandleAbove), ".".concat(_VideoPlayerModule["default"].controlsFrame)] }, RootComponent)); var ControlsContainer = (0, _SpotlightContainerDecorator.SpotlightContainerDecorator)({ enterTo: 'default-element', straightOnly: true }, 'div'); var memoGetDurFmt = (0, _util.memoize)(function /* locale */ () { return new _DurationFmt["default"]({ length: 'medium', style: 'clock', useNative: false }); }); var getDurFmt = function getDurFmt(locale) { if (typeof window === 'undefined') return null; return memoGetDurFmt(locale); }; var forwardWithState = function forwardWithState(type) { return (0, _handle.adaptEvent)(function () { return { type: type }; }, (0, _handle.handle)((0, _handle.adaptEvent)((0, _handle.call)('addStateToEvent'), (0, _handle.forwardWithPrevent)(type)))); }; var forwardToggleMore = (0, _handle.forward)('onToggleMore'); // provide forwarding of events on media controls var forwardControlsAvailable = (0, _handle.forwardCustom)('onControlsAvailable'); var forwardPlay = forwardWithState('onPlay'); var forwardWillPlay = forwardWithState('onWillPlay'); var forwardPause = forwardWithState('onPause'); var forwardWillPause = forwardWithState('onWillPause'); var forwardRewind = forwardWithState('onRewind'); var forwardWillRewind = forwardWithState('onWillRewind'); var forwardFastForward = forwardWithState('onFastForward'); var forwardWillFastForward = forwardWithState('onWillFastForward'); var forwardJumpBackward = forwardWithState('onJumpBackward'); var forwardWillJumpBackward = forwardWithState('onWillJumpBackward'); var forwardJumpForward = forwardWithState('onJumpForward'); var forwardWillJumpForward = forwardWithState('onWillJumpForward'); var AnnounceState = { // Video is loaded but additional announcements have not been made READY: 0, // The title should be announced TITLE: 1, // The title has been announce TITLE_READ: 2, // The infoComponents should be announce INFO: 3, // All announcements have been made DONE: 4 }; /** * Every callback sent by {@link sandstone/VideoPlayer|VideoPlayer} receives a status package, * which includes an object with the following key/value pairs as the first argument: * * @typedef {Object} videoStatus * @memberof sandstone/VideoPlayer * @property {String} type - Type of event that triggered this callback * @property {Number} currentTime - Playback index of the media in seconds * @property {Number} duration - Media's entire duration in seconds * @property {Boolean} paused - Playing vs paused state. `true` means the media is paused * @property {Number} playbackRate - Current playback rate, as a number * @property {Number} proportionLoaded - A value between `0` and `1` representing the proportion of the media that has loaded * @property {Number} proportionPlayed - A value between `0` and `1` representing the proportion of the media that has already been shown * * @public */ /** * A set of playback rates when media fast forwards, rewinds, slow-forwards, or slow-rewinds. * * The number used for each operation is proportional to the normal playing speed, 1. If the rate * is less than 1, it will play slower than normal speed, and, if it is larger than 1, it will play * faster. If it is negative, it will play backward. * * The order of numbers represents the incremental order of rates that will be used for each * operation. Note that rates can be expressed as decimals, strings, and fractions. * (e.g.: `0.5`, `'0.5'`, `'1/2'`). * * @typedef {Object} playbackRateHash * @memberof sandstone/VideoPlayer * @property {[]} fastForward - An array of playback rates when media fast forwards * @property {[]} rewind - An array of playback rates when media rewinds * @property {[]} slowForward - An array of playback rates when media slow-forwards * @property {[]} slowRewind - An array of playback rates when media slow-rewinds * * @public */ /** * A player for video {@link sandstone/VideoPlayer.VideoPlayerBase}. * * @class VideoPlayerBase * @memberof sandstone/VideoPlayer * @ui * @public */ var VideoPlayerBase = exports.VideoPlayerBase = (_class = /*#__PURE__*/function (_Component) { _inherits(VideoPlayerBase, _Component); var _super = _createSuper(VideoPlayerBase); function VideoPlayerBase(_props) { var _this; _classCallCheck(this, VideoPlayerBase); _this = _super.call(this, _props); // Internal State // // Internal Methods // _this.announceJob = new _util.Job(function (msg, clear) { return _this.announceRef && _this.announceRef.announce(msg, clear); }, 200); _this.announce = function (msg, clear) { _this.announceJob.start(msg, clear); }; _this.activityDetected = function () { _this.startAutoCloseTimeout(); }; _this.startAutoCloseTimeout = function () { // If this.state.more is used as a reference for when this function should fire, timing for // detection of when "more" is pressed vs when the state is updated is mismatched. Using an // instance variable that's only set and used for this express purpose seems cleanest. if (_this.props.autoCloseTimeout && _this.state.mediaControlsVisible) { _this.autoCloseJob.startAfter(_this.props.autoCloseTimeout); } }; _this.stopAutoCloseTimeout = function () { _this.autoCloseJob.stop(); }; _this.generateId = function () { return Math.random().toString(36).substr(2, 8); }; /** * If the announce state is either ready to read the title or ready to read info, advance the * state to "read". * * @returns {Boolean} Returns true to be used in event handlers * @private */ _this.markAnnounceRead = function () { if (_this.state.announce === AnnounceState.TITLE) { _this.setState({ announce: AnnounceState.TITLE_READ }); } else if (_this.state.announce === AnnounceState.INFO) { _this.setState({ announce: AnnounceState.DONE }); } return true; }; /** * Shows media controls. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @public */ _this.showControls = function () { if (_this.props.disabled) { return; } _this.startDelayedFeedbackHide(); _this.startDelayedTitleHide(); _this.setState(function (_ref5) { var announce = _ref5.announce; if (announce === AnnounceState.READY) { // if we haven't read the title yet, do so this time announce = AnnounceState.TITLE; } else if (announce === AnnounceState.TITLE) { // if we have read the title, advance to INFO so title isn't read again announce = AnnounceState.TITLE_READ; } return { announce: announce, bottomControlsRendered: true, feedbackAction: 'idle', feedbackVisible: true, mediaControlsVisible: true, mediaSliderVisible: true, miniFeedbackVisible: false, titleVisible: true }; }); }; /** * Hides media controls. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @public */ _this.hideControls = function () { _this.stopDelayedFeedbackHide(); _this.stopDelayedMiniFeedbackHide(); _this.stopDelayedTitleHide(); _this.stopAutoCloseTimeout(); _this.setState({ feedbackAction: 'idle', feedbackVisible: false, mediaControlsVisible: false, mediaSliderVisible: false, miniFeedbackVisible: false, infoVisible: false }); _this.markAnnounceRead(); }; /** * Toggles the media controls. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @public */ _this.toggleControls = function () { if (_this.state.mediaControlsVisible) { _this.hideControls(); } else { _this.showControls(); } }; _this.doAutoClose = function () { _this.stopDelayedFeedbackHide(); _this.stopDelayedTitleHide(); _this.setState(function (_ref6) { var mediaSliderVisible = _ref6.mediaSliderVisible, miniFeedbackVisible = _ref6.miniFeedbackVisible; return { feedbackVisible: false, mediaControlsVisible: false, mediaSliderVisible: mediaSliderVisible && miniFeedbackVisible, infoVisible: false }; }); _this.markAnnounceRead(); }; _this.autoCloseJob = new _util.Job(_this.doAutoClose); _this.startDelayedTitleHide = function () { if (_this.props.titleHideDelay) { _this.hideTitleJob.startAfter(_this.props.titleHideDelay); } }; _this.stopDelayedTitleHide = function () { _this.hideTitleJob.stop(); }; _this.hideTitle = function () { _this.setState({ titleVisible: false }); }; _this.hideTitleJob = new _util.Job(_this.hideTitle); _this.startDelayedFeedbackHide = function () { if (_this.props.feedbackHideDelay) { _this.hideFeedbackJob.startAfter(_this.props.feedbackHideDelay); } }; _this.stopDelayedFeedbackHide = function () { _this.hideFeedbackJob.stop(); }; _this.showFeedback = function () { if (_this.state.mediaControlsVisible) { _this.setState({ feedbackVisible: true }); } else { var shouldShowSlider = _this.pulsedPlaybackState !== null || calcNumberValueOfPlaybackRate(_this.playbackRate) !== 1; if (_this.showMiniFeedback && (!_this.state.miniFeedbackVisible || _this.state.mediaSliderVisible !== shouldShowSlider)) { _this.setState(function (_ref7) { var loading = _ref7.loading, duration = _ref7.duration, error = _ref7.error; return { mediaSliderVisible: shouldShowSlider && !_this.props.noMediaSliderFeedback, miniFeedbackVisible: !(loading || !duration || error) }; }); } } }; _this.hideFeedback = function () { if (_this.state.feedbackVisible && _this.state.feedbackAction !== 'focus') { _this.setState({ feedbackVisible: false, feedbackAction: 'idle' }); } }; _this.hideFeedbackJob = new _util.Job(_this.hideFeedback); _this.startDelayedMiniFeedbackHide = function () { var delay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.props.miniFeedbackHideDelay; if (delay) { _this.hideMiniFeedbackJob.startAfter(delay); } }; _this.stopDelayedMiniFeedbackHide = function () { _this.hideMiniFeedbackJob.stop(); }; _this.hideMiniFeedback = function () { if (_this.state.miniFeedbackVisible) { _this.showMiniFeedback = false; _this.setState({ mediaSliderVisible: false, miniFeedbackVisible: false }); } }; _this.hideMiniFeedbackJob = new _util.Job(_this.hideMiniFeedback); _this.handle = _handle.handle.bind(_assertThisInitialized(_this)); _this.showControlsFromPointer = function () { _spotlight.Spotlight.setPointerMode(false); _this.showControls(); }; _this.clearPulsedPlayback = function () { _this.pulsedPlaybackRate = null; _this.pulsedPlaybackState = null; }; // only show mini feedback if playback controls are invoked by a key event _this.shouldShowMiniFeedback = function (ev) { if (ev.type === 'keyup') { _this.showMiniFeedback = true; } return true; }; _this.handleLoadStart = function () { _this.firstPlayReadFlag = true; _this.prevCommand = _this.props.noAutoPlay ? 'pause' : 'play'; _this.speedIndex = 0; _this.setState({ announce: AnnounceState.READY, currentTime: 0, sourceUnavailable: true, proportionPlayed: 0, proportionLoaded: 0 }); if (!_this.props.noAutoShowMediaControls) { if (!_this.state.bottomControlsRendered) { _this.renderBottomControl.idle(); } else { _this.showControls(); } } }; _this.handlePlay = _this.handle(forwardWillPlay, _this.shouldShowMiniFeedback, function () { return _this.play(); }, forwardPlay); _this.handlePause = _this.handle(forwardWillPause, _this.shouldShowMiniFeedback, function () { return _this.pause(); }, forwardPause); _this.handleRewind = _this.handle(forwardWillRewind, _this.shouldShowMiniFeedback, function () { return _this.rewind(); }, forwardRewind); _this.handleFastForward = _this.handle(forwardWillFastForward, _this.shouldShowMiniFeedback, function () { return _this.fastForward(); }, forwardFastForward); _this.handleJump = function (_ref8) { var keyCode = _ref8.keyCode; if (_this.props.seekDisabled) { (0, _handle.forwardCustom)('onSeekFailed')(null, _this.props); } else { var jumpBy = ((0, _keymap.is)('left', keyCode) ? -1 : 1) * _this.props.jumpBy; var time = Math.min(_this.state.duration, Math.max(0, _this.state.currentTime + jumpBy)); if (_this.preventTimeChange(time)) return; _this.showMiniFeedback = true; _this.jump(jumpBy); _this.announceJob.startAfter(500, (0, _MediaPlayer.secondsToTime)(_this.video.currentTime, getDurFmt(_this.props.locale), { includeHour: true })); } }; _this.handleGlobalKeyDown = _this.handle((0, _handle.returnsTrue)(_this.activityDetected), (0, _handle.forKey)('down'), function () { return !_this.state.mediaControlsVisible && (!_spotlight.Spotlight.getCurrent() && _spotlight.Spotlight.getPointerMode() || !_spotlight.Spotlight.getPointerMode()) && !_this.props.spotlightDisabled; }, _handle.preventDefault, _handle.stopImmediate, _this.showControlsFromPointer); _this.handleControlsHandleAboveHold = function () { if (shouldJump(_this.props, _this.state)) { _this.handleJump({ keyCode: _this.jumpButtonPressed === -1 ? jumpBackKeyCode : jumpForwardKeyCode }); } }; _this.handleControlsHandleAboveKeyDown = function (_ref9) { var keyCode = _ref9.keyCode; if (isEnter(keyCode)) { _this.jumpButtonPressed = 0; } else if (isLeft(keyCode)) { _this.jumpButtonPressed = -1; } else if (isRight(keyCode)) { _this.jumpButtonPressed = 1; } }; _this.handleControlsHandleAboveKeyUp = function (_ref10) { var keyCode = _ref10.keyCode; if (isEnter(keyCode) || isLeft(keyCode) || isRight(keyCode)) { _this.jumpButtonPressed = null; } }; _this.handleControlsHandleAboveDown = function () { if (_this.jumpButtonPressed === 0) { _this.showControls(); } else if (_this.jumpButtonPressed === -1 || _this.jumpButtonPressed === 1) { var keyCode = _this.jumpButtonPressed === -1 ? jumpBackKeyCode : jumpForwardKeyCode; if (shouldJump(_this.props, _this.state)) { _this.handleJump({ keyCode: keyCode }); } else { _spotlight.Spotlight.move((0, _spotlight.getDirection)(keyCode)); } } }; // // Media Interaction Methods // _this.handleEvent = function () { var el = _this.video; var updatedState = { // Standard media properties currentTime: el.currentTime, duration: el.duration, paused: _this.state.seekingMode ? el.playbackRate !== 1 || el.paused : el.paused, playbackRate: el.playbackRate, // Non-standard state computed from properties error: el.error, loading: el.loading, proportionLoaded: el.proportionLoaded, proportionPlayed: el.proportionPlayed || 0, sliderTooltipTime: el.currentTime, // note: `el.loading && this.state.sourceUnavailable == false` is equivalent to `oncanplaythrough` sourceUnavailable: el.loading && _this.state.sourceUnavailable || el.error }; // If there's an error, we're obviously not loading, no matter what the readyState is. if (updatedState.error) updatedState.loading = false; var isRewind = _this.prevCommand === 'rewind' || _this.prevCommand === 'slowRewind'; var isForward = _this.prevCommand === 'fastForward' || _this.prevCommand === 'slowForward'; if (_this.props.pauseAtEnd && (el.currentTime === 0 && isRewind || el.currentTime === el.duration && isForward)) { _this.pause(); } _this.setState(updatedState); }; _this.renderBottomControl = new _util.Job(function () { if (!_this.state.bottomControlsRendered) { _this.setState({ bottomControlsRendered: true }); } }); /** * Returns an object with the current state of the media including `currentTime`, `duration`, * `paused`, `playbackRate`, `proportionLoaded`, and `proportionPlayed`. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @returns {Object} * @public */ _this.getMediaState = function () { return { currentTime: _this.video.currentTime, duration: _this.state.duration, paused: _this.state.seekingMode ? _this.video.playbackRate !== 1 || _this.video.paused : _this.video.paused, playbackRate: _this.video.playbackRate, proportionLoaded: _this.video.proportionLoaded, proportionPlayed: _this.video.proportionPlayed || 0 }; }; /** * The primary means of interacting with the `<video>` element. * * @param {String} action The method to preform. * @param {Multiple} props The arguments, in the format that the action method requires. * * @private */ _this.send = function (action, props) { _this.clearPulsedPlayback(); _this.showFeedback(); _this.startDelayedFeedbackHide(); _this.video[action](props); }; /** * Programmatically plays the current media. * If you call this function during fast forwarding or rewinding, the playback speed will be set to normal. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @public */ _this.play = function () { if (_this.state.sourceUnavailable) { return false; } if (_this.state.seekingMode) { _this.setPlaybackRate(1); } _this.speedIndex = 0; // must happen before send() to ensure feedback uses the right value // TODO: refactor into this.state member _this.prevCommand = 'play'; _this.send('play'); _this.announce((0, _$L["default"])('Play')); _this.startDelayedMiniFeedbackHide(5000); return true; }; /** * Programmatically pauses the current media. * If you call this function during fast forwarding or rewinding, the playback speed will be set to normal. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @public */ _this.pause = function () { if (_this.state.sourceUnavailable) { return false; } if (_this.state.seekingMode) { _this.setPlaybackRate(1); } _this.speedIndex = 0; // must happen before send() to ensure feedback uses the right value // TODO: refactor into this.state member _this.prevCommand = 'pause'; _this.send('pause'); _this.announce((0, _$L["default"])('Pause')); _this.stopDelayedMiniFeedbackHide(); return true; }; /** * Sets the media playback time index. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @param {Number} timeIndex - Time index to seek * @public */ _this.seek = function (timeIndex) { if (!_this.props.seekDisabled && !isNaN(_this.video.duration) && !_this.state.sourceUnavailable) { _this.video.currentTime = timeIndex; } else { (0, _handle.forwardCustom)('onSeekFailed')(null, _this.props); } }; /** * Step a given amount of time away from the current playback position. * Like {@link sandstone/VideoPlayer.VideoPlayerBase.seek|seek} but relative. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @param {Number} distance - Time value to jump * @public */ _this.jump = function (distance) { if (_this.state.sourceUnavailable) { return false; } _this.pulsedPlaybackRate = (0, _util2.toUpperCase)(new _DurationFmt["default"]({ length: 'long' }).format({ second: _this.props.jumpBy })); _this.pulsedPlaybackState = distance > 0 ? 'jumpForward' : 'jumpBackward'; _this.showFeedback(); _this.startDelayedFeedbackHide(); _this.seek(_this.state.currentTime + distance); _this.startDelayedMiniFeedbackHide(); return true; }; /** * Fast forwards the current media for seeking. * This function changes the playback rate. * If you call `play` or `pause` during fast forwarding, the playback speed will be set to normal. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @public */ _this.fastForward = function () { if (_this.state.sourceUnavailable) { return false; } _this.setState({ seekingMode: true }); var shouldResumePlayback = false; switch (_this.prevCommand) { case 'slowForward': if (_this.speedIndex === _this.playbackRates.length - 1) { // reached to the end of array => fastforward _this.selectPlaybackRates('fastForward'); _this.speedIndex = 0; _this.prevCommand = 'fastForward'; } else { _this.speedIndex = _this.clampPlaybackRate(_this.speedIndex + 1); } break; case 'pause': _this.selectPlaybackRates('slowForward'); if (_this.state.paused) { shouldResumePlayback = true; } _this.speedIndex = 0; _this.prevCommand = 'slowForward'; break; case 'fastForward': _this.speedIndex = _this.clampPlaybackRate(_this.speedIndex + 1); _this.prevCommand = 'fastForward'; break; default: _this.selectPlaybackRates('fastForward'); _this.speedIndex = 0; _this.prevCommand = 'fastForward'; if (_this.state.paused) { shouldResumePlayback = true; } break; } _this.setPlaybackRate(_this.selectPlaybackRate(_this.speedIndex)); if (shouldResumePlayback) _this.send('play'); _this.stopDelayedFeedbackHide(); _this.stopDelayedMiniFeedbackHide(); _this.clearPulsedPlayback(); _this.showFeedback(); return true; }; /** * Rewinds the current media for seeking. * This function changes the playback rate. * If you call `play` or `pause` during rewinding, the playback speed will be set to normal. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @public */ _this.rewind = function () { if (_this.state.sourceUnavailable) { return false; } _this.setState({ seekingMode: true }); var rateForSlowRewind = _this.props.playbackRateHash['slowRewind']; var shouldResumePlayback = false, command = 'rewind'; if (_this.video.currentTime === 0) { // Do not rewind if currentTime is 0. We're already at the beginning. return true; } switch (_this.prevCommand) { case 'slowRewind': if (_this.speedIndex === _this.playbackRates.length - 1) { // reached to the end of array => go to rewind _this.selectPlaybackRates(command); _this.speedIndex = 0; _this.prevCommand = command; } else { _this.speedIndex = _this.clampPlaybackRate(_this.speedIndex + 1); } break; case 'pause': // If it's possible to slowRewind, do it, otherwise just leave it as normal rewind : QEVENTSEVT-17386 if (rateForSlowRewind && rateForSlowRewind.length >= 0) { command = 'slowRewind'; } _this.selectPlaybackRates(command); if (_this.state.paused && _this.state.duration > _this.state.currentTime) { shouldResumePlayback = true; } _this.speedIndex = 0; _this.prevCommand = command; break; case 'rewind': _this.speedIndex = _this.clampPlaybackRate(_this.speedIndex + 1); _this.prevCommand = command; break; default: _this.selectPlaybackRates(command); _this.speedIndex = 0; _this.prevCommand = command; break; } _this.setPlaybackRate(_this.selectPlaybackRate(_this.speedIndex)); if (shouldResumePlayback) _this.send('play'); _this.stopDelayedFeedbackHide(); _this.stopDelayedMiniFeedbackHide(); _this.clearPulsedPlayback(); _this.showFeedback(); return true; }; /** * Sets the playback speed. * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @param {Number} rate - The desired playback rate. This value is passed to the `playbackRate` property of `HTMLMediaElement`. * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/playbackRate|MDN playbackRate property} * @returns {Boolean} Returns true if the speed changes successfully. * @public */ _this.setPlaybackSpeed = function (rate) { if (_this.state.sourceUnavailable) { return false; } _this.setState({ seekingMode: false }); _this.setPlaybackRate(rate); return true; }; // Creates a proxy to the video node if Proxy is supported _this.videoProxy = typeof Proxy !== 'function' ? null : new Proxy({}, { get: function get(target, name) { var value = _this.video[name]; if (typeof value === 'function') { value = value.bind(_this.video); } return value; }, set: function set(target, name, value) { return _this.video[name] = value; } }); /** * Returns a proxy to the underlying `<video>` node currently used by the VideoPlayer * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype * @public */ _this.getVideoNode = function () { return _this.videoProxy || _this.video; }; _this.areControlsVisible = function () { return _this.state.mediaControlsVisible; }; /** * Sets the playback rate type for video seeking (from the keys of {@link sandstone/VideoPlayer.VideoPlayer.playbackRateHash|playbackRateHash}). * * @param {String} cmd - Key of the playback rate type. * @private */ _this.selectPlaybackRates = function (cmd) { _this.playbackRates = _this.props.playbackRateHash[cmd]; }; /** * Changes playbackRate to a valid value when initiating fast forward or rewind. * * @param {Number} idx - The index of the desired playback rate. * @private */ _this.clampPlaybackRate = function (idx) { if (!_this.playbackRates) { return; } return idx % _this.playbackRates.length; }; /** * Retrieves the playback rate value. * * @param {Number} idx - The index of the desired playback rate. * @returns {Number|String} The playback rate value. * @private */ _this.selectPlaybackRate = function (idx) { return _this.playbackRates[idx]; }; /** * Sets playbackRate. * * @param {Number|String} rate - The desired playback rate. * @private */ _this.setPlaybackRate = function (rate) { if (_this.state.seekingMode) { // Stop rewind (if happening) _this.stopRewindJob(); } // Make sure rate is a string _this.playbackRate = String(rate); var pbNumber = calcNumberValueOfPlaybackRate(_this.playbackRate); if (_platform.platform.type !== 'webos') { // ReactDOM throws error for setting negative value for playbackRate _this.video.playbackRate = pbNumber < 0 ? 0 : pbNumber; if (_this.state.seekingMode) { // For supporting cross browser behavior if (pbNumber < 0) { _this.beginRewind(); } } } else { // Set native playback rate _this.video.playbackRate = pbNumber; } }; /** * Calculates the time that has elapsed since. This is necessary for browsers until negative * playback rate is directly supported. * * @private */ _this.rewindManually = function () { var now = (0, _util.perfNow)(), distance = now - _this.rewindBeginTime, pbRate = calcNumberValueOfPlaybackRate(_this.playbackRate), adjustedDistance = distance * pbRate / 1000; _this.jump(adjustedDistance); _this.stopDelayedMiniFeedbackHide(); _this.clearPulsedPlayback(); _this.startRewindJob(); // Issue another rewind tick }; _this.rewindJob = new _util.Job(_this.rewindManually, 100); /** * Starts rewind job. * * @private */ _this.startRewindJob = function () { _this.rewindBeginTime = (0, _util.perfNow)(); _this.rewindJob.start(); }; /** * Stops rewind job. * * @private */ _this.stopRewindJob = function () { _this.rewindJob.stop(); }; /** * Implements custom rewind functionality (until browsers support negative playback rate). * * @private */ _this.beginRewind = function () { _this.send('pause'); _this.startRewindJob(); }; // // Handled Media events // _this.addStateToEvent = function (ev) { return _objectSpread({ // More props from `ev` may be added here as needed, but a full copy via `...ev` // overloads Storybook's Action Logger and likely has other perf fallout. type: ev.type }, _this.getMediaState()); }; // // Player Interaction events // _this.onVideoClick = function () { _this.toggleControls(); }; _this.onSliderChange = function (_ref11) { var value = _ref11.value; var time = value * _this.state.duration; if (_this.preventTimeChange(time)) return; _this.seek(time); _this.sliderScrubbing = false; }; _this.handleBack = _this.handle((0, _handle.forwardCustom)('onBack')); _this.handleKnobMove = function (ev) { _this.sliderScrubbing = true; // prevent announcing repeatedly when the knob is detached from the progress. // TODO: fix Slider to not send onKnobMove when the knob hasn't, in fact, moved if (_this.sliderKnobProportion !== ev.proportion) { _this.sliderKnobProportion = ev.proportion; var seconds = Math.floor(_this.sliderKnobProportion * _this.video.duration); if (!isNaN(seconds)) { var knobTime = (0, _MediaPlayer.secondsToTime)(seconds, getDurFmt(_this.props.locale), { includeHour: true }); (0, _handle.forward)('onScrub', _objectSpread(_objectSpread({}, ev), {}, { seconds: seconds, type: 'onScrub' }), _this.props); _this.announce("".concat((0, _$L["default"])('jump to'), " ").concat(knobTime), true); } } }; _this.handleSliderFocus = function () { var seconds = Math.floor(_this.sliderKnobProportion * _this.video.duration); _this.sliderScrubbing = true; _this.setState({ feedbackAction: 'focus', feedbackVisible: true }); _this.stopDelayedFeedbackHide(); if (!isNaN(seconds)) { var knobTime = (0, _MediaPlayer.secondsToTime)(seconds, getDurFmt(_this.props.locale), { includeHour: true }); (0, _handle.forward)('onScrub', { detached: _this.sliderScrubbing, proportion: _this.sliderKnobProportion, seconds: seconds, type: 'onScrub' }, _this.props); _this.announce("".concat((0, _$L["default"])('jump to'), " ").concat(knobTime), true); } }; _this.handleSliderBlur = function () { _this.sliderScrubbing = false; _this.startDelayedFeedbackHide(); _this.setState(function () { return { feedbackAction: 'blur', feedbackVisible: true }; }); }; _this.slider5WayPressJob = new _util.Job(function () { _this.setState({ slider5WayPressed: false }); }, 200); _this.handleSliderKeyDown = function (ev) { var keyCode = ev.keyCode; if ((0, _keymap.is)('enter', keyCode)) { _this.setState({ slider5WayPressed: true }, _this.slider5WayPressJob.start()); } else if ((0, _keymap.is)('down', keyCode)) { _spotlight.Spotlight.setPointerMode(false); if (_spotlight.Spotlight.focus(_this.mediaControlsSpotlightId)) { (0, _handle.preventDefault)(ev); (0, _handle.stopImmediate)(ev); } } else if ((0, _keymap.is)('up', keyCode)) { _spotlight.Spotlight.setPointerMode(false); } }; _this.onJumpBackward = _this.handle(forwardWillJumpBackward, function () { return _this.jump(-1 * _this.props.jumpBy); }, forwardJumpBackward); _this.onJumpForward = _this.handle(forwardWillJumpForward, function () { return _this.jump(_this.props.jumpBy); }, forwardJumpForward); _this.handleToggleMore = function (ev) { var showMoreComponents = ev.showMoreComponents, liftDistance = ev.liftDistance; forwardToggleMore(ev, _this.props); if (!showMoreComponents) { _this.startAutoCloseTimeout(); // Restore the timer since we are leaving "more. // Restore the title-hide now that we're finished with "more". _this.startDelayedTitleHide(); } else { // Interrupt the title-hide since we don't want it hiding autonomously in "more". _this.stopDelayedTitleHide(); } _this.playerRef.current.style.setProperty('--liftDistance', "".concat(liftDistance, "px")); _this.setState(function (_ref12) { var announce = _ref12.announce; return { infoVisible: showMoreComponents, titleVisible: true, announce: announce < AnnounceState.INFO ? AnnounceState.INFO : AnnounceState.DONE }; }); }; _this.handleMediaControlsClose = function (ev) { _this.hideControls(); ev.stopPropagation(); }; _this.setVideoRef = function (video) { _this.video = video; }; _this.setTitleRef = function (node) { _this.titleRef = node; }; _this.setAnnounceRef = function (node) { _this.announceRef = node; }; _this.video = null; _this.pulsedPlaybackRate = null; _this.pulsedPlaybackState = null; _this.prevCommand = _props.noAutoPlay ? 'pause' : 'play'; _this.showMiniFeedback = false; _this.speedIndex = 0; _this.seekingMode = true; _this.id = _this.generateId(); _this.selectPlaybackRates('fastForward'); _this.sliderKnobProportion = 0; _this.mediaControlsSpotlightId = _props.spotlightId + '_mediaControls'; _this.jumpButtonPressed = null; _this.playerRef = /*#__PURE__*/(0, _react.createRef)(); _this.playbackRate = 1; // Re-render-necessary State _this.state = { announce: AnnounceState.READY, currentTime: 0, duration: 0, error: false, loading: false, paused: _props.noAutoPlay, playbackRate: 1, titleOffsetHeight: 0, bottomOffsetHeight: 0, // Non-standard state computed from properties bottomControlsRendered: false, feedbackAction: 'idle', feedbackVisible: false, infoVisible: false, mediaControlsVisible: false, mediaSliderVisible: false, miniFeedbackVisible: false, proportionLoaded: 0, proportionPlayed: 0, sourceUnavailable: true, titleVisible: true }; if (_props.setApiProvider) { _props.setApiProvider(_assertThisInitialized(_this)); } return _this; } _createClass(VideoPlayerBase, [{ key: "componentDidMount", value: function componentDidMount() { (0, _dispatcher.on)('mousemove', this.activityDetected); if (_platform.platform.touchEvent) { (0, _dispatcher.on)('touchmove', this.activityDetected); } document.addEventListener('keydown', this.handleGlobalKeyDown, { capture: true }); document.addEventListener('wheel', this.activityDetected, { capture: true }); this.startDelayedFeedbackHide(); if (this.context && typeof this.context === 'function') { this.floatingLayerController = this.context(function () {}); } } }, { key: "shouldComponentUpdate", value: function shouldComponentUpdate(nextProps, nextState) { if ( // Use shallow props compare instead of source comparison to support possible changes // from mediaComponent. (0, _util.shallowEqual)(this.props, nextProps) && !this.state.miniFeedbackVisible && this.state.miniFeedbackVisible === nextState.miniFeedbackVisible && !this.state.mediaSliderVisible && this.state.mediaSliderVisible === nextState.mediaSliderVisible && this.state.loading === nextState.loading && this.props.loading === nextProps.loading && (this.state.currentTime !== nextState.currentTime || this.state.proportionPlayed !== nextState.proportionPlayed || this.state.sliderTooltipTime !== nextState.sliderTooltipTime)) { return false; } return true; } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState) { if (this.titleRef && this.state.infoVisible && (!prevState.infoVisible || !(0, _equals["default"])(this.props.