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.
981 lines
31 KiB
981 lines
31 KiB
<!DOCTYPE html> |
|
<html lang="en"> |
|
|
|
<head> |
|
<meta charset="utf-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
<meta http-equiv="x-ua-compatible" content="ie=edge"> |
|
|
|
<title>RTSPtoWEB</title> |
|
<link rel="stylesheet" href="/../static/plugins/fontawesome-free/css/all.min.css"> |
|
<link rel="stylesheet" href="/../static/css/adminlte.min.css"> |
|
<link rel="stylesheet" href="/../static/plugins/sweetalert2/sweetalert2.min.css"> |
|
<link rel="stylesheet" href="/../static/css/index.css"> |
|
<link rel="stylesheet" href="/../static/css/fullmulti.css"> |
|
<link href="/../static/css/google-fonts.css" rel="stylesheet"> |
|
</head> |
|
|
|
<body class="hold-transition layout-top-nav img-background"> |
|
|
|
<div class="wrapper"> |
|
|
|
<!-- Navbar --> |
|
<nav class="main-header navbar navbar-expand-md navbar-light navbar-dark"> |
|
<div class="container-fluid"> |
|
|
|
|
|
<button class="navbar-toggler order-1" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> |
|
<span class="navbar-toggler-icon"></span> |
|
</button> |
|
|
|
<div class="collapse navbar-collapse order-3" id="navbarCollapse"> |
|
<ul class="order-1 order-md-3 navbar-nav navbar-no-expand ml-auto"> |
|
{{ if .query.controls}} |
|
<li class="nav-item dropdown"> |
|
<a href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle"><i class="fas fa-photo-video"></i></a> |
|
<div class="dropdown-menu dropdown-menu-right custom-dropdown"> |
|
<div class="custom-dropdown-item with-img" onclick="changeBackground('back')"> |
|
<img src="/../static/img/back.jpg" /> |
|
</div> |
|
<div class="custom-dropdown-item with-img" onclick="changeBackground('red')"> |
|
<img src="/../static/img/red.jpg" /> |
|
</div> |
|
<div class="custom-dropdown-item with-img" onclick="changeBackground('green')"> |
|
<img src="/../static/img/green.jpg" /> |
|
</div> |
|
<div class="custom-dropdown-item with-img" onclick="changeBackground('white')"> |
|
<img src="/../static/img/white.jpg" /> |
|
</div> |
|
</div> |
|
</li> |
|
{{end}} |
|
<li class="nav-item dropdown"> |
|
<a id="defaultGrid" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle"><i class="fas fa-th-large"></i></a> |
|
<div aria-labelledby="defaultGrid" class="dropdown-menu dropdown-menu-right custom-dropdown"> |
|
<div class="custom-dropdown-item grid-maker" grid="1" onclick="gridMaker(1)"> |
|
1 x 1 |
|
</div> |
|
<div class="custom-dropdown-item grid-maker" grid="4" onclick="gridMaker(4)"> |
|
2 x 2 |
|
</div> |
|
<div class="custom-dropdown-item grid-maker" grid="6" onclick="gridMaker(6)"> |
|
3 x 2 |
|
</div> |
|
<div class="custom-dropdown-item grid-maker" grid="9" onclick="gridMaker(9)"> |
|
3 x 3 |
|
</div> |
|
<div class="custom-dropdown-item grid-maker" grid="12" onclick="gridMaker(12)"> |
|
4 x 3 |
|
</div> |
|
<div class="custom-dropdown-item grid-maker" grid="16" onclick="gridMaker(16)"> |
|
4 x 4 |
|
</div> |
|
<div class="custom-dropdown-item grid-maker" grid="25" onclick="gridMaker(25)"> |
|
5 x 5 |
|
</div> |
|
<div class="custom-dropdown-item grid-maker" grid="36" onclick="gridMaker(36)"> |
|
6 x 6 |
|
</div> |
|
<div class="custom-dropdown-item grid-maker" grid="49" onclick="gridMaker(49)"> |
|
7 x 7 |
|
</div> |
|
</div> |
|
</li> |
|
{{ if .query.controls}} |
|
<li class="nav-item dropdown"> |
|
<input type="hidden" id="defaultPlayer" value="mse" /> |
|
<a id="defaultPlayerMenu" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle">MSE</a> |
|
<ul aria-labelledby="defaultPlayerMenu" class="dropdown-menu custom-dropdown"> |
|
<li><a href="#" class="dropdown-item" onclick="defaultPlayer('mse',this)">MSE</a></li> |
|
<li><a href="#" class="dropdown-item" onclick="defaultPlayer('hls',this)">HLS</a></li> |
|
<li><a href="#" class="dropdown-item" onclick="defaultPlayer('webrtc',this)">WebRTC</a></li> |
|
</ul> |
|
</li> |
|
<li class="nav-item"> |
|
<a class="nav-link" href="/" role="button"><i class="fas fa-times"></i> Close</a> |
|
</li> |
|
{{end}} |
|
</ul> |
|
</div> |
|
</div> |
|
</nav> |
|
<!-- /.navbar --> |
|
<div class="content-wrapper p-0"> |
|
<div class="content p-0"> |
|
<div class="container-fluid p-0" style="overflow: hidden;"> |
|
<div class="grid-wrapper" id="grid-wrapper"> |
|
|
|
</div> |
|
<div class="main-player-wrapper d-none"> |
|
|
|
<div class="main-player" data-player="none" data-uuid="0"> |
|
<video class="main-video-player" id="videoPlayer" autoplay muted playsinline></video> |
|
<div class="play-info"> </div> |
|
<img class="loader d-none" src="/../static/img/loader.svg" /> |
|
<canvas id="canvas" class="d-none"></canvas> |
|
<input type="hidden" id="uuid" value="0" /> |
|
</div> |
|
<a onclick="closeMain()"><i class="fas fa-times"></i></a> |
|
</div> |
|
|
|
|
|
</div> |
|
</div> |
|
|
|
<!-- foot --> |
|
</div> |
|
<!-- content-wrapper --> |
|
<!-- STREAMS LIST --> |
|
<aside class="control-sidebar custom"> |
|
<!-- Control sidebar content goes here --> |
|
<p> |
|
<a class="control-sidebar-close-btn pull-right" href="#" onclick="$('.control-sidebar').ControlSidebar('collapse')"><i class="fas fa-times"></i></a> |
|
</p> |
|
|
|
<h5>Available streams:</h5> |
|
<input type="hidden" id="player-index" value="0" /> |
|
<div class="row"> |
|
{{ range $key, $value := .streams }} |
|
<div class="col-12" id="{{ $key }}"> |
|
<div class="card card-success"> |
|
<div id="carousel_{{$key}}" class="carousel slide" data-ride="carousel"> |
|
|
|
<div class="carousel-inner"> |
|
{{if eq (len $value.Channels) 1}} |
|
<div class="carousel-item active"> |
|
<a onclick="play('{{ $key }}',null,0)" href="#"><img class="d-block w-100 stream-img" channel="0" src="/../static/img/noimage.svg"></a> |
|
<div class="carousel-caption d-none d-md-block"> |
|
<h5>{{$value.Name}}</h5> |
|
</div> |
|
</div> |
|
{{else}} |
|
{{ range $k, $v := .Channels }} |
|
{{ if eq $k "1"}} |
|
<div class="carousel-item active"> |
|
<a onclick="play('{{ $key }}',null,{{$k}})" href="#"><img class="d-block w-100 stream-img" channel="{{$k}}" src="/../static/img/noimage.svg"></a> |
|
<div class="carousel-caption d-none d-md-block"> |
|
<h5>{{$value.Name}}</h5> |
|
</div> |
|
</div> |
|
{{end}} |
|
{{end}} |
|
{{end}} |
|
|
|
</div> |
|
</div> |
|
|
|
</div> |
|
</div> |
|
{{ end }} |
|
</div> |
|
</aside> |
|
|
|
</div> |
|
<!-- ./wrapper --> |
|
<script> |
|
let streams = {{ .streams }}; |
|
let presetOptions = {{ .options }}; |
|
let get_query={{.query}}; |
|
</script> |
|
<script src="/../static/plugins/jquery/jquery.min.js"></script> |
|
<script src="/../static/plugins/bootstrap/js/bootstrap.bundle.min.js"></script> |
|
<script src="/../static/js/adminlte.min.js"></script> |
|
<script src="/../static/plugins/sweetalert2/sweetalert2.min.js"></script> |
|
<script src="/../static/js/index.js"></script> |
|
|
|
|
|
</body> |
|
|
|
</html> |
|
<!-- end foot --> |
|
<script src="/../static/plugins/hlsjs/hls.min.js"></script> |
|
<script> |
|
let colordebug = false; |
|
|
|
let players = {}; |
|
$(document).ready(() => { |
|
logger(9, 'page is ready') |
|
changeBackground(localStorage.getItem('backgroundImage')); |
|
if('grid' in get_query){ |
|
let get_grid=get_query.grid[0]||4; |
|
multiviewGrid('set', get_grid); |
|
} |
|
if (presetOptions.grid != 0) { |
|
multiviewGrid('set', presetOptions.grid); |
|
} |
|
|
|
if (presetOptions.player != null) { |
|
localStorage.setItem('multiviewPlayers', JSON.stringify(presetOptions.player)); |
|
} |
|
gridMaker(multiviewGrid('get')); |
|
restoreStreams(); |
|
}); |
|
|
|
function defaultPlayer(type, el) { |
|
$('#defaultPlayer').val(type); |
|
$(el).closest('.nav-item').children('a').html($(el).text()); |
|
} |
|
|
|
function gridMaker(col = 4) { |
|
let grid_sizes=[1,4,6,9,12,16,25,36,49]; |
|
col = parseInt(col); |
|
col=grid_sizes.filter(v => v >= col)[0]||4; |
|
let colW; |
|
switch (col) { |
|
case 1: |
|
colW = 'grid-1'; |
|
break; |
|
case 6: |
|
colW = 'grid-6'; |
|
break; |
|
case 9: |
|
colW = 'grid-9'; |
|
break; |
|
case 12: |
|
colW = 'grid-12'; |
|
break; |
|
case 16: |
|
colW = 'grid-16'; |
|
break; |
|
|
|
case 25: |
|
colW = 'grid-25'; |
|
break; |
|
case 36: |
|
colW = 'grid-36'; |
|
break; |
|
case 49: |
|
colW = 'grid-49'; |
|
break; |
|
default: |
|
colW = ''; |
|
break; |
|
} |
|
let memory = localStorage.getItem('multiviewPlayers'); |
|
destroyGrid(); |
|
for (var i = 0; i < col; i++) { |
|
$('#grid-wrapper').append( |
|
`<div class=" player ` + colW + ` empty" data-player="none" data-uuid="0"> |
|
<div class="play-info"></div> |
|
<video class="video-class empty" autoplay muted playsinline></video> |
|
` + |
|
`<a class="remove-btn" onclick="destoyPlayer(` + i + `)"><i class="fas fa-times"></i></a>` + |
|
`<a class="add-stream-btn" onclick="openChoise(this)"><i class="fas fa-plus"></i><br />Click to select stream</a>` + |
|
`<a href="#" class="btn btn-info btn-xs btn-play-main d-none" onclick="playMainStream(this)"><i class="fas fa-expand"></i> Expand</a>` + |
|
`<img class="loader d-none" src="/../static/img/loader.svg" />` + |
|
`</div>`); |
|
} |
|
multiviewGrid('set', col); |
|
addEventListenerToVideo(); |
|
$('.grid-maker').removeClass('active'); |
|
$('.grid-maker[grid="' + col + '"]').addClass('active'); |
|
if (memory != null) { |
|
localStorage.setItem('multiviewPlayers', memory); |
|
restoreStreams(); |
|
} |
|
|
|
} |
|
|
|
function addEventListenerToVideo() { |
|
$('.video-class').each(function() { |
|
let _this = this; |
|
let index = $(this).closest('.player').index(); |
|
let uuid = $(this).closest('.player').attr('data-uuid'); |
|
|
|
this.addEventListener('loadeddata', () => { |
|
_this.play(); |
|
makePic(this, $(_this).closest('.player').attr('data-uuid'), 1); |
|
logger(index, '[video]: loadeddata'); |
|
}); |
|
this.addEventListener('stalled', () => { |
|
logger(index, '[video]: stalled'); |
|
}); |
|
this.addEventListener('pause', () => { |
|
if (_this.currentTime > _this.buffered.end(_this.buffered.length - 1)) { |
|
_this.currentTime = _this.buffered.end(_this.buffered.length - 1) - 0.1; |
|
_this.play(); |
|
} |
|
logger(index, '[video]: pause'); |
|
}); |
|
|
|
this.addEventListener('error', (e) => { |
|
logger(index, '[video]: error', e); |
|
//console.log(e); |
|
}); |
|
|
|
this.addEventListener('abort', () => { |
|
logger(index, '[video]: abort'); |
|
}); |
|
|
|
this.addEventListener('emptied', (e) => { |
|
logger(index, '[video]: emptied'); |
|
}); |
|
|
|
this.addEventListener('ended', (e) => { |
|
logger(index, '[video]: ended'); |
|
}); |
|
|
|
this.addEventListener('play', (e) => { |
|
logger(index, '[video]: play'); |
|
}); |
|
|
|
this.addEventListener('suspend', (e) => { |
|
logger(index, '[video]: suspend'); |
|
}); |
|
this.addEventListener('waiting', (e) => { |
|
logger(index, '[video]: waiting'); |
|
}); |
|
this.addEventListener('canplaythrough', (e) => { |
|
$(_this).closest('div').find('.loader').addClass('d-none'); |
|
logger(index, '[video]: canplaythrough'); |
|
}); |
|
this.addEventListener('playing', (e) => { |
|
$(_this).closest('div').find('.loader').addClass('d-none'); |
|
logger(index, '[video]: playing'); |
|
}); |
|
this.addEventListener('loadedmetadata', (e) => { |
|
logger(index, '[video]: loadedmetadata'); |
|
}); |
|
this.addEventListener('loadstart', (e) => { |
|
logger(index, '[video]: loadstart'); |
|
}); |
|
}); |
|
} |
|
|
|
$('.main-video-player')[0].addEventListener('canplaythrough', (e) => { |
|
$('.main-player').find('.loader').addClass('d-none'); |
|
logger(0, '[video]: canplaythrough'); |
|
}); |
|
$('.main-video-player')[0].addEventListener('playing', (e) => { |
|
$('.main-player').find('.loader').addClass('d-none'); |
|
logger(0, '[video]: canplaythrough'); |
|
}); |
|
|
|
$("#videoPlayer")[0].addEventListener('loadeddata', () => { |
|
$("#videoPlayer")[0].play(); |
|
let browser = browserDetector(); |
|
if (!browser.safari) { |
|
makePic(); |
|
} |
|
}); |
|
|
|
function destroyGrid() { |
|
$('.player').each(function(index) { |
|
destoyPlayer(index); |
|
}); |
|
$('#grid-wrapper').empty(); |
|
} |
|
|
|
function openChoise(dom) { |
|
$('#player-index').val($(dom).closest('.player').index()); |
|
$('.control-sidebar').ControlSidebar('show') |
|
|
|
} |
|
|
|
function play(uuid, index, chan, typePlayer) { |
|
$('#uuid').val(uuid); |
|
if (typeof(index) == 'undefined' || index == null) { |
|
index = $('#player-index').val(); |
|
} |
|
let videoPlayerVar = $('.main-player'); |
|
if (index != 'main') { |
|
videoPlayerVar = $('.player').eq(index); |
|
} |
|
|
|
$('.control-sidebar').ControlSidebar('collapse'); |
|
destoyPlayer(index); |
|
videoPlayerVar.removeClass('empty'); |
|
videoPlayerVar.find('video').removeClass('empty'); |
|
|
|
|
|
let playerType = $('#defaultPlayer').val(); |
|
if (!!typePlayer) { |
|
playerType = typePlayer; |
|
} |
|
videoPlayerVar.attr('data-player', playerType); |
|
videoPlayerVar.attr('data-uuid', uuid); |
|
|
|
let channel = 0; |
|
|
|
if (typeof(streams[uuid].channels[1]) !== "undefined") { |
|
channel = 1; |
|
} |
|
|
|
if (index == 'main') { |
|
channel = 0; |
|
} else { |
|
packStreamms(index, uuid, chan, playerType); |
|
} |
|
if (colordebug) { |
|
videoPlayerVar.find('.play-info').html('Stream: ' + streams[uuid].name + ' | player type:' + playerType + ' | channel: ' + channel); |
|
} |
|
videoPlayerVar.find('.loader').removeClass('d-none'); |
|
//fix stalled video in safari |
|
videoPlayerVar.find('video')[0].addEventListener('pause', () => { |
|
if (videoPlayerVar.find('video')[0].currentTime > videoPlayerVar.find('video')[0].buffered.end((videoPlayerVar.find('video')[0].buffered.length - 1))) { |
|
videoPlayerVar.find('video')[0].currentTime = videoPlayerVar.find('video')[0].buffered.end((videoPlayerVar.find('video')[0].buffered.length - 1)) - 0.1; |
|
videoPlayerVar.find('video')[0].play(); |
|
} |
|
}); |
|
|
|
switch (playerType) { |
|
case 'hls': |
|
let url = '/stream/' + uuid + '/channel/' + channel + '/hls/live/index.m3u8'; |
|
|
|
if (videoPlayerVar.find('video')[0].canPlayType('application/vnd.apple.mpegurl')) { |
|
videoPlayerVar.find('video')[0].src = url; |
|
videoPlayerVar.find('video')[0].load(); |
|
} else if (Hls.isSupported()) { |
|
players[index] = new Hls({ |
|
manifestLoadingTimeOut: 60000 |
|
}); |
|
players[index].loadSource(url); |
|
players[index].attachMedia(videoPlayerVar.find('video')[0]); |
|
} else { |
|
Swal.fire({ |
|
icon: 'error', |
|
title: 'Oops...', |
|
text: 'Your browser don`t support hls ' |
|
}); |
|
} |
|
break; |
|
case 'webrtc': |
|
players[index] = new WebRTCPlayer(uuid, videoPlayerVar, channel); |
|
players[index].playWebrtc(); |
|
break; |
|
case 'mse': |
|
default: |
|
|
|
players[index] = new msePlayer(uuid, videoPlayerVar, channel); |
|
players[index].playMse(); |
|
break; |
|
} |
|
|
|
} |
|
|
|
function destoyPlayer(index) { |
|
let videoPlayerVar = $('.main-player'); |
|
if (index != 'main') { |
|
videoPlayerVar = $('.player').eq(index); |
|
} |
|
let type = videoPlayerVar.attr('data-player'); |
|
videoPlayerVar.addClass('empty'); |
|
videoPlayerVar.find('video').addClass('empty') |
|
|
|
switch (type) { |
|
case 'hls': |
|
if (!!players[index]) { |
|
players[index].destroy(); |
|
delete players[index]; |
|
} |
|
break; |
|
case 'mse': |
|
players[index].destroy(); |
|
delete players[index]; |
|
|
|
break; |
|
case 'webrtc': |
|
players[index].destroy(); |
|
delete players[index]; |
|
break; |
|
default: |
|
|
|
break; |
|
} |
|
videoPlayerVar.attr('data-player', 'none'); |
|
videoPlayerVar.attr('data-uuid', 0); |
|
videoPlayerVar.find('.play-info').html(''); |
|
videoPlayerVar.find('video')[0].src = ''; |
|
videoPlayerVar.find('video')[0].load(); |
|
videoPlayerVar.find('.loader').addClass('d-none'); |
|
|
|
unpackStreams(index); |
|
} |
|
|
|
function expand(element) { |
|
fullscreenOn($('#grid-wrapper').parent()[0]); |
|
} |
|
|
|
function playMainStream(element) { |
|
let uuid = $(element).closest('.player').attr('data-uuid'); |
|
if (uuid == 0) { |
|
return; |
|
} |
|
$('.main-player-wrapper').removeClass('d-none'); |
|
play(uuid, 'main'); |
|
} |
|
|
|
function closeMain() { |
|
destoyPlayer('main'); |
|
$('.main-player-wrapper').addClass('d-none'); |
|
} |
|
/*************************mse obect **************************/ |
|
function msePlayer(uuid, videoPlayerVar, channel) { |
|
this.ws = null, |
|
this.video = videoPlayerVar.find('video')[0], |
|
this.sound=false, |
|
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; |
|
logger(videoPlayerVar.index(), |
|
'func playMse', |
|
'streams: ' + uuid, |
|
'channel: ' + channel); |
|
|
|
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); |
|
} |
|
if(mimeCodec.indexOf(',')>0){ |
|
_this.sound=true; |
|
} |
|
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) { |
|
if(!_this.sound){ |
|
_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); |
|
} |
|
} |
|
/*************************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; |
|
} |
|
} |
|
|
|
/*********************FULSCREEN******************/ |
|
function fullscreenEnabled() { |
|
return !!( |
|
document.fullscreenEnabled || |
|
document.webkitFullscreenEnabled || |
|
document.mozFullScreenEnabled || |
|
document.msFullscreenEnabled |
|
); |
|
} |
|
|
|
function fullscreenOn(elem) { |
|
if (elem.requestFullscreen) { |
|
elem.requestFullscreen(); |
|
} else if (elem.mozRequestFullScreen) { |
|
elem.mozRequestFullScreen(); |
|
} else if (elem.webkitRequestFullscreen) { |
|
elem.webkitRequestFullscreen(); |
|
} else if (elem.msRequestFullscreen) { |
|
elem.msRequestFullscreen(); |
|
} |
|
} |
|
|
|
function fullscreenOff() { |
|
if (document.requestFullscreen) { |
|
document.requestFullscreen(); |
|
} else if (document.webkitRequestFullscreen) { |
|
document.webkitRequestFullscreen(); |
|
} else if (document.mozRequestFullscreen) { |
|
document.mozRequestFullScreen(); |
|
} |
|
} |
|
|
|
function packStreamms(index, uuid, channel, type) { |
|
let multiviewPlayers; |
|
if (localStorage.getItem('multiviewPlayers') != null) { |
|
multiviewPlayers = JSON.parse(localStorage.getItem('multiviewPlayers')); |
|
} else { |
|
multiviewPlayers = {}; |
|
} |
|
multiviewPlayers[index] = { |
|
uuid: uuid, |
|
channel: channel, |
|
playerType: type |
|
} |
|
localStorage.setItem('multiviewPlayers', JSON.stringify(multiviewPlayers)); |
|
} |
|
|
|
function unpackStreams(index) { |
|
if (localStorage.getItem('multiviewPlayers') != null) { |
|
let multiviewPlayers = JSON.parse(localStorage.getItem('multiviewPlayers')); |
|
delete multiviewPlayers[index]; |
|
localStorage.setItem('multiviewPlayers', JSON.stringify(multiviewPlayers)); |
|
} |
|
} |
|
|
|
function restoreStreams() { |
|
if (localStorage.getItem('multiviewPlayers') != null) { |
|
|
|
let multiviewPlayers = JSON.parse(localStorage.getItem('multiviewPlayers')); |
|
if (Object.keys(multiviewPlayers).length > 0) { |
|
$.each(multiviewPlayers, function(key, val) { |
|
|
|
if (val.uuid in streams && val.channel in streams[val.uuid].channels) { |
|
if ($('.player').eq(key).length > 0) { |
|
play(val.uuid, key, val.channel, val.playerType); |
|
} |
|
|
|
} else { |
|
unpackStreams(key); |
|
} |
|
}) |
|
} |
|
|
|
} else { |
|
|
|
if (Object.keys(streams).length > 0) { |
|
gridMaker(Object.keys(streams).length); |
|
let playerIndex = 0; |
|
let gridSize = $('.player').length; |
|
$.each(streams, function(uuid, params) { |
|
//console.log(i,uuid); |
|
if (playerIndex < gridSize) { |
|
let channel = 0; |
|
if ('1' in params.channels) { |
|
channel = 1; |
|
} |
|
play(uuid, playerIndex, channel, 'mse'); |
|
playerIndex++; |
|
} else { |
|
return; |
|
} |
|
|
|
|
|
}) |
|
} |
|
|
|
} |
|
} |
|
|
|
function multiviewGrid(type, grid) { |
|
let defGrid = 25; |
|
switch (type) { |
|
case 'set': |
|
localStorage.setItem('multiviewGrid', grid); |
|
break; |
|
case 'get': |
|
if (localStorage.getItem('multiviewGrid') != null) { |
|
return localStorage.getItem('multiviewGrid'); |
|
} else { |
|
return defGrid |
|
} |
|
break; |
|
default: |
|
return defGrid |
|
} |
|
return defGrid |
|
} |
|
|
|
$('#grid-wrapper').on('dblclick', '.player', function() { |
|
$(this).find('.btn-play-main').click(); |
|
}); |
|
$('.main-player').on('dblclick', function() { |
|
closeMain() |
|
}); |
|
|
|
function changeBackground(num) { |
|
let back = '/../static/img/'; |
|
localStorage.setItem('backgroundImage', num); |
|
switch (num) { |
|
case 'green': |
|
back += 'green.jpg' |
|
break; |
|
case 'red': |
|
back += 'red.jpg' |
|
break; |
|
case 'white': |
|
back += 'white.jpg' |
|
break; |
|
default: |
|
back += 'back.jpg' |
|
} |
|
$('.img-background').css('background-image', 'url("' + back + '")'); |
|
} |
|
</script>
|
|
|