/*
Embed library for videos.bentasker.co.uk

Copyright (c) 2019 B Tasker

Loads in player (basically videoJS and plugins) Javascript and writes a videoobject into the page.

v0.18
*/


window.BensPlayerInstances = (window.BensPlayerInstances === undefined) ? [] : window.BensPlayerInstances;
window.BensPD='https://videos.bentasker.co.uk/';
window.BensPlayerDomain='https://videos.bentasker.co.uk/';

/* Introduced in websites/videos.bentasker.co.uk#1  */
window.BensPDTor='http://dnkpodkuqowld7pnmpt5droubfzgv7tziz2fbiuufpvocbdonobq4xyd.onion/';
window.BensPlayerDomainTor='http://dnkpodkuqowld7pnmpt5droubfzgv7tziz2fbiuufpvocbdonobq4xyd.onion/';



function cE(t){
    return document.createElement(t);
}


// From https://stackoverflow.com/a/19757717
document.write=function(s){
    var scripts = document.getElementsByTagName('script');
    var lastScript = scripts[scripts.length-1];
    lastScript.insertAdjacentHTML("beforebegin", s);
}



function guessMimeFromUrl(vidurl){
    /* VID-14: If a mimetype wasn't provided, try and figure it out from the url
    * 
    * The main thing being to avoid declaring HLS if it isn't - in some browsers the result would be trying 
    * to parse an MP4 as if it were a manifest....
    * 
    * 
    */
    
    // Get and check the file extension
    var segs = vidurl.split(".")
    var ext = segs[segs.length -1]
    
    // Check there actually was an extension
    if (!ext){
        return 'video/mp4'; // hail mary pass 
    }
    
    switch(ext.toLowerCase()) {
        case 'm3u8':
            return "application/x-mpegURL";
            break;
            
            
        case 'avi':
            return 'video/avi';
            break;
            
        case 'mp3':
            return 'audio/mpeg';
            break;
        
        case 'mkv':
            return 'video/x-matroska';
            break;
            
        case 'webm':
            return 'video/webm';
            break;
            
        case 'mov':
            return 'video/quicktime';
            break;
            
        case 'mpg':
            return 'video/mpeg';
            break;
            
        case 'mpeg':
            return 'video/mpeg';
            break;
        
        case 'mp4':
            return 'video/mp4';
            break;
            
        case 'mp4a':
            return 'audio/mp4';
            break;
            
        case 'wav':
            return 'audio/wav';
            break;
            
        case 'ts':
            return 'video/mp2t';
            break;
            
        default:
            console.log("Calculating mimetype failed for " + vidurl + " ext: " + ext.toLowerCase());
            return 'video/mp4';
    }
    

}


function embedBensPlayer(vidurl,vidtype){
    
    // What's the TLD we're embedded into?
    if (window.location.hostname.split('.').pop().toLowerCase() == "onion"){
        // We're embedding into a Tor hidden service
        // save some exit bandwidth and use the onion domain instead
        window.BensPD = window.BensPDTor;
        window.BensPlayerDomain = window.BensPlayerDomainTor;
    }
    
    //if (vidtype == undefined){
    vidtype=guessMimeFromUrl(vidurl);
    //}
    
    var vidid = 0;
  
    while (true){
        if (window.BensPlayerInstances.includes(vidid)){
            vidid++;
        }else{
            window.BensPlayerInstances.push(vidid);
            break;
        }
    }
    
    // We do this for 2 reasons
    //
    // * 1 saves pasting the full URL into the function call
    // * 2 stops my player being so easily embedded to play videos that aren't mine
    //
    if (vidurl.substring(0,31) != window.BensPD){
        vidurl = window.BensPD + vidurl;
    }
    
    document.write("<div id='BensplayerWrapper" + vidid + "' class='BensVideoCont'></div>");
    var c = document.getElementById('BensplayerWrapper'+vidid);
       
    // First thing we need to do is get the player files loaded
    
    var e = cE('link');
    e.setAttribute('rel','stylesheet');
    e.setAttribute('href',window.BensPD + 'resources/js/videojs/video-js.min.css');
    c.appendChild(e);
    
    
    playerSettings = { 'vidtype': vidtype, 'vidurl': vidurl, 'vidid': vidid}
    
    
    // We then trigger the next steps in stages to make sure everything that needs to be loaded, is
    getBensToken(vidid,playerSettings);
}


