
/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2020-2025
 *					All rights reserved
 *
 *  This file is part of GPAC / vout default ui
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*very simple controller for vout and vout+aout allowing stats display and playback control */

import * as evg from 'evg'
import { Sys as sys } from 'gpaccore'

_gpac_log_name="";

print("Type 'h' in window for command list");

let aout = null;
let vout = null;
let speed = 1;
let win_id = 0;
let drop=0;
let fullscreen = false;
let paused=0;
let duration=-1;
let codec_slow=false;

let audio_only = false;
let ol_buffer = null;
let ol_canvas = null;
let ol_width = 200;
let ol_height = 20;

let text = new evg.Text();
text.font = 'SERIF';
text.fontsize = 20;
text.baseline = GF_TEXT_BASELINE_HANGING;
text.align = GF_TEXT_ALIGN_LEFT;
text.lineSpacing = 20;

const OL_NONE = 0;
const OL_HELP = 1;
const OL_STATS = 2;
const OL_PLAY = 3;
const OL_AUTH = 4;
let overlay_type = OL_NONE;
let overlay_type_bck = OL_NONE;

let brush = new evg.SolidBrush();
brush.set_color('white');

let disp_size = null;
let task_reschedule = 500;
let rot = 0;
let flip = 0;
let use_libcaca=false;

let do_coverage=0;
if (sys.get_opt('temp', 'vout_cov') == 'yes') do_coverage = 1;


function check_vout()
{
	let drv = vout.get_arg('drv');
	if (!drv) drv = sys.get_opt('core', 'video-output');
	if (drv == 'caca') {
		session.set_auth_fun( null );
		use_libcaca=true;
	}

	speed = vout.get_arg('speed');
	drop = vout.get_arg('drop');
	fullscreen = vout.get_arg('fullscreen');
	win_id = vout.get_arg('wid');
}

if (parent_filter && parent_filter.type=='vout') {
	vout = parent_filter;
	check_vout();
}

function check_filters()
{
	if (aout && vout) return;
	let i;
	for (i=0; i< session.nb_filters; i++) {
		let f = session.get_filter(i);
		if (!aout && (f.type==="aout")) aout = f;
		else if (!vout && (f.type==="vout")) {
			vout = f;
			check_vout();
		}
	}
}
check_filters();


function setup_overlay()
{
	let target_width=ol_width;
	let target_height=ol_height;

	if (vout.nb_ipid) audio_only=false;
	disp_size = vout.get_arg('owsize');
	if (disp_size==null) return false;
	if (audio_only && !disp_size.x) {
		disp_size = {"x": 320, "y": 80};
	}

	task_reschedule = 500;
	switch (overlay_type) {
	case OL_HELP:
		target_width = Math.floor(disp_size.x/2);
		target_height = Math.floor( (shortcuts.length+2) * 30);
		break;
	case OL_PLAY:
		target_width = Math.floor(disp_size.x/2);
		target_height = Math.floor( 60);
		task_reschedule = 500;
		break;
	case OL_STATS:
		target_width = Math.floor(disp_size.x/2);
		target_height = Math.floor(disp_size.y/2);
		task_reschedule = 250;
		break;
	case OL_AUTH:
		target_width = Math.floor(4*disp_size.x/5);
		target_height = Math.floor(4*disp_size.y/5);
		task_reschedule = 100;
		break;
	default:
		if (!ol_width)
				ol_width = Math.floor(disp_size.x/3);
		if (!ol_height)
			ol_height = Math.floor(disp_size.y/4);
		break;
	}
	init_wnd=true;

	if (audio_only || !vout.nb_ipid) {
		target_width = ol_width = disp_size.x;
		target_height = ol_height = disp_size.y;
	}
	while (target_width%2) target_width--;
	while (target_height%2) target_height--;

	if ((ol_width!=target_width) || (ol_height!=target_height)) {
		ol_buffer = null;
		ol_width = target_width;
		ol_height = target_height;
	}

	if (use_libcaca) return true;

	if (ol_buffer != null) return true;
	ol_buffer = new ArrayBuffer(ol_width*ol_height*4);
	ol_canvas = new evg.Canvas(ol_width, ol_height, 'rgba', ol_buffer);
	ol_canvas.clearf(0.2, 0.2, 0.2, 0.6);
	return true;
}

