wchat
#import getscript
#import parallel
#import jquery
#import lodash
#import ytplayer
#import marked
#import subchannel
function testbottom() {
var t1 = $('table.content')[0];
return t1.scrollTopMax && t1.scrollTop === t1.scrollTopMax || t1.scrollHeight-t1.scrollTop-t1.offsetHeight<=5;
}
function restorebottom(bottom) {
if(bottom) {
var t1 = $('table.content')[0]
t1.scrollTop = t1.scrollTopMax || t1.scrollHeight;
var interval = setInterval(()=>t1.scrollTop = t1.scrollTopMax || t1.scrollHeight,10);
setTimeout(()=>{
clearInterval(interval);
},500);
}
}
function focuschanged(state) {
console.log('focus changed',state);
if(state=='focus') {
window.hasfocus=true;
if(window.myonfoci) {
while(window.myonfoci.length) {
window.myonfoci.shift()();
}
}
}
else {
window.hasfocus=false;
}
//$('#compose').val($('#compose').val()+' '+state);
}
function announcetoserver(roomname) {
getjquery(()=>{
$.post('/announce/wchat/'+roomname);
});
}
function announcetopeerlist(peers,notrim) {
var room=window.roomname;
peers = peers.filter(peer=>{
return peer!=window.user;
});
if(!notrim) {
peers = peers.filter(peer=>{
return !window.knownpeers[peer];
});
}
window.ws.send(JSON.stringify({to:peers,msg:{cmd:'announce',room:room,nonce:window.sessionid}}));
window.failedtosendlock = true;
//Find a random peer that responded to ask if they know any more after two seconds
//Load lodash in the meantime.
parallel([cb=>{
setTimeout(cb,2000);
},getlodash],cb=>{
peers = _.shuffle(peers);
for(var i=0;i<peers.length;++i) {
//Find a peer that announced back in within 2 seconds and ask for more peers;
if(window.knownpeers[peers[i]]) {
//They announced back;
console.log('Requesting posts and peers from',peers[i]);
window.ws.send(JSON.stringify({to:peers[i],msg:{cmd:'requestpeers',room:room}}));
return;
}
}
//If no one new responded that's it. We have our peers.
window.allpostsgathered=true;
});
}
function announcetopeers(roomname) {
window.knownpeers = window.knownpeers || {};
getjquery(()=>{
$.post('/findpeers/wchat/'+roomname,peerlist=>{
announcetopeerlist(peerlist);
peerlist.forEach(peer=>{
if(peer!=window.user) {
getpostsfrompkey(roomname,null,peer);
}
});
});
});
}
function wsonmessage(message) {
console.log('message',message);
message=JSON.parse(message.data);
if(message.failedtosend) {
window.failedtosend = message.failedtosend;
window.failedtosendlock = false;
return;
}
var data=message.msg;
console.log('revieved',data);
if(data.room!=window.roomname) {
console.log('Ignored, wrong room',data.room,window.roomname,data);
return;
}
if(data.cmd=='announce' && data.room==window.roomname) {
if(window.knownpeers[message.from]!=data.nonce) {
window.knownpeers[message.from]=data.nonce;
add_user_to_ui(message.from);
announcetopeerlist([message.from],true);
}
//We don't have to do anything because we are treating every valid message as an announce.
//It's enought to note this as valid with an if statement.
}
else if(data.cmd=='requestpeers'&&data.room===window.roomname) {
window.posts = window.posts || {};
window.ws.send(JSON.stringify({to:message.from,msg:{cmd:'peerlist',room:window.roomname,msg:Object.keys(window.knownpeers)}}));
var sendlist=Object.keys(window.posts).sort((a,b)=>a-b).map(i=>window.posts[i]);
window.ws.send(JSON.stringify({to:message.from,msg:{cmd:'posts',room:window.roomname,msg:sendlist}}));
}
else if(data.cmd=='post'&&data.msg&&message.from) {
add_msg_to_ui(message.from,data.msg,data.nonce);
}
else if(data.cmd=='left'&&message.from) {
delete window.knownpeers[message.from];
remove_user_from_ui(message.from);
}
else if(data.cmd=='posts'&&data.msg&&message.from) {
data.msg.forEach(msg=>{
add_msg_to_ui(msg.user,msg.msg,msg.nonce);
});
}
else if(data.cmd=='peerlist'&&data.msg) {
announcetopeerlist(data.msg)
}
else {
//Must have been invalid.
return;
}
if(!window.knownpeers[message.from]) {
//We got a message from someone we don't know.
//That could be bad.
//Let's add them.
window.knownpeers[message.from]=true;
add_user_to_ui(message.from);
announcetopeerlist([message.from]);
}
}
function sendmessagetopeers(msg,nonce) {
nonce=nonce||Date.now();
window.ws.send(JSON.stringify({to:Object.keys(window.knownpeers),msg:{cmd:'post',room:window.roomname,user:window.user,msg,nonce}}));
}
function sendclick (e) {
var msg=$('#compose').val().trim();
$('#compose').val('');
if(msg.length===0) {
return;
}
var nonce=Date.now();
sendmessagetopeers(msg,nonce);
add_msg_to_ui(user,msg,nonce);
}
function init_chat_ui () {
//We want a user list on the right
//We want a text bar on the bottom
//We want a content on the top
add_style(getchatcss());
setTimeout(()=>{
document.body.innerHTML=getchathtml();
getcss('https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css');
getcss('https://use.fontawesome.com/releases/v5.6.3/css/all.css');
getjquery(()=>{
register_embed_form_listeners();
$('#send').click(sendclick);
$('#compose').on('keydown',e=>{
if(e.keyCode==13 && !e.shiftKey) {
e.preventDefault();
sendclick(e);
}
});
getjqueryui(()=>{
$('.compose .self').click(()=>{
$('.dialogform').dialog();
});
});
});
//add_ui_listeners();
},50);
}
function openwebsocket() {
window.ws = new WebSocket(location.protocol.replace('http','ws')+'//'+location.host+'/peerws');
window.ws.onmessage = wsonmessage;
window.ws.onclose = ()=>{
if(window.hasfocus) {
openwebsocket();
}
else {
window.myonfoci = window.myonfoci || [];
window.myonfoci.push(openwebsocket);
}
}
window.ws.onopen = ()=>{
getjquery(()=>{
$('#status').text('Chatting');
});
announcetoserver(window.roomname);
announcetopeers(window.roomname);
//getpostsfrompkey(window.roomname);
add_user_to_ui(user);
};
}
function startChat(servername) {
//var roomname = location.pathname.replace(/\/run/,'/').replace(/\/app\/\w+\/?/,'/').split('/').slice('-1')[0] || servername || 'wchat';
var roomname = appsubchannel() || servername || 'wchat';
if(roomname.startsWith('/')) {
roomname=roomname.replace('/','');
}
window.sessionid=Math.round(Math.random()*100000);
window.roomname = roomname;
console.log('roomname',roomname);
getmarked(()=>{});
init_chat_ui();
getpostsfrompkey(roomname);
openwebsocket();
window.hasfocus=false;
window.addEventListener('blur',()=>{
focuschanged('blur');
});
window.addEventListener('focus',()=>{
focuschanged('focus');
});
}
function getchatcss () {
return `
html,body {
width:100%;
height:100%;
margin:0;
padding:0;
display:flex;
font-family:'Nunito', Arial, Helvetica, Sans-Serif;
}
body.openside table.content > tbody > tr {
display:flex;
flex-flow:column;
}
body.openside td.user {
text-align:left;
}
ul {
list-style-type: none;
padding:0;
margin:0;
height:auto;
font-size:14px;
}
.flow {
display:flex;
}
.flowv {
flex-flow:column;
}
.flowh {
flex-flow:row;
}
.users {
}
.chat {
flex: 1 1 auto
}
.content {
flex:1 1 auto;
overflow-y:scroll;
font-size:14px;
}
.compose {
background:#5668b5;
height:2em;
border:8px solid #f6f6f6;
}
.compose .self {
background: #f0f0f0;
padding: 4px 16px;
margin: 2px;
align-content: center;
cursor:pointer
}
.compose textarea {
flex: 1 1 auto;
resize:none;
padding-top:4px;
padding-left:4px;
}
.indent {
padding-left:74px;
}
td {
vertical-align:top;
padding-left:8px;
}
.content .user {
overflow-y:wrap;
text-align:right;
color:green;
}
.container {
width:100%;
}
body.openside .container {
max-width:50%;
}
body.openside .chat {
flex-direction:row-reverse;
}
#sidebar {
width:200px;
background-color:#f6f6f6;
}
#sidebar a {
color:black;
text-decoration:none;
}
#sidebar a:hover {
text-decoration:underline;
}
/*.self .more {
display:none;
position:absolute;
margin-top:0;
transition:margin-top 0.5s;
height:100px;
width:100px;
}
.more.show {
display:block;
margin-top:-37px;
}
.more li:hover {
background:lightblue;
}*/
.dialogform {
display:none;
}
video {
max-width:100%;
max-height:100%;
flex-shrink:1;
}
.side {
font-size:1.8em;
width:100%;
display:none;
}
.side i {
background:#ffffff4d;
}
.side i:hover {
font-size:1.4em;
}
.side .mirror {
transition:opacity 0.4s;
}
.side.hasinteracted .mirror {
opacity:0;
}
.side .mirror:hover {
font-size:0.8823529411764706em;
opacity:1;
}
.hide {
display:none;
}
.container > .chat {
overflow-y:scroll;
}
.dialogform .onimage, .dialogform .onvideo, .dialogform .onpoll, .dialogform .onsync, .dialogform .onavatar, .dialogform .onupload {
display:none;
}
.dialogform.onimage .onimage, .dialogform.onvideo .onvideo, .dialogform.onpoll .onpoll, .dialogform.onavatar .onavatar, .dialogform.onupload .onupload, .dialogform.onvideo.onsync .onsync {
display:inline;
}
td.user {
max-width:128px;
overflow:hidden;
text-overflow: ellipsis;
}
td.msg {
min-width:256px;
}
body.openside #sidebar {
overflow:hidden;
text-overflow: ellipsis;
}
body.openside .side {
display:flex;
}
iframe {
display:none;
}
p {
margin:0;
}
#mirrorheader {
display:none;
}
#controllers > li {
cursor:pointer;
}
#broadcast {
display:none;
cursor:pointer;
font-size:0.7em;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 2000px;
visibility:hidden;
margin-right:3px;
}
#joined a {
display:flex;
align-items:center;
}
`
}
function getchathtml () {
return `
<div class="dialogform" title="Embed">
<input class="image hide" id="imagetyperadio" type="radio" name="type" value="jpg"/>
<label for="imagetyperadio" class="hide">Image</label>
<input class="video" id="videotyperadio" type="radio" name="type" value="mp4"/>
<label for="videotyperadio">Video</label>
<input class="poll" id="polltyperadio" type="radio" name="type" value="poll"/>
<label for="polltyperadio">Poll</label>
<input class="mirror" id="mirrortyperadio" type="radio" name="type" value="mirror"/>
<label for="mirrortyperadio">Mirror</label>
<br />
<input id="avatartyperadio" type="radio" name="type" value="avatar"/>
<label for="avatartyperadio">Avatar</label>
<input id="uploadtyperadio" type="radio" name="type" value="upload"/>
<label for="uploadtyperadio">Upload</label>
<br />
<input class="title onvideo onpoll" placeholder="Title (optional)"/><br class="onvideo onimage"/>
<input class="url onimage onvideo onavatar" placeholder="Url"/><br class="onvideo"/>
<input class="onvideo" id="syncbox" type="checkbox"/>
<label class="onvideo" for="syncbox">Synchonized</label><br class="onsync"/>
<input class="onsync" id="loopbox" type="checkbox"/>
<label class="onsync" for="loopbox">Loop</label><br class="onsync"/>
<input class="onsync delay" placeholder="Delay, use h,m,s suffixes"/><br class="onpoll"/>
<input class="onpoll polloption" placeholder="Option"/>
<a class="onupload" href="/app/upload" style="color:blue" target="_blank"><br>Head to the upload app<br></a><br />
<button>Post</button>
</div>
<div class="flow flowv container">
<div class="flow flowh chat">
<table class="content flow flowv">
<tbody>
</tbody>
</table>
<div class="flow flowv users" id="sidebar">
<h3 id="status">Connecting</h3>
<ul id="joined"></ul>
<h3 id="mirrorheader">Mirror</h3>
<ul id="controllers"></ul>
<h4 id="broadcast" title="Click to toggle">Broadcast on</h4>
</div>
</div>
<div class="flow flowh compose">
<span class="self" title="Embed"></span>
<textarea id='compose' placeholder='Send message...'></textarea>
<button id='send' class='hide'>Send</button>
</div>
</div>
<div class="side flow flowv">
</div>`
}
function media_filters () {
return [video_msg_filter,poll_msg_filter,mirror_msg_filter];
}
function media_message_to_td(msg,user) {
console.log('msg',msg);
var media_attributes;
try {
media_attributes=JSON.parse(msg.replace('::{"','{"'));
} catch(e) {
return $('<td>').text(msg);
}
var msgtd;
var omituser;
media_filters().some(mf=>{
var [handled,tmpmsgtd,tmpomituser]=mf(media_attributes,user);
if(handled) {
msgtd=tmpmsgtd;
omituser=tmpomituser;
return true;
}
return false;
});
return [msgtd,omituser];
}
function mirror_msg_filter(attr,user) {
if(attr.type=='mirror') {
if(user==window.user) {
if($('#broadcast').css('display')=='none') { //We will only do this once
//Add broadcast control to sidebar
var on=true;
$('#broadcast').css('display','block').click(()=>{
on=!on;
$('#broadcast').text('Broadcast '+(on?'on':'off'));
});
//Listen for clicks that we need to broadcast relevent javascript for
$('body').click((e)=>{
console.log('broadcast click',e.target);
//The two click events I want to watch for are embed clicks and close clicks
var target=e.target;
while(on && target!=document.body) {
if(target.dataset.rebroadcast) {
sendmessagetopeers('::'+JSON.stringify({'type':'ctrl_instr',run:target.dataset.rebroadcast}));
break;
}
target=target.parentElement;
}
});
}
}
var td=$('<td>').css('cursor','pointer');
var button=$('<button>').text(`Give ${user} control`);
td.append(button);
td.click(()=>{
//Add user to white list for control
window.whitelist = window.whitelist || new Set();
if(user!=window.user && !window.whitelist.has(user)) {
window.whitelist.add(user);
var li = $('<li>').text(user).attr('title','Click to remove');
$('#mirrorheader').css('display','block');
$('#controllers').append(li);
li.click(()=>{
li.remove();
window.whitelist.delete(user);
if($('#controllers').children().length==0) {
$('#mirrorheader').css('display','none');
}
});
}
});
return [true,td];
}
else if(attr.type="ctrl_instr") {
if(window.whitelist && window.whitelist.has(user)) {
//We are trusting this.
eval(attr.run);
}
return [true,null];
}
}
function video_msg_filter(attr,user) {
if(attr.type==='mp4') {
console.log('embedbutton',attr,user);
var td = $('<td>').html((attr.title||attr.url)+'<button class="embedbutton">embed'+(attr.sync?' sync':'')+(attr.loop?' loop':'')+'</button>');
td.css('color','blue').css('text-decoration','underline').css('cursor','pointer');
td.attr('data-rebroadcast',`display_mp4_in_chat('${attr.url}',${attr.sync?attr.start:null},${attr.loop})`);
td.click(()=>{
display_mp4_in_chat(attr.url,attr.sync?attr.start:null,attr.loop);
});
return [true,td];
}
return [false,null];
}
function poll_msg_filter(attr,user) {
if(attr.type=='poll') {
console.log('A poll by',attr.user)
if(user==window.user) {
//This is me.
window.mypolls = window.mypolls||new Set();
window.mypolls.add(attr.id);
}
var td = $('<td>').attr('id','poll'+attr.id);
td[0].votes=[];
var title = $('<h3>').text('Poll: '+attr.title)
.css('font-size','24px')
.css('font-family','Helvetica Neue",Helvetica,Arial,sans-serif')
.css('font-weight','600')
.css('color','#068f06')
.css('margin','8px 0 8px 0');
td.append(title);
var table = $('<table>');
var tbody = $('<tbody>');
table.append(tbody);
attr.options.forEach((o,i)=>{
var clicked = false;
var tr=$('<tr>').css('cursor','pointer');
var count=$('<td>').text(0);
var option = $('<td>').text(o);
td[0].votes[i]={};
tbody.append(tr.append(count,option));
tr.click(()=>{
var retr={};
retr.type='pollvote'
retr.forid=attr.id;
retr.for=i;
if(clicked) {
retr.nullify=true;
}
clicked=!clicked;
$('#compose').val('::'+JSON.stringify(retr));
$('#send').click();
});
});
td.append(table);
return [true,td];
}
else if(attr.type=='pollvote') {
var polltd = $('#poll'+attr.forid)
if(!polltd[0]) {
return [true,null]; //The poll does not exist
}
var votes = polltd[0].votes[attr.for];
if(attr.nullify) {
delete votes[user]; //They removed their vote
}
else {
votes[user]=true; //They voted for something
}
polltd.find(`tr:nth-child(${attr.for+1}) td`).first().text(Object.keys(votes).length); //Update the cound for the option in ui
if(window.mypolls && window.mypolls.has(attr.forid)&&user!=window.user) {
//They voted on my poll, and they aren't me
if(!(window.lasttovote && window.lasttovote==user)) {
//We don't want a bunch of notifications about the same person
var td=$('<td>').css('font-size','0.7em').text(user+' voted on your poll: '+polltd.find('h3').text().replace('Poll: ',''));
window.lasttovote = user; //Store that they voted because we don't want a notice for every vote.
return [true,td,true]; //We are going to send a notification; //We are not going to have the user td
}
}
return [true,null];
}
else {
return [false,null];
}
}
function newestNPosts(posts,n) {
var retr={};
Object.keys(posts).sort((a,b)=>{
return a-b;
}).slice(-n).forEach(nonce=>{
retr[nonce]=posts[nonce];
});
return retr;
}
function storepostsinpkey() {
if(!window.storepkeylock) {
window.storepkeylock = true;
setTimeout(()=>{
console.log('storing posts',window.rooomname);
$.post('/pkey/set/wchat/'+window.roomname+'/posts',JSON.stringify(newestNPosts(window.posts,200)));
delete window.storepkeylock;
},1000*30);
}
}
function getpostsfrompkey(room,cb,targetuser) {
getjquery(()=>{
$.post('/pkey/get/'+(targetuser||window.user)+'/wchat/'+room+'/posts',posts=>{
if(!(posts && posts.length)) {
posts='{}';
}
posts=JSON.parse(posts);
if(cb) {
cb(posts);
}
else {
Object.values(posts).forEach(p=>{
add_msg_to_ui(p.user,p.msg,p.nonce);
});
}
});
});
}
function add_msg_to_ui(user,msg,nonce) {
var msgtd;
var omituser;
window.posts = window.posts || {};
if(window.posts[nonce]) {
return;
}
window.posts[nonce]={user,msg,nonce};
if(window.allpostsgathered) {
storepostsinpkey();
}
var bottom = testbottom();
if(msg.startsWith('::{"')) {
[msgtd,omituser] = media_message_to_td(msg,user);
}
else {
msgtd = $('<td>').text(msg).addClass('msg');
getmarked(()=>{
msgtd.html(marked(msg));
restorebottom(bottom);
});
}
if(!msgtd) {
return;
}
var table = $('.chat > table.content');
var tr = $('<tr>').attr('data-user',user).attr('data-nonce',nonce);
var usertext;
if(omituser || table.find('tr').last().attr('data-user')==user) {
usertext="";
}
else {
usertext=user;
}
var userA = $('<a>');
userA.attr('href','/u/'+user).attr('target','_blank').html(usertext);
var usertd = $('<td>').append(userA).addClass('user');
var t1 = table[0];
//table.find('>tbody').append(tr.append(usertd).append(msgtd));
tr.append(usertd).append(msgtd);
var othertr = table.find('> tbody > tr');
var aftertr=null
for(var c=0;c<othertr.length;++c) {
var othernonce = parseInt(othertr[c].getAttribute('data-nonce'));
if(othernonce==nonce) {
//This should never be possible. It should have been stopped at window.posts[nonce];
//In either case return because we aren't going to post it now.
return;
}
if(othernonce>nonce) {
aftertr=othertr[c];
break;
}
}
if(aftertr) {
$(aftertr).before(tr);
}
else {
console.log('aftertr not found')
table.children().first().append(tr);
}
restorebottom(bottom);
}
function avatarurlfor(user,cb) {
window.avatarmap = window.avatarmap || {};
if(avatarmap[user]) {
if(avatarmap[user]=='none') {
return cb('');
}
return cb(avatarmap[user]);
}
$.post('/pkey/get/'+user+'/wchat/avatar',src=>{
avatarmap[user]=src||'none';
cb(src);
});
}
function avatarfor(user) {
var retr=$('<img>').addClass('avatar');
avatarurlfor(user,src=>{
retr.attr('src',src);
if(src) {
retr.css('border','1px solid gray');
retr.css('visibility','visible');
}
});
return retr;
}
function add_user_to_ui(user) {
getjquery(()=>{
if($('#user_'+user).length===0) {
var a = $('<a>');
a.attr('href','/u/'+user).attr('target','_blank').text(user+(user==window.user?'(you)':''));
a.prepend(avatarfor(user));
$('#joined').append($('<li>').attr('id','user_'+user).append(a));
if(user==window.user) {
$('.compose .self').append($('<span>').text('Embed'));
}
}
});
}
function remove_user_from_ui(user) {
$('#joined #user_'+user).remove();
}
function register_embed_form_listeners () {
var dialogform = $('.dialogform');
['image','video','poll','mirror','avatar','upload'].forEach((i,idx,list)=>{
$('#'+i+'typeradio').click(()=>{
$('.dialogform').addClass('on'+i);
list.forEach((i2,idx2)=>{
if(idx==idx2) { return; }
$('.dialogform').removeClass('on'+i2);
});
});
});
$('#syncbox').click(()=>{
if($('#syncbox').prop('checked')) {
$('.dialogform').addClass('onsync');
}
else {
$('#syncbox').removeClass('onsync');
}
});
$('.dialogform .polloption')[0].addEventListener('keydown',bottomoptionkeydown);
$('.dialogform button').click(()=>{
console.log('clicked');
var embedobject = makeembedobject();
if(!embedobject) {
return;
}
$('#compose').val('::'+JSON.stringify(embedobject));
//sendclick();
$('#send').click();
$('.ui-dialog-titlebar-close').click();
});
}
function bottomoptionkeydown () {
var newoption = $('<input>').addClass('polloption')
.addClass('onpoll')
.attr('placeholder','Option');
var lastoption = $(this);
var br = $('<br>').addClass('onpoll');
lastoption[0].removeEventListener('keydown',bottomoptionkeydown);
newoption[0].addEventListener('keydown',bottomoptionkeydown);
lastoption.change(()=>{
if(lastoption.val().length===0) {
lastoption.remove();
br.remove();
}
});
lastoption.after(newoption);
lastoption.after(br);
}
function makeembedobject () {
var retr = {};
var form = $('.dialogform');
var thetype=form.find("input[name='type']:checked").val();
if(!thetype) {
alert('Please select a type');
return null;
}
retr['type']=thetype;
var title=form.find('.title').val();
if(title) {
retr.title = title;
}
if(thetype=='jpg'||thetype=='mp4') {
var url=form.find('.url').val();
if(!url) {
alert('Missing URL');
return null;
}
retr.url = url;
}
if(thetype=='mp4') {
if($('#syncbox').prop('checked')) {
retr.sync=true;
if($('#loopbox').prop('checked')) {
retr.loop=true;
}
var delay=form.find('.delay').val();
var seconds=0;
delay.split(' ').forEach(i=>{
var value = parseFloat(i);
var mult = 1;
if(isNaN(value)) {
return;
}
switch(i.slice(-1)) {
case 'w':
mult*=7;
case 'd':
mult*=24;
case 'h':
mult*=60;
case 'm':
mult*=60
}
seconds+=value*mult;
});
retr.start = Date.now()+seconds*1000;
}
}
if(thetype=='poll') {
retr.id = Math.floor(Math.random()*10000);
retr.options = form.find('.polloption').toArray().map(e=>{
return e.value
}).filter(e=>{
return e.length;
});
}
if(thetype=='avatar') {
$.post('/pkey/set/wchat/avatar',form.find('.url').val());
return;
}
form.find('input').empty();
['image,video,poll,sync'].forEach(kind=>{
form.removeClass('on'+kind);
});
retr.target='side';
return retr;
}
function start (servername) {
startChat(servername);
}
function display_youtube_in_chat(videoId,start,loop) {
var t1 = $('.chat > table.content')[0];
var bottom = testbottom();
var video=$('<div>').attr('id','yt'+Date.now());
$('body').addClass('flex').addClass('flowh').addClass('openside');
var controls = $('<div>').addClass('control')
.attr('position','absolute')
.css('width','100%')
.css('height','0')
.css('text-align','right')
.css('z-index',1);
var close = $('<i>').addClass('far').addClass('fa-window-close');
close.attr('data-rebroadcast','$(".fa-window-close").click()');
var grow = $('<i>').addClass('far').addClass('fa-plus-square');
var shrink = $('<i>').addClass('far').addClass('fa-minus-square');
$('body > .side').prepend(video);
$('body > .side').prepend(controls.append(shrink).append(grow).append(close));
restorebottom(bottom);
grow.click(()=>{
var bottom = testbottom();
$('.container').css('max-width',parseInt($('.container').css('max-width'))-10+'%');
var content=$('table.content')[0];
$('#sidebar').css('display',content.scrollWidth>content.offsetWidth?'none':'flex');
restorebottom(bottom);
});
shrink.click(()=>{
$('.container').css('max-width',parseInt($('.container').css('max-width'))+10+'%');
$('#sidebar').css('display',content.scrollWidth>content.offsetWidth?'none':'flex');
});
controls.find('i').mouseover(()=>{
$('.side').addClass('hasinteracted');
});
var initialnow = Date.now();
console.log('videoId',videoId);
getytplayer(()=>{
console.log('We have yt api');
new YT.Player(video[0],{
width:1280,
height:720,
videoId:videoId,
playerVars:{autoplay:1},
events:{
onError:console.log,
onStateChange:event=>{
console.log('state change',event);
if(loop&&event.data===0) {
event.target.playVideo();
}
},
onReady:event=>{
var player=event.target;
window.myplayer=player;
$('body>.side>iframe').css('width','auto').css('display','block');
restorebottom(bottom);
close.click(()=>{
controls.remove();
$(player.a).remove();
if($('body > .side').children().length===0) {
$('body').removeClass('openside');
$('body .container').css('max-width','');
$('body > .side').removeClass('hasinteracted');
}
var content=$('table.content')[0];
$('#sidebar').css('display',content.scrollWidth>content.offsetWidth?'none':'flex');
});
console.log('We have player',player);
var resynccount = 0;
function resync () {
++resynccount;
if(start>Date.now()) {
player.pauseVideo();
setTimeout(resync,start-Date.now());
setTimeout(()=>{
player.playVideo();
initialnow = start;
},start-Date.now());
return;
}
var currentTime = ((Date.now()-start)%(loop?player.getDuration()*1000:Infinity))/1000;
console.log('seeking player',currentTime);
player.seekTo(currentTime);
console.log('offset set',currentTime,start);
setTimeout(resync,(Date.now()-initialnow)*resynccount);
}
if(start) {
resync();
}
}
}
});
});
}
function display_mp4_in_chat(mp4url,start,loop) {
var possibleYoutubeId = isyoutubedomaingetvidid(mp4url);
if(possibleYoutubeId) {
return display_youtube_in_chat(possibleYoutubeId,start,loop);
}
var t1 = $('.chat > table.content')[0];
var bottom = testbottom();
var video = $('<video>').attr('src',mp4url).attr('controls',true).attr('autoplay',true).attr('loop',loop);
$('body').addClass('flex').addClass('flowh').addClass('openside');
var controls = $('<div>').addClass('control')
.attr('position','absolute')
.css('width','100%')
.css('height','0')
.css('text-align','right')
.css('z-index',1);
var close = $('<i>').addClass('far').addClass('fa-window-close');
close.attr('data-rebroadcast','$(".fa-window-close").click()');
var grow = $('<i>').addClass('far').addClass('fa-plus-square');
var shrink = $('<i>').addClass('far').addClass('fa-minus-square');
var t1 = $('.chat > table.content')[0];
$('body > .side').prepend(video);
$('body > .side').prepend(controls.append(shrink).append(grow).append(close));
restorebottom(bottom);
close.click(()=>{
controls.remove();
video.remove();
if($('body > .side').children().length===0) {
$('body').removeClass('openside');
$('body .container').css('max-width','');
$('body > .side').removeClass('hasinteracted');
}
var content=$('table.content')[0];
$('#sidebar').css('display',content.scrollWidth>content.offsetWidth?'none':'flex');
});
grow.click(()=>{
var bottom = testbottom();
$('.container').css('max-width',parseInt($('.container').css('max-width'))-10+'%');
var content=$('table.content')[0];
$('#sidebar').css('display',content.scrollWidth>content.offsetWidth?'none':'flex');
restorebottom(bottom);
});
shrink.click(()=>{
$('.container').css('max-width',parseInt($('.container').css('max-width'))+10+'%');
$('#sidebar').css('display',content.scrollWidth>content.offsetWidth?'none':'flex');
});
controls.find('i').mouseover(()=>{
$('.side').addClass('hasinteracted');
});
var initialnow = Date.now();
function resync () {
if(start>Date.now()) {
video[0].pause();
video[0].controls=false;
setTimeout(resync,start-Date.now());
setTimeout(()=>{
video[0].play();
video[0].controls=true;
initialnow = start;
},start-Date.now());
return;
}
var currentTime = ((Date.now()-start)%(loop?video[0].duration*1000:Infinity))/1000;
video[0].currentTime = currentTime;
console.log('offset set',currentTime,start);
setTimeout(resync,(Date.now()-initialnow)*3);
}
if(start) {
video[0].addEventListener('loadedmetadata',resync,false);
}
}
function isyoutubedomaingetvidid(str) {
var url=new URL(str);
var against = new Set(['youtube.com','hooktube.com','invidio.us','youtu.be','youtube-nocookie.com','m.youtube.com','www.youtube.com','tube.poal.co']);
var shortend = new Set(['youtu.be']); //https://youtu.be/PHgc8Q6qTjc
if(shortend.has(url.host)) {
return url.pathname.replace('/','');
}
else if(against.has(url.host)) {
var match=url.search.match(/[?&]v=\w+/)
if(!match) {
return; //We don't have v={videoId}
}
return match[0].replace(/[?&]v=(\w+)/,'$1');
}
}
startChat();
wchat