@theoplayer/react-native-ui
Version:
A React Native UI for @theoplayer/react-native
260 lines (257 loc) • 8.82 kB
JavaScript
;
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