session.post_task( () => {
	if (session.last_task) {
		return false;
	}
	check_filters();
	if (!vout) return 1000;
	if (vout.nb_ipid) {
		audio_only=false;
		return false;
	}
	if (aout) {
		if (!aout.nb_ipid || !session.connected) {
			return 100;
		}

		if (!audio_only) {
			overlay_type=OL_PLAY;
			audio_only=true;
			toggle_overlay();
		}
	}
	return false;
}, "check_vout_pids");

let last_x=0, last_y=0;

session.set_event_fun( (evt)=> {
	if (evt.type != GF_FEVT_USER) return 0;

	if ((evt.ui_type == GF_EVENT_CODEC_SLOW) || (evt.ui_type == GF_EVENT_CODEC_OK)) {
		codec_slow = (evt.ui_type == GF_EVENT_CODEC_SLOW) ? true : false;

		if (overlay_type==OL_AUTH) return 0;
		ol_visible = false;
		overlay_type=OL_PLAY;
		toggle_overlay();
		update_play();
		return false;
	}

	try {
		if(evt.window != win_id) return true;
	} catch (e) {
		return true;
	}

	switch (evt.ui_type) {
	case GF_EVENT_MOUSEDOWN:
		if (use_libcaca) return GF_FALSE;
		last_x = evt.mouse_x;
		last_y = evt.mouse_y;
		break;

	case GF_EVENT_MOUSEUP:
		if (use_libcaca) return GF_FALSE;
		if ((last_x != evt.mouse_x) || (last_y != evt.mouse_y)) return 0;
		if (overlay_type==OL_AUTH) return 0;

		if (overlay_type==OL_PLAY) {
			if (prog_interact(evt)) return 0;
		}
		if (ol_visible) {
			toggle_overlay();
			return true;
		} else if (!audio_only) {
			overlay_type=OL_PLAY;
			toggle_overlay();
			return true;
		}
		break;

	case GF_EVENT_KEYDOWN:
		return process_keyboard(evt);
	case GF_EVENT_TEXTINPUT:
		if (overlay_type==OL_AUTH) {
			if (auth_state==0) {
				if ((evt.char == '\n') || (evt.char == '\r'))
					auth_state = 1;
				else if (evt.char == '\b')
					auth_name = auth_name.slice(0, auth_name.length - 1);
				else
					auth_name += ''+evt.char;
			} else if (auth_state==1) {
				if ((evt.char == '\n') || (evt.char == '\r')) {
					auth_state = 2;
					auth_requests[0].done(auth_name, auth_pass);
					auth_name = '';
					auth_pass = '';
					auth_requests.shift();
					if (auth_requests.length) {
						auth_state = 0;
					} else {
						auth_state = 0;
						toggle_overlay();

						text.font = 'SERIF';
						text.fontsize = 20;
						text.baseline = GF_TEXT_BASELINE_HANGING;
						text.align = GF_TEXT_ALIGN_LEFT;
						text.lineSpacing = 20;
						overlay_type = overlay_type_bck;
						if (overlay_type) toggle_overlay();
					}
				} else if (evt.char == '\b')
					auth_pass = auth_pass.slice(0, auth_pass.length - 1);
				else
					auth_pass += ''+evt.char;
			}
			auth_modified = true;
		}
		return false;

	case GF_EVENT_SIZE:
		if (do_coverage) {
			overlay_type=OL_PLAY;
			ol_visible=true;
			if (do_coverage==1) {
				do_coverage = 0;
				coverage_tests();
			}
		}
		if (ol_visible) {
			let old_type = overlay_type;
			vout.lock(true);
			toggle_overlay();
			overlay_type = old_type;
			toggle_overlay();
			vout.update('oldata', ol_buffer);
			vout.lock(false);
		}
		break;
	default:
	}
	return false;
}
);

function coverage_tests()
{
	print('coverage tests');
	if (aout) aout.update('speed', '1');
	let evt = new FilterEvent(GF_FEVT_QUALITY_SWITCH);
	evt.up = true;
	let src = vout.ipid_source(0);
	session.fire_event(evt, src);
	evt.up = false;
	session.fire_event(evt, src);
}

