核心播放器 没有测试过
npm install react-native-video
该库的设计理念是「提供底层播放能力,上层 UI 完全交给开发者自定义」,因此内置控制器仅作为基础兜底,高级自定义(如滑块图标、分集、弹幕)需要开发者基于其 API 封装。
弹幕扩展(可选,npm可安装)
npm install react-native-barrage 没有测试过
tmd好烦人啊
xgplayer 下载后进过测试发送弹幕的输入框和按钮 显示不出来
Dplayer 弹幕好用 但是无法自定义进度条滑块 笔者测试通过 全屏 横屏 需要手动打开手机的自动旋转设置
Nplayer 不好用
RN使用webView 加载 vsCode 运行的 Dplayer
npm install Dplayer
index.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>DPlayer 本地版(node_modules)</title>
<!-- 引用 node_modules 里的 DPlayer CSS -->
<link rel="stylesheet" href="./node_modules/dplayer/dist/DPlayer.min.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body, html { width: 100%; height: 100%; background: #000; }
#dplayer { width: 100%; height: 100%; }
/* 自定义滑块(必生效) */
.dplayer-bar-thumb { display: none !important; }
#custom-thumb {
position: absolute;
width: 16px;
height: 16px;
border-radius: 50%;
background: #409eff;
box-shadow: 0 0 4px rgba(64,158,255,0.8);
z-index: 9999;
display: none;
cursor: pointer;
touch-action: manipulation;
}
</style>
</head>
<body>
<div id="dplayer"></div>
<div id="custom-thumb"></div>
<!-- 引用 node_modules 里的 DPlayer JS -->
<script src="./node_modules/dplayer/dist/DPlayer.min.js"></script>
<script>
const dp = new DPlayer({
container: document.getElementById('dplayer'),
video: {
url: 'https://www.w3schools.com/html/mov_bbb.mp4', // 稳定视频源
inline: true,
autoplay: false
},
danmaku: {
enable: true,
pool: [
{ text: '本地 node_modules 测试', color: '#ff0000', time: 1 },
{ text: '弹幕输入框正常', color: '#00ff00', time: 3 }
],
speed: 5,
opacity: 0.8,
unlimited: true
},
volume: 0.7,
loop: true,
hotkey: true
});
// 自定义滑块逻辑
const customThumb = document.getElementById('custom-thumb');
const bar = document.querySelector('.dplayer-bar');
let barRect = null;
dp.on('ready', () => {
customThumb.style.display = 'block';
barRect = bar.getBoundingClientRect();
bar.addEventListener('click', updateThumb);
bar.addEventListener('touchstart', updateThumb);
bar.addEventListener('touchmove', updateThumb);
dp.on('timeupdate', updateThumb);
dp.on('seek', updateThumb);
});
window.addEventListener('resize', () => {
barRect = bar.getBoundingClientRect();
updateThumb();
});
function updateThumb() {
if (!barRect || !dp.video.duration) return;
const progress = dp.video.currentTime / dp.video.duration;
const left = barRect.left + progress * barRect.width - 8 + 'px';
const top = barRect.top + (barRect.height - 16)/2 + 'px';
customThumb.style.left = left;
customThumb.style.top = top;
}
// 弹幕发送同步到RN
dp.on('danmaku_send', (data) => {
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'danmaku_send',
data: data
}));
}
});
// 接收RN控制指令
window.addEventListener('message', (e) => {
const msg = JSON.parse(e.data);
switch(msg.type) {
case 'play': dp.play(); break;
case 'pause': dp.pause(); break;
case 'fullscreen': dp.fullscreen.toggle(); break;
}
});
</script>
</body>
</html>
npm install -g serve
serve -p 3000
RN中页面
html
import { StatusBar } from "expo-status-bar";
import React, { useRef } from "react";
import { Alert, StyleSheet, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import WebView from "react-native-webview";
// **************************
// 关键:替换成你的本地服务器地址
// 电脑局域网 IP + 端口(比如 http://192.168.31.105:3000)
// **************************
const LOCAL_DPLAYER_URL = "http://192.168.0.102:3000";
// 类型定义
type WebViewMessage = {
type: "danmaku_send";
data: { text: string; color?: string; time: number };
};
type PlayerAction = "play" | "pause" | "fullscreen";
const DPlayerScreen = () => {
const webViewRef = useRef<WebView>(null);
const onMessage = (event: { nativeEvent: { data: string } }) => {
try {
const data = JSON.parse(event.nativeEvent.data) as WebViewMessage;
if (data.type === "danmaku_send") {
Alert.alert("弹幕发送成功", `内容:${data.data.text}`);
console.log("接收到弹幕数据:", data.data);
}
} catch (e) {
console.error("解析弹幕消息失败:", e);
}
};
const controlPlayer = (action: PlayerAction) => {
if (webViewRef.current) {
webViewRef.current.postMessage(JSON.stringify({ type: action }));
}
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.webViewContainer}>
<WebView
ref={webViewRef}
source={{ uri: LOCAL_DPLAYER_URL }}
javaScriptEnabled={true}
domStorageEnabled={true}
allowsInlineMediaPlayback={true}
mediaPlaybackRequiresUserAction={false}
scalesPageToFit={false}
allowsFullscreenVideo={true}
androidLayerType="hardware"
allowFileAccess={true}
allowUniversalAccessFromFileURLs={true}
allowFileAccessFromFileURLs={true}
onMessage={onMessage}
// 简化:不再依赖 statusCode,直接打印日志
onLoadEnd={() => {
console.log("✅ WebView 页面加载完成(忽略状态码)");
}}
onLoadStart={() => {
console.log("🔄 WebView 开始加载页面...");
}}
// 兜底:如果确实加载失败,会有其他日志提示
onError={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
console.error("❌ WebView 加载失败:", nativeEvent);
Alert.alert("WebView 错误", "页面加载失败,请检查网络和服务器状态");
}}
style={styles.webView}
/>
</View>
<StatusBar style="light" backgroundColor="#000" />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#000",
},
webViewContainer: {
flex: 1,
},
webView: {
flex: 1,
},
});
export default DPlayerScreen;