UNPKG

@cassette/core

Version:

A simple, clean, and responsive visual wrapper for the HTML audio tag, built with React.

1,261 lines (1,025 loc) 103 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(require("prop-types"), require("react")); else if(typeof define === 'function' && define.amd) define(["prop-types", "react"], factory); else if(typeof exports === 'object') exports["cassetteCore"] = factory(require("prop-types"), require("react")); else root["cassetteCore"] = factory(root["PropTypes"], root["React"]); })((typeof self !== "undefined" ? self : this), function(__WEBPACK_EXTERNAL_MODULE__0__, __WEBPACK_EXTERNAL_MODULE__1__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = "/dist"; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 7); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE__0__; /***/ }), /* 1 */ /***/ (function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE__1__; /***/ }), /* 2 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return logError; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return logWarning; }); /* eslint-disable no-console */ const log = console.log.bind(console); const logError = console.error ? console.error.bind(console) : log; const logWarning = console.warn ? console.warn.bind(console) : log; /***/ }), /* 3 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _console__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); const packageVersion = __webpack_require__(6).version; const _global = typeof window === 'undefined' ? global : window; _global.__cassette_contexts__ = _global.__cassette_contexts__ || {}; function createSingleGlobalContext(_ref) { let displayName = _ref.displayName, _ref$defaultValue = _ref.defaultValue, defaultValue = _ref$defaultValue === void 0 ? null : _ref$defaultValue, keysWillUpdate = _ref.keysWillUpdate; const ExistingContext = _global.__cassette_contexts__[displayName]; if (ExistingContext) { if (ExistingContext.packageVersion !== packageVersion) { Object(_console__WEBPACK_IMPORTED_MODULE_1__[/* logWarning */ "b"])(`Warning: multiple versions of ${displayName} from the @cassette/core` + ` package have been loaded. v${packageVersion} will be ignored and` + ` v${ExistingContext.packageVersion} will be used instead.`); } return ExistingContext; } // inspired by: // https://github.com/philosaf/observed-bits/blob/master/src/index.js const flags = {}; let i = 0; for (const key of keysWillUpdate) { flags[key] = 1 << i++; } const Context = Object(react__WEBPACK_IMPORTED_MODULE_0__["createContext"])(defaultValue, function getChangedBits(prev, next) { let mask = 0; for (const key of keysWillUpdate) { if (prev[key] !== next[key]) { mask |= flags[key]; } } return mask; }); Context.__cassetteGetObservedBits = keys => { let observedBits = 0; for (const key of keys) { observedBits |= flags[key]; } return observedBits; }; Context.displayName = displayName; Context.packageVersion = packageVersion; _global.__cassette_contexts__[displayName] = Context; return Context; } /* harmony default export */ __webpack_exports__["a"] = (createSingleGlobalContext); /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5))) /***/ }), /* 4 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = function (arr, predicate, ctx) { if (typeof Array.prototype.findIndex === 'function') { return arr.findIndex(predicate, ctx); } if (typeof predicate !== 'function') { throw new TypeError('predicate must be a function'); } var list = Object(arr); var len = list.length; if (len === 0) { return -1; } for (var i = 0; i < len; i++) { if (predicate.call(ctx, list[i], i, list)) { return i; } } return -1; }; /***/ }), /* 5 */ /***/ (function(module, exports) { var g; // This works in non-strict mode g = (function() { return this; })(); try { // This works if eval is allowed (see CSP) g = g || Function("return this")() || (1, eval)("this"); } catch (e) { // This works if the window reference is available if (typeof window === "object") g = window; } // g can still be undefined, but nothing to do about it... // We return undefined, instead of nothing here, so it's // easier to handle this case. if(!global) { ...} module.exports = g; /***/ }), /* 6 */ /***/ (function(module) { module.exports = {"name":"@cassette/core","version":"2.0.0-beta.4","description":"A simple, clean, and responsive visual wrapper for the HTML audio tag, built with React.","main":"dist/es5/cassette-core.js","scripts":{"build:clean":"rimraf dist","build:webpack":"BUILD_MODE=all webpack --progress","build":"npm run build:clean && npm run build:webpack","prepare":"npm run build","test":"echo \"Error: no test specified\" && exit 1"},"repository":{"type":"git","url":"https://github.com/benwiley4000/cassette.git"},"engines":{"node":">=6.0.0","npm":">=5.0.0"},"keywords":["audio","video","media","ui","react","reactjs","responsive","music","player","html5","component","components"],"author":{"name":"Ben Wiley","email":"therealbenwiley@gmail.com","url":"http://benwiley.org/"},"license":"MIT","peerDependencies":{"react":"^16.3.0"},"devDependencies":{"array-find-index":"^1.0.2","rimraf":"^2.5.4","webpack":"^4.17.1"},"dependencies":{"prop-types":"^15.5.10"},"publishConfig":{"access":"public"},"gitHead":"0b53bfc554d37befcf6340dda3e94019f657e7d0"}; /***/ }), /* 7 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); var PlayerPropTypes_namespaceObject = {}; __webpack_require__.r(PlayerPropTypes_namespaceObject); __webpack_require__.d(PlayerPropTypes_namespaceObject, "controlKeyword", function() { return controlKeyword; }); __webpack_require__.d(PlayerPropTypes_namespaceObject, "control", function() { return control; }); __webpack_require__.d(PlayerPropTypes_namespaceObject, "crossOriginAttribute", function() { return crossOriginAttribute; }); __webpack_require__.d(PlayerPropTypes_namespaceObject, "repeatStrategy", function() { return PlayerPropTypes_repeatStrategy; }); __webpack_require__.d(PlayerPropTypes_namespaceObject, "mediaSource", function() { return mediaSource; }); __webpack_require__.d(PlayerPropTypes_namespaceObject, "mediaSessionAction", function() { return mediaSessionAction; }); __webpack_require__.d(PlayerPropTypes_namespaceObject, "mediaSessionArtwork", function() { return mediaSessionArtwork; }); __webpack_require__.d(PlayerPropTypes_namespaceObject, "track", function() { return PlayerPropTypes_track; }); __webpack_require__.d(PlayerPropTypes_namespaceObject, "seekMode", function() { return seekMode; }); // EXTERNAL MODULE: external {"root":"React","commonjs":"react","commonjs2":"react","amd":"react"} var external_root_React_commonjs_react_commonjs2_react_amd_react_ = __webpack_require__(1); var external_root_React_commonjs_react_commonjs2_react_amd_react_default = /*#__PURE__*/__webpack_require__.n(external_root_React_commonjs_react_commonjs2_react_amd_react_); // EXTERNAL MODULE: external {"root":"PropTypes","commonjs":"prop-types","commonjs2":"prop-types","amd":"prop-types"} var external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_ = __webpack_require__(0); var external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default = /*#__PURE__*/__webpack_require__.n(external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_); // EXTERNAL MODULE: ./node_modules/array-find-index/index.js var array_find_index = __webpack_require__(4); var array_find_index_default = /*#__PURE__*/__webpack_require__.n(array_find_index); // EXTERNAL MODULE: ./src/utils/createSingleGlobalContext.js var createSingleGlobalContext = __webpack_require__(3); // CONCATENATED MODULE: ./src/PlayerContext.js // TODO: test to make sure context contents stay in sync with // enumerated list here /* harmony default export */ var PlayerContext = (Object(createSingleGlobalContext["a" /* default */])({ displayName: 'PlayerContext', keysWillUpdate: ['playlist', 'activeTrackIndex', 'trackLoading', 'paused', 'currentTime', 'seekPreviewTime', 'seekInProgress', 'awaitingPlayResume', 'duration', 'bufferedRanges', 'playedRanges', 'seekableRanges', 'volume', 'muted', 'shuffle', 'stalled', 'playbackRate', 'setVolumeInProgress', 'repeatStrategy', 'mediaCannotPlay'] })); // CONCATENATED MODULE: ./src/GroupContext.js /* harmony default export */ var GroupContext = (Object(createSingleGlobalContext["a" /* default */])({ displayName: 'GroupContext', keysWillUpdate: ['groupProps'] })); // CONCATENATED MODULE: ./src/constants.js const repeatStrategyOptions = ['none', 'playlist', 'track']; const MEDIA_ERR_NETWORK = 2; // EXTERNAL MODULE: ./src/utils/console.js var console = __webpack_require__(2); // CONCATENATED MODULE: ./src/PlayerPropTypes.js function requiredOnlyUnlessHasProp(propType, altPropName) { let warnedAboutDefiningBoth = false; function validate(props, propName, componentName) { if (propName in props) { if (!warnedAboutDefiningBoth && altPropName in props) { Object(console["b" /* logWarning */])(`Do not define both the '${propName}' and '${altPropName}' props.`); warnedAboutDefiningBoth = true; } for (var _len = arguments.length, rest = new Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) { rest[_key - 3] = arguments[_key]; } return propType.isRequired(props, propName, componentName, ...rest); } if (!(altPropName in props)) { return new Error(`If the '${altPropName}' prop is not defined, '${propName}' must be.`); } } return validate; } const controlKeyword = external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.oneOf(['playpause', 'backskip', 'forwardskip', 'volume', 'mute', 'repeat', 'shuffle', 'progress', 'progressdisplay', 'fullscreen', 'spacer']); const control = external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.oneOfType([external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.func, controlKeyword]); const crossOriginAttribute = external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.oneOf(['anonymous', 'use-credentials']); const PlayerPropTypes_repeatStrategy = external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.oneOf(repeatStrategyOptions); const mediaSource = external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.shape({ src: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.string.isRequired, type: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.string.isRequired }); const mediaSessionAction = external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.oneOf(['play', 'pause', 'previoustrack', 'nexttrack', 'seekbackward', 'seekforward']); const mediaSessionArtwork = external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.shape({ src: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.string.isRequired, sizes: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.string, type: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.string }); const PlayerPropTypes_track = external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.shape({ url: requiredOnlyUnlessHasProp(external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.string, 'sources'), sources: requiredOnlyUnlessHasProp(external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.arrayOf(mediaSource.isRequired), 'url'), title: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.string.isRequired, artist: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.string, album: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.string, artwork: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.arrayOf(mediaSessionArtwork.isRequired), duration: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.oneOfType([external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.string, external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.number]), startingTime: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.number, isUnboundedStream: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.bool, meta: external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.object }); const seekMode = external_root_PropTypes_commonjs_prop_types_commonjs2_prop_types_amd_prop_types_default.a.oneOf(['paused', 'immediate', 'onrelease']); // CONCATENATED MODULE: ./src/factories/createCustomMediaElement.js const loopchange = 'loopchange'; const srcrequest = 'srcrequest'; function createCustomMediaElement(media) { new MutationObserver(() => { media.dispatchEvent(new Event(loopchange)); }).observe(media, { attributes: true, attributeFilter: ['loop'] }); // Don't let the media src property get modified directly. // Instead, when it does get set, dispatch an event to be // handled in a way that doesn't conflict with the loaded // playlist. Object.defineProperty(media, 'src', { get: () => media.currentSrc, set: src => { const e = new Event(srcrequest); e.srcRequested = src; media.dispatchEvent(e); } }); return media; } /* harmony default export */ var factories_createCustomMediaElement = (createCustomMediaElement); // CONCATENATED MODULE: ./src/utils/ShuffleManager.js /* ShuffleManager * * Manages navigation throughout a list which is: * - Sourced from another provided list * - In random order (except to avoid consecutive duplicates) * - Extended endlessly on-the-fly, as needed * - Able to have future history overwritten by non-random choices * - Able to swap source lists and maintain shuffle order for common members */ class ShuffleManager { constructor(list, options) { if (options === void 0) { options = {}; } this._list = list; this._forwardStack = []; this._backStack = []; this._currentItem = undefined; this._allowBackShuffle = Boolean(options.allowBackShuffle); } findNextItem(currentIndex) { if (currentIndex !== undefined) { this.setCurrentIndex(currentIndex); } this._currentItem = _findNextItem(this._list, this._forwardStack, this._backStack, this._currentItem, true); return this._currentItem; } findPreviousItem(currentIndex) { if (currentIndex !== undefined) { this.setCurrentIndex(currentIndex); } this._currentItem = _findNextItem(this._list, this._backStack, this._forwardStack, this._currentItem, this._allowBackShuffle); return this._currentItem; } pickNextItem(index, currentIndex) { if (currentIndex !== undefined) { this.setCurrentIndex(currentIndex); } if (this._list[index] === undefined) { return undefined; } if (this._currentItem !== undefined) { this._backStack.push(this._currentItem); } this._forwardStack.length = 0; this._currentItem = this._list[index]; return this._currentItem; } setList(list) { this._list = list; } setOptions(options) { for (const o of Object.keys(options)) { switch (o) { case 'allowBackShuffle': this[`_${o}`] = Boolean(options[o]); break; default: break; } } } setCurrentIndex(currentIndex) { const item = this._list[currentIndex]; if (this._currentItem !== item) { this.clear(); this._currentItem = item; } } clear() { this._forwardStack.length = 0; this._backStack.length = 0; this._currentItem = undefined; } } function _goForward(n, forwardStack, backStack, currentItem) { let item = currentItem; for (let i = 0; i < n; i++) { if (!forwardStack.length) { // rollback before erroring (note stack reversal) _goForward(i, backStack, forwardStack, item); throw `Moving ${n} places was not possible!`; } backStack.push(item); item = forwardStack.pop(); } return item; } function _allItemsMatch(list, item) { if (!list.length) { return false; } for (let i = 0; i < list.length; i++) { if (item !== list[i]) { return false; } } return true; } function _findNextItem(list, forwardStack, backStack, currentItem, allowMore) { let item = currentItem; if (!list.length) { return undefined; } for (let i = 1; i <= forwardStack.length; i++) { if (list.indexOf(forwardStack[forwardStack.length - i]) !== -1) { return _goForward(i, forwardStack, backStack, item); } } if (!allowMore) { return undefined; } if (_allItemsMatch(list, item)) { // we can serve this as our "next" item but we // won't modify our history since it's the same. return item; } let nextItem; do { nextItem = list[Math.floor(Math.random() * list.length)]; } while (item === nextItem || nextItem === undefined); // if we're skipping items that aren't in our current list we may // have some items in our forwardStack - make sure we move to the front. item = _goForward(forwardStack.length, forwardStack, backStack, item); if (item !== undefined) { backStack.push(item); } return nextItem; } /* harmony default export */ var utils_ShuffleManager = (ShuffleManager); // CONCATENATED MODULE: ./src/utils/isPlaylistValid.js function isPlaylistValid(playlist) { return Boolean(playlist && playlist.length); } /* harmony default export */ var utils_isPlaylistValid = (isPlaylistValid); // CONCATENATED MODULE: ./src/utils/getTrackSources.js const blankSources = [{ src: '' }]; function getTrackSources(playlist, index) { if (!utils_isPlaylistValid(playlist)) { return blankSources; } const _playlist$index = playlist[index], sources = _playlist$index.sources, url = _playlist$index.url; if (sources) { return sources.length ? sources : blankSources; } return [{ src: url }]; } /* harmony default export */ var utils_getTrackSources = (getTrackSources); // CONCATENATED MODULE: ./src/utils/findTrackIndexByUrl.js function findTrackIndexByUrl(playlist, url) { return array_find_index_default()(playlist, track => { if (track.sources) { return array_find_index_default()(track.sources, source => source.src === url) !== -1; } return track.url && url === track.url; }); } /* harmony default export */ var utils_findTrackIndexByUrl = (findTrackIndexByUrl); // CONCATENATED MODULE: ./src/utils/snapshot.js function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 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; } const veryLongKey = '__highly_unstable_snapshot_internals_which_will_break_your_app_if_you_use_them_directly__'; const versionKey = '__cassette_snapshot_version__'; // IMPORTANT: new migrations *must* always be added to the end since // the tracked snapshot version is based on the migration index. // If there is a crash-inducing bug in an existing migration, it can be patched // in-place, but it should never be removed from the migrations array. const migrations = [oldSnapshot => { const __unstable__ = oldSnapshot.__unstable__, rest = _objectWithoutPropertiesLoose(oldSnapshot, ["__unstable__"]); return _objectSpread({}, rest, { [veryLongKey]: __unstable__ }); }]; function getStateSnapshot(state) { const paused = state.paused, currentTime = state.currentTime, activeTrackIndex = state.activeTrackIndex, volume = state.volume, muted = state.muted, loop = state.loop, cycle = state.cycle, shuffle = state.shuffle, playbackRate = state.playbackRate, duration = state.duration, __playlist__ = state.__playlist__; return { [versionKey]: migrations.length, [veryLongKey]: { paused, // currentTime can't be restored for unbounded live streams currentTime: duration === Infinity ? 0 : currentTime, activeTrackIndex, volume, muted, loop, cycle, shuffle, playbackRate, activeTrackSrc: utils_isPlaylistValid(__playlist__) ? utils_getTrackSources(__playlist__, activeTrackIndex)[0].src : null } }; } function restoreStateFromSnapshot(snapshot, props) { const migratedSnapshot = migrations.slice(snapshot[versionKey] || 0).reduce((oldSnapshot, migration) => migration(oldSnapshot), snapshot); const _migratedSnapshot$ver = migratedSnapshot[veryLongKey], paused = _migratedSnapshot$ver.paused, currentTime = _migratedSnapshot$ver.currentTime, activeTrackIndex = _migratedSnapshot$ver.activeTrackIndex, volume = _migratedSnapshot$ver.volume, muted = _migratedSnapshot$ver.muted, loop = _migratedSnapshot$ver.loop, cycle = _migratedSnapshot$ver.cycle, shuffle = _migratedSnapshot$ver.shuffle, playbackRate = _migratedSnapshot$ver.playbackRate, activeTrackSrc = _migratedSnapshot$ver.activeTrackSrc; const restoredStateValues = {}; if (utils_isPlaylistValid(props.playlist) && typeof paused === 'boolean') { // using awaitingPlay instead of paused triggers an animation restoredStateValues.awaitingPlay = !paused; } if (typeof volume === 'number' && volume >= 0 && volume <= 1) { restoredStateValues.volume = volume; } if (typeof muted === 'boolean') { restoredStateValues.muted = muted; } if (typeof loop === 'boolean') { restoredStateValues.loop = loop; } if (typeof cycle === 'boolean') { restoredStateValues.cycle = cycle; } if (typeof shuffle === 'boolean') { restoredStateValues.shuffle = shuffle; } if (typeof playbackRate === 'number') { restoredStateValues.playbackRate = playbackRate; } let useCurrentTime = false; if (typeof activeTrackSrc === 'string' && typeof activeTrackIndex === 'number' && activeTrackIndex >= 0) { // let's try staying on the same track index const currentSrc = props.playlist[activeTrackIndex] && utils_getTrackSources(props.playlist, activeTrackIndex)[0].src; if (currentSrc && activeTrackSrc === currentSrc) { restoredStateValues.activeTrackIndex = activeTrackIndex; useCurrentTime = true; } else { /* if the track we were playing before is in the new playlist, * update the activeTrackIndex. */ const newTrackIndex = utils_findTrackIndexByUrl(props.playlist, activeTrackSrc); if (newTrackIndex !== -1) { restoredStateValues.activeTrackIndex = newTrackIndex; useCurrentTime = true; } } } if (useCurrentTime && typeof currentTime === 'number' && currentTime >= 0) { restoredStateValues.currentTime = currentTime; } return restoredStateValues; } // CONCATENATED MODULE: ./src/utils/getSourceList.js // collapses playlist into flat list containing // the first source url for each track function getSourceList(playlist) { return (playlist || []).map((_, i) => utils_getTrackSources(playlist, i)[0].src); } /* harmony default export */ var utils_getSourceList = (getSourceList); // CONCATENATED MODULE: ./src/utils/getTimeRangesArray.js function getTimeRangesArray(timeRangesObj) { const timeRangesArray = Array(timeRangesObj.length); for (let i = 0; i < timeRangesObj.length; i++) { timeRangesArray[i] = { start: timeRangesObj.start(i), end: timeRangesObj.end(i) }; } return timeRangesArray; } /* harmony default export */ var utils_getTimeRangesArray = (getTimeRangesArray); // CONCATENATED MODULE: ./src/utils/getRepeatStrategy.js function getRepeatStrategy(loop, cycle) { if (loop) { return 'track'; } if (cycle) { return 'playlist'; } return 'none'; } /* harmony default export */ var utils_getRepeatStrategy = (getRepeatStrategy); // CONCATENATED MODULE: ./src/utils/convertToNumberWithinIntervalBounds.js function convertToNumberWithinIntervalBounds(number, min, max) { min = typeof min === 'number' ? min : -Infinity; max = typeof max === 'number' ? max : Infinity; return Math.max(min, Math.min(number, max)); } /* harmony default export */ var utils_convertToNumberWithinIntervalBounds = (convertToNumberWithinIntervalBounds); // CONCATENATED MODULE: ./src/utils/getDisplayText.js function getDisplayText(track) { if (!track) { return ''; } if (track.title && track.artist) { return `${track.artist} - ${track.title}`; } return track.title || track.artist || track.album || ''; } /* harmony default export */ var utils_getDisplayText = (getDisplayText); // CONCATENATED MODULE: ./src/utils/parseTimeString.js function parseTimeString(str) { let seconds = 0; let factor = 1; const times = str.split(':').slice(-3); while (times.length > 0) { seconds += factor * parseInt(times.pop(), 10); factor *= 60; } return seconds; } /* harmony default export */ var utils_parseTimeString = (parseTimeString); // CONCATENATED MODULE: ./src/utils/getInitialDuration.js function getInitialDuration(track) { let duration = 0; if (track.duration) { if (typeof track.duration === 'string') { duration = utils_parseTimeString(track.duration); } else { duration = track.duration; } } return duration; } /* harmony default export */ var utils_getInitialDuration = (getInitialDuration); // CONCATENATED MODULE: ./src/PlayerContextProvider.js function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function PlayerContextProvider_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; } function PlayerContextProvider_objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { PlayerContextProvider_defineProperty(target, key, source[key]); }); } return target; } function PlayerContextProvider_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function playErrorHandler(err) { Object(console["a" /* logError */])(err); if (err.name === 'NotAllowedError') { const warningMessage = 'Media playback failed at ' + new Date().toLocaleTimeString() + '! (Perhaps autoplay is disabled in this browser.)'; Object(console["b" /* logWarning */])(warningMessage); } } // Existing Media Session API implementations have default handlers // for play/pause, and may yield unexpected behavior if custom // play/pause handlers are defined - so let's leave them be. const supportableMediaSessionActions = ['previoustrack', 'nexttrack', 'seekbackward', 'seekforward']; const defaultState = { // indicates whether media player should be paused paused: true, // elapsed time for active track, in seconds currentTime: 0, // The most recent targeted time, in seconds, for seek preview seekPreviewTime: 0, /* true if the user is currently dragging the mouse * to seek a new track position */ seekInProgress: false, /* true if media was playing when seek previewing began, * it was paused, and it should be resumed on seek * complete */ awaitingResumeOnSeekComplete: false, // true if media will play once new track has loaded awaitingPlayAfterTrackLoad: false, // the duration in seconds of the loaded track duration: 0, // array describing the buffered ranges in the loaded track bufferedRanges: [], // array describing the already-played ranges in the loaded track playedRanges: [], // array describing the seekable ranges in the loaded track seekableRanges: [], // true if the media is currently stalled pending data buffering stalled: false, // true if the active track should play on the next componentDidUpdate shouldRequestPlayOnNextUpdate: false, /* true if an error occurs while fetching the active track media data * or if its type is not a supported media format */ mediaCannotPlay: false, // maximum currentTime since the current track has been playing maxKnownTime: 0 }; // assumes playlist is valid function getGoToTrackState(_ref) { let prevState = _ref.prevState, index = _ref.index, track = _ref.track, _ref$shouldPlay = _ref.shouldPlay, shouldPlay = _ref$shouldPlay === void 0 ? true : _ref$shouldPlay, _ref$shouldForceLoad = _ref.shouldForceLoad, shouldForceLoad = _ref$shouldForceLoad === void 0 ? false : _ref$shouldForceLoad, startingTime = _ref.startingTime; const isNewTrack = prevState.activeTrackIndex !== index; const shouldLoadAsNew = Boolean(isNewTrack || shouldForceLoad); const currentTime = startingTime || track.startingTime || 0; return { duration: utils_getInitialDuration(track), activeTrackIndex: index, trackLoading: shouldLoadAsNew, mediaCannotPlay: prevState.mediaCannotPlay && !shouldLoadAsNew, currentTime: utils_convertToNumberWithinIntervalBounds(currentTime, 0), loop: shouldLoadAsNew ? false : prevState.loop, shouldRequestPlayOnNextUpdate: Boolean(shouldPlay), awaitingPlayAfterTrackLoad: Boolean(shouldPlay), awaitingForceLoad: Boolean(shouldForceLoad), maxKnownTime: shouldLoadAsNew ? 0 : prevState.maxKnownTime }; } /** * Wraps an area which shares a common [`playerContext`](#playercontext) */ class PlayerContextProvider_PlayerContextProvider extends external_root_React_commonjs_react_commonjs2_react_amd_react_["Component"] { constructor(props) { super(props); let currentTime = 0; let activeTrackIndex = utils_convertToNumberWithinIntervalBounds(props.startingTrackIndex, 0); const playlistIsValid = utils_isPlaylistValid(props.playlist); if (playlistIsValid && props.playlist[activeTrackIndex]) { currentTime = props.playlist[activeTrackIndex].startingTime || 0; } const initialStateSnapshot = props.initialStateSnapshot; let restoredStateFromSnapshot = {}; if (initialStateSnapshot) { try { restoredStateFromSnapshot = restoreStateFromSnapshot(initialStateSnapshot, props); const _restoredStateFromSna = restoredStateFromSnapshot, a = _restoredStateFromSna.activeTrackIndex, c = _restoredStateFromSna.currentTime; if (typeof a === 'number') { activeTrackIndex = a; } if (typeof c === 'number') { currentTime = c; } } catch (err) { Object(console["b" /* logWarning */])(err); Object(console["b" /* logWarning */])('Loading Cassette state from snapshot failed.'); Object(console["b" /* logWarning */])(`Failed snapshot:\n${JSON.stringify(initialStateSnapshot, null, 2)}`); } } this.state = PlayerContextProvider_objectSpread({}, defaultState, { // index matching requested track (whether track has loaded or not) activeTrackIndex, // whether we're waiting on loading metadata for the active track trackLoading: utils_isPlaylistValid(props.playlist), // the current timestamp on the active track in seconds currentTime: utils_convertToNumberWithinIntervalBounds(currentTime, 0), // the latest volume of the media, between 0 and 1. volume: utils_convertToNumberWithinIntervalBounds(props.defaultVolume, 0, 1), // true if the media has been muted muted: props.defaultMuted, // whether to loop the active track loop: props.defaultRepeatStrategy === 'track', // true if playlist should continue at start after completion cycle: props.defaultRepeatStrategy === 'playlist', // whether to randomly pick next track from playlist after one finishes shuffle: props.defaultShuffle, // Rate at which media should be played. 1.0 is normal speed. playbackRate: props.defaultPlaybackRate, // true if user is currently dragging mouse to change the volume setVolumeInProgress: false, // initialize shouldRequestPlayOnNextUpdate from autoplay prop shouldRequestPlayOnNextUpdate: props.autoplay && playlistIsValid, awaitingForceLoad: false, // duration might be set on track object duration: utils_getInitialDuration(playlistIsValid && props.playlist[activeTrackIndex]), // playlist prop copied to state (for getDerivedStateFromProps) __playlist__: props.playlist }, restoredStateFromSnapshot); // volume at last time we were unmuted and not actively setting volume this.lastStableVolume = this.state.volume; // used to keep track of play history when we are shuffling this.shuffler = new utils_ShuffleManager(utils_getSourceList(props.playlist), { allowBackShuffle: props.allowBackShuffle }); // html media element used for playback this.media = null; this.videoHostElementList = []; this.videoHostOccupiedCallbacks = new Map(); this.videoHostVacatedCallbacks = new Map(); // bind internal methods this.handleTrackPlaybackFailure = this.handleTrackPlaybackFailure.bind(this); this.handlePlayerOnlineAfterFailure = this.handlePlayerOnlineAfterFailure.bind(this); // bind callback methods to pass to descendant elements this.togglePause = this.togglePause.bind(this); this.selectTrackIndex = this.selectTrackIndex.bind(this); this.forwardSkip = this.forwardSkip.bind(this); this.backSkip = this.backSkip.bind(this); this.seekPreview = this.seekPreview.bind(this); this.seekComplete = this.seekComplete.bind(this); this.setVolume = this.setVolume.bind(this); this.setVolumeComplete = this.setVolumeComplete.bind(this); this.toggleMuted = this.toggleMuted.bind(this); this.toggleShuffle = this.toggleShuffle.bind(this); this.setRepeatStrategy = this.setRepeatStrategy.bind(this); this.setPlaybackRate = this.setPlaybackRate.bind(this); this.registerVideoHostElement = this.registerVideoHostElement.bind(this); this.renderVideoIntoHostElement = this.renderVideoIntoHostElement.bind(this); this.unregisterVideoHostElement = this.unregisterVideoHostElement.bind(this); this.updateVideoHostElement = this.updateVideoHostElement.bind(this); // bind media event handlers this.handleMediaPlay = this.handleMediaPlay.bind(this); this.handleMediaPause = this.handleMediaPause.bind(this); this.handleMediaSrcrequest = this.handleMediaSrcrequest.bind(this); this.handleMediaEnded = this.handleMediaEnded.bind(this); this.handleMediaEmptied = this.handleMediaEmptied.bind(this); this.handleMediaStalled = this.handleMediaStalled.bind(this); this.handleMediaCanplaythrough = this.handleMediaCanplaythrough.bind(this); this.handleMediaCanplay = this.handleMediaCanplay.bind(this); this.handleMediaTimeupdate = this.handleMediaTimeupdate.bind(this); this.handleMediaLoadeddata = this.handleMediaLoadeddata.bind(this); this.handleMediaVolumechange = this.handleMediaVolumechange.bind(this); this.handleMediaDurationchange = this.handleMediaDurationchange.bind(this); this.handleMediaProgress = this.handleMediaProgress.bind(this); this.handleMediaLoopchange = this.handleMediaLoopchange.bind(this); this.handleMediaRatechange = this.handleMediaRatechange.bind(this); } componentDidMount() { const media = this.media = factories_createCustomMediaElement(this.props.createMediaElement()); const _this$props = this.props, defaultPlaybackRate = _this$props.defaultPlaybackRate, crossOrigin = _this$props.crossOrigin, playlist = _this$props.playlist, autoplayDelayInSeconds = _this$props.autoplayDelayInSeconds, mediaElementRef = _this$props.mediaElementRef, getPosterImageForTrack = _this$props.getPosterImageForTrack, getMediaTitleAttributeForTrack = _this$props.getMediaTitleAttributeForTrack, onActiveTrackUpdate = _this$props.onActiveTrackUpdate; const _this$state = this.state, volume = _this$state.volume, muted = _this$state.muted, playbackRate = _this$state.playbackRate, loop = _this$state.loop, activeTrackIndex = _this$state.activeTrackIndex, shouldRequestPlayOnNextUpdate = _this$state.shouldRequestPlayOnNextUpdate; // initialize media properties // We used to set currentTime here.. now waiting for loadeddata. // This avoids an issue where some browsers ignore or delay currentTime // updates when in the HAVE_NOTHING state. media.defaultPlaybackRate = defaultPlaybackRate; if (crossOrigin) { media.crossOrigin = crossOrigin; } media.volume = volume; media.muted = muted; media.playbackRate = playbackRate; media.loop = loop; media.setAttribute('playsinline', ''); media.setAttribute('webkit-playsinline', ''); media.setAttribute('preload', 'metadata'); media.setAttribute('poster', getPosterImageForTrack(playlist[activeTrackIndex])); media.setAttribute('title', getMediaTitleAttributeForTrack(playlist[activeTrackIndex])); // add listeners for media events media.addEventListener('play', this.handleMediaPlay); media.addEventListener('pause', this.handleMediaPause); media.addEventListener('ended', this.handleMediaEnded); media.addEventListener('stalled', this.handleMediaStalled); media.addEventListener('emptied', this.handleMediaEmptied); media.addEventListener('canplay', this.handleMediaCanplay); media.addEventListener('canplaythrough', this.handleMediaCanplaythrough); media.addEventListener('timeupdate', this.handleMediaTimeupdate); media.addEventListener('loadeddata', this.handleMediaLoadeddata); media.addEventListener('volumechange', this.handleMediaVolumechange); media.addEventListener('durationchange', this.handleMediaDurationchange); media.addEventListener('progress', this.handleMediaProgress); media.addEventListener('ratechange', this.handleMediaRatechange); media.addEventListener('error', this.handleTrackPlaybackFailure); // add listeners for special events media.addEventListener('srcrequest', this.handleMediaSrcrequest); media.addEventListener('loopchange', this.handleMediaLoopchange); // set source elements for current track this.setMediaElementSources(); // initially mount media element in the hidden container (this may change) this.mediaContainer.appendChild(media); if (shouldRequestPlayOnNextUpdate) { this.setState({ shouldRequestPlayOnNextUpdate: false }); this.delayTimeout = setTimeout(() => { this.togglePause(false); }, autoplayDelayInSeconds * 1000); } if (mediaElementRef) { mediaElementRef(media); } if (onActiveTrackUpdate) { onActiveTrackUpdate({ track: playlist[activeTrackIndex], trackIndex: activeTrackIndex, previousTrack: null, previousTrackIndex: null }); } } static getDerivedStateFromProps(nextProps, prevState) { const newPlaylist = nextProps.playlist; if (newPlaylist === prevState.__playlist__) { // reference comparison is equal so we'll // assume the playlist is unchanged. return null; } const baseNewState = { __playlist__: newPlaylist }; // check if the new playlist is invalid if (!utils_isPlaylistValid(newPlaylist)) { return PlayerContextProvider_objectSpread({}, defaultState, baseNewState, { activeTrackIndex: 0, trackLoading: false }); } // check if the activeTrackIndex doesn't need to be updated const prevSources = utils_getTrackSources(prevState.__playlist__, prevState.activeTrackIndex); if (newPlaylist[prevState.activeTrackIndex]) { // the sources if we stay on the same track index const currentSources = utils_getTrackSources(newPlaylist, prevState.activeTrackIndex); // non-comprehensive but probably accurate check if (prevSources[0].src === currentSources[0].src) { // our active track index already matches return baseNewState; } } /* if the track we're already playing is in the new playlist, update the * activeTrackIndex. */ const newTrackIndex = utils_findTrackIndexByUrl(newPlaylist, prevSources[0].src); if (newTrackIndex !== -1) { return PlayerContextProvider_objectSpread({}, baseNewState, { activeTrackIndex: newTrackIndex }); } // if not, then load the first track in the new playlist, and pause. return PlayerContextProvider_objectSpread({}, baseNewState, getGoToTrackState({ prevState, track: newPlaylist[0], index: 0, shouldPlay: false, shouldForceLoad: true }), { mediaCannotPlay: false, awaitingPlayAfterTrackLoad: false }); } componentDidUpdate(prevProps, prevState) { this.media.defaultPlaybackRate = this.props.defaultPlaybackRate; this.media.crossOrigin = this.props.crossOrigin; this.shuffler.setList(utils_getSourceList(this.props.playlist)); this.shuffler.setOptions({ allowBackShuffle: this.props.allowBackShuffle }); const prevSources = utils_getTrackSources(prevProps.playlist, prevState.activeTrackIndex); const newSources = utils_getTrackSources(this.props.playlist, this.state.activeTrackIndex); const prevTrack = prevProps.playlist[prevState.activeTrackIndex]; const newTrack = this.props.playlist[this.state.activeTrackIndex]; if (this.state.awaitingForceLoad || prevSources[0].src !== newSources[0].src) { this.setMediaElementSources(); this.media.setAttribute('poster', this.props.getPosterImageForTrack(newTrack)); this.media.setAttribute('title', this.props.getMediaTitleAttributeForTrack(newTrack)); this.setState({ awaitingForceLoad: false }); if (!this.state.shuffle) { // after toggling off shuffle, we defer clearing the shuffle // history until we actually change tracks - if the user quickly // toggles shuffle off then back on again, we don't want to have // lost our history. this.shuffler.clear(); } // If track changes before player is back online, the previous track // shouldn't be reloaded. window.removeEventListener('online', this.handlePlayerOnlineAfterFailure); } if (this.props.onActiveTrackUpdate && prevTrack !== newTrack) { this.props.onActiveTrackUpdate({ track: newTrack, trackIndex: this.state.activeTrackIndex, previousTrack: prevTrack, previousTrackIndex: prevState.activeTrackIndex }); } if (prevProps !== this.props && !this.media.paused) { // update running media session based on new props this.stealMediaSession(); } if (this.state.shouldRequestPlayOnNextUpdate) { this.setState({ shouldRequestPlayOnNextUpdate: false }); // media.currentSrc is updated asynchronously so we should // play async to avoid weird intermediate state issues setTimeout(() => { this.togglePause(false); }); } clearTimeout(this.snapshotUpdateTimeout); this.snapshotUpdateTimeout = setTimeout(() => { if (this.props.onStateSnapshot) { this.props.onStateSnapshot(getStateSnapshot(this.state)); } }, 100); } componentWillUnmount() { const media = this.media; // Media element creation will have failed if MutationObserver isn't // supported by the browser. The parent might use an Error Boundary // to display a fallback and so we try to avoid triggering *additional* //