let interactive_scene=0;
function check_duration()
{
	if (duration!=-1) return (duration>=0) ? true : false;
	let dur=null;
	if (audio_only) {
		dur = aout.ipid_props(0, 'Duration');
		interactive_scene = aout.ipid_props(0, 'InteractiveScene');
	} else if (vout.nb_ipid) {
		dur = vout.ipid_props(0, 'Duration');
		interactive_scene = vout.ipid_props(0, 'InteractiveScene');
	}
	if (interactive_scene==1) {
		duration = 0;
		return;
	}
	if (dur==null) return;
	duration = dur.n;
	duration /= dur.d;
	return true;
}

function do_seek(val, mods, absolute)
{
	if (! check_duration())
		return;

	let seek_to=-1;
	if (!absolute) {
		if (mods & GF_KEY_MOD_CTRL) val *= 30; 
		else if (mods & GF_KEY_MOD_SHIFT) val *= 10;
		seek_to = duration * val / 100;
	}
	//in seconds
	else {
		//30smin
		if (mods & GF_KEY_MOD_CTRL) val *= 1800;
		//10min
		else if (mods & GF_KEY_MOD_SHIFT) val *= 600;
		//30s
		else val *= 30;
		seek_to = val;
	}


	let last_ts_f = vout.last_ts_drop;
	if (last_ts_f==null) return;


	let last_ts = last_ts_f.n;
	last_ts /= last_ts_f.d;
	if (audio_only)
		last_ts -= aout.get_arg('media_offset');
	else
		last_ts -= vout.get_arg('media_offset');
	last_ts += seek_to;	
	if (last_ts<0) last_ts = 0;
	else if (last_ts>duration) last_ts = duration-10;
	
	vout.update('start', ''+last_ts);
	if (aout) aout.update('start', '' + last_ts);
}

let init_wnd=false;
function update_help()
{
	let args = []; //['Shortcuts for vout', '  '];

	shortcuts.forEach( (key, index) => {
		if (use_libcaca && (index>10))
			return;
		args.push( key.code + ': ' + key.desc);
	});
	if (audio_only)
		text.fontsize = 20;
	else
		text.fontsize = Math.floor(0.6*ol_height/args.length);
	text.set_text(args);

	if (use_libcaca) {
		let str = '';
		args.forEach(a => {
			str+=''+a+'\n';
		});
		vout.update('oltxt', str);
		return;
	}

	//lock vout since we will modify data of the canvas
	vout.lock(true);

	if (init_wnd && audio_only) {
		let txtdim = text.measure();

		ol_width = Math.floor(txtdim.width*1.2);
		while (ol_width%2) ol_width--;
		ol_height = Math.floor(txtdim.height*1.2);
		while (ol_height%2) ol_height--;
		ol_buffer = new ArrayBuffer(ol_width*ol_height*4);
		ol_canvas = new evg.Canvas(ol_width, ol_height, 'rgba', ol_buffer);
		vout.update('olsize', ''+ol_width+'x'+ol_height);
		vout.update('olwnd', '0x0x'+ol_width+'x'+ol_height);
		init_wnd=false;
	}

	ol_canvas.clearf(0.2, 0.2, 0.2, 0.6);
	let mx = new evg.Matrix2D();
	mx.translate(-ol_width/2, ol_height/3);
	ol_canvas.matrix = mx;
	ol_canvas.path = text;
	ol_canvas.fill(brush);


	overlay_type=OL_NONE;
	vout.update('oldata', ol_buffer);
	vout.lock(false);
}

let progress_bar_path=null;
let prog_ox=0;
let prog_length=0;
let reverse=false;

function prog_interact(evt)
{
	let ret = audio_only ? true : false;
	let x = evt.mouse_x - disp_size.x/2;
	let y = disp_size.y - evt.mouse_y - ol_height/2;

	if (x<-ol_width/2) return ret;
	if (x>ol_width/2) return ret;
	if (y>ol_height/2) return ret;

	if (!check_duration())
		return true;

	if (!progress_bar_path.point_over(x, y)) {
		x -= prog_ox;
		x /= prog_length;
		x += 0.5;
		if (x>1) reverse = !reverse;
		return true;
	}

	x -= prog_ox;
	x /= prog_length;
	x += 0.5;
	x *= duration;
	if (audio_only) {
		aout.update('start', '' + x);
	} else {
		vout.update('start', '' + x);
		if (aout) aout.update('start', '' + x);
	}
	return true;
}

