需求分析
最近有个做app播放器,实现视频实时监控语回放的功能需求。
背景:APP是使用React Native开发的,使用萤石云监控设备。 基于我们没有暂时没有原生开发人员支持,采用技术栈为React Native嵌套Webview。 我们目前是敏捷迭代,需要两天开发好功能。
使用技术
在网上找了一个框架,是基于萤石云视频的二次封装: EZUIKit-JavaScript-npm
由于技术为Webview嵌套h5,直接用最基础的html模版生成h5就好,
使用了下图的两个模版 mobileLive 与 mobileRect
主要核心代码在ezuikits.js,如果要修改部分逻辑,可直接修改这个js
代码
webview嵌套逻辑,动态渲染不同模版:
import { live, replay } from "./templates";
<WebView
style={{
width: "100%",
height: "100%",
backgroundColor: "#fff"
}}
ref={webviewRef}
originWhitelist={["*"]}
useWebKit // ios使用最新webkit内核渲染
allowUniversalAccessFromFileURLs
geolocationEnabled
mixedContentMode={"always"}
scrollEnabled={false}
nestedScrollEnabled
javaScriptEnabled
startInLoadingState={false}
onMessage={handleMessage}
hideKeyboardAccessoryView={false}
renderError={() => {
return <Text>加载失败</Text>;
}}
source={{
baseUrl: "",
html:
type === "live"
? live({ url, accessToken, newAlarmText, deviceName })
: replay({ url, accessToken, isAlarm, deviceName })
}}
/>
模版Html:
编写样式:要设置body的padding跟margin,不然会有样式问题;
初始化播放器:new EZUIKit.EZUIKitPlayer()
react native回调事件:window.ReactNativeWebView.postMessage()
// 直播
const live = ({ url, accessToken, newAlarmText, deviceName }: templateOption): string => {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>Document</title>
<script src="./ezuikit.js"></script>
<style>
body {
margin: 0;
padding: 0;
background: #F5F5F5;
}
.device-name {
font-size: 16px;
font-weight: bold;
color: #333333;
background-color: #fff;
padding: 16px;
position: relative;
top: -3px;
z-index: -1;
}
.header-back {
line-height: 36px;
color: #FFFFFF;
position: absolute;
top: 36px;
left: 0;
right: 0;
background: linear-gradient(180deg, rgba(3,3,3,0.5) 0%, rgba(0,0,0,0) 100%);
z-index: 1;
display: none;
}
.back-container {
display: inline-block;
padding: 0 24px 0 16px;
line-height: 36px;
color: #FFFFFF;
}
.mobile-ez-ptz-container, .live-ptz-title {
display: none !important;
}
.video-containerdraw-window {
border: 0 !important;
}
</style>
</head>
<body>
<div class="live">
<div class="header-back" id="back-container">
<div class="back-container" onclick="handleBackClick()">
<svg viewBox="64 64 896 896" focusable="false" data-icon="left" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"></path></svg>
</div>
</div>
<div id="video-container" style="width: 100%"></div>
<div class="device-name" id="deviceName">摄像头</div>
<div class="ysy-list-content"
<div class="list-item" onclick="ysyToReplay()">
<div class="left">
<div class="content">
<div>回看</div>
<div class="desc">查看历史监测记录</div>
</div>
</div>
<img class="icon-arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARxJREFUWEft1SFuwzAUxvHvSQMFzXVCiqKiqXS5weDAYEHBWOHAYA9QsgOMhYz5KaE7wngCk5fJLGCR7fhZIQlNpP/PtvxCWPmhlfvYAF470DTNoe/7S9d1ZVEUneaxOQE2PgzDF4A9gO+2bR81EU4AM18BnCerVkU4ATZsjHknotcUCC9ASoQ3IBUiCGARzHwD8Kx1HMGAqqoesiy7A3jSQAQDbFQTsQigiVgM0EJEATQQ0YCZ6/kjIkWe57+u/0Y04J8p6R23uChAbDwKoBFfDNCKLwJoxoMB2vEgQIq4NyBV3AtgjPkgopfJQAm659GDiJlP4zh+EtEOgGrcawfsR3VdH0XkTURKn/HqWvX0fdQkDAnNfbsBVt+BP7ve4iEfMHDpAAAAAElFTkSuQmCC" alt="icon-arrow">
</div>
</div>
</div>
<script>
window.onload = (event) => {
const videoWidth = document.body.clientWidth;
const videoHeight = videoWidth * 0.6;
var playr;
playr = new EZUIKit.EZUIKitPlayer({
id: 'video-container', // 视频容器ID
accessToken: '${accessToken}',
url: '${url}',
template: 'mobileLive',
width: videoWidth,
height: videoHeight,
});
window.ysyToReplay = () => {
window.ReactNativeWebView.postMessage("replay");
}
window.handleBackClick = () => {
window.ReactNativeWebView.postMessage("back");
}
function getStyle(obj,attr){
return obj.currentStyle?obj.currentStyle[attr]:getComputedStyle(obj)[attr];
}
let backShowId = null;
setTimeout(() => {
// 控制返回按钮显隐
document.getElementById("video-container").addEventListener("touchstart", function() {
const backContainer = document.getElementById("back-container");
if (getStyle(backContainer, "display") === "none") {
backContainer.style.display = "block";
if (backShowId) {
clearTimeout(backShowId);
}
backShowId = setTimeout(() => {
backContainer.style.display = "none";
}, 8000)
}
});
}, 1000);
};
</script>
</body>
</html>
`;
};