function getBensToken(vidid,playerSettings){
    // VID-10 Tokenisation support 
    
    var ref = encodeURIComponent(window.location.href);
    var vurl = encodeURIComponent(playerSettings['vidurl'].replace(window.BensPD,''));
    var url = window.BensPD + "token_endpoint?ref="+ref+"&vidpath="+vurl;
    
    
    var xmlhttp;
    if (window.XMLHttpRequest){
        // code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp=new XMLHttpRequest();
    }else{
        // code for IE6, IE5 (why am I still supporting these? Die... Die.... Die....
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }

    xmlhttp.onreadystatechange=function(){
        if (xmlhttp.readyState==4){
            if(xmlhttp.status==200){
                    // OK, we've got a token back, so now we need to deal with it
                    var j = JSON.parse(xmlhttp.responseText);
                    if (!j['status']){
                        console.log("Failed to retrieve token for video");
                        embedFailedNotif(vidid)
                        return false;
                    }
                
                    // Append the token onto the URL
                    playerSettings['vidurl'] += '?t=' + encodeURIComponent(j['token']) + '&e=' + encodeURIComponent(j['exp']);
                
                    // Trigger the next stage of loading
                    loadVJS(vidid,playerSettings);
                    
                }else{
                    console.log("Failed to retrieve token for video");
                    // Should probably write an error code into the player rather than just dying
                    embedFailedNotif(vidid)
                    return false;
                }

        } 
        
    }

    xmlhttp.open("GET",url,true);
    xmlhttp.send();    
    
}