function update_play()
{
	if (init_wnd) {
		let pos = '0x' + Math.floor(ol_height/2 - disp_size.y/2) + 'x'+ol_width+'x'+ol_height;
		vout.update('olwnd', pos);
		init_wnd=false;
		progress_bar_path=null;
	}

	if (!use_libcaca)
		ol_canvas.clearf(0.2, 0.2, 0.2, 0.6);

	let length = ol_width - 20;
	let last_ts_f = vout.last_ts_drop;
	if (!last_ts_f && aout)
		last_ts_f = aout.last_ts_drop;

	if (!last_ts_f) {
		let str = 'No input connected';
		if (use_libcaca) {
			vout.update('oltxt', str);
			return;
		}
		text.fontsize = 20;
		text.set_text(str);
		let mx = new evg.Matrix2D();
		mx.translate(- str.length * text.fontsize/3, -10);
		ol_canvas.matrix = mx;
		ol_canvas.path = text;
		ol_canvas.fill(brush);
		vout.update('oldata', ol_buffer);
		return;
	}

	let has_dur = check_duration();
	if (!has_dur) reverse = false;

	let h, m, s;
	let last_ts = last_ts_f.n;
	last_ts /= last_ts_f.d;
	if (audio_only)
		last_ts -= aout.get_arg('media_offset');
	else
		last_ts -= vout.get_arg('media_offset');

	if (reverse) last_ts = duration - last_ts;

	h = Math.floor(last_ts/3600);
	m = Math.floor(last_ts/60 - h*60);
	s = Math.floor(last_ts - h*3600) - m*60;
	let str = reverse ? '-' : ' ';

	if (duration>=3600) {
		if (h<10) str+='0';
		str+=h+':';
	}
	if (m<10) str+='0';
	str+=m+':';
	if (s<10) str+='0';
	str+=s;

	if (use_libcaca) {
		if (duration) {
			let progress = Math.ceil(100 * last_ts / duration);
			let i=0;
			str += ' ';
			while (i*10<progress) {
				str += '=';
				i++;
			}
			while (i<10) {
				str += '-';
				i++;
			}
			str += ' ' + progress + ' %';
		}
		vout.update('oltxt', str);
		return;
	}

	let fs = Math.floor(0.5*ol_height);
	if (fs > 60) fs=60;
	text.fontsize = fs; 
	text.set_text(str);
	let mx = new evg.Matrix2D();
	if (has_dur)
		mx.translate(ol_width/2 - str.length * text.fontsize/2, -10);
	else
		mx.translate(- str.length * text.fontsize/3, -10);
	ol_canvas.matrix = mx;
	ol_canvas.path = text;
	ol_canvas.fill(brush);
	length -= str.length * text.fontsize/2;
	

	if (has_dur) {
		if (!progress_bar_path) {
			prog_length = length;
			prog_ox = 10 + (length - ol_width) / 2;
			progress_bar_path = new evg.Path();
			progress_bar_path.rectangle(prog_ox, 0, prog_length, 20, true);
		}
		ol_canvas.matrix = null;
		ol_canvas.path = progress_bar_path;
		brush.set_color('grey');
		ol_canvas.fill(brush);

		let	progress_path = new evg.Path();
		let progress = last_ts / duration;
		brush.set_color('white');
		progress_path.rectangle(prog_ox - prog_length/2, 9, prog_length*progress, 18, false);
		ol_canvas.path = progress_path;
		ol_canvas.fill(brush);
	}

	if (paused || (speed != 1) || codec_slow) {
		text.fontsize = 16;
		if (codec_slow)
			text.set_text('Codec Too Slow !');
		else if (paused)
			text.set_text((paused==2) ? 'Step mode' : 'Paused');
		else
			text.set_text('Speed: ' + speed);
		let mx = new evg.Matrix2D();
		mx.translate(0, ol_height/4);
		ol_canvas.matrix = mx;
		ol_canvas.path = text;
		brush.set_color(codec_slow ? 'red' : 'cyan');
		ol_canvas.fill(brush);
		brush.set_color('white');
	}

	let src = vout ? vout.ipid_source(0) : aout.ipid_source(0);
	let hdr = null;
	if (src) {
		hdr = src.ipid_props(0, 'X-From-MABR');
		if (!hdr) hdr = src.ipid_props(0, 'x-from-mabr');
	}
	if (hdr) {
		text.fontsize = 16;
		let msg = 'MABR ' + ((hdr=='yes') ? 'on' : ((hdr=='no') ? 'off' : hdr));
		text.set_text(msg);
		let mx = new evg.Matrix2D();
		mx.translate(ol_width/2-10*msg.length, 5-ol_height/2);
		ol_canvas.matrix = mx;
		ol_canvas.path = text;
		brush.set_color((hdr=='yes') ? 'green' : 'red');
		ol_canvas.fill(brush);
		brush.set_color('white');
	}

	//we could lock vout to avoid any tearing ...
	vout.update('oldata', ol_buffer);
}

