@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
JavaScript
"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.