mirror of
https://github.com/cnotch/ipchub.git
synced 2025-09-26 19:41:18 +08:00
475 lines
18 KiB
HTML
475 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>RTSP player example(based streamedian)</title>
|
||
<link rel="stylesheet" href="style.css">
|
||
</head>
|
||
<body>
|
||
<div id="sourcesNode"></div>
|
||
<div>
|
||
<input id="stream_url" value="rtsp://localhost:1554/test/live1" size="36">
|
||
<button id="set_new_url">Set</button>
|
||
<div class="custom-control custom-switch">
|
||
<input type="checkbox" id="continuous_recording">
|
||
<label class="custom-control-label" for="customSwitch1">Continuous recording</label>
|
||
</div>
|
||
<div>
|
||
<p style="color:#808080">Enter your rtsp link to the stream, for example: "rtsp://192.168.1.1:554/h264"</p>
|
||
</div>
|
||
|
||
<div>
|
||
<span style="color:#808080">Change video file length of the continuous recording</span>
|
||
<input id="continuous_file_length" type="range" min="10000" max="200000" value="180000" step="1000" style="width:40%;">
|
||
<p id="continuous_file_length_label">180sec.</p>
|
||
</div>
|
||
|
||
<div>
|
||
<span style="color:#808080">Change video file length of the event recording</span>
|
||
<input id="event_file_length" type="range" min="10000" max="200000" value="180000" step="1000" style="width:40%;">
|
||
<p id="event_file_length_label">180sec.</p>
|
||
</div>
|
||
|
||
<div>
|
||
<span style="color:#808080">Change buffer duration</span>
|
||
<input id="buffer_duration" type="range" min="10" max="200" style="width:40%;">
|
||
<p id="buffer_value">120sec.</p>
|
||
</div>
|
||
|
||
<canvas id="video_canvas" width="0" height="0"></canvas>
|
||
<video id="test_video" controls autoplay>
|
||
<!--<source src="rtsp://192.168.10.205:554/ch01.264" type="application/x-rtsp">-->
|
||
<!--<source src="rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov" type="application/x-rtsp">-->
|
||
</video>
|
||
|
||
<div class="controls form">
|
||
<div>
|
||
Playback rate:
|
||
<input id="rate" class="input" type="range" min="0.5" max="5.0" value="1.0" step="0.5">
|
||
<output for="rate" id="rate_res">live</output>
|
||
</div>
|
||
<div>
|
||
<button id="to_end" class="btn btn-success" disabled>live</button>
|
||
<button id="event_recording" class="btn btn-success" disabled>Start recording</button>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<span style="color:#808080">Select a recorded file to playback</span>
|
||
<input id="file_input" type="file" accept=".mp4">
|
||
</div>
|
||
|
||
<p><br>Have any suggestions to improve our player? <br>Feel free to leave comments or ideas email: streamedian.player@gmail.com</p>
|
||
<p>View HTML5 RTSP video player log</p>
|
||
<div id="pllogs" class="logs"></div>
|
||
<button class="btn btn-success" onclick="cleanLog(pllogs)">clear</button>
|
||
<button class="btn btn-success" onclick="scrollset(pllogs, true)">scroll up</button>
|
||
<button class="btn btn-success" onclick="scrollset(pllogs, false)">scroll down</button>
|
||
<button id="scrollSetPl" class="btn btn-success" onclick="scrollswitch(pllogs)">Scroll off</button>
|
||
<button class="btn btn-success" onclick="statisticRequest('GET_INFO')">Get statistic</button>
|
||
<button class="btn btn-success" onclick="statisticRequest('SUBSCRIBE')">Subscribe statistic</button>
|
||
<br/><br/>
|
||
|
||
<b>How to use the player in the global network</b>
|
||
<p>
|
||
With an empty license file, you can only watch the stream on your computer locally (intranet).<br/>
|
||
If you would like to stream into the global network please take a key to activate the license.<br/>
|
||
You have personal 1 month validity key in the personal cabinet.<br/>
|
||
To activate key, please, use the activation application that is placed:
|
||
</p>
|
||
<p>
|
||
<b>Windows:</b> C:\Program Files\Streamedian\WS RTSP Proxy Server\activation_app<br/>
|
||
<b>Mac OS:</b> /Library/Application Support/Streamedian/WS RTSP Proxy Server/activation_app<br/>
|
||
<b>Linux (Ubunty, Debian, Centos, Fedora ):</b> /usr/bin/wsp/activation_app<br/>
|
||
</p>
|
||
<p>For more information go to <a href="https://streamedian.com/docs/">documentation</a></p>
|
||
|
||
<script src="libde265.js"></script>
|
||
<script src="free.player.3.3.js"></script>
|
||
|
||
<script>
|
||
var scrollStatPl = true;
|
||
var scrollStatWs = true;
|
||
var pllogs = document.getElementById("pllogs");
|
||
var wslogs = document.getElementById("wslogs");
|
||
|
||
// define a new console
|
||
var console=(function(oldConsole){
|
||
return {
|
||
log: function(){
|
||
oldConsole.log(newConsole(arguments, "black", "#A9F5A9"));
|
||
},
|
||
info: function () {
|
||
oldConsole.info(newConsole(arguments, "black", "#A9F5A9"));
|
||
},
|
||
warn: function () {
|
||
oldConsole.warn(newConsole(arguments, "black", "#F3F781"));
|
||
},
|
||
error: function () {
|
||
oldConsole.error(newConsole(arguments, "black", "#F5A9A9"));
|
||
}
|
||
};
|
||
}(window.console));
|
||
|
||
function newConsole(args, textColor, backColor){
|
||
let text = '';
|
||
let node = document.createElement("div");
|
||
for (let arg in args){
|
||
text +=' ' + args[arg];
|
||
}
|
||
node.appendChild(document.createTextNode(text));
|
||
node.style.color = textColor;
|
||
node.style.backgroundColor = backColor;
|
||
pllogs.appendChild(node);
|
||
autoscroll(pllogs);
|
||
return text;
|
||
}
|
||
|
||
//Then redefine the old console
|
||
window.console = console;
|
||
|
||
function cleanLog(element){
|
||
while (element.firstChild) {
|
||
element.removeChild(element.firstChild);
|
||
}
|
||
}
|
||
|
||
function autoscroll(element){
|
||
if(scrollStatus(element)){
|
||
element.scrollTop = element.scrollHeight;
|
||
}
|
||
if(element.childElementCount > 1000){
|
||
element.removeChild(element.firstChild);
|
||
}
|
||
}
|
||
|
||
function scrollset(element, state){
|
||
if(state){
|
||
element.scrollTop = 0;
|
||
scrollChange(element, false);
|
||
} else {
|
||
element.scrollTop = element.scrollHeight;
|
||
scrollChange(element, true);
|
||
}
|
||
}
|
||
|
||
function scrollswitch(element){
|
||
if(scrollStatus(element)){
|
||
scrollChange(element, false);
|
||
} else {
|
||
scrollChange(element, true);
|
||
}
|
||
}
|
||
|
||
function scrollChange(element, status){
|
||
if(scrollStatus(element)){
|
||
scrollStatPl = false;
|
||
document.getElementById("scrollSetPl").innerText = "Scroll on";
|
||
} else {
|
||
scrollStatPl = true;
|
||
document.getElementById("scrollSetPl").innerText = "Scroll off";
|
||
}
|
||
}
|
||
|
||
function scrollStatus(element){
|
||
if(element.id === "pllogs"){
|
||
return scrollStatPl;
|
||
} else {
|
||
return scrollStatWs;
|
||
}
|
||
}
|
||
|
||
|
||
</script>
|
||
|
||
<script>
|
||
if (window.Streamedian) {
|
||
let errHandler = function(err){
|
||
// alert(err.message);
|
||
};
|
||
|
||
let infHandler = function(inf) {
|
||
let sourcesNode = document.getElementById("sourcesNode");
|
||
let clients = inf.clients;
|
||
sourcesNode.innerHTML = "";
|
||
|
||
for (let client in clients) {
|
||
clients[client].forEach((sources) => {
|
||
let nodeButton = document.createElement("button");
|
||
nodeButton.setAttribute('data', sources.url + ' ' + client);
|
||
nodeButton.appendChild(document.createTextNode(sources.description));
|
||
nodeButton.onclick = (event)=> {
|
||
setPlayerSource(event.target.getAttribute('data'));
|
||
};
|
||
sourcesNode.appendChild(nodeButton);
|
||
});
|
||
}
|
||
};
|
||
|
||
var link = document.createElement('a');
|
||
let dataHandler = function(data, prefix) {
|
||
let blob = new Blob([data], {type: "application/mp4"});
|
||
link.href = window.URL.createObjectURL(blob);
|
||
link.download = `${prefix}_${formatDate(new Date())}.mp4`;
|
||
link.click();
|
||
}
|
||
|
||
let formatHandler = function (format) {
|
||
if (html5Player && html5Canvas) {
|
||
if (format === 'h265') {
|
||
html5Player.setAttribute('hidden', true);
|
||
html5Canvas.removeAttribute('hidden');
|
||
} else if (format === 'h264') {
|
||
html5Player.removeAttribute('hidden');
|
||
html5Canvas.setAttribute('hidden', true);
|
||
}
|
||
}
|
||
}
|
||
|
||
function formatDate(dateObj) {
|
||
let month = String(dateObj.getMonth() + 1).padStart(2, '0');
|
||
let date = String(dateObj.getDate()).padStart(2, '0');
|
||
let hours = String(dateObj.getHours()).padStart(2, '0');
|
||
let minutes = String(dateObj.getMinutes()).padStart(2, '0');
|
||
let seconds = String(dateObj.getSeconds()).padStart(2, '0');
|
||
return`${dateObj.getFullYear()}-${month}-${date} ${hours}:${minutes}:${seconds}`;
|
||
}
|
||
|
||
var playerOptions = {
|
||
socket: "ws://localhost:8088/streams/",
|
||
redirectNativeMediaErrors : true,
|
||
bufferDuration: 30,
|
||
errorHandler: errHandler,
|
||
infoHandler: infHandler,
|
||
dataHandler: dataHandler,
|
||
videoFormatHandler: formatHandler,
|
||
continuousFileLength: 180000,
|
||
eventFileLength: 10000,
|
||
canvas: 'video_canvas',
|
||
};
|
||
|
||
var html5Player = document.getElementById("test_video");
|
||
var html5Canvas = document.getElementById("video_canvas");
|
||
var urlButton = document.getElementById("set_new_url");
|
||
var urlEdit = document.getElementById("stream_url");
|
||
var bufferRange = document.getElementById("buffer_duration");
|
||
var bufferValue = document.getElementById("buffer_value");
|
||
var continuousRecording = document.getElementById("continuous_recording");
|
||
var eventRecording = document.getElementById("event_recording");
|
||
var continuousFileLength = document.getElementById("continuous_file_length");
|
||
var continuousFileLengthLabel = document.getElementById("continuous_file_length_label");
|
||
var eventFileLength = document.getElementById("event_file_length");
|
||
var eventFileLengthLabel = document.getElementById("event_file_length_label");
|
||
|
||
var player = Streamedian.player('test_video', playerOptions);
|
||
var nativePlayer = document.getElementById('test_video');
|
||
var range = document.getElementById('rate');
|
||
var set_live = document.getElementById('to_end');
|
||
var range_out = document.getElementById('rate_res');
|
||
|
||
var socket;
|
||
var keepAliveTimer;
|
||
var password = btoa('streamedian');
|
||
|
||
range.addEventListener('input', function () {
|
||
nativePlayer.playbackRate = range.value;
|
||
range_out.innerHTML = `x${range.value}`;
|
||
});
|
||
set_live.addEventListener('click', function () {
|
||
range.value = 1.0;
|
||
range_out.innerHTML = `live`;
|
||
nativePlayer.playbackRate = 1;
|
||
nativePlayer.currentTime = nativePlayer.buffered.end(0);
|
||
});
|
||
|
||
// Tab switching and window minimization processing
|
||
// for browsers that use the chrome rendering engine.
|
||
if (!!window.chrome) {
|
||
document.addEventListener('visibilitychange', function() {
|
||
if(document.visibilityState === 'hidden') {
|
||
nativePlayer.pause()
|
||
} else {
|
||
nativePlayer.play();
|
||
|
||
// Automatic jump to buffer end for view live video when returning to the web page.
|
||
setTimeout(function() {
|
||
nativePlayer.currentTime = nativePlayer.buffered.end(0)
|
||
}, 3000); // Delay for a few seconds is required for the player has time to update the timeline.
|
||
}
|
||
});
|
||
}
|
||
|
||
var updateRangeControls = function(){
|
||
bufferRange.value = player.bufferDuration;
|
||
bufferValue.innerHTML = bufferRange.value + "sec.";
|
||
|
||
continuousFileLength.value = player.continuousRecording.fileLength;
|
||
continuousFileLengthLabel.innerText = `${continuousFileLength.value / 1000} sec.`;
|
||
|
||
eventFileLength.value = player.eventRecording.fileLength;
|
||
event_file_length_label.innerText = `${eventFileLength.value / 1000} sec.`;
|
||
};
|
||
|
||
bufferRange.addEventListener('input', function(){
|
||
var iValue = parseInt(this.value, 10);
|
||
player.bufferDuration = iValue;
|
||
bufferValue.innerHTML = this.value + "sec.";
|
||
});
|
||
|
||
bufferRange.innerHTML = player.bufferDuration + "sec.";
|
||
updateRangeControls();
|
||
|
||
continuousRecording.addEventListener('change', function(event) {
|
||
player.continuousRecording.record(event.target.checked);
|
||
continuousFileLength.disabled = event.target.checked;
|
||
});
|
||
|
||
eventRecording.addEventListener('click', function(event) {
|
||
let state = !event.target.classList.contains('btn-recording');
|
||
player.eventRecording.record(state);
|
||
event.target.classList.toggle('btn-success');
|
||
event.target.classList.toggle('btn-recording');
|
||
event.target.innerText = state ? 'Stop recording': 'Start recording';
|
||
eventFileLength.disabled = state;
|
||
});
|
||
|
||
continuousFileLength.addEventListener('input', function(event) {
|
||
let milliseconds = parseInt(event.target.value, 10);
|
||
player.continuousRecording.fileLength = milliseconds;
|
||
continuousFileLengthLabel.innerText = `${milliseconds / 1000} sec.`;
|
||
});
|
||
|
||
eventFileLength.addEventListener('input', function(event) {
|
||
let milliseconds = parseInt(event.target.value, 10);
|
||
player.eventRecording.fileLength = milliseconds;
|
||
eventFileLengthLabel.innerText = `${milliseconds / 1000} sec.`;
|
||
});
|
||
|
||
urlButton.onclick = ()=> {
|
||
setPlayerSource(urlEdit.value);
|
||
};
|
||
|
||
function setPlayerSource(newSource) {
|
||
player.destroy();
|
||
player = null;
|
||
html5Player.src = newSource;
|
||
// 修改原例子 begn =======>
|
||
// 我们直接使用ws来决定播放的路径,
|
||
// 比如:ws://192.168.1.100:1554/streams/test/live1
|
||
// 表示要播放服务器上路径为/test/live1
|
||
// 如果播放失败,可能是以下情况(1和2发生在升级websocket阶段,3发生在rtsp通讯阶段)
|
||
// 1. 如果服务器rtsp的验证模式不为NONE,则需要登录后获取到token才能访问;像这样ws://.../test/live1?token=...
|
||
// 2. 可能没有权限,需要联系人对你登录的用户授权
|
||
// 3. 可能找不到流媒体
|
||
//
|
||
// 如果不是使用例子,我们可以这样
|
||
// html5Player.src = "rtsp://placehold"
|
||
// playerOptions.socket ="ws://localhost:1554/streams/test/live1"
|
||
// 知道ws主机后,实际上只需要提供流媒体path即可,这也更好
|
||
//
|
||
let rtspUrl = new URL(newSource)
|
||
rtspUrl.protocol = "ws"
|
||
rtspUrl.pathname = "/streams"+rtspUrl.pathname
|
||
|
||
playerOptions.socket = rtspUrl.href
|
||
// <========= end
|
||
|
||
player = Streamedian.player("test_video", playerOptions);
|
||
player.continuousRecording.record(continuousRecording.checked);
|
||
updateRangeControls();
|
||
|
||
eventRecording.removeAttribute('disabled');
|
||
set_live.removeAttribute('disabled');
|
||
}
|
||
|
||
file_input.addEventListener('change', function(event) {
|
||
html5Player.src = URL.createObjectURL(event.target.files[0]);
|
||
});
|
||
|
||
window.addEventListener('unload', function() {
|
||
player.continuousRecording.record(false);
|
||
player.eventRecording.record(false);
|
||
});
|
||
|
||
function statisticRequest(cmd) {
|
||
if (socket == undefined || socket.readyState != 1) {
|
||
socket = new WebSocket(playerOptions.socket, "statistic");
|
||
socket.onmessage = onStatistic;
|
||
socket.onopen = function() {
|
||
socket.send(`WSP/1.1 ${cmd}\nAuthorization: ${password}\nseq: 1\n\n`);
|
||
|
||
keepAliveTimer = setInterval(()=>{
|
||
socket.send(`WSP/1.1 KEEPALIVE\nAuthorization: ${password}\nseq: 1\n\n`);
|
||
}, 30000); // Every 30 seconds
|
||
}
|
||
|
||
socket.onclose = function() {
|
||
clearInterval(keepAliveTimer);
|
||
}
|
||
} else {
|
||
socket.send(`WSP/1.1 ${cmd}\nAuthorization: ${password}\nseq: 1\n\n`);
|
||
}
|
||
}
|
||
|
||
function onStatistic(msg) {
|
||
if (msg.data.length) {
|
||
let data = msg.data.split('\r\n\r\n');
|
||
|
||
if (data.length > 1) {
|
||
parseStatistic(JSON.parse(data[1]));
|
||
} else {
|
||
console.log("------------- Info -------------");
|
||
parseSession(JSON.parse(data[0]));
|
||
}
|
||
|
||
}
|
||
};
|
||
|
||
function parseSession(session) {
|
||
console.log(`Requested domain: ${session.user.requestedDomain}`);
|
||
console.log(`User address: ${session.user.address}`);
|
||
console.log(`RTSP address: ${session.rtsp.host}:${session.rtsp.port}`);
|
||
console.log(`Session start time: ${new Date(Number(session.connectionTime + '000'))}`);
|
||
|
||
if (session.disconnectionTime) {
|
||
console.log(`Session end time: ${new Date(Number(session.disconnectionTime + '000'))}`);
|
||
}
|
||
|
||
console.log('---');
|
||
}
|
||
|
||
function parseStatistic(data) {
|
||
for (let i = 0; i < data.licenses.length; i++) {
|
||
let license = data.licenses[i].license;
|
||
let sessions = data.licenses[i].sessions;
|
||
let sessionNumber = data.licenses[i].sessionNumber;
|
||
|
||
console.log("------------- Info -------------");
|
||
console.log(' License ' + i);
|
||
console.log(`Activation Key: ${license.key}`)
|
||
console.log(`Expires: ${license.expiresAt}`);
|
||
console.log(`License max posible watchers: ${license.maxWatchers}`);
|
||
|
||
if (license.maxWatchers !== 'unlimited') {
|
||
console.log(`Remain watchers: ${license.maxWatchers - sessionNumber}`);
|
||
}
|
||
|
||
console.log('Sessions:');
|
||
for (let j = 0; j < sessions.length; j++) {
|
||
parseSession(sessions[j]);
|
||
}
|
||
}
|
||
}
|
||
|
||
function getStatistic() {
|
||
statisticRequest('GET_INFO', password);
|
||
socket.onmessage = statisticInfoParse;
|
||
}
|
||
|
||
function subscribeStatistic() {
|
||
statisticRequest('SUBSCRIBE', password);
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|