function build_graph(f, str, str_list, fdone)
{
	let i, j, num_tiles=0;
	if (!f.nb_opid) {
		str += '->' + f.name;
		str_list.push(str);
		str = '';
		fdone.push(f);
		return;
	}
	if (fdone.indexOf(f)>=0) {
		str += '->' + f.name;
		str_list.push(str);
		str = '';
		return;
	}

	let len=0;
	if (!str.length) str = f.name;
	else str += '->' + f.name;
	text.set_text(str);
	len = text.measure().width;

	for (i=0; i<f.nb_opid; i++) {
		let sinks = f.opid_sinks(i);
		let sub_len=0;
		let sub_str;

		let p = f.opid_props(i, 'CodecID');
		if (p == 'hvt1') {
			if (num_tiles) continue;
			num_tiles++;
			for (j=i+1; j<f.nb_opid; j++) {
				let p = f.opid_props(j, 'CodecID');
				if (p == 'hvt1') {
					//check this tile pid sinks intersect the current pid sinks
					let t_sinks = f.opid_sinks(j);
					const found = t_sinks.some(r => sinks.includes(r))
					if (found) num_tiles++;
				}
			}
		}

		if (i) {
			str=' ';
			let k=0;
			while (1) { 
				text.set_text(str);
				let slen = text.measure().width;
				if (slen >= len) break;
				str += ' ';
			}
		}
		sub_str = str;
		if (f.nb_opid>1) {
			if (num_tiles>1) {
				sub_str += '#tilePIDs[' + num_tiles + ']';
			} else {
				sub_str += '#' + f.opid_props(i, 'name');
			}
		}

		if (!sinks.length) {
			str_list.push(sub_str + ': Not connected');
			continue;
		}

		for (j=0; j<sinks.length; j++) {
			let sf = sinks[j];

			if (!j) {
				text.set_text(sub_str);
				sub_len = text.measure().width;
			} else {
				sub_str=' ';
				let k=0;
				while (1) { 
					text.set_text(sub_str);
					let slen = text.measure().width;
					if (slen >= sub_len) break;
					sub_str += ' ';
				}
			}
			build_graph(sinks[j], sub_str, str_list, fdone);
		}
	}
	fdone.push(f);
}

let stats_translate_y=0;
let stats_graph = [];

