综合
1. 综合演示
2. 操作说明
1、open、close、destroy 按钮: 分别对应打开、关闭、销毁播放器
2、URL 输入框: 对应播放的视频URL
3、Live 选项:表示是否是直播流
4、Use Flv 选项:表示是否优先使用flv格式播放
5、DANMU 输入框: 输入弹幕内容,
6、Send Danmu、Enable Danmu、Disable Danmu 按钮: 分别对应发送弹幕、允许弹幕、禁止弹幕,注意:只是本地测试,并未发送到弹幕服务器。
7、Load Chapters 按钮: 加载视频章节
8、Load Subtitle、Remove Subtitle 按钮: 分别对应加载字幕、移除字幕
9、MStreams 按钮:点开后弹出输入框,可输入多个视频源,支持多种格式,具体参看源地址url参数说明:
{ "HD1": "https://example.com/stream-hd.flv", "SD1": "https://example.com/stream-ld.flv ", "SD2": "https://example.com/stream-sd.flv" }2、json列表 方式一: 每个列表项对应一个json对象,包含name和url两个属性,name表示视频源的名称,url表示视频源的URL
[ { "name": "HD1", "url": "https://example.com/media/stream-hd.flv" }, { "name": "SD1", "url": "https://example.com/media/stream-ld.flv", "default": true }, { "name": "SD2", "url": "https://example.com/media/stream-sd.flv" } ]3、json列表 方式二: 每个列表项对应一个列表:第0个位置为url;第1个位置为流的名称name,可以缺省;第2个位置为mime类型,通常缺省。
[ [ "https://example.com/stream-uhd.flv", "HD1" ], [ "https://example.com/stream-hd.flv", "SD1" ], [ "https://example.com/stream-ld.flv", "SD2" ] ]
1、json对象
10、stream_type 列表框: 选择视频源的格式,可选值有: httpflv、hls、dash、webrtc、mpegts
11、CurrentTime 按钮: 获得当前时间
12、Start、End、diffTime 显示:表示开始时间、结束时间、时间差(毫秒),可看到一个输入源从打开到开始播放需要的耗时
3. 注意事项
1、测试rtsp协议,需要开启媒体网关服务器,具体参看播放rtsp流,
2、自带弹幕UI发送弹幕,需要开启弹幕服务器,具体参看弹幕设置说明
3、如需测试rtsp协议、弹幕功能,请加微信号: chenfanyu42 申请加入(请备注"
zwplayer")4、进度条预览图、字幕、章节标记针对的是测试视频,使用时需要替换为实际的进度条预览图、字幕、章节标记。
4. 示例代码
示例代码仅供参考,实际使用时需要根据实际情况进行修改。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="renderer" content="webkit">
<meta name="viewport"
content="width=device-width,initial-scale=1, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="x5-fullscreen" content="true">
<meta name="full-screen" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="format-detection" content="telephone=no">
<title>zwplayer 播放器综合演示 | zwplayer 官网</title>
<meta name="keywords"
content="ZWPlayer播放器, 播放器功能演示, 流媒体播放测试, HTTP-FLV直播, HLS播放, DASH视频, WebRTC直播, 视频弹幕功能, 外挂字幕, 章节标记, 多码率切换, 开发者集成">
<meta name="description"
content="ZWPlayer播放器一站式功能演示与测试平台。完整展示对HTTP-FLV、HLS、DASH、WebRTC等流媒体协议的支持,并提供弹幕、字幕加载、章节标记、多码率切换等高级功能的交互测试。包含详尽的代码示例,助力开发者快速集成与调试。">
<script type="text/javascript" charset="utf-8" src="https://cdn.zwplayer.cn/v3/zwplayer/zwplayer.js"></script>
<style>
/* ===== 设计变量 ===== */
:root {
--primary: #00c6ff;
--primary-dark: #0084cc;
--bg: #ffffff;
--surface: #f5f7fa;
--text: #222;
--text2: #555;
--border: #e2e8f0;
--radius: 12px;
--shadow: 0 4px 12px rgba(0, 0, 0, .08);
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--safe-top: env(safe-area-inset-top, 0);
--safe-bottom: env(safe-area-inset-bottom, 0);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #121212;
--surface: #1e1e1e;
--text: #eee;
--text2: #bbb;
--border: #333;
--shadow: 0 4px 12px rgba(0, 0, 0, .3);
}
}
/* ===== 全局重置 ===== */
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-tap-highlight-color: transparent;
}
body {
margin: 0;
font-family: var(--font);
background: var(--bg);
color: var(--text);
line-height: 1.5;
padding-top: var(--safe-top);
padding-bottom: var(--safe-bottom);
}
/* ===== 播放器容器 ===== */
.player-wrap {
max-width: 960px;
margin: 0 auto;
padding: .75rem;
display: flex;
flex-direction: column;
/* gap: .75rem;*/
}
/* 16:9 自适应视频区 */
#player-holder {
position: relative;
width: 100%;
/* border-radius: var(--radius);*/
overflow: hidden;
background: #000;
box-shadow: var(--shadow);
aspect-ratio: 16 / 9;
}
.main_bar {
max-width: 960px;
margin: 0 auto;
padding: .75rem;
display: flex;
flex-direction: column;
gap: .75rem;
}
/* ===== 工具栏通用 ===== */
.danmubar .btn {
border: 1px solid #a0a0a0;
border-radius: 5px;
min-width: 60px;
height: 26px;
box-sizing: border-box;
margin: 0 5px;
float: left;
}
.danmubar .btn:hover {
background: #b8e1ee;
border-color: #89baf2;
color: #003566;
}
.danmubar .btn:hover {
background: #83aab7;
color: #016789;
border-color: #2c5c81;
}
.danmubar {
border: 1px solid #c0c0c0;
width: 100%;
margin-left: auto;
margin-right: auto;
padding: 6px 10px;
box-sizing: border-box;
position: relative;
height: 48px;
background-color: #474343;
padding-left: 200px;
margin-top: 0;
border-top: 0;
border-color: #000;
}
.vxplayer-toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .5rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: .75rem 1rem;
box-shadow: var(--shadow);
}
/* 按钮 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px; /* iOS 推荐触控高度 */
padding: .5rem 1rem;
font-size: .95rem;
font-weight: 500;
color: #fff;
background: var(--primary);
border: none;
border-radius: 8px;
cursor: pointer;
transition: background .2s;
}
.btn:hover {
background: var(--primary-dark);
}
@media (hover: none) {
.btn:hover {
background: var(--primary);
}
}
.btn:active {
transform: scale(.97);
}
/* 输入框 */
input[type="text"],
textarea,
.cbx {
flex: 1 1 140px;
min-width: 0;
padding: .5rem .75rem;
font-size: .95rem;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--text);
}
input[type="text"]:focus,
textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px var(--primary-dark);
}
/* 复选框 */
.opt-panel {
display: flex;
align-items: center;
gap: .25rem .75rem;
flex-wrap: wrap;
}
.opt-panel input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: var(--primary);
}
/* ===== 弹幕栏 ===== */
#player-dammubar {
gap: .5rem;
}
#player-dammubar .url-inputbox {
flex: 1 1 180px;
display: flex;
align-items: center;
gap: .5rem;
}
#player-dammubar .label {
font-weight: 500;
color: var(--text2);
white-space: nowrap;
}
/* ===== 多流弹窗 ===== */
.popup_box {
position: fixed;
inset: 50% auto auto 50%;
transform: translate(-50%, -50%);
z-index: 999;
width: 90vw;
max-width: 480px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 1rem;
display: none;
flex-direction: column;
gap: 1rem;
}
.popup_box.active {
display: flex;
}
.m-urls-box {
width: 100%;
min-height: 120px;
resize: vertical;
}
.popup-btoolbar {
display: flex;
justify-content: flex-end;
gap: .5rem;
}
/* ===== 底部时间信息 ===== */
.time-info {
font-size: .8rem;
color: var(--text2);
justify-content: space-between;
}
.time-item {
display: flex;
align-items: center;
gap: .25rem;
}
.time-value {
font-weight: 600;
color: var(--primary);
}
/* ===== 窄屏微调 ===== */
@media (max-width: 600px) {
.vxplayer-toolbar {
gap: .5rem;
}
.btn {
font-size: .9rem;
padding: .5rem .75rem;
}
.time-info {
flex-direction: column;
align-items: flex-start;
gap: .25rem;
}
}
</style>
<script language="javascript">
var thumbnails = {
"url": "https://cdn.zwplayer.cn/media/b44c43c90be3521bc352aad1e80f9cd0_thumb.jpg",
"width": 160,
"height": 90,
"row": 9,
"col": 9,
"total": 74
};
function formatTimeShortWithMs(timestamp) {
// 精确到毫秒的简短格式:HH:MM:SS.mmm 使用本地时间
var date = new Date(timestamp);
var hours = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
var milliseconds = date.getMilliseconds();
seconds = seconds % 60;
minutes = minutes % 60;
hours = hours % 24;
return [
hours.toString().padStart(2, '0'),
minutes.toString().padStart(2, '0'),
seconds.toString().padStart(2, '0')
].join(':') + '.' + milliseconds.toString().padStart(3, '0');
}
function onOpenUrl(urlObj) {
var url;
var streamtype = "";
if (urlObj) {
url = urlObj;
} else {
var urlbox = document.getElementById('url-box');
url = urlbox.value;
if (!url) {
alert('Please enter url.');
urlbox.focus();
return;
}
streamtype = document.getElementById('stream_type_cbx').value;
}
var isLive = document.getElementById('isLive-flag').checked;
var isUseFlv = document.getElementById('isUseFlv-flag').checked;
var timer = new Date();
var timeStart = timer.getTime();
document.getElementById('time-start').innerHTML = formatTimeShortWithMs(timeStart);
if (!window.zwplayer) {
//var playerDom = document.querySelector('#player-holder');
window.zwplayer = new ZWPlayer({
url: url,
playerElm: '#player-holder', //player 元素ID ,也可以直接的DOM对象 playerDom
videoElm: '#videoview', //videoElm 元素
// videostyle: "width:100%;height:100%;",
reconnect: true,
autoplay: false,
nativecontrols: false,
isLive: isLive,
useOldFlv: false,
useFlv: isUseFlv,
streamtype: streamtype,
// hasAudio: false,
xmc_url: 'https://xmcdemo.zwplayer.cn:11683/', //请填写实际的地址
rtsp_over_tcp: true, // 使用TCP传输
//rtsp_bypass: true, // 启用编码绕过
controlbar: true,
infoButton: true,
speedButton: true,
optionButton: true,
snapshotButton: true,
chapterButton: true,
enableDanmu: true,
//poster: 'https://cdn.zwplayer.cn/media/VMAP9lxJvRpgn5sP3lV6rQ9qkzQmh5psggso3185.jpg',
useProgressTooltip: true,
fixedControlbar: true,
hidePlayBtn: false,
disablePlayBtn: false,
disableSeek: false,
disableFullscreenWin: false,
disablePicInPic: false,
disableVolumeControl: false,
thumbnails: thumbnails, //采用实际的缩略图配置
disableMutedConfirm: true,
fluid: true,
ratio: "16:9",
// chapters: 'http://192.168.1.202/chapters.json?v=3',
muted: false,
onready: function () {
console.log("Player onready");
},
onfirstframe: function () {
console.log("Player onfirstframe");
},
onnetclose: function () {
console.log("Player onnetclose");
},
onneterror: function () {
console.log("Player onneterror");
},
onmediaevent: function (event) {
if (['play', 'pause', 'seeked', 'ended', 'error'].includes(event.type)) {
console.log("player video Event:", event);
if (event.type == 'seeked') {
var currentTime = event.srcElement.currentTime;
console.log("player currentTime:", currentTime);
}
if (event.type === 'play') {
var timeEnd = (new Date()).getTime();
var timeTotal = timeEnd - timeStart;
document.getElementById('time-end').innerHTML = formatTimeShortWithMs(timeEnd);
document.getElementById('time-total').innerHTML = timeTotal;
}
}
},
sendDanmu: function (text) {
//alert(text);
if (typeof window.ws_send === 'function') {
window.ws_send(text);
}
}
});
window.zwplayer.buildDanmuControlbar('player-dammu-controlbar');
} else {
window.zwplayer.play(url, isLive, false);
}
}
function onClose() {
if (window.zwplayer) {
window.zwplayer.stop();
}
}
function ondestroy() {
if (window.zwplayer) {
window.zwplayer.destroy();
delete window.zwplayer;
}
}
//multistream-urls multistream-urls-input
function onMultiStreams() {
var popupbox = document.getElementById('multistream-urls-box');
var display = popupbox.style.display;
display = display !== 'block' ? 'block' : 'none';
popupbox.style.display = display;
}
function onGetCurrentTime() {
if (window.zwplayer) {
var curTime = window.zwplayer.CurrentTime;
document.getElementById('current-time-view').innerHTML = curTime;
}
}
function onMultiStreamsOpen() {
var inputBox = document.getElementById('multistream-urls-input');
var urlText = inputBox.value;
urlText = urlText.trim();
if (urlText.startsWith('{') || urlText.startsWith('[')) {
try {
var urls = JSON.parse(urlText);
if (urls) {
var urlInfo = {
murls: urls,
multistream: 1
};
onOpenUrl(urlInfo);
document.getElementById('multistream-urls-box').style.display = 'none';
}
} catch (err) {
console.error(err);
}
}
}
function onMultiStreamsCancel() {
document.getElementById('multistream-urls-box').style.display = 'none';
}
function onload() {
url = 'https://cdn.zwplayer.cn/media/VMAP9lxJvRpgn5sP3lV6rQ9qkzQmh5psggso3185.mp4';
document.getElementById('url-box').value = url;
setTimeout(function () {
onOpenUrl();
}, 100);
//new VConsole();
}
function onSendDanmu() {
var danmuText = document.getElementById('danmu-box').value;
if (!danmuText)
return;
var danmu = {
//outlineColor: '#f00',
border: '1px solid #ccc',
text: danmuText
};
if (window.zwplayer) {
window.zwplayer.appendDanmu(danmu);
}
}
function onDisableDanmu() {
if (window.zwplayer) {
window.zwplayer.setEnableDanmu(false);
}
}
function onEnableDanmu() {
if (window.zwplayer) {
window.zwplayer.setEnableDanmu(true);
}
}
function onLoadChapters() {
if (window.zwplayer) {
window.zwplayer.setChapters('https://cdn.zwplayer.cn/media/VMAP9lxJvRpgn5sP3lV6rQ9qkzQmh5psggso3185_chapter.json');
}
}
function onLoadSubtitle() {
if (window.zwplayer) {
var subtitleURL_korean = 'https://cdn.zwplayer.cn/media/VMAP9lxJvRpgn5sP3lV6rQ9qkzQmh5psggtFawR3lNR7hQ2_kore.srt';
var subtitleURL_japan = 'https://cdn.zwplayer.cn/media/VMAP9lxJvRpgn5sP3lV6rQ9qkzQmh5psggtFawR3lNR7hQ2_jpan.srt';
window.zwplayer.addSubtitle(subtitleURL_korean, '1');
window.zwplayer.addSubtitle(subtitleURL_japan, '2');
}
}
function onRemoveSubtitle() {
if (window.zwplayer) {
window.zwplayer.removeSubtitle();
}
}
window.channelinfo = {
id: '001'
};
(function (root) {
var wsChat = null;
var logView = null;
var wschat_server = "ws://10.234.1.106:3000/"; //替换为实际弹幕服务器地址
var reconnect = false;
var msg_queue = [];
var force_close = false;
var userCurrent = {};
function chat_log(str) {
console.log(str);
}
function toast(type, msg) {
}
root.ws_init = function chatSocketInit() {
if (wsChat) return wsChat;
// Connect to Web Socket.
// Change host/port here to your own Web Socket server.
try {
wsChat = new WebSocket(wschat_server);
} catch (e) {
return false;
reconnect = false;
}
// Set event handlers.
wsChat.onopen = function () {
chat_log("wsChat onopen");
if (window.enableBalance == '1' && !window.mediaserver) {
root.ws_getmediaserver();
}
window.setTimeout(function () {
delete userCurrent.loginChat;
if (!wsChat) return;
wsChat.send('{type:"hello"}');
if (reconnect) {
toast('showToast', {
text: '与互动服务器的重新建立连接成功。',
sticky: false,
stayTime: 3000,
position: 'top-center',
type: 'notice'
});
reconnect = false;
if (msg_queue['userlogin']) {
wsChat.send(msg_queue['userlogin']);
delete msg_queue['userlogin'];
}
}
}, 40);
};
wsChat.onmessage = function (e) {
// e.data contains received string.
chat_log("wsChat onmessage: " + e.data);
if (e.data.length > 0) {
if (e.data.charAt(0) === '{') {
var msgContent;
var msgObj = JSON.parse(e.data);
if (typeof (msgObj) === 'object') {
if (msgObj.text)
msgContent = msgObj.text;
else
msgContent = '';
if (msgObj.type === 'danmu') {
if (window.zwplayer) {
if (msgContent == '') return;
// msgObj.text = msgContent;
window.zwplayer.appendDanmu(msgObj);
}
} else if (msgObj.type === 'hello') {
window.joinRoomOk = true;
//获得授予的UID
window.current_uid = msgObj.uid;
var roomname = 'videoroom_' + window.channelinfo.id;
window.ws_send('{"type":"join","room":"' + roomname + '"}');
} else if (msgObj.type === 'login') {
if (!msgObj.result || msgObj.result != 'success') {
toast('showNoticeToast', '登录到互动服务器返回不成功。<br/>可能影响聊天互动!');
} else {
userCurrent.loginChat = true;
}
} else if (msgObj.type === 'logout') {
//用户自己登出的回馈
delete userCurrent.loginChat;
} else if (msgObj.type === 'event') {
if (msgObj.event === 'joinroom') {
//别的用户与自己连接到聊天服务器
if (msgObj.uid) {
if (msgObj.uid === window.current_uid) {
userCurrent.joinroom = true;
}
}
} else if (msgObj.event === 'leaveroom' || msgObj.event === 'exitroom') {
//别的用户离开聊天服务器
if (msgObj.uid) {
if (msgObj.uid === window.current_uid) {
userCurrent.joinroom = false;
}
}
} else if (msgObj.event === 'login') {
//别的用户连接到聊天服务器
if (msgObj.uid) {
}
} else if (msgObj.event === 'logout') {
//别的用户离开聊天服务器
if (msgObj.uid) {
}
}
} else if (msgObj.type == 'userlist') {
//连接到聊天服务器后服务器马上推送在线用户列表,该列表不包括自己
if (msgObj.users.length > 1) {
}
} else if (msgObj.type === 'setuserid') {
//连接建立后将收到这个事件
window.current_uid = msgObj.uid;
userCurrent.userid = msgObj.uid;
}
}
}
}
};
wsChat.onclose = function () {
chat_log("wsChat onclose");
wsChat = null;
toast('showToast', {
text: '与互动服务器的连接已经断开。',
sticky: false,
stayTime: 3000,
position: 'top-center',
type: 'notice'
});
if (force_close) {
} else if (!reconnect) { //当前不是重连状态,则尝试重连
reconnect = true; //设置为正在重连状态
var on_reconnect = function () {
if (force_close) return;
wschat = root.wschat = root.ws_init();
if (!wschat) {
reconnect = true;
window.setTimeout(on_reconnect, 10000);
}
}
window.setTimeout(on_reconnect, 10000);
}
};
wsChat.onerror = function () {
chat_log("wsChat onerror");
wsChat = null;
};
return wsChat;
}
root.ws_send = function wsChatSend(data) {
chat_log("wsChat send: " + data);
if (wsChat) {
wsChat.send(data);
return true;
} else {
toast('showToast', {
text: '聊天交互服务没有连接成功或已经中断!<br/>正在尝试重连,请稍候再试...',
sticky: false,
stayTime: 3000,
position: 'top-center',
type: 'warning'
});
reconnect = true;
wschat = root.wschat = root.ws_init();
return false;
}
}
root.ws_queue = function wsQueue(type, msg) {
msg_queue[type] = msg;
}
root.ws_close = function wsChatClose() {
if (wsChat) {
force_close = true;
wsChat.close();
wsChat = null;
} else {
}
}
root.wschat = wsChat;
})(window);
window.ws_init();
// 2. 页面加载完自动调用
document.addEventListener('DOMContentLoaded', onload);
</script>
</head>
<body>
<div class="player-wrap">
<div class="vxplayer" id="player-holder">
<!-- video id="videoview"> </video //-->
</div>
<div class="danmubar" id="player-dammu-controlbar"></div>
<div class="main_bar">
<div class="vxplayer-toolbar" id="player-toolbar">
<button class="btn" onclick="onOpenUrl();">Open</button>
<button class="btn" onclick="onClose();">Close</button>
<button class="btn" onclick="ondestroy();">Destroy</button>
<div class="url-inputbox">
<span class="label">URL:</span>
<input type="text" id="url-box"
value="https://cdn.zwplayer.cn/media/VMAP9lxJvRpgn5sP3lV6rQ9qkzQmh5psggso3185.mp4">
</div>
<div class="opt-panel">
<input type="checkbox" id="isLive-flag" value="1">
<label for="isLive-flag">LIVE</label>
<input type="checkbox" id="isUseFlv-flag" value="1" title="use flv player">
<label for="isUseFlv-flag">Use Flv</label>
</div>
<div class="popup_box" id="multistream-urls-box">
<textarea class="m-urls-box" id="multistream-urls-input"></textarea>
<div class="popup-btoolbar">
<button class="btn" onclick="onMultiStreamsOpen();">Open</button>
<button class="btn" onclick="onMultiStreamsCancel();">Cancel</button>
</div>
</div>
</div>
<div class="vxplayer-toolbar" id="player-dammubar">
<div class="url-inputbox">
<span class="label">DANMU:</span>
<input type="text" id="danmu-box" value="">
</div>
<button class="btn" onclick="onSendDanmu();">Send</button>
<button class="btn" onclick="onEnableDanmu();">Enable Danmu</button>
<button class="btn" onclick="onDisableDanmu();">Disable Danmu</button>
</div>
<div class="vxplayer-toolbar" id="player-other">
<button class="btn" onclick="onLoadChapters();">Load Chapters</button>
<button class="btn" onclick="onLoadSubtitle();">Load Subtitle</button>
<button class="btn" onclick="onRemoveSubtitle();">Remove Subtitle</button>
<button class="btn" onclick="onMultiStreams();">MStreams</button>
<select class="cbx" id="stream_type_cbx">
<option value="">stream_type No specified</option>
<option value="httpflv">httpflv</option>
<option value="hls">hls</option>
<option value="dash">dash</option>
<option value="webrtc">webrtc</option>
<option value="mpegts">mpegts</option>
</select>
<button class="btn" onclick="onGetCurrentTime();">CurrentTime</button>
<span id="current-time-view" class="current-time-view"></span>
</div>
<div class="vxplayer-toolbar time-info">
<div class="time-item"><span>start:</span><span class="time-value" id="time-start"></span></div>
<div class="time-item"><span>end:</span><span class="time-value" id="time-end"></span></div>
<div class="time-item"><span>diffTime:</span><span class="time-value" id="time-total"></span>ms</div>
</div>
<div>
<span>注意:建议使用chrome浏览器,如果遇到跨域问题不能播放,可加 --disable-web-security 参数启动chrome浏览器。</span>
</div>
</div>
</div>
</body>
</html>