You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
557 lines
20 KiB
Cheetah
557 lines
20 KiB
Cheetah
{{template "head.tmpl" .}}
|
|
<div class="content-header">
|
|
<div class="container-fluid">
|
|
<div class="row mb-2">
|
|
<div class="col-sm-6">
|
|
<h1 class="m-0 text-dark">Comparsion page</h1>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<ol class="breadcrumb float-sm-right">
|
|
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
|
<li class="breadcrumb-item active">Comparsion page</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div><!-- /.container-fluid -->
|
|
</div>
|
|
<div class="content">
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<!-- player -->
|
|
<div class="col-12 col-md-8 col-lg-9">
|
|
<div class="row">
|
|
<div class="col-12 col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title one-line-header">HLS</h3>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<video class="videoPlayer" vtype="hls" id="videoPlayer" autoplay controls muted playsinline></video>
|
|
<canvas id="canvas" class="d-none"></canvas>
|
|
</div>
|
|
<input type="hidden" id="uuid" value="{{ .uuid }}" />
|
|
<input type="hidden" id="channel" value="{{ .channel }}" />
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title one-line-header">HLSLL</h3>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<video class="videoPlayer" vtype="hlsll" autoplay controls muted playsinline></video>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title one-line-header">MSE</h3>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<video class="videoPlayer" vtype="mse" autoplay controls muted playsinline></video>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title one-line-header">WEBRTC</h3>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<video class="videoPlayer" vtype="webrtc" autoplay controls muted playsinline></video>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4 col-lg-3">
|
|
<div class="row">
|
|
{{ range $key, $value := .streams }}
|
|
<div class="col-12" id="{{ $key }}">
|
|
|
|
<div class="card card-outline card-success">
|
|
<div class="card-header">
|
|
<h3 class="card-title one-line-header">{{.Name}}</h3>
|
|
<div class="card-tools">
|
|
<span data-toggle="tooltip" title="avaliable channels" class="badge badge-success">{{len .Channels }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
|
|
<div id="carousel_{{$key}}" class="carousel slide" data-ride="carousel">
|
|
<ol class="carousel-indicators">
|
|
{{ range $k, $v := .Channels }}
|
|
<li data-target="#carousel_{{$key}}" data-slide-to="{{$k}}" class="{{ if eq $k "0"}} active {{end}}"></li>
|
|
{{end}}
|
|
</ol>
|
|
<div class="carousel-inner">
|
|
{{ range $k, $v := .Channels }}
|
|
<div class="carousel-item {{ if eq $k "0"}} active {{end}}">
|
|
<img class="d-block w-100 stream-img fix-height" channel="{{$k}}" src="/../static/img/noimage.svg">
|
|
<div class="carousel-caption d-none d-md-block">
|
|
<h5>Channel: {{$k}}</h5>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
<a class="carousel-control-prev" href="#carousel_{{$key}}" role="button" data-slide="prev">
|
|
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
|
<span class="sr-only">Previous</span>
|
|
</a>
|
|
<a class="carousel-control-next" href="#carousel_{{$key}}" role="button" data-slide="next">
|
|
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
|
<span class="sr-only">Next</span>
|
|
</a>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="btn-group stream">
|
|
{{ if gt (len .Channels) 1}}
|
|
<div class="input-group-prepend">
|
|
<a class="btn btn-info btn-flat btn-xs" data-toggle="dropdown" href="#"><i class="fas fa-play"></i> MSE</a>
|
|
<div class="dropdown-menu">
|
|
{{ range $k, $v := .Channels }}
|
|
<a class="dropdown-item" href="/pages/player/mse/{{$key}}/{{$k}}">Channel {{$k}}</a>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
<div class="input-group-prepend">
|
|
<a class="btn btn-info btn-flat btn-xs" data-toggle="dropdown" href="#"><i class="fas fa-play"></i> HLS</a>
|
|
<div class="dropdown-menu">
|
|
{{ range $k, $v := .Channels }}
|
|
<a class="dropdown-item" href="/pages/player/hls/{{$key}}/{{$k}}">Channel {{$k}}</a>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
<div class="input-group-prepend">
|
|
<a class="btn btn-info btn-flat btn-xs" data-toggle="dropdown" href="#"><i class="fas fa-play"></i> WebRTC</a>
|
|
<div class="dropdown-menu">
|
|
{{ range $k, $v := .Channels }}
|
|
<a class="dropdown-item" href="/pages/player/webrtc/{{$key}}/{{$k}}">Channel {{$k}}</a>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
<div class="input-group-prepend">
|
|
<a class="btn btn-info btn-flat btn-xs" data-toggle="dropdown" href="#"><i class="fas fa-play"></i> ALL</a>
|
|
<div class="dropdown-menu">
|
|
{{ range $k, $v := .Channels }}
|
|
<a class="dropdown-item" href="/pages/player/all/{{$key}}/{{$k}}">Channel {{$k}}</a>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{else}}
|
|
<a class="btn btn-info btn-flat btn-xs" href="/pages/player/mse/{{$key}}/0"><i class="fas fa-play"></i> MSE</a>
|
|
<a class="btn btn-info btn-flat btn-xs" href="/pages/player/hls/{{$key}}/0"><i class="fas fa-play"></i> HLS</a>
|
|
<a class="btn btn-info btn-flat btn-xs" href="/pages/player/webrtc/{{$key}}/0"><i class="fas fa-play"></i> WebRTC</a>
|
|
<a class="btn btn-info btn-flat btn-xs" href="/pages/player/all/{{$key}}/0"><i class="fas fa-play"></i> ALL</a>
|
|
{{end}}
|
|
<a class="btn btn-secondary btn-flat btn-xs" href="/pages/stream/edit/{{$key}}"><i class="fas fa-edit"></i> Edit</a>
|
|
<a class="btn btn-danger btn-flat btn-xs" onclick="deleteStream('{{ $key }}')" href="#"><i class="fas fa-times"></i> Delete</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{template "foot.tmpl" .}}
|
|
</div>
|
|
<script src="/../static/plugins/hlsjs/hls.min.js?version=3"></script>
|
|
<script>
|
|
let uuid=$('#uuid').val();
|
|
let channel=$('#channel').val();
|
|
let colordebug = true;
|
|
let isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && navigator.userAgent && navigator.userAgent.indexOf('CriOS') == -1 && navigator.userAgent.indexOf('FxiOS') == -1;
|
|
$(document).ready(()=>{
|
|
$('.videoPlayer').each(function() {
|
|
|
|
switch ($(this).attr('vtype')) {
|
|
case 'hls':
|
|
var url = '/stream/' + uuid + '/channel/' + channel + '/hls/live/index.m3u8';
|
|
if (isSafari && $(this)[0].canPlayType('application/vnd.apple.mpegurl')) {
|
|
$(this)[0].src = url;
|
|
$(this)[0].load();
|
|
} else if (Hls.isSupported()) {
|
|
let hlsplayer = new Hls({
|
|
manifestLoadingTimeOut: 60000
|
|
});
|
|
hlsplayer.loadSource(url);
|
|
hlsplayer.attachMedia($(this)[0]);
|
|
} else {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Oops...',
|
|
text: 'Your browser don`t support hls '
|
|
});
|
|
}
|
|
break;
|
|
case 'hlsll':
|
|
var url = '/stream/' + uuid + '/channel/' + channel + '/hlsll/live/index.m3u8';
|
|
if (isSafari && $(this)[0].canPlayType('application/vnd.apple.mpegurl')) {
|
|
$(this)[0].src = url;
|
|
$(this)[0].load();
|
|
} else if (Hls.isSupported()) {
|
|
let hlsplayer = new Hls({
|
|
manifestLoadingTimeOut: 60000
|
|
});
|
|
hlsplayer.loadSource(url);
|
|
hlsplayer.attachMedia($(this)[0]);
|
|
} else {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Oops...',
|
|
text: 'Your browser don`t support hls '
|
|
});
|
|
}
|
|
break;
|
|
case 'mse':
|
|
var mse = new msePlayer(uuid, $(this).closest('div'), channel);
|
|
mse.playMse()
|
|
break;
|
|
case 'webrtc':
|
|
var webrtc = new WebRTCPlayer(uuid, $(this).closest('div'), channel);
|
|
webrtc.playWebrtc()
|
|
break;
|
|
default:
|
|
|
|
}
|
|
})
|
|
});
|
|
|
|
function msePlayer(uuid, videoPlayerVar, channel) {
|
|
this.ws = null,
|
|
this.video = videoPlayerVar.find('video')[0],
|
|
this.mseSourceBuffer = null,
|
|
this.mse = null,
|
|
this.mseQueue = [],
|
|
this.mseStreamingStarted = false,
|
|
this.uuid = uuid,
|
|
this.channel = channel || 0;
|
|
this.timeout = null;
|
|
this.checktime = null;
|
|
this.checktimecounter = 0;
|
|
this.playMse = function() {
|
|
let _this = this;
|
|
this.mse = new MediaSource();
|
|
this.video.src = window.URL.createObjectURL(this.mse);
|
|
|
|
let potocol = 'ws';
|
|
if (location.protocol == 'https:') {
|
|
potocol = 'wss';
|
|
}
|
|
|
|
let ws_url = potocol + '://' + location.host + '/stream/' + this.uuid + '/channel/' + this.channel + '/mse?uuid=' + this.uuid + '&channel=' + this.channel;
|
|
|
|
this.mse.addEventListener('sourceopen', function() {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[MSE]: sourceopen');
|
|
_this.ws = new WebSocket(ws_url);
|
|
_this.ws.binaryType = "arraybuffer";
|
|
_this.ws.onopen = function(event) {
|
|
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[websocket]: connected');
|
|
}
|
|
|
|
_this.ws.onclose = function(event) {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[websocket]: closed');
|
|
if (_this.timeout != null) {
|
|
clearInterval(_this.timeout);
|
|
_this.timeout = null;
|
|
}
|
|
_this.timeout = setTimeout(() => {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[websocket]: timeouted func play');
|
|
//play(uuid, videoPlayerVar.index(), channel, 'mse')
|
|
}, 15000)
|
|
|
|
|
|
}
|
|
_this.ws.onerror = (e) => {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[websocket]: error');
|
|
}
|
|
_this.ws.onmessage = function(event) {
|
|
_this.checkStalled();
|
|
let data = new Uint8Array(event.data);
|
|
if (data[3] == 24) {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[data]: init_file');
|
|
}
|
|
|
|
if (data[0] == 9) {
|
|
decoded_arr = data.slice(1);
|
|
if (window.TextDecoder) {
|
|
mimeCodec = new TextDecoder("utf-8").decode(decoded_arr);
|
|
} else {
|
|
mimeCodec = Utf8ArrayToStr(decoded_arr);
|
|
}
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[codec]: ' + mimeCodec);
|
|
//console.log(mimeCodec);
|
|
_this.mseSourceBuffer = _this.mse.addSourceBuffer('video/mp4; codecs="' + mimeCodec + '"');
|
|
_this.mseSourceBuffer.mode = "segments"
|
|
_this.mseSourceBuffer.addEventListener("updateend", _this.pushPacket.bind(_this));
|
|
|
|
} else {
|
|
_this.readPacket(event.data);
|
|
}
|
|
};
|
|
}, false);
|
|
|
|
this.mse.addEventListener('sourceended', function() {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[MSE]: sourceended');
|
|
})
|
|
this.mse.addEventListener('sourceclose', function() {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[MSE]: sourceclose');
|
|
})
|
|
|
|
this.mse.addEventListener('error', function() {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[MSE]: error');
|
|
})
|
|
this.mse.addEventListener('abort', function() {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[MSE]: abort');
|
|
})
|
|
this.mse.addEventListener('updatestart', function() {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[MSE]: updatestart');
|
|
})
|
|
this.mse.addEventListener('update', function() {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[MSE]: update');
|
|
})
|
|
this.mse.addEventListener('updateend', function() {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[MSE]: updateend');
|
|
})
|
|
this.mse.addEventListener('addsourcebuffer', function() {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[MSE]: addsourcebuffer');
|
|
})
|
|
this.mse.addEventListener('removesourcebuffer', function() {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[MSE]: removesourcebuffer');
|
|
})
|
|
|
|
}
|
|
|
|
this.readPacket = function(packet) {
|
|
if (!this.mseStreamingStarted) {
|
|
try {
|
|
this.mseSourceBuffer.appendBuffer(packet);
|
|
this.mseStreamingStarted = true;
|
|
} catch (e) {
|
|
logger(videoPlayerVar.index(),
|
|
'readPacket error',
|
|
'streams: ' + uuid,
|
|
'channel: ' + channel);
|
|
console.log(e);
|
|
|
|
//play(uuid, videoPlayerVar.index(), channel, 'mse');
|
|
|
|
} finally {
|
|
return;
|
|
}
|
|
|
|
|
|
}
|
|
this.mseQueue.push(packet);
|
|
|
|
if (!this.mseSourceBuffer.updating) {
|
|
this.pushPacket();
|
|
}
|
|
},
|
|
|
|
this.pushPacket = function() {
|
|
let _this = this;
|
|
if (!_this.mseSourceBuffer.updating) {
|
|
if (_this.mseQueue.length > 0) {
|
|
packet = _this.mseQueue.shift();
|
|
|
|
try {
|
|
_this.mseSourceBuffer.appendBuffer(packet)
|
|
} catch (e) {
|
|
logger(videoPlayerVar.index(),
|
|
'pushPacket error',
|
|
'streams: ' + uuid,
|
|
'channel: ' + channel);
|
|
console.log(e);
|
|
|
|
//play(uuid, videoPlayerVar.index(), channel, 'mse');
|
|
} finally {
|
|
|
|
}
|
|
} else {
|
|
_this.mseStreamingStarted = false;
|
|
}
|
|
}
|
|
if (_this.video.buffered.length > 0) {
|
|
if (typeof document.hidden !== "undefined" && document.hidden) {
|
|
_this.video.currentTime = _this.video.buffered.end((_this.video.buffered.length - 1)) - 0.5;
|
|
} else {
|
|
if ((_this.video.buffered.end((_this.video.buffered.length - 1)) - _this.video.currentTime) > 60) {
|
|
_this.video.currentTime = _this.video.buffered.end((_this.video.buffered.length - 1)) - 0.5;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.checkStalled = function() {
|
|
if (!!this.video.currentTime) {
|
|
if (this.video.currentTime == this.checktime) {
|
|
this.checktimecounter += 1;
|
|
} else {
|
|
this.checktimecounter = 0;
|
|
}
|
|
}
|
|
if (this.checktimecounter > 10) {
|
|
logger(videoPlayerVar.index(),
|
|
uuid,
|
|
channel,
|
|
'[FIX]: player not move');
|
|
//play(uuid, videoPlayerVar.index(), channel, 'mse');
|
|
}
|
|
this.checktime = this.video.currentTime;
|
|
|
|
}
|
|
|
|
this.destroy = function() {
|
|
if (this.timeout != null) {
|
|
clearInterval(this.timeout);
|
|
}
|
|
if (this.ws != null) {
|
|
|
|
this.ws.onclose = null;
|
|
this.ws.close(1000, "stop streaming");
|
|
}
|
|
|
|
|
|
|
|
logger(videoPlayerVar.index(),
|
|
'Event: PlayerDestroy',
|
|
'streams: ' + uuid,
|
|
'channel: ' + channel);
|
|
}
|
|
this.video.addEventListener('pause', () => {
|
|
if (this.video.currentTime > this.video.buffered.end((this.video.buffered.length - 1))) {
|
|
this.video.currentTime = this.video.buffered.end((this.video.buffered.length - 1)) - 0.1;
|
|
this.video.play();
|
|
}
|
|
});
|
|
}
|
|
/*************************end mse obect **************************/
|
|
/*************************WEBRTC obect **************************/
|
|
function WebRTCPlayer(uuid, videoPlayerVar, channel) {
|
|
this.webrtc = null;
|
|
this.webrtcSendChannel = null;
|
|
this.webrtcSendChannelInterval = null;
|
|
this.uuid = uuid;
|
|
this.video = videoPlayerVar.find('video')[0];
|
|
this.channel = channel || 0;
|
|
this.playWebrtc = function() {
|
|
var _this = this;
|
|
this.webrtc = new RTCPeerConnection({
|
|
iceServers: [{
|
|
urls: ["stun:stun.l.google.com:19302"]
|
|
}]
|
|
});
|
|
this.webrtc.onnegotiationneeded = this.handleNegotiationNeeded.bind(this);
|
|
this.webrtc.ontrack = function(event) {
|
|
console.log(event.streams.length + ' track is delivered');
|
|
_this.video.srcObject = event.streams[0];
|
|
_this.video.play();
|
|
}
|
|
this.webrtc.addTransceiver('video', {
|
|
'direction': 'sendrecv'
|
|
});
|
|
this.webrtcSendChannel = this.webrtc.createDataChannel('foo');
|
|
this.webrtcSendChannel.onclose = (e) => console.log('sendChannel has closed', e);
|
|
this.webrtcSendChannel.onopen = () => {
|
|
console.log('sendChannel has opened');
|
|
this.webrtcSendChannel.send('ping');
|
|
this.webrtcSendChannelInterval = setInterval(() => {
|
|
this.webrtcSendChannel.send('ping');
|
|
}, 1000)
|
|
}
|
|
|
|
this.webrtcSendChannel.onmessage = e => console.log(e.data);
|
|
},
|
|
this.handleNegotiationNeeded = async function() {
|
|
var _this = this;
|
|
|
|
offer = await _this.webrtc.createOffer();
|
|
await _this.webrtc.setLocalDescription(offer);
|
|
$.post("/stream/" + _this.uuid + "/channel/" + this.channel + "/webrtc?uuid=" + _this.uuid + "&channel=" + this.channel, {
|
|
data: btoa(_this.webrtc.localDescription.sdp)
|
|
}, function(data) {
|
|
try {
|
|
_this.webrtc.setRemoteDescription(new RTCSessionDescription({
|
|
type: 'answer',
|
|
sdp: atob(data)
|
|
}))
|
|
} catch (e) {
|
|
console.warn(e);
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
this.destroy = function() {
|
|
clearInterval(this.webrtcSendChannelInterval);
|
|
this.webrtc.close();
|
|
this.video.srcObject = null;
|
|
}
|
|
}
|
|
|
|
|
|
</script>
|