function update_stats()
{
	let stats = [];

	let sys_info='CPU: '+sys.process_cpu_usage + ' Mem: ' + Math.floor(sys.process_memory/1000000) + ' MB';
	if (session.http_bitrate) {
		let r = session.http_bitrate;
		sys_info += ' HTTP: '; 
		if (r>1000000) sys_info += '' + Math.floor(r/100000) / 10 + ' mbps';
		else if (r>1000) sys_info += '' + Math.floor(r/100) / 10 + ' kbps';
		else sys_info += '' + r + ' bps';
	}
	stats.push(sys_info);
//	stats.push('  ');

	let i;
	for (i=0; i< session.nb_filters; i++) {
	 	let f = session.get_filter(i);
	 	//only output filters (no out, single in) 
	 	if ((f.nb_ipid!=1) || f.nb_opid) continue;

		let str = f.streamtype;
		let src = f.ipid_source(0);
		let names = src.name.split(':');

		str += ' (' + names[0];
		let decrate = src.pck_done;
		if (!decrate) decrate = src.pck_sent + src.pck_ifce_sent;
		decrate /= src.time/1000000;
		decrate = Math.floor(decrate);
		str += ') ' + decrate + ' f/s';
		let p = f.ipid_props(0, 'Width');
		if (p) {
			str += ' ' + p;
			p = f.ipid_props(0, 'Height');
			str += 'x' + p;
			p = f.ipid_props(0, 'FPS');
			if (p) {
				let fps = Math.floor(p.n/p.d);
				if (fps * p.d == p.n)
					str += '@' + fps;
				else
					str += '@' + p.n + '/'+p.d;
			}
			p = f.ipid_props(0, 'SAR');
			if (p) str += ' SAR ' + p.n + '/' + p.d;			
			p = f.ipid_props(0, 'PixelFormat');
			if (p) str += ' pf ' + p;
		} else {
			p = f.ipid_props(0, 'SampleRate');
			if (p) {
				str += ' ' + p;
				p = f.ipid_props(0, 'Channels');
				if (p) str += ' ' + p;
				p = f.ipid_props(0, 'ChannelLayout');
				if (p) str += ' ' + p;
				p = f.ipid_props(0, 'AudioFormat');
				if (p) str += ' fmt ' + p;
			} 
		}

		let buffer = f.ipid_props(0, 'buffer')/1000;
		let rebuf = 0;
		if (audio_only) {
			rebuf = aout.get_arg('rebuffer');			
		}
		else {
			rebuf = vout.get_arg('rebuffer');
		}
		if (rebuf>0) {
			let play_buf = audio_only ? aout.get_arg('buffer') : vout.get_arg('buffer');
			let pc = Math.floor(100 * buffer / play_buf);
			str += ' - rebuffering ' + pc + ' %';
		} else {
			str += ' - buffer ' + Math.floor(f.ipid_props(0, 'buffer')/1000) + ' ms';
		}
		stats.push(str);
	}

//	stats.push(' ');

	//recompute graph only when initializing the window
	if (init_wnd)
		stats_graph = [];

	if (! stats_graph.length) {
		let fdone = [];
		//browse all sources, and build graph
		for (i=0; i< session.nb_filters; i++) {
		 	let f = session.get_filter(i);
		 	//source filter: no input and outputs
		 	if (f.nb_ipid || !f.nb_opid) continue;

		 	build_graph(f, '', stats_graph, fdone);
		}
		let not_done = [];
		for (i=0; i< session.nb_filters; i++) {
		 	let f = session.get_filter(i);
		 	if (f.alias) continue;

		 	if (fdone.indexOf(f)<0)
		 		not_done.push(f);
		 }
		 if (not_done.length) {
			stats.push(' ');
		 	let str = "Filters not connected:";
		 	not_done.forEach(f => { str += ' '+f.name;});
			stats.push(str);
		 }
	 }

	 //push graph to stats string array
	 stats_graph.forEach(str => { stats.push(str);});


	if (use_libcaca) {
		let str = '';
		stats.forEach(a => {
			str+=''+a+'\n';
		});
		vout.update('oltxt', str);
		return;
	}

	text.fontsize = 20; 
	text.set_text(stats);

	if (init_wnd) {
		let txtdim = text.measure();

		ol_width = Math.floor(txtdim.width*1.2);
		while (ol_width%2) ol_width--;
		ol_height = Math.floor(txtdim.height*1.4);
		while (ol_height%2) ol_height--;
		//we resize / realloc the overlay buffer, lock vout and update it
		vout.lock(true);
		ol_buffer = new ArrayBuffer(ol_width*ol_height*4);
		ol_canvas = new evg.Canvas(ol_width, ol_height, 'rgba', ol_buffer);
		vout.update('olsize', ''+ol_width+'x'+ol_height);
		stats_translate_y = txtdim.height/2;
		vout.update('oldata', ol_buffer);
		vout.lock(false);

		let pos;
		if (audio_only)
			pos = '0x0x'+ol_width+'x'+ol_height;
		else
			pos = ''+ Math.floor(ol_width/2 - disp_size.x/2) + 'x' + Math.floor(ol_height/2 - disp_size.y/2) + 'x'+ol_width+'x'+ol_height;
		vout.update('olwnd', pos);
		init_wnd=false;
	}

	ol_canvas.clearf(0.2, 0.2, 0.2, 0.6);

	let mx = new evg.Matrix2D();
	mx.translate(-ol_width/2, stats_translate_y);
	ol_canvas.matrix = mx;
	ol_canvas.path = text;
	ol_canvas.fill(brush);
	//we could lock vout to avoid any tearing ...
	vout.update('oldata', ol_buffer);
}

