UNPKG

@theoplayer/react-native-ui

Version:

A React Native UI for @theoplayer/react-native

260 lines (257 loc) 8.82 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ThumbnailView = exports.DEFAULT_THUMBNAIL_VIEW_STYLE = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _reactNativeTheoplayer = require("react-native-theoplayer"); var _reactNativeUi = require("@theoplayer/react-native-ui"); var _Thumbnail = require("./Thumbnail"); var _Urlpolyfill = require("./Urlpolyfill"); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } const SPRITE_REGEX = /^([^#]*)#xywh=(\d+),(\d+),(\d+),(\d+)\s*$/; const TAG = 'ThumbnailView'; const DEFAULT_THUMBNAIL_VIEW_STYLE = exports.DEFAULT_THUMBNAIL_VIEW_STYLE = { containerThumbnail: { alignItems: 'center', flexDirection: 'row' }, thumbnail: { overflow: 'hidden', backgroundColor: 'transparent' } }; function getCueIndexAtTime(thumbnailTrack, time) { // Ignore if it's an invalid track or not a thumbnail track. if (!(0, _reactNativeTheoplayer.isThumbnailTrack)(thumbnailTrack)) { console.warn(TAG, 'Invalid thumbnail track'); return undefined; } // Ignore if the track does not have cues if (thumbnailTrack.cues == null || thumbnailTrack.cues.length == 0) { return undefined; } const cues = thumbnailTrack.cues; let cueIndex = 0; for (const [index, cue] of cues.entries()) { if (cue.startTime <= time) { cueIndex = index; } else if (time >= cue.endTime) { return cueIndex; } } return cueIndex; } function resolveThumbnailUrl(thumbnailTrack, thumbnail) { if (thumbnailTrack && thumbnailTrack.src) { return new _Urlpolyfill.URL(thumbnail, thumbnailTrack.src).href; } else { return thumbnail; } } function getThumbnailImageForCue(thumbnailTrack, cue) { const thumbnailContent = cue && cue.content; if (!thumbnailContent) { // Cue does not contain any thumbnail info. return null; } const spriteMatch = thumbnailContent.match(SPRITE_REGEX); if (spriteMatch) { // The thumbnail is part of a tile. const [, url, x, y, w, h] = spriteMatch; return { tileX: +x, tileY: +y, tileWidth: +w, tileHeight: +h, url: resolveThumbnailUrl(thumbnailTrack, url) }; } else { // The thumbnail is a separate image. return { url: resolveThumbnailUrl(thumbnailTrack, thumbnailContent) }; } } /** * Calculate the dimensions of the thumbnail tile map based on the W3C Media Fragments URIs for all cues. * * @param thumbnailTrack */ function maxThumbnailSize(thumbnailTrack) { let maxWidth = 0, maxHeight = 0; thumbnailTrack.cues?.forEach(cue => { if (cue && cue.content) { const spriteMatch = cue.content.match(SPRITE_REGEX); if (spriteMatch) { const [,, tileX, tileY, tileWidth, tileHeight] = spriteMatch; maxWidth = Math.max(maxWidth, +tileX + +tileWidth); maxHeight = Math.max(maxHeight, +tileY + +tileHeight); } } }); return { maxTileWidth: maxWidth, maxTileHeight: maxHeight }; } const ThumbnailView = props => { const [mounted, setMounted] = (0, _react.useState)(false); const [imageWidth, setImageWidth] = (0, _react.useState)(props.size); const [imageHeight, setImageHeight] = (0, _react.useState)(props.size); const [renderWidth, setRenderWidth] = (0, _react.useState)(1); const [renderHeight, setRenderHeight] = (0, _react.useState)(1); (0, _react.useEffect)(() => { setMounted(true); return () => { setMounted(false); }; }, []); const onTileImageLoad = thumbnail => () => { if (!mounted) { return; } const { size } = props; const { tileWidth, tileHeight } = thumbnail; if (tileWidth && tileHeight) { /** * On Android, Fresco can scale the React Native `<Image>` component internally if it is considered to be * 'huge' (larger than 2048px width). There is no way to know the original size without using another * image package. * This work-around calculates the maximum tile size based on all cue W3C Media Fragments URIs. * * {@link https://github.com/facebook/react-native/issues/22145} */ const { maxTileWidth, maxTileHeight } = _reactNative.Platform.OS === 'android' ? maxThumbnailSize(props.thumbnailTrack) : { maxTileWidth: 0, maxTileHeight: 0 }; _reactNative.Image.getSize(thumbnail.url, (width, height) => { setImageWidth(Math.max(width, maxTileWidth)); setImageHeight(Math.max(height, maxTileHeight)); setRenderWidth(size); setRenderHeight(tileHeight * size / tileWidth); }); } }; const onImageLoadError = event => { console.error(TAG, 'Failed to load thumbnail url:', event.nativeEvent.error); }; const onImageLoad = thumbnail => () => { if (!mounted) { return; } const { size } = props; _reactNative.Image.getSize(thumbnail.url, (width, height) => { setImageWidth(width); setImageHeight(height); setRenderWidth(size); setRenderHeight(height * size / width); }); }; const renderThumbnail = (thumbnail, index) => { const { size } = props; const scale = 1.0; if ((0, _Thumbnail.isTileMapThumbnail)(thumbnail)) { const ratio = thumbnail.tileWidth == 0 ? 0 : scale * size / thumbnail.tileWidth; return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: [DEFAULT_THUMBNAIL_VIEW_STYLE.thumbnail, { width: scale * renderWidth, height: scale * renderHeight }], children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, { resizeMode: 'cover', style: { position: 'absolute', top: -ratio * thumbnail.tileY, left: -ratio * thumbnail.tileX, width: ratio * imageWidth, height: ratio * imageHeight }, source: { uri: thumbnail.url }, onError: onImageLoadError, onLoad: onTileImageLoad(thumbnail) }) }, index); } else { return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: [DEFAULT_THUMBNAIL_VIEW_STYLE.thumbnail, { width: scale * renderWidth, height: scale * renderHeight }], children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, { resizeMode: 'contain', style: { width: scale * size, height: scale * renderHeight }, source: { uri: thumbnail.url }, onError: onImageLoadError, onLoad: onImageLoad(thumbnail) }) }, index); } }; const { time, duration, thumbnailTrack, showTimeLabel, timeLabelStyle } = props; if (!thumbnailTrack || !thumbnailTrack.cues || thumbnailTrack.cues.length === 0) { // No thumbnails to render. return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {}); } const nowCueIndex = getCueIndexAtTime(thumbnailTrack, time); if (nowCueIndex === undefined) { // No thumbnail for current time return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {}); } const current = getThumbnailImageForCue(thumbnailTrack, thumbnailTrack.cues[nowCueIndex]); if (current === null) { // No thumbnail for current time return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {}); } return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: { flexDirection: 'column' }, children: [showTimeLabel && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeUi.StaticTimeLabel, { style: [{ marginLeft: 20, height: 20, alignSelf: 'center' }, timeLabelStyle], time: time, duration: duration, showDuration: false }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: [DEFAULT_THUMBNAIL_VIEW_STYLE.containerThumbnail, { height: renderHeight }], children: renderThumbnail(current, 0) })] }); }; exports.ThumbnailView = ThumbnailView; //# sourceMappingURL=ThumbnailView.js.map