You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
556 lines
20 KiB
556 lines
20 KiB
{{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>
|
|
|