let auth_name="";
let auth_pass="";
let auth_state=0;
let auth_modified = false;

let auth_requests = [];

function update_auth()
{
	if (init_wnd) {
		let pos = '0x0x'+ol_width+'x'+ol_height;
		vout.update('olwnd', pos);
		init_wnd=false;
		progress_bar_path=null;
		auth_modified = true;

		auth_name = auth_requests[0].user;
		auth_pass = auth_requests[0].pass;
	}
	if (!auth_modified) return false;
	auth_modified = false;

	ol_canvas.clearf(1, 1, 1, 1);
	let str = ['Authentication for ' + auth_requests[0].site];
	str.push(auth_requests[0].secure ? "Secured Connection" : "NOT SECURED Connection");
	if (auth_state) {
		str.push('Enter password');
		let p = '';
		let i = auth_pass.length;
		while (i) {
			p+='*';
			i--;
		}
		str.push(p);
	} else {
		str.push('Enter user name');
		str.push(auth_name);
	}
	let fs = Math.floor(0.1*ol_height);
	if (fs > 60) fs=60;
	text.fontsize = fs;
	text.lineSpacing = 1.5*fs;
	text.align = GF_TEXT_ALIGN_CENTER;
	text.baseline = GF_TEXT_BASELINE_TOP;
	text.set_text(str);

	let txtdim = text.measure();
	let mx = new evg.Matrix2D();
	mx.translate(- txtdim.width/2, -0);

	ol_canvas.matrix = mx;
	ol_canvas.path = text;
	brush.set_color('black');
	ol_canvas.fill(brush);
	brush.set_color('white');


	//we could lock vout to avoid any tearing ...
	vout.update('oldata', ol_buffer);
}

function update_overlay()
{
	if (overlay_type==OL_NONE) return;

	//help screen
	if (overlay_type==OL_HELP) {
		update_help();
		return;
	}
	//play screen
	if (overlay_type==OL_PLAY) {
		update_play();
	}
	//stats screen
	if (overlay_type==OL_STATS) {
		update_stats();
	}
	//auth screen
	if (overlay_type==OL_AUTH) {
		update_auth();
	}
}

let ol_visible=false;
let oltask_scheduled=false;

function toggle_overlay()
{
	ol_visible = !ol_visible;
	if (!ol_visible) {
		//we don't lock vout because the overlay buffer is still valid
		vout.update('oldata', null);
		vout.update('oltxt', '');
		overlay_type=OL_NONE;
		return;
	}
	//we will potentially destroy the previous overlay bffer, lock vout
	vout.lock(true);
	if (!setup_overlay()) {
		vout.lock(false);
		return;
	}

	vout.update('olwnd', '0x0x'+ol_width+'x'+ol_height);
	vout.update('olsize', ''+ol_width+'x'+ol_height);
	vout.update('oldata', ol_buffer);
	vout.lock(false);
	if (!oltask_scheduled) {
		try {
			session.post_task( () => {
				oltask_scheduled=false;
				if (audio_only && aout.ipid_props(0, 'eos') ) {
					//we don't lock vout because the overlay buffer is still valid
					vout.update('oldata', null);
					return false;
				}
				if (session.last_task) {
					return false;
				}
				update_overlay();
				if (!ol_visible) return false;
				if (overlay_type==OL_NONE) return false;
				oltask_scheduled=true;
				return task_reschedule;
			}, "overlay_update");
			oltask_scheduled = true;
		} catch (e) {}
	}
}

let shortcuts = [
	{ "code": "B", "desc": "speeds up by 2"},
	{ "code": "V", "desc": "speeds down by 2"},
	{ "code": "N", "desc": "normal play speed"},
	{ "code": "space", "desc": "pause/resume"},
	{ "code": "S", "desc": "step one frame"},
	{ "code": "Right", "desc": "seek forward by 1%, 10% if alt down, 30% if ctrl down"},
	{ "code": "Left", "desc": "seek backward "},
	{ "code": "Up", "desc": "seek forward by 30s, 10m if alt down, 30m if ctrl down"},
	{ "code": "Down", "desc": "seek backward"},
	{ "code": "I", "desc": "show info and statistics"},
	{ "code": "P", "desc": "show playback info"},
	{ "code": "F", "desc": "fullscreen mode"},
	{ "code": "M", "desc": "flip video"},
	{ "code": "R", "desc": "rotate video by 90 degree"},
];