function writePlayer(vidid,playerSettings){

    
    // TODO: Be good to make this one conditional. Detect whether we've either been passed a list, or a M3U file and not load playlist
    // if not needed
    /*
    var e = cE('script');
    e.setAttribute('type','text/javascript');
    e.setAttribute('src',window.BensPD + 'resources/js/playlist/videojs-playlist.js');
    c.appendChild(e);    
    */
    
    // Get the width of our parent container to set video width later
    var c = document.getElementById('BensplayerWrapper'+vidid);
    var width = c.parentNode.clientWidth
    

    
    
    // Now start setting up the player itself
    var d = cE('div');
    d.id = "videowrapper"+vidid;
    
    v = cE('video');
    v.setAttribute('id','BensVideo' + vidid);
    v.setAttribute('class','video-js');
    v.setAttribute('controls','');
    v.setAttribute('preload','auto');
    v.setAttribute('width',width);
    
    // now create the source
    var src = cE('source');
    src.setAttribute('src',playerSettings['vidurl']);
    src.setAttribute('type',playerSettings['vidtype']);
    v.appendChild(src);
   
    // Stitch everything together
    d.appendChild(v);    
    c.appendChild(d);
    
    var s = cE('script');
    s.setAttribute('type','text/javascript');
    ss = "window.player" + vidid +" = videojs('BensVideo" + vidid + "',\"{'fluid':true}\", function onPlayerReady(){console.log('Player Ready'); submit(BensVidgatherInfo(window.player" + vidid + ".currentSrc().split('?')[0],'ready'))});";
    ss += "window.player" + vidid + ".on('playing', function() {submit(BensVidgatherInfo(window.player" + vidid + ".currentSrc().split('?')[0],'playing'))});";
    s.innerHTML = ss;

    
    c.appendChild(s);
    
    // VID-9 check for metadata and write schema.org markup if it exists
    dirname = playerSettings['vidurl'].match(/.*\//);
    fetchPage(dirname + 'details.json',BenswriteSchemaOrg,function(e){},playerSettings);
}


function loadVJS(vidid,playerSettings){
   var c = document.getElementById('BensplayerWrapper'+vidid);
    var e = cE('script');
    e.setAttribute('type','text/javascript');
    e.setAttribute('src',window.BensPD + 'resources/js/videojs/video.min.js');
    
    if (window.playerSettings['vidtype'] == "application/x-mpegURL"){
        e.onload = function () { loadVJSHLS(vidid,playerSettings); };    
    }else{
        e.onload = function () { writePlayer(vidid,playerSettings); };
    }
    c.appendChild(e);

}


function loadVJSHLS(vidid,playerSettings){
    var c = document.getElementById('BensplayerWrapper'+vidid);
    var e = cE('script');
    e.setAttribute('type','text/javascript');
    e.setAttribute('src',window.BensPD + 'resources/js/videojs-contrib-hls.min.js');
    e.onload = function () { writePlayer(vidid,playerSettings); };
    c.appendChild(e);
}


function fetchPage(url,callback,errcallback,storage){
    var xmlhttp;
    if (window.XMLHttpRequest){
        // code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp=new XMLHttpRequest();
    }else{
        // code for IE6, IE5 (why am I still supporting these? Die... Die.... Die....
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }

    xmlhttp.onreadystatechange=function(){
        if (xmlhttp.readyState==4){
    if(xmlhttp.status==200){
            callback(xmlhttp.responseText,storage,url)
        }else{
            errcallback(xmlhttp.responseText)
        }

} }

    xmlhttp.open("GET",url,true);
    xmlhttp.send();
}


function embedFailedNotif(vidid){
    s = cE('img');
    s.setAttribute('src',window.BensPD + 'resources/embed/failed.png');
    
    var c = document.getElementById('BensplayerWrapper'+vidid);
    var width = c.parentNode.clientWidth
    
    s.setAttribute('style','max-width: ' + width + 'px');
    c.appendChild(s);
}


function BenswriteSchemaOrg(text,storage,url){
    
    
    if (!text || !storage || !url){
        // Bail out and do nothing, it's not that important
        return;
    }
    
    var j = JSON.parse(text);
    
    var d = document.getElementById('BensplayerWrapper'+storage['vidid']);
    d.setAttribute('itemscope','');
    d.setAttribute('itemtype','https://schema.org/VideoObject');
        
    m = cE('meta');
    var url = window.BensPlayerDomain + 'Player.html?url=' + encodeURIComponent(url.replace(window.BensPD,''));
    m.setAttribute('itemprop','url');
    m.setAttribute('content', url);
    d.appendChild(m);
    
    if (j.hasOwnProperty('title')){
        var m = cE('meta');
        m.setAttribute('itemprop','name');
        m.setAttribute('content',j['title']);
        d.appendChild(m);
    }
        
    if (j.hasOwnProperty('description')){
        var m = cE('meta');
        m.setAttribute('itemprop','description');
        m.setAttribute('content',j['description'].replace(/'/g, ''));
        d.appendChild(m);
    }
    
    if (j.hasOwnProperty('keywords')){
        var m = cE('meta');
        m.setAttribute('itemprop','keywords');
        m.setAttribute('content',j['keywords'].replace(/'/g, ''));
        d.appendChild(m);
    }


    var m = cE('meta');
    m.setAttribute('itemprop','playerType');
    m.setAttribute('content','html5');
    d.appendChild(m);       
    
}


/* Analytics stuff - see https://www.bentasker.co.uk/posts/designing-privacy-friendly-analytics.html
 * 
 * 
 * Usage for videos is: submit(gatherInfo(vid))
 * 
 */
function BensVidgatherInfo(vid, state){
    var tz = new Date().getTimezoneOffset();
    
    return {
        domain : "videos.bentasker.co.uk",
        page: "/" + vid.replace(window.BensPD,""),
        referrer: document.location.href,
        referrer_domain: document.location.hostname,
        platform: navigator.platform,
        timezone: tz,
        sess_id: getSessionId(),
        state: state,
        responseTime: Date.now() - window.performance.timing.navigationStart
    }
}

function getSessionId(){
    // Only if enabled
    if (!window.analytics_gen_psuedoid){
        return "none";
    }

    // Every so often, we want to rotate the session ID to mitigate its impact
    var n = randomInteger(1, 10);
    
    var k = window.location.hostname + "_sess-id";
    var i = sessionStorage.getItem(k);
    
    // If there's no item in storage, or if we've decided to rotate
    if (!i || n == 5){
        i = createUUID();
        sessionStorage.setItem(k,i);
    }
    
    return i;
}

function createUUID() {
    // Create a short UUID
    return 'xxxx-xxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

function submit(info){
    var xml = new XMLHttpRequest();
    xml.onreadystatechange = function () {}; // Do nothing
    
    // If we're being viewed on a .onion, use the .onion instead
    // (keeps everything within the tor network)
    if (window.location.hostname.toLowerCase().endsWith(".onion")){
        xml.open("POST", window.analytics_endpoint_onion + "/write");
    }else{
        xml.open("POST", window.analytics_endpoint);
    }
    xml.setRequestHeader("Content-Type", "application/json");
    xml.send(JSON.stringify(info));
}

// All this does is place a request to Tor the endpoint
// its only purpose is to ensure we have a circuit open ready for use during write
// so that writes can complete faster
function ping(){
    if (window.location.hostname.toLowerCase().endsWith(".onion")){
        var xml = new XMLHttpRequest();
        xml.onreadystatechange = function () {}; // Do nothing        
        xml.open("GET", window.analytics_endpoint_onion + "/ping");
        xml.send();
    }
}

function randomInteger(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

/* These can be overridden in the base document */
window.analytics_active = true;
window.analytics_gen_psuedoid = true;
window.analytics_endpoint = 'https://pfanalytics.bentasker.co.uk/write';
window.analytics_endpoint_onion = 'http://wftlf4ke7xwqkjrrju4aok57pkr7kfks5t5uqmipxlyjucwjycdql7id.onion';