function set_speed(speed)
{
	if (audio_only) aout.update('speed', ''+speed);
	else {
		vout.update('speed', ''+speed);
		if (aout) aout.update('speed', ''+speed);

		if (speed>2) vout.update('drop', '1');
		else if (speed<=1) vout.update('drop', ''+drop);

	}
}

function process_keyboard(evt)
{
	if (interactive_scene==1) return false;

	if (overlay_type==OL_AUTH) {
		return true;
	}
	if(evt.window != win_id) return true;

	switch (sys.keyname(evt.keycode)) {
	case 'B':
		if (evt.keymods & GF_KEY_MOD_SHIFT) speed *= 1.2;
		else speed *= 2;
		set_speed(speed);
		return true;
	case 'V':
		if (evt.keymods & GF_KEY_MOD_SHIFT) speed /= 1.2;
		else speed /= 2;
		set_speed(speed);
		return true;
	case 'N':
		speed = 1;
		set_speed(speed);
		return true;
	case 'space':
		paused = !paused;
		set_speed(paused ? 0 : speed);
		return true;
	case 'S':
		paused = 2;
		vout.update('step', '1');
		if (aout) aout.update('speed', '0');
		check_duration();
		return true;
	case 'Right':
		if (interactive_scene) return false;
		do_seek(1, evt.keymods, false);
		return true;
	case 'Left':
		if (interactive_scene) return false;
		do_seek(-1, evt.keymods, false);
		return true;
	case 'Up':
		if (interactive_scene) return false;
		do_seek(1, evt.keymods, true);
		return true;
	case 'Down':
		if (interactive_scene) return false;
		do_seek(-1, evt.keymods, true);
		return true;

	case 'F':
		if (audio_only) return;
		fullscreen = !fullscreen;
		vout.update('fullscreen', ''+fullscreen);
		return true;

	case 'H':
		if (overlay_type==OL_AUTH) break;
		//hide player
		if (audio_only && (overlay_type==OL_PLAY)) {
			toggle_overlay();
		}
		overlay_type=OL_HELP;
		toggle_overlay();
		//show player
		if (!ol_visible && audio_only) {
			overlay_type=OL_PLAY;
			toggle_overlay();
		}
		return true;
	case 'I':
		if (overlay_type==OL_AUTH) break;
		//hide player
		if (audio_only && (overlay_type==OL_PLAY)) {
			toggle_overlay();
		}
		overlay_type=OL_STATS;
		toggle_overlay();
		//show player
		if (!ol_visible && audio_only) {
			overlay_type=OL_PLAY;
			toggle_overlay();
		}
		return true;
	case 'P':
		if (overlay_type==OL_AUTH) break;
		//do not untoggle for audio only
		if (audio_only) return;
		overlay_type=OL_PLAY;
		if (!ol_visible) init_wnd=true;
		toggle_overlay();
		return true;
	case 'R':
		if (audio_only) return;
		rot = vout.get_arg('vrot');
		rot++;
		if (rot==4) rot = 0;
		if (!rot) vout.update('vrot', '0');
		else if (rot==1) vout.update('vrot', '90');
		else if (rot==2) vout.update('vrot', '180');
		else vout.update('vrot', '270');
		return true;
	case 'M':
		if (audio_only) return;
		flip = vout.get_arg('vflip');
		flip++;
		if (flip==4) flip = 0;
		if (!flip) vout.update('vflip', 'no');
		else if (flip==1) vout.update('vflip', 'v');
		else if (flip==2) vout.update('vflip', 'h');
		else vout.update('vflip', 'vh');
		return;
	default:
		break;
	}
	return false;
}


session.set_auth_fun( (site, user, pass, secure, auth_cbk) => {
	overlay_type_bck = overlay_type;
	overlay_type = OL_AUTH;
	auth_cbk.site = site;
	auth_cbk.user = user ? user : "";
	auth_cbk.pass = pass ? pass : "";
	auth_cbk.secure = secure;
	auth_requests.push(auth_cbk);

	if (overlay_type_bck) toggle_overlay();
	toggle_overlay();
});

