const { pipelines, components, utils, Buffer } = window.mediaStreamLibrary;


Number.prototype.toUnsigned = function() {
	return ((this >>> 1) * 2 + (this & 1));
};

const ulawencodeTable = [
	0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
	5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
	5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7];


window.vxgwsplayer = function(id, options_){
	window.vxgwsplayer.version="1.1.2"; //version is updated by 'npm run build'
	window.vxgwsplayer.players = window.vxgwsplayer.players || {};

	if(!document.getElementById(id)){
		console.error(" Player with " + id + " did not found");
		return undefined;
	}

	if(!window.vxgwsplayer.players[id]){
/*
		if(typeof chrome == "undefined"){
			vxgwsplayer.showAvailableInChrome(id);
			return undefined;
		}

		if(navigator.userAgent.match(/Android/i)
		  || navigator.userAgent.match(/webOS/i)
		  || navigator.userAgent.match(/iPhone/i)
		  || navigator.userAgent.match(/iPad/i)
		  || navigator.userAgent.match(/iPod/i)
		  || navigator.userAgent.match(/BlackBerry/i)
		  || navigator.userAgent.match(/Windows Phone/i)
			){
			vxgwsplayer.showAvailableInChrome(id);
			return undefined;
		};
*/
		/*
		if(!vxgwsplayer.browserSupportsPluginPnacl()){
			console.log("Not installed vxg_media_player");
			vxgwsplayer.showNotInstalled(id);

			var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
			if (fs) {
				fs(window.TEMPORARY,100,function(){},function(){ vxgwsplayer.showNotInstalledInIncognitoMode(id); });
			}
			return undefined;
		}
		*/

		// magic run app
		/*
		var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
		if (!fs) {
  			//console.log("RequestFileSystem failed");
			window.location.href = "https://www.videoexpertsgroup.com/player_start/";
		} else {
  			fs(window.TEMPORARY, 1, function(){
			    //console.log("not in incognito mode");
			    if(!vxgwsplayer.isFrame()){
					//console.log("Start Chrome App");
					window.location.href = "https://www.videoexpertsgroup.com/player_start/";
				}
  			},
  			function(){
				console.log("Not installed vxg_media_player");
  			});
		}
		*/

		// check web socket server
		/*
		if(window.location.protocol != "https:" && !vxgwsplayer.isFrame()){
			vxgwsplayer.checkWebSocket().done(function(result){
				//console.log('websocket: success');
			}).fail(function(err){
				// check incognito mode
				fs(window.TEMPORARY,1,function(){ vxgwsplayer.showWebSocketFailed(id); },function(){ vxgwsplayer.showNotStartedInIncognitoMode(id); });
			});
		}
		*/

		if(window.location.protocol != "https:" && vxgwsplayer.isFrame()){
			vxgwsplayer.checkWebSocket().done(function(result){
				//console.log('websocket: success');
			}).fail(function(err){
				vxgwsplayer.showNotStartedInIFrame(id);
			});
		}

		//window.location.href = "https://videoexpertsgroup.com/player_start/";
		window.vxgwsplayer.players[id] = new function(id, opts){
			const self = this;
			self.id = id;
			self.player = document.getElementById(id);
			/* init options */
			window.vxgwsplayer.initOptions(self, opts);

			self.playerWidth=self.options.width || 640;
			self.playerHeight=self.options.height || 480;
			self.playerWidth = parseInt(self.player.getAttribute('width'),10) || self.playerWidth;
			self.playerHeight = parseInt(self.player.getAttribute('height'),10) || self.playerHeight;
			self.player.style.width = self.playerWidth + 'px';
			self.player.style.height = self.playerHeight + 'px';
			self.wd = null;
			self.wdtries = 0;
			self.wdwork = null;

			var html = ''
				+ '<div class="vxgwsplayer-loader" style="display: inline-block"></div>'
				+ '<div class="vxgwsplayer-screenshot-loading" style="display: none">'
				+ '		<div class="vxgwsplayer-screenshot-loading">'
				+ '     </div>'
				+ '</div>'
				+ '<div class="vxgwsplayer-error" style="display: none">'
				+ '	<div class="vxgwsplayer-error-text" style="display: none"></div>'
				+ '</div>'
				+ '<div class="vxgwsplayer-controls-zoom-position">'
				+ '		<div class="vxgwsplayer-zoom-position-cursor"></div>'
				+ '</div>'
				+ '<div class="vxgwsplayer-controls-zoom">'
				+ '	<div class="vxgwsplayer-zoom-up"></div>'
				+ '	<div class="vxgwsplayer-zoom-progress zoom10x"></div>'
				+ '	<div class="vxgwsplayer-zoom-down"></div>'
				+ '</div>'
				+ '<div class="vxgwsplayer-controls">'
				+ '	<div class="vxgwsplayer-volume-mute"></div>'
				+ '	<div class="vxgwsplayer-volume-down"></div>'
				+ '	<div class="vxgwsplayer-volume-progress vol7"></div>'
				+ '	<div class="vxgwsplayer-volume-up"></div>'
				+ '	<div class="vxgwsplayer-play"></div>'
				+ '	<div class="vxgwsplayer-pause" style="display: none"></div>'
				+ '	<div class="vxgwsplayer-stop" style="display: none"></div>'
				+'	<div class="vxgwsplayer-fullscreen"></div>'
				+'	<div class="vxgwsplayer-takescreenshot"></div>'
				+'	<div class="vxgwsplayer-scale"></div>'
				+ '</div>'
				+ '<canvas class="vxgwsplayer-stub-snapshot" style="width:100%; height:100%; display:none;"></canvas>'
//				+ '<video class="vxgwsplayer-native-video contain" autoplay="yes" muted="yes" style="width:100%; height: 100%; position: absolute;"></video>'
				+ '<video class="vxgwsplayer-native-video contain" muted="yes" volume="1.0" style="width:100%; height: 100%; position: absolute; transform: scale(1); left: 0%; top: 0%;"></video>'
				+ '<video class="vxgwsplayer-stub-record" style="display: none;"></video>'

				/*
				+ window.vxgwsplayer.generateEmbededElement(self);
				*/

			self.player.innerHTML = html;

			var el_controls = self.player.getElementsByClassName('vxgwsplayer-controls')[0];
			var el_controls_zoom = self.player.getElementsByClassName('vxgwsplayer-controls-zoom')[0];
			var el_controls_zoom_position = self.player.getElementsByClassName('vxgwsplayer-controls-zoom-position')[0];
			var el_play = self.player.getElementsByClassName('vxgwsplayer-play')[0];
			var el_pause = self.player.getElementsByClassName('vxgwsplayer-pause')[0];
			var el_stop = self.player.getElementsByClassName('vxgwsplayer-stop')[0];
			var el_fullscreen = self.player.getElementsByClassName('vxgwsplayer-fullscreen')[0];
			var el_takescreenshot = self.player.getElementsByClassName('vxgwsplayer-takescreenshot')[0];
			var el_screenshot_loading = self.player.getElementsByClassName('vxgwsplayer-screenshot-loading')[0];
			var el_scale = self.player.getElementsByClassName('vxgwsplayer-scale')[0];
			var el_zoomUp = self.player.getElementsByClassName('vxgwsplayer-zoom-up')[0];
			var el_zoomDown = self.player.getElementsByClassName('vxgwsplayer-zoom-down')[0];
			var el_zoomProgress = self.player.getElementsByClassName('vxgwsplayer-zoom-progress')[0];
			var el_zoomPositionCursor = self.player.getElementsByClassName('vxgwsplayer-zoom-position-cursor')[0];
			var el_loader = self.player.getElementsByClassName('vxgwsplayer-loader')[0];
			var el_error = self.player.getElementsByClassName('vxgwsplayer-error')[0];
			var el_error_text = self.player.getElementsByClassName('vxgwsplayer-error-text')[0];
			var el_video  = self.player.getElementsByClassName('vxgwsplayer-native-video')[0];
			var el_snap_stub = self.player.getElementsByClassName('vxgwsplayer-stub-snapshot')[0];
			var el_record = self.player.getElementsByClassName('vxgwsplayer-stub-record')[0];
			
			var el_btnstart = document.getElementById(id + '_btnstart');			
			
			self.video = el_video;
			
			
			self.video.onplay = function() {
				var b = self.video.buffered;
				if (b.length > 0)
					self.video.currentTime = b.end(b.length - 1) - self.m.latency/1000;
				console.log('onplay: start=' + self.video.currentTime + ', buffer=(' + b.start(b.length - 1) + ', ' + b.end(b.length - 1) + ')');
				/*
				var b = self.video.buffered;
				if (b.length == 0) {
					console.log('no buffer');
				} else {
					if ((b.end(0)*1000 - self.m.latency > 0) && (self.video.paused )) {
						self.video.paused = false;
						self.video.currentTime = b.end(0) - self.m.latency/1000;
						self.pipeline.play();
					}
					console.log('buffer:' + b.start(0) + '~' + b.end(0));
				}
				*/
			}
			
			self.video.onpause = function() {
				console.log('onpause');
			}
/*
			self.module = document.getElementById(id + '_nacl_module');
			self.module.command = function(){
				var cmd = [];
				for(var i = 0; i < arguments.length; i++){
					cmd.push(arguments[i]);
				}
				if(id != self.id || self.id + '_nacl_module' != self.module.id){
					console.error("Mixed player id");
				}
				
				if(self.m.debug){
					console.log('Player: ' + self.id + ' cmd=[' + cmd.join('] '));
				}
				self.module.postMessage(cmd);
				// self.module.postMessage(arguments);
			}

			// Request the offsetTop property to force a relayout. As of Apr 10, 2014    
			// this is needed if the module is being loaded on a Chrome App's    
			// background page (see crbug.com/350445).    
			self.module.offsetTop;
*/
			self.m.versionapp = "unknown";
			self.m.debug = self.options.debug || self.player.hasAttribute('debug') || false;
			
			var watchdog_tr = self.player.getAttribute('watchdog');
//			self.m.watchdog = ( (watchdog_tr !== undefined) && (watchdog_tr !==null) && (watchdog_tr !== "") ) ? parseInt(watchdog_tr,10) : 3;
			// if fps=1, 3sec is not enough to fill buffer to satisfy latency.
			self.m.watchdog = ( (watchdog_tr !== undefined) && (watchdog_tr !==null) && (watchdog_tr !== "") ) ? parseInt(watchdog_tr,10) : 5;
			self.m.autostart = self.player.hasAttribute('autostart');
			self.m.is_opened = false;
			self.m.latency = 0;
			self.m.controls = true;
			self.m.avsync = self.options.avsync || false;
			self.m.vxgReadyState = 0;
			self.m.autohide = self.options.autohide || 2000;
			self.m.lastErrorCode = -1;
			self.m.lastErrorDecoder = 0;
			self.m.autoreconnect = self.options.autoreconnect || 0;
			self.m.connection_timeout = self.options.connection_timeout || 0;
			self.m.connection_udp = self.options.connection_udp || 0;
			self.m.isCustomDigitalZoom = self.options.custom_digital_zoom || false;

			self.currentZoom = 10;
			self.m.snapshotFile = "";
			self.m.snapshotPTS = "-1";
			self.m.PTSVideo = "-1";
			self.m.PTSAudio = "-1";

			self.m.playAfterBufferFill = false;
			self.m.lastRecvTime = Date.now();
			
			vxgwsplayer.initVolumeControls(self, false);

			if(self.m.debug){
				console.log("Player " + self.id + " - init new player");
			}

			self.set_controls_opacity = function(val){
				el_controls.style.opacity = val;
				el_controls_zoom.style.opacity = val;
				el_controls_zoom_position.style.opacity = val;
			}
			
			self.set_controls_display = function(val){
				el_controls.style.display = val;
				if(self.m.isCustomDigitalZoom == true){
					el_controls_zoom.style.display = "none";
					el_controls_zoom_position.style.display = "none";
				}else{
					el_controls_zoom.style.display = val;
					el_controls_zoom_position.style.display = self.currentZoom == 10 ? "none" : "";
				}
			}
			if(self.m.isCustomDigitalZoom == false){
				el_controls_zoom_position.style.display = self.currentZoom == 10 ? "none" : "";
			}

			if(self.player.hasAttribute('custom-digital-zoom')){
				self.m.isCustomDigitalZoom = true;
				el_controls_zoom.style.display = "none";
				el_controls_zoom_position.style.display = "none";
			}

			if(!self.player.hasAttribute('controls')){
				self.m.controls = false;
				self.set_controls_display("none");
			}
			
			if(self.options.controls && self.options.controls == true){
				self.m.controls = true;
				self.set_controls_display("");
			}

			self.m.avsync = self.player.hasAttribute('avsync');
			self.m.aspectRatio = (self.player.hasAttribute('aspect-ratio-mode'))?(parseInt(self.player.getAttribute('aspect-ratio-mode'),10)):0;
			
			if(self.player.hasAttribute('autohide')){
				self.m.autohide = parseInt(self.player.getAttribute('autohide'),10)*1000;
			}else if(self.options.autohide){
				self.m.autohide = self.options.autohide*1000;
			}
			
			self.timeout = undefined;

			self.loadSettings = function(){

				if(self.m.debug){
					console.log('Player ' + self.id + ' - loadSettings');
				}
				/*
				if(self.player.hasAttribute('useragent-prefix')){
					self.module.command('setuseragent', self.player.getAttribute('useragent-prefix') + ' ' + navigator.userAgent)
				}else if(self.options.useragent_prefix){
					self.module.command('setuseragent', self.options.useragent_prefix + ' ' + navigator.userAgent)
				}
				*/
				/*
				self.module = document.getElementById(self.id + '_nacl_module');
				self.module.command = function(){
					var cmd = [];
					for(var i = 0; i < arguments.length; i++){
						cmd.push(arguments[i]);
					}
					if(id != self.id || self.id + '_nacl_module' != self.module.id){
						console.error("Mixed player id");
					}
					
					if(self.m.debug){
						console.log('Player: ' + self.id + ' cmd=[' + cmd.join('] '));
					}
					self.module.postMessage(cmd);
					// self.module.postMessage(arguments);
				}
				self.module.offsetTop;
				self.module.command('setversion', window.vxgwsplayer.version);
				*/
				//self.m.autoreconnect = 0;
				if(self.player.hasAttribute('auto-reconnect') || self.options.autoreconnect){
					self.m.autoreconnect = 1;
/*
					self.module.command('setautoreconnect', '1');	
*/					
				}
				if(self.player.hasAttribute('connection-timeout')){
					self.m.connection_timeout = parseInt(self.player.getAttribute('connection-timeout'), 10);
				}
				if(self.options.connection_timeout != 0){
/*				
					self.module.command('setconnection_timeout', self.m.connection_timeout.toString());	
*/
				}
				if(self.player.hasAttribute('connection-udp') || self.m.connection_udp){
					self.m.connection_udp = 1;
/*
					self.module.command('setconnection_udp', '1');	
*/
				}

				self.m.avsync = self.player.hasAttribute('avsync');
				self.m.aspectRatio = 0;
				
				if(self.player.hasAttribute('aspect-ratio-mode')){
					self.m.aspectRatio = parseInt(self.player.getAttribute('aspect-ratio-mode'), 10);
				}else if(self.options.aspect_ratio_mode){
					self.m.aspectRatio = self.options.aspect_ratio_mode;
				}
				self.aspectRatioMode(self.m.aspectRatio);
				/*
				self.module.command('setavsync', self.m.avsync ? '1' : '0');
				*/
				if(self.player.hasAttribute('latency')){
					self.m.latency = parseInt(self.player.getAttribute('latency'), 10);
					/*
					self.module.command('setlatency', self.m.latency.toString());
					*/
				}else if(self.options.latency){
					self.m.latency = self.options.latency;
					/*
					self.module.command('setlatency', self.m.latency.toString());
					*/
				}
                if (self.m.latency < 0) {
                    self.m.latency = 0;
                }
			
				window.vxgwsplayer.initVolumeControls(self, true);

				if(self.player.hasAttribute('autohide')){
					self.m.autohide = parseInt(self.player.getAttribute('autohide'),10)*1000;
				}else if(self.options.autohide){
					self.m.autohide = self.options.autohide*1000;
				}
			}
			
			self.moduleDidLoad = function(){
				if(self.m.debug){
					console.log('Player ' + self.id + ' - moduleDidLoad');
				}
				self.loadSettings();
				
				if(window.location.protocol == "https:"){
					//use Native protocol
					self.connectToApp();
				}else{
					//use Websocket protocol
					/*
					self.module.command('startwebsclient', vxgwsplayer.webserverport)
					*/
				}
			}
			self.playerDidLoad = function(){
				self.loadSettings();
			
				if(self.m.debug){
					console.log('Player ' + self.id + " - playerDidLoad");
				}
				
				if(self.onReadyStateCallback){
					self.m.is_opened = false;
					self.onReadyStateCallback();
				}else{
					self.setSource(); //binary, wsurl and rtspurl should be inited
				}
			}

			self.connectToApp = function(){
				if(self.m.debug){
					console.log('Player ' + self.id + ' connectToApp');
				}
				//self.m.port = chrome.runtime.connect("hncknjnnbahamgpjoafdebabmoamcnni");
				//self.m.port = chrome.runtime.connect("invalid");
				if(self.m.debug){
					console.log('Player ' + self.id + ' connected port='+self.m.port);
				}
				self.m.portName = ""+ new Date().getTime().toString();
				/*
				self.m.port.onDisconnect.addListener(function(){
					if(self.m.debug)
						console.log('disconnected portName='+self.m.portName);
					self.module.command( 'stopnativeclient', '@'+self.m.portName);

					self.m.port = undefined;
				});
				*/
				if(self.m.debug)
					console.log('connected portName='+self.m.portName);
				/*
				self.m.port.onMessage.addListener(function(msg) {
					if( msg != undefined && msg.id == undefined && msg[0] != undefined && msg[0].charAt(0) == '@'){
						if(self.module.postMessage == undefined){
							self.m.port.disconnect();
						}else{
							self.module.postMessage(msg);
						}
					}else
					if( msg != undefined && msg.cmd == 'getversionapp'){
						if(0 == msg.data.indexOf("VERSION_APP")){
							self.m.versionapp = msg.data.split(' ')[1];
							if(self.m.debug){
								console.log('Player ' + self.id + ' =VERSION_APP '+self.m.versionapp);
							}

							self.module.command( 'setappversion', self.m.versionapp);
							self.module.command( 'startnativeclient', '@'+self.m.portName);
							self.playerDidLoad();
						}else{
							console.log('Player ' + self.id + ' Invalid VERSION_APP msg.data='+msg.data);
						}
					}else{
						console.log('Player ' + self.id + 'getversionapp unknown msg=', msg);
					}
				});
				self.m.port.postMessage({id: ""+self.m.portName, cmd: "getversionapp", data: ""} );
				*/
			}

			self.showerror = function(text){
				el_loader.style.display = "none";
				el_error.style.display = "inline-block";
				el_error_text.style.display = "inline-block";
				el_error_text.innerHTML = text;
			}
			
			self.hideerror = function(text){
				el_error.style.display = "none";
				el_error_text.style.display = "none";
			}

			self.readyState = function(){
				return self.m.vxgReadyState;
			}

			self.onReadyStateChange = function(cb){
				self.onReadyStateCallback = cb;
			}
			self.ready = self.onReadyStateChange;

			self.onStateChange = function(cb){
				self.onStateChangeCallback = cb;
			}
			
			self.onBandwidthError = function(cb){
				self.m.handlerBandwidthError = cb;
			}

			self.onError = function(cb){
				self.m.handlerError = cb;
			}
/*
			self.handleMessage = function(msgEvent){
				if(self.m.debug){
					console.log('Player ' + self.id + ' handleMessage: ' + msgEvent.data);
				}
				if(msgEvent == undefined || msgEvent.data == undefined){
					return;
				}
				if(msgEvent.data[0] != undefined && msgEvent.data[0].charAt(0) == '@'){
					//proto native send to app
					if(self.m.port != undefined){
						self.m.port.postMessage(msgEvent.data);
					}
				}else if(0 == msgEvent.data.indexOf("PLAYER_INITED")){
					self.moduleDidLoad();
				}else if(0 == msgEvent.data.indexOf("PLAYER_INIT_ERR")){
					str = "PLAYER_INIT_ERR ";
					err = parseInt(msgEvent.data.split(' ')[1], 10);
					err_type = (err >> 16)&0xffff;
					console.log('err_type='+err_type);
					switch(err_type){
					case 4:	
						str += "Init audio output error ";		
						break;
					case 2:	
						str += "Init video decoder error ";		
						break;
					default:
						str += "Init D3D error ";		
						break;
					}
					str += ""+err.toString();
					vxgwsplayer.showInitFailed(id, str);
				}else
				// Player source error=60935 WSS status=6
				// Player listener2: Skip picture: 170, frame_duration: 41, latencyms: 0<=frames_buf:0, b: 0, p: 0
				if(0 == msgEvent.data.indexOf("VERSION_APP")){
					self.m.versionapp = msgEvent.data.split(' ')[1];
					if(self.m.debug)
						console.log('=VERSION_APP '+self.m.versionapp);
					self.playerDidLoad();
				}else if(0 == msgEvent.data.indexOf("PTS_VIDEO")){
					self.m.PTSVideo = msgEvent.data.split(' ')[1];
					//console.log('=PTS_VIDEO '+self.m.PTSVideo);
				}else if(0 == msgEvent.data.indexOf("PTS_AUDIO")){
					self.m.PTSAudio = msgEvent.data.split(' ')[1];
					//console.log('=PTS_AUDIO '+self.m.PTSAudio);
				}else if(0 == msgEvent.data.indexOf("TAKE_SNAPSHOT")){
					snap_status = msgEvent.data.split(' ')[1];
					snap_pts = msgEvent.data.split(' ')[2];
					console.log('TAKE_SNAPSHOT snap_status='+snap_status+' snap_pts='+snap_pts);
					if(0 != snap_status.indexOf("ERROR")){
						self.m.snapshotFile = snap_status;
						if(snap_pts != undefined)
							self.m.snapshotPTS = snap_pts;
							window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
							window.requestFileSystem(window.TEMPORARY, 1, function(fs) {
							fs.root.getFile(self.m.snapshotFile, {create: false}, function(fileEntry) { // /test is filename
								var pom = document.createElement('a');
								pom.setAttribute('href', fileEntry.toURL());
								if(self.m.snapshotPTS == -1){
									pom.setAttribute('download', "snapshot.jpg");
								}else{
									pom.setAttribute('download', "snapshot_"+self.m.snapshotPTS+".jpg");
								}
								pom.style.display = 'none';
								document.body.appendChild(pom);
								pom.click();
								document.body.removeChild(pom);
							}, function(e) {
								console.error("[vxgwsplayer] TAKE_SNAPSHOT fs.root.getFile FAILED")
							});
						}, function(e) {
							console.error("[vxgwsplayer] TAKE_SNAPSHOT requestFileSystem window.TEMPORARY FAILED")
						});
					}
				}else if(msgEvent.data == "MEDIA_ERR_URL"){
					self.showerror('Problem with URL');
					self.m.lastErrorCode = 0;
					if(self.m.handlerError)
						self.m.handlerError(self);
				}else if(0 == msgEvent.data.indexOf("MEDIA_ERR_NETWORK")){
					self.showerror('Problem with network');
					self.m.lastErrorCode = 1;
					if(self.m.handlerError)
						self.m.handlerError(self);
				}else if(0 == msgEvent.data.indexOf("MEDIA_ERR_SOURCE")){
					self.showerror('Problem with source');
					self.m.lastErrorCode = 2;
					if(self.m.handlerError)
						self.m.handlerError(self);
				}else if(0 == msgEvent.data.indexOf("MEDIA_ERR_CARRIER")){
					self.showerror('Problem with carrier');
					self.m.lastErrorCode = 3;
					if(self.m.handlerError)
						self.m.handlerError(self);
				}else if(0 == msgEvent.data.indexOf("MEDIA_ERR_AUDIO")){
					self.showerror('Problem with audio');
					self.m.lastErrorCode = 4;
					if(self.m.handlerError)
						self.m.handlerError(self);
				}else if(0 == msgEvent.data.indexOf("MEDIA_ERR_VIDEO")){
					self.showerror('Problem with video');
					self.m.lastErrorCode = 5;
					if(self.m.handlerError)
						self.m.handlerError(self);
				}else if(0 == msgEvent.data.indexOf("MEDIA_ERR_AUTHENTICATION")){
					self.showerror('Problem with authentification');
					self.m.lastErrorCode = 6;
					if(self.m.handlerError)
						self.m.handlerError(self);
				}else if(0 == msgEvent.data.indexOf("MEDIA_ERR_BANDWIDTH")){
					//self.stop();
					self.m.lastErrorCode = 7;
					if(self.m.handlerError)
						self.m.handlerError(self);
					if(self.m.handlerBandwidthError){
						self.m.handlerBandwidthError(self);
					}else{
						//self.showerror('Problem with bandwidth');
					}
				}else if(0 == msgEvent.data.indexOf("MEDIA_ERR_EOF")){
					self.showerror('End of File');
					self.m.lastErrorCode = 8;
					if(self.m.handlerError)
						self.m.handlerError(self);
				}else if(0 == msgEvent.data.indexOf("MEDIA_ERR_DECODER")){
					var arr = msgEvent.data.split(' ');
					self.m.lastErrorDecoder = (arr.length > 1) ? parseInt(arr[1],10) : 0;
					if(self.m.debug)
						console.log('=MEDIA_ERR_DECODER '+self.m.lastErrorDecoder + ' ' + (self.m.lastErrorDecoder == 0)?'Decoder resumed':'');
					if(self.m.lastErrorDecoder == 0){
						self.m.lastErrorCode = -1;
						//self.hideerror();
					}else{
						self.m.lastErrorCode = 9;
						//self.showerror('Decoder error '+self.m.lastErrorDecoder);
					}
					if(self.m.handlerError)
						self.m.handlerError(self);
					
				}else if(0 == msgEvent.data.indexOf("PLAYER_CONNECTING")){
					self.m.vxgReadyState = 1;
					//el_play.style.display = "none";
					// el_stop.style.display = "none";
					// self.hideerror();
					el_loader.style.display = "inline-block";
					el_takescreenshot.style.display = "none";
					if(self.onStateChangeCallback)
						self.onStateChangeCallback(self.m.vxgReadyState);
				}else if(0 == msgEvent.data.indexOf("PLAYER_PLAYING")){
					//console.log('PLAYER_PLAYING');
					self.m.vxgReadyState = 2;
					el_play.style.display = "none";
					el_stop.style.display = "inline-block";
					el_pause.style.display = "inline-block";
					el_takescreenshot.style.display = "inline-block";
					self.hideerror();
					el_loader.style.display = "none";
					if(self.onStateChangeCallback)
						self.onStateChangeCallback(self.m.vxgReadyState);
				}else if(0 == msgEvent.data.indexOf("PLAYER_STOPPING")){
					self.m.vxgReadyState = 3;
					// el_error.style.display = "none";
					// el_loader.style.display = "inline-block";
					el_play.style.display = "none";
					el_stop.style.display = "none";
					el_pause.style.display = "none";
					el_takescreenshot.style.display = "none";
				}else if(0 == msgEvent.data.indexOf("PLAYER_STOPPED")){
					self.m.vxgReadyState = 0;
					el_play.style.display = "inline-block";
					el_stop.style.display = "none";
					el_pause.style.display = "none";
					el_takescreenshot.style.display = "none";
					if(self.onStateChangeCallback)
						self.onStateChangeCallback(self.m.vxgReadyState);
				}else if(0 == msgEvent.data.indexOf("PLAYER_PAUSED")){
					//console.log('PLAYER_PAUSED');
					self.m.vxgReadyState = 4;
					el_play.style.display = "inline-block";
					el_stop.style.display = "inline-block";
					el_pause.style.display = "none";
					// el_takescreenshot.style.display = "none";
					if(self.onStateChangeCallback)
						self.onStateChangeCallback(self.m.vxgReadyState);
				}
			}
*/
			self.handleError = function(){
				el_loader.style.display = "none";
				el_error.style.display = "block";
				console.error("ERROR");
				self.showerror('Unknown error');
			}

			self.handleCrash = function(){
				el_loader.style.display = "none";
				el_error.style.display = "block";
				self.showerror('Crashed');
			}

			self.restartTimeout = function(){
				if(self.m.autohide <= 0){
					self.set_controls_opacity("0");
					return;
				}
				self.set_controls_opacity("0.7");
				clearTimeout(self.timeout);
				self.timeout = setTimeout(function(){
					self.set_controls_opacity("0");
				},self.m.autohide);
			};

			self.player.addEventListener('mousemove', function(){
				self.restartTimeout();
			}, true);

			self.restartTimeout();
			
/*
			//self.module.addEventListener('load', self.moduleDidLoad, true);
			self.module.addEventListener('message', self.handleMessage, true);
			self.module.addEventListener('error', self.handleError, true);
			self.module.addEventListener('crash', self.handleCrash, true);
*/
			if (typeof window.attachListeners !== 'undefined') {
			  window.attachListeners();
			}

			self.error = function(){
				return self.m.lastErrorCode;
			}
			self.errorDecoder = function(){
				return self.m.lastErrorDecoder;
			}

			self.controls = function(val){
				if(val == undefined){
					return self.m.controls;
				}else{
					if(val == true){
						self.set_controls_display("");
						self.m.controls = true;
					}else if(val == false){
						self.set_controls_display("none");
						self.m.controls = false;
					}
				}
			}

			self.debug = function(val){
				if(val == undefined){
					return self.m.debug;
				}else{
					self.m.debug = val;
				}			
			}

            // Truen
            var stinit = 0;
            var svt = 0;
            var sdt = 0;

			self.play = function(){
				self.hideerror();

				self.m.vxgReadyState = 1;

				if (self.pipeline === undefined || self.pipeline == null) {
					var isCreated = self.createPipeline();
					if (isCreated != true) {
						el_loader.style.display = "none";
						return;
					}
				}
				self.pipeline.ready.then(function(){
					firstPackage = null;
					self.pipeline.rtsp.play();
					//self.pipeline.play();
					//record-on-start 300s
					//self.startRecord(300*1000);
					el_stop.style.display = "inline-block";
					el_pause.style.display = "inline-block";
					el_takescreenshot.style.display = "inline-block";
					el_loader.style.display = "none";
					if(self.onStateChangeCallback){
						self.onStateChangeCallback(self.m.vxgReadyState);
					}
				});

				if(self.m.debug)
					console.log( 'self.play self.m.url='+self.m.url + ' self.m.is_opened='+self.m.is_opened);

				if(!self.m.is_opened){
					self.m.is_opened = true;
/*					
					self.module.command('open', self.m.url);
*/					
				}

				el_play.style.display = "none";
				if(self.m.vxgReadyState != 4) {//not paused=>play, show progress
					el_loader.style.display = "inline-block";
				}
/*
				self.module.command('play', '0');
*/				
				self.applyVolume();
				
                stinit = 0;
                svt = 0;
                sdt = 0;
				self.wdwork = setTimeout(function(){
					self.updateStreamInfo();
				}, 1000);
			};

			self.restart = function (reason) {
				console.log(reason);
				
				self.wdtries	= 0;
				self.wd		= null;
				clearTimeout(self.wdwork);
				self.wdwork = null;

				self.stop();
				self.play();
			}

			self.updateStreamInfo = function() {
				if (self.pipeline) {
					var curtime = self.pipeline.currentTime;
					
					if(self.m.debug){
						console.log("curtime:" + curtime + "; wd:" + self.wd);
					}

                    if (stinit == 0) {
                        svt = curtime;
                        sdt = Date.now();
                        stinit = 1;
                    }

                    var stdiff = ((Date.now() - sdt) / 1000) - (curtime - svt);
                    if (stdiff >= 0.5) {
                        //console.warn('update currenttime! id : ' + self.id + ', dif : ' + stdiff);
                        //el_video.currentTime = Date.now();                        
                    }
					
					if (self.m.watchdog <= 0 ) {
						console.log('Watchdog disabled by user');
						return;
					}

					if (self.video.paused && !self.m.playAfterBufferFill) {
						console.log('Video paused. Skip watchdog');
						var now = Date.now();
						//console.log("Now: " + now + ", lastRecvTime: " + self.m.lastRecvTime);
						if ((now - self.m.lastRecvTime) > 5 * 1000) {
							self.m.lastRecvTime = now;
							self.restart('restart by recvTime');
							return;
						}

						self.wdtries = 0;
						self.wd = curtime;
						self.wdwork = setTimeout(function(){
							self.updateStreamInfo();
						}, 1000);
						return;
					}

					if ((self.wd == null) || /*(self.video.paused) || */((curtime - self.wd >= 0.0) && (self.wdtries < self.m.watchdog) ) ){
						if ((curtime - self.wd > 0.2)/* || (self.video.paused)*/ ){
							self.wdtries = 0;
						} else {
							self.wdtries += 1;
						}
						self.wd = curtime;
						self.wdwork = setTimeout(function(){
							self.updateStreamInfo();
						}, 1000);
					} else {
						self.restart('restart by wd');
					}
				} 
			}

			self.stop = function(){
/*
				self.module.command('stop', '0');
*/				
				
				pps = '';
				sps = '';
				
				if (self.wdwork) {
					clearTimeout(self.wdwork);
					self.wdwork = null;
				}
				self.wdtries = 0;
				self.wd = null;
                if (dataCatcherWdId) {
                    clearTimeout(dataCatcherWdId);
                    dataCatcherWdId = null;
                }
                dataCatcherWdCnt = 0;

				if (self.pipeline !== undefined && self.pipeline != null) {
					self.pipeline.pause();
					self.pipeline.close();
					self.pipeline = null;
				}
				if (self.audBuffer) {
					self.audBuffer.clear();
					//self.audBuffer = null;
				}

				self.m.vxgReadyState = 0;
				el_play.style.display = "inline-block";
				el_stop.style.display = "none";
				el_pause.style.display = "none";
				el_takescreenshot.style.display = "none";
				if(self.onStateChangeCallback){
					self.onStateChangeCallback(self.m.vxgReadyState);
				}

				el_loader.style.display = "none";
			};

			self.pause = function(){
				//console.log("pause");
/*
				self.module.command('pause', '0');
*/
				self.pipeline.pause();
				if (self.audBuffer) {
					self.audBuffer.clear();
				}
				self.m.vxgReadyState = 4;
				el_play.style.display = "inline-block";
				el_stop.style.display = "inline-block";
				el_pause.style.display = "none";
				// el_takescreenshot.style.display = "none";
				if(self.onStateChangeCallback) {
					self.onStateChangeCallback(self.m.vxgReadyState);
				}
				el_loader.style.display = "none";
			};
			
			self.autohide = function(val){
				if(val){
					self.m.autohide = val*1000;
				}else{
					return self.m.autohide/1000;
				}
			}

			self.autoreconnect = function(val){
				if(val == undefined){
					return self.m.autoreconnect;
				}else{
					self.m.autoreconnect = parseInt(val,10);
/*					
					self.module.command('setautoreconnect', self.m.autoreconnect.toString());
*/					
				}
			};


			self.latency = function(val){
				if(val && val >= 0){
					self.m.latency = parseInt(val,10);
/*
					self.module.command('setlatency', val.toString());
*/					
				}else{
					return self.m.latency;
				}
			};
			self.connection_timeout = function(val){
				if(val){
					self.m.connection_timeout = parseInt(val,10);
/*					
					self.module.command('setconnection_timeout', val.toString());
*/					
				}else{
					return self.m.connection_timeout;
				}
			};
			self.connection_udp = function(val){
				if(val){
					self.m.connection_udp = parseInt(val,10);
/*					
					self.module.command('setconnection_udp', val.toString());
*/					
				}else{
					return self.m.connection_udp;
				}
			};
/*
			self.aspectRatio = function(val){
				if(val == undefined){
					return self.m.aspectRatio?true:false;
				}else{
					self.m.aspectRatio = (val)?self.m.aspectRatioMode:0;
					
					self.module.command('setaspectratio', self.m.aspectRatio.toString());
				}
			}
*/			
			self.aspectRatioMode = function(val){
				if(val == undefined){
					return self.m.aspectRatioMode;
				}else{
					self.m.aspectRatioMode = (val > 0)?val:self.m.aspectRatioMode;
					self.m.aspectRatio = val;
					
					if (val == 0) {
						self.video.classList.remove('fill');
						self.video.classList.remove('cover');
						self.video.classList.add('contain');
					} else if (val == 1) {
						self.video.classList.remove('fill');
						self.video.classList.add('cover');
						self.video.classList.remove('contain');
					} else if (val == 2) {
						self.video.classList.add('fill');
						self.video.classList.remove('cover');
						self.video.classList.remove('contain');
					}
				}
			}
			self.aspectRatioMode(self.m.aspectRatio);
			
			self.avsync = function(val){
				if(val == undefined){
					return self.m.avsync;
				}else{
					self.m.avsync = val;
/*					
					self.module.command('setavsync', self.m.avsync ? '1':'0');
*/					
				}
			}

			self.isPlaying = function(){
				return (self.m.vxgReadyState == 2);
			}
			self.versionPLG = function(){
				return window.vxgwsplayer.version;
			}
			self.versionAPP = function(){
				return self.m.versionapp;
			}

			self.size = function(width, height){
				if(width && height){
					if(Number.isInteger(width) && Number.isInteger(height)){
						var w = parseInt(width,10);
						var h = parseInt(height,10);
						self.playerWidth = self.playerWidth != w ? w : self.playerWidth;
						self.playerHeight = self.playerHeight != h ? h : self.playerHeight;
						self.player.style.width = width + 'px';
						self.player.style.height = height + 'px';
					}else{
						self.player.style.width = width;
						self.player.style.height = height;
					}
				}else{
					return  { width: self.playerWidth, height: self.playerHeight };
				}
			};

			self.changedFullscreen = function(){
				console.log('changedFullscreen');
				if (document.webkitIsFullScreen){
					self.size('100%', '100%');
					console.log('changedFullscreen -> fullscreen');
				}else{
					self.size(self.playerWidth + 'px', self.playerHeight + 'px');
					console.log('changedFullscreen -> NOT fullscreen');
				}
			};

			if (document.addEventListener){
				document.addEventListener('webkitfullscreenchange', self.changedFullscreen, false);
				document.addEventListener('mozfullscreenchange', self.changedFullscreen, false);
				document.addEventListener('fullscreenchange', self.changedFullscreen, false);
				document.addEventListener('MSFullscreenChange', self.changedFullscreen, false);
			}

			self.fullscreen = function(){
				console.log("fullscreen: clicked");
				if(document.webkitIsFullScreen == true){
					document.webkitCancelFullScreen();
				}else{
					if(self.player.requestFullscreen) {
						self.player.requestFullscreen();
					} else if(self.player.webkitRequestFullscreen) {
						self.player.webkitRequestFullscreen();
					} else if(self.player.mozRequestFullscreen) {
						self.player.mozRequestFullScreen();
					}
				}
			};

			self.takescreenshot = function(){
				var stub	= el_snap_stub;
				var videotag	= el_video;
				if (videotag != null ) {
					var width 	= videotag.videoWidth ;
					var height	= videotag.videoHeight;
					if ((width > 0) && (height > 0)) {
						stub.setAttribute('width', width);
						stub.setAttribute('height', height);
						context = stub.getContext('2d');
						if (context != null) {
							context.fillRect(0, 0, width, height);
							context.drawImage(videotag, 0, 0, width, height);
							var downloadLink = document.createElement('a');
                            // jpg
                            downloadLink.setAttribute('download', 'snapshot.jpg');
                            stub.toBlob(function(blob) {
                                var url = URL.createObjectURL(blob);
                                downloadLink.setAttribute('href', url);
                                downloadLink.click();
                            }, 'image/jpeg', 1.0);
                            /* png
							downloadLink.setAttribute('download', 'snapshot.png');
							stub.toBlob(function(blob) {
								var url = URL.createObjectURL(blob);
								downloadLink.setAttribute('href', url);
								downloadLink.click();
							});
                            */
						}
					}
				}
				el_screenshot_loading.style.display = "block";
				setTimeout(function(){
					el_screenshot_loading.style.display = "";
				},3000);
			};
			
			self.getScreenshotPTS = function(){
				return self.m.snapshotPTS;
			};
			self.getPTSVideo = function(){
				return self.m.PTSVideo;
			};
			self.getPTSAudio = function(){
				return self.m.PTSAudio;
			};
			
			var audioCtx = null;
			var audioGain = null;
			var audioFlt = null;
			var audIsRun = false;
			var audBuffer = null;

			self.initAudioCtx = function() {
				if (self.audioCtx != null || self.audioCtx !== undefined) {
					return;
				}
				try {
					window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext;
					self.audioCtx = new AudioContext();
					self.audioCtx.onstatechange = function() {
						if (self.audioCtx !== null && self.audioCtx !== undefined) {
							if (self.audioCtx.state === "running") {
								console.log("<binary> audio context running");
								self.audIsRun = true;
							} else if (audioContext.state === "closed") {
								console.log("<binary> audio context close");
								self.audioCtx = null;
								self.audIsRun = false;
							}
						}
					}
					if (self.audioCtx !== undefined && self.audioCtx !== null) {
						self.audioGain = self.audioCtx.createGain();
						self.audBuffer = new audioBufferPCM(self.audioCtx, self.audioGain, 8000, 8/*48*/); 
						self.audioGain.connect(self.audioCtx.destination)
					}
					return true;
				} catch (err) {
					console.warn("Web Audio API is not supported in this web browser! : ", err);
				}
			}
			
			self.audioPlayBuffer = function( data ) {
				if (!self.audIsRun) {
					return;
				}
				//test sinus for noise camera
				//for (var i = 0; i < data.length; ++i) {
				//	data[i] = (Math.sin( i ) + 1.0) * 256;
				//}

				var data_js = new Float32Array(data.length);
				for (var i = 0; i < data.length; i++) {
					data_js[i] = data[i] / Math.pow(2, 15)
				}
			
				if(self.isMute()) {
					self.audioGain.gain.value = 0; 
				} else {
					self.audioGain.gain.value = self.m.volume; 
				}
				
				if (self.audBuffer != null && self.audBuffer !== undefined) {
					self.audBuffer.addBuffer( data_js);
				}
				
				delete data_js;
				data_js = null;
			}
			
			
			self.recordedData264	= null;
			self.recordedData711	= null;
			var recData264Len	= 0;
			var recData711Len	= 0;
			var isRecording		= false;
            var isRecStarted    = false;
			var isRecStopping	= false;
			var firstPackage	= null;
			var record_wd		= null;
			var record_try		= 0;
			var prevRecLen		= 0;
			self.rech264maxsize	= self.options.clipH264maxsize || Number(self.player.getAttribute('clipH264maxsize')) || 768*1024*1024; //~10min for 10Mbit
			self.recg711maxsize	= self.options.clipG711maxsize || Number(self.player.getAttribute('clipG711maxsize')) || 64*1024*1024;
			self.defaultClipName	= self.options.clipname || self.player.getAttribute('clipname') || 'clip';
			var pps			= '';
			var sps			= '';

            // Truen 
            var dataCatcherWdId = undefined;
            var dataCatcherWdCnt = 0;
			
			self.m.debug = self.options.debug || self.player.hasAttribute('debug') || false;
			
			function arraysAreEqual(a, b) {
				var ret_val =  
				Array.isArray(a) &&
				Array.isArray(b) &&  
				a.length === b.length &&
				a.every((val, index) => val === b[index]);
				
				return ret_val;
			}
			
			self.debug = function(){
				//pps = '1'; //test possible pps-changing
			}
			
			self.createPipeline = function(){
				el_loader.style.display = "inline-block";
				firstPackage = null;
				if ((self.m.wsurl  !== undefined && self.m.wsurl.length > 0)
				&& (self.m.rtspurl !== undefined && self.m.rtspurl.length > 0)){
					self.m.is_opened = true;
					let Pipeline = pipelines.Html5VideoPipeline;
					let mediaElement = el_video;
					
					pps = '';
					sps = '';
					
					var authopts = {}
					if (self.m.user && self.m.password) {
						authopts = {
							username: self.m.user,
							password: self.m.password
						}
					}

					self.pipeline = new Pipeline({
						ws: {
							uri: self.m.wsurl,
						},
						rtsp: {
							uri: self.m.rtspurl,
						},
						auth: authopts,
						mediaElement,
					});

					self.pipeline.onSourceOpen = function(mse, tracks) {
						//mse.duration = 10.0;
						//mse.sourceBuffers[0].timestampOffset = -3.0;
					}

					var dataCatcherRTP = new components.dataCatcherDepay(0);//ULAW
					dataCatcherRTP.onDataCallback = function( msg) {
						if(self.m.debug){
							console.log('Playback' + ((isRecording)?('+Recording'):('')) +': data(ulaw) catcher: len ' + msg.data.length + ' type:' + msg.type + ' rawdata len ' + msg.rawdata.length  );
						}
						if (isRecStarted == true) {
							if (recData711Len + msg.data.length < self.recg711maxsize) {
								var data = msg.data;
								self.recordedData711.set( data, recData711Len);
								recData711Len += data.length;
								delete msg.data;
								delete data; data = null;
							}
						}
						msg = null;
					}
					self.pipeline.insertAfter(self.pipeline.firstComponent.next, dataCatcherRTP);

                    var lastupdatetime = Date.now();
                    var forceupdatecheckcount = 0;
                    var forceupdatecountthreshold = 10;
                    var forceupdatetimethreshold = 100; //ms
					self.pipeline.onDataCallback = function(sourceBuffer, msg) { // mp4 with H264 or H264+AAC
						self.m.lastRecvTime = Date.now();
						var b = sourceBuffer.buffered;
						if (b.length == 0) {
							//console.log('no buffer');
						} else if (false) {
							if ((b.end(0)*1000 - self.m.latency > 0) && (self.video.paused ) && (self.m.vxgReadyState != 4)) {
								self.video.paused = false;
								self.pipeline.play();
							}
                            else {
                                if ((Date.now() - lastupdatetime >= 100) || forceupdatecheckcount > 0) {
                                    if ((self.video.currentTime + (self.m.latency + forceupdatetimethreshold)/1000) < b.end(0)) {
                                        forceupdatecheckcount++;
                                        if (forceupdatecheckcount > forceupdatecountthreshold) {
                                            console.log('update time'); 
                                            self.video.currentTime = b.end(0) - self.m.latency/1000;
                                            forceupdatecheckcount = 0;
                                        }
                                    }
                                    else {
                                        if (forceupdatecheckcount > 0) {
                                            forceupdatecheckcount = 0;
                                        }
                                    }
                                    lastupdatetime = Date.now();
                                }
                            }
							//console.log('buffer:' + b.start(0) + '~' + b.end(0));
						}
						else {
							if (self.m.playAfterBufferFill) {
								/*
								for (var i = 0; i < b.length; i++) {
									console.log('buffer[' + i + ']: ' + b.start(i) + ', ' + b.end(i));
								}
								*/
								if (b.end(b.length - 1) * 1000 - self.m.latency >= 0) {
									self.m.playAfterBufferFill = false;
									console.log("Buffer filled. Start pipeline.");
									self.pipeline.play();
								}
							}
						}
						
						if(self.m.debug){
							console.log( 'Playback' + ((isRecording)?('+Recording'):('')) +': w.data length:' + msg.data.length );
						}
						if (firstPackage == null) {
							firstPackage = msg.data;
						}
						
						if (isRecording == true) {
                            if (msg.idrPicture == true) {
                                isRecStarted = true;
                            }
                            if (isRecStarted) {
                                if( recData264Len + msg.data.length < self.rech264maxsize) {
                                    var data = msg.data;
                                    self.recordedData264.set( data, recData264Len);
                                    recData264Len += data.length;
                                    
                                    delete msg.data; 
                                    delete data; data = null;
                                }
                                else { // Truen 
                                    console.warn('len(' + recData264Len + ') + new(' + msg.data.length + ') >= max(' + self.rech264maxsize + ')');
                                    clearTimeout(record_wd);
                                    self.stopRecord();
                                    record_try = 0;
                                }
                            }
						}
						msg = null;
					}

                    // Truen 
                    function dataCatcherWd() {
                        dataCatcherWdCnt++;
                        if (dataCatcherWdCnt > 4) {
                            setTimeout(function() {
                                self.restart('restart by dataCatcherWd');
                            },10);
                        }
                        else {
                            dataCatcherWdId = setTimeout(function() {
                                dataCatcherWd();
                            }, 1000);
                        }
                    }

/* raw pcm (after g711 decode) data callback*/
				    var dataCatcherPCM = new components.g711toPCM(); //96-h264, 97-aac, 98-metadata, 0-g711?
				    dataCatcherPCM.onDataCallback = function( msg) {
					self.initAudioCtx();
					self.audioPlayBuffer(msg.data);
					
					delete msg.data;
					msg = null;

                    // Truen 
                    if (dataCatcherWdId != undefined) {
                        clearTimeout(dataCatcherWdId);
                    }
                    dataCatcherWdCnt = 0;
                    dataCatcherWdId = setTimeout(function() {
                        dataCatcherWd();
                    },1000);
				    }
				    self.pipeline.insertAfter(self.pipeline.firstComponent.next.next, dataCatcherPCM);
				    
/* catch h264 sps/pps data callback*/
				    var dataCatcherH264 = new components.dataCatcherDepay(96);
				    dataCatcherH264.onDataCallback = function( msg) {
                    /* Truen
					if (msg.data[0] == 0x67) {
						//console.log('data[H264] : SPS');
						if (sps == '') {
							sps = Array.from(msg.data);
						} else if ( !arraysAreEqual (sps, Array.from(msg.data)) ) {
							setTimeout(function(){ 
								self.restart('restart by sps-changed');
							},10);
						}
                        
					} else */if (msg.data[0] == 0x68) {
						//console.log('data[H264] : PPS');
						if (pps == '') {
							pps = Array.from(msg.data);
						} else if ( !arraysAreEqual ( pps, Array.from(msg.data)) ) {
							setTimeout(function(){ 
								self.restart('restart by pps-changed');
							},10);
						}
					}

                    // Truen 
                    if (dataCatcherWdId != undefined) {
                        clearTimeout(dataCatcherWdId);
                    }
                    dataCatcherWdCnt = 0;
                    dataCatcherWdId = setTimeout(function() {
                        dataCatcherWd();
                    },1000);
                    }
				    self.pipeline.insertAfter(self.pipeline.firstComponent.next, dataCatcherH264);

/*
// aac - data callback
				    var dataCatcherAAC = new components.dataCatcherDepay(97); //96-h264, 97-aac, 98-metadata, 0-g711?
				    dataCatcherAAC.onDataCallback = function( msg) {
					console.log('data(aac) catcher: ' + msg.data.length + ' type:' + msg.type );
				    }
				    self.pipeline.insertAfter(self.pipeline.firstComponent.next, dataCatcherAAC);
*/				    
				    self.pipeline.onSdp = function(sdp) {
						console.log('onSdp');
						var str = sdp.rawdata.toString();
						console.log(str);
						console.log(sdp);
				     }

					 self.pipeline.onPlay = function(range) {
						 console.log('onPlay: latency: ' + self.m.latency);
						 self.m.playAfterBufferFill = true;
						 //self.pipeline.play();
					 }

				    return true;
				}
				return false;
			}
			
			self.setSource = function (cameraIP, user, pwd, wsport=80, rtspport=554 ) {
				if (cameraIP !== undefined) {
					self.m.wsurl	= 'ws://'+cameraIP+':'+wsport+'/rtsp_over_websocket';
					self.m.rtspurl	= 'rtsp://'+cameraIP+':'+rtspport+'/video1_audio1';
					self.m.user = user;
					self.m.password = pwd;
				}
			
				var isCreated = self.createPipeline();
				if (isCreated) {
					self.wdtries = 0;
					self.play();
				} else {
					self.m.is_opened = false;
					self.stop();
					return undefined;
				}
/*
				if(url != undefined){
					self.m.url = url;
					console.log('Player ' + self.id+' src='+self.m.url+' autostart='+self.m.autostart+' is_opened='+self.m.is_opened);
					if(self.m.url.length > 0 && self.m.autostart){
						self.m.is_opened = true;
						self.module.command('open', url);
					}else{
						self.m.is_opened = false;
						//el_play.style.display = "inline-block";
						//el_stop.style.display = "none";
						//el_loader.style.display = 'none';
						self.stop();
					}
				} else {
					return self.m.url;
				}
*/				
			}

			self.dispose = function(){
				self.player.innerHTML = "";
				delete window.vxgwsplayer.players[self.id];
			}

			self.custom_digital_zoom = function(newval){
				if(newval != undefined){
					if(self.m.isCustomDigitalZoom == false && newval == true){
						self.m.isCustomDigitalZoom = true;
						self.setCustomDigitalZoom(100,0,0); // reset
						self.set_controls_display("");
					}else if(self.m.isCustomDigitalZoom == true && newval == false){
						self.m.isCustomDigitalZoom = false;
						self.set_controls_display("");
						self.setNewZoom(10);
					}
				}else{
					return self.m.isCustomDigitalZoom;
				}
			}
				
			self.setCustomDigitalZoom = function(ratio, x, y){
				if (ratio !== parseInt(ratio, 10) || x !== parseInt(x, 10) || y !== parseInt(y, 10)){
					throw "[vxgwsplayer] setDigitalZoom / Some values is not integer";
				}
				if(ratio < 100 || ratio > 1000/*500*/){ // Truen
					throw "[vxgwsplayer] setDigitalZoom / Parameter Ratio must be 100..500";
				}
				if(self.m.isCustomDigitalZoom != true){
					throw "[vxgwsplayer] setDigitalZoom / Please enable custom digital zoom";
				}
				self.video.style.transform = "scale(" + (ratio/100) + ")";

				el_controls_zoom_position.style.display = "none";
				var s = self.size();
				var newx = x - s.width/2;
				var newy = y - s.height/2;
				var neww = s.width*(100/ratio);
				var newh = s.height*(100/ratio);
				var left = Math.floor(-100*(newx + neww/2)/neww);
				var top = Math.floor(-100*(newy + newh/2)/newh);

				self.video.style.left = left + '%';
				self.video.style.top = top + '%';
			}
			
			self.setNewZoom = function(v){
				if(v >= 30){ v = 30; }
				if(v <= 10){ v = 10; }
				
				if(self.currentZoom != v){
					self.currentZoom = v;

					self.video.style.transform = "scale(" + (ratio/100) + ")";


					el_zoomPositionCursor.style.transform = "scale(" + (10/self.currentZoom) + ")";
					el_zoomProgress.className = el_zoomProgress.className.replace(/zoom\d+x/g,'zoom' + Math.ceil(self.currentZoom) + 'x');
					el_controls_zoom_position.style.display = self.currentZoom == 10 ? "none" : "";

					self.video.style.left = left + '%';
					self.video.style.top = top + '%';

					el_zoomPositionCursor.style.left = '';
					el_zoomPositionCursor.style.top = '';
				}
			}
			
			self.zoomUp = function(){
				self.setNewZoom(self.currentZoom + 5)
			}
			self.zoomDown = function(){
				self.setNewZoom(self.currentZoom - 5);
			}
			self.zoomProgressDownBool = false;
			self.zoomProgressDown = function(e){
				self.zoomProgressDownBool = true;
			}
  
			self.zoomProgressMove = function(e){
				if(self.zoomProgressDownBool == true){
					var y = e.pageY - vxgwsplayer.getAbsolutePosition(e.currentTarget).y;
					var height = el_zoomProgress.offsetHeight;
					var steps = height/5;
					y = 10*(Math.floor((height-y)/steps)/2 + 1);
					self.setNewZoom(y);				
				}
			}
			self.zoomProgressLeave = function(e){
				self.zoomProgressDownBool = false;
			}
			self.zoomProgressUp = function(e){
				if(self.zoomProgressDownBool == true){
					var y = e.pageY - vxgwsplayer.getAbsolutePosition(e.currentTarget).y;
					var height = el_zoomProgress.offsetHeight;
					var steps = height/5;
					y = 10*(Math.floor((height-y)/steps)/2 + 1);
					self.setNewZoom(y);	
				}
				self.zoomProgressDownBool = false;
			}
			
			self.zoomCursorDownBool = false;
			self.zoomCursorX = 0;
			self.zoomCursorY = 0;
			self.zoomCursorWidth = 160;
			self.zoomCursorHeight = 120;
			self.zoomControlsWidth = 0;
			self.zoomControlsHeight = 0;
			self.zoomCursorDown = function(e){
				self.zoomCursorX = e.pageX;
				self.zoomCursorY = e.pageY;
				self.zoomCursorWidth = el_zoomPositionCursor.offsetWidth;
				self.zoomCursorHeight = el_zoomPositionCursor.offsetHeight;
				self.zoomControlsWidth = el_controls_zoom_position.offsetWidth;
				self.zoomControlsHeight = el_controls_zoom_position.offsetHeight;
				self.zoomCursorDownBool = true;
			}
			
			self.zoomCursorUp = function(e){
				console.log("zoomCursorUp");
				self.zoomCursorDownBool = false;
			}
			
			self.zoomCursorMove = function(e){
				if(self.zoomCursorDownBool == true){
					var diffX = self.zoomCursorX - e.pageX;
					var diffY = self.zoomCursorY - e.pageY;
					self.zoomCursorX = e.pageX;
					self.zoomCursorY = e.pageY;
					var newx = el_zoomPositionCursor.offsetLeft - diffX;
					var newy = el_zoomPositionCursor.offsetTop - diffY;
					var d2x = (self.zoomControlsWidth - self.zoomCursorWidth*(10/self.currentZoom));
					var d2y = (self.zoomControlsHeight - self.zoomCursorHeight*(10/self.currentZoom));
					var minX = -1*d2x/2;
					var maxX = d2x/2;
					var minY = -1*d2y/2;
					var maxY = d2y/2;
					if(newx < minX) newx = minX;
					if(newy < minY) newy = minY;
					if(newx >= maxX) newx = maxX;
					if(newy >= maxY) newy = maxY;
					el_zoomPositionCursor.style.left = newx + "px";
					el_zoomPositionCursor.style.top = newy + "px";
					var zoom = self.currentZoom/10 - 1;
					var left = Math.floor(-100*((newx/d2x)*zoom));
					var top = Math.floor(-100*((newy/d2y)*zoom));

					self.video.style.left = left + '%';
					self.video.style.top = top + '%';
				}
			}

			self.setNewZoom = function(v){
				if(v >= 30){ v = 30; }
				if(v <= 10){ v = 10; }
				
				if(self.currentZoom != v){
					self.currentZoom = v;

					self.video.style.transform = "scale(" + (self.currentZoom/10) + ")";
					
					el_zoomPositionCursor.style.transform = "scale(" + (10/self.currentZoom) + ")";
					el_zoomProgress.className = el_zoomProgress.className.replace(/zoom\d+x/g,'zoom' + Math.ceil(self.currentZoom) + 'x');
					el_controls_zoom_position.style.display = self.currentZoom == 10 ? "none" : "";
					self.video.style.left = '';
					self.video.style.top = '';

					el_zoomPositionCursor.style.left = '';
					el_zoomPositionCursor.style.top = '';
				}
			}
			
			self.zoomUp = function(){
				self.setNewZoom(self.currentZoom + 5)
			}
			self.zoomDown = function(){
				self.setNewZoom(self.currentZoom - 5);
			}
			self.zoomProgressDownBool = false;
			self.zoomProgressDown = function(e){
				self.zoomProgressDownBool = true;
			}
  
			self.zoomProgressMove = function(e){
				if(self.zoomProgressDownBool == true){
					var y = e.pageY - vxgwsplayer.getAbsolutePosition(e.currentTarget).y;
					var height = el_zoomProgress.offsetHeight;
					var steps = height/5;
					y = 10*(Math.floor((height-y)/steps)/2 + 1);
					self.setNewZoom(y);				
				}
			}
			self.zoomProgressLeave = function(e){
				self.zoomProgressDownBool = false;
			}
			self.zoomProgressUp = function(e){
				if(self.zoomProgressDownBool == true){
					var y = e.pageY - vxgwsplayer.getAbsolutePosition(e.currentTarget).y;
					var height = el_zoomProgress.offsetHeight;
					var steps = height/5;
					y = 10*(Math.floor((height-y)/steps)/2 + 1);
					self.setNewZoom(y);	
				}
				self.zoomProgressDownBool = false;
			}
			
			self.zoomCursorDownBool = false;
			self.zoomCursorX = 0;
			self.zoomCursorY = 0;
			self.zoomCursorWidth = 160;
			self.zoomCursorHeight = 120;
			self.zoomControlsWidth = 0;
			self.zoomControlsHeight = 0;
			self.zoomCursorDown = function(e){
				self.zoomCursorX = e.pageX;
				self.zoomCursorY = e.pageY;
				self.zoomCursorWidth = el_zoomPositionCursor.offsetWidth;
				self.zoomCursorHeight = el_zoomPositionCursor.offsetHeight;
				self.zoomControlsWidth = el_controls_zoom_position.offsetWidth;
				self.zoomControlsHeight = el_controls_zoom_position.offsetHeight;
				self.zoomCursorDownBool = true;
			}
			
			self.zoomCursorUp = function(e){
				console.log("zoomCursorUp");
				self.zoomCursorDownBool = false;
			}
			
			self.zoomCursorMove = function(e){
				if(self.zoomCursorDownBool == true){
					var diffX = self.zoomCursorX - e.pageX;
					var diffY = self.zoomCursorY - e.pageY;
					self.zoomCursorX = e.pageX;
					self.zoomCursorY = e.pageY;
					var newx = el_zoomPositionCursor.offsetLeft - diffX;
					var newy = el_zoomPositionCursor.offsetTop - diffY;
					var d2x = (self.zoomControlsWidth - self.zoomCursorWidth*(10/self.currentZoom));
					var d2y = (self.zoomControlsHeight - self.zoomCursorHeight*(10/self.currentZoom));
					var minX = -1*d2x/2;
					var maxX = d2x/2;
					var minY = -1*d2y/2;
					var maxY = d2y/2;
					if(newx < minX) newx = minX;
					if(newy < minY) newy = minY;
					if(newx >= maxX) newx = maxX;
					if(newy >= maxY) newy = maxY;
					el_zoomPositionCursor.style.left = newx + "px";
					el_zoomPositionCursor.style.top = newy + "px";
					var zoom = self.currentZoom/10 - 1;
					var left = Math.floor(-100*((newx/d2x)*zoom));
					var top = Math.floor(-100*((newy/d2y)*zoom));
					self.video.style.left = left + '%';
					self.video.style.top = top + '%';
					
				}
			}
			
			var isAudioCapture = false;
			var audioCapture = null;
			
			self.startBackwardAudio =  function () {
				if (self.isAudioCapture) {
					return;
				}

				if (!self.pipeline.rtsp.audioback_chnl) {
					console.warn('backward channel not found in SDP');
					return;
				}

				if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
					console.log('getUserMedia supported.');
					navigator.mediaDevices.getUserMedia ({ audio: true })
					.then(function(stream) {
						var buflen	= 2048;
						var numchannels = 1;
						var rtp_pkt;
						var rtsp_hdr;
						
						self.audioCapture = new AudioContext({ sampleRate: 8000 });
						var input = self.audioCapture.createMediaStreamSource(stream);
						
						var node = (input.context.createScriptProcessor ||
							input.context.createJavaScriptNode).call(input.context,
							    buflen, numchannels, numchannels
							);
						self.isAudioCapture = true;
						input.connect(node);
						node.connect(input.context.destination);
						
						node.onaudioprocess = function(e) {
							if (self.isAudioCapture) {
								var data = e.inputBuffer.getChannelData(0);

							/*
								console.log("got audio buffer: " + e.inputBuffer.length
								+ " for " + e.inputBuffer.duration
								+ " samplerate " + e.inputBuffer.sampleRate
								+ " data: [" + data[0] + "][" + data[1] + "][" + data[2] + "][" + data[3] + "]"
								);
							*/


								if (!rtsp_hdr) {

									//console.log(self.pipeline);
									//console.log(self.pipeline.rtsp);
									//console.log(self.pipeline.rtsp.audioback_chnl);

									// Stream data (such as RTP packets) is encapsulated by an ASCII dollar sign
									// (0x24 hexadecimal), followed by one byte of channel identifier, followed by two bytes of the length of
									// the encapsulated binary data.The stream data follows immediately afterwards.Each $ block contains
									// only one upper layer protocol data unit, such as an RTP packet.
									var len = e.inputBuffer.length + 12;
									rtsp_hdr = new Uint8Array(4);
									rtsp_hdr[0] = 0x24;
									rtsp_hdr[1] = self.pipeline.rtsp.audioback_chnl;
									rtsp_hdr[2] = (len >>> 8);
									rtsp_hdr[3] = (len & 0xFF);
									rtsp_hdr._isBuffer = true;
									rtsp_hdr.data = rtsp_hdr;

									/* RFC3550 http://www.ietf.org/rfc/rfc3550.txt
									V = 2,  // version. always 2 (2 bits)
									P = 0,  // padding. (1 bit)
									X = 0,  // header extension (1 bit)
									CC = 0, // CSRC count (4 bits)
									M = 0,  // marker (1 bit)
									PT = 0, // payload type. (7 bits)
									SN =    // sequence number. SHOULD be random (16 bits)
									TS = 1, // timestamp (32 bits)
									SSRC = 1; // synchronization source (32 bits)
									//CSRC = 0, // contributing sources (32 bits)
									//DP = 0, // header extension, 'Defined By Profile' (16 bits)
									//EL = 0; // header extension length (16 bits)
									*/
									rtp_pkt = new Uint8Array(len);
									rtp_pkt[0] = 0x80;
									rtp_pkt[1] = 0;//ulaw
									var SN = Math.floor(1000 * Math.random());
									rtp_pkt[2] = (SN >>> 8)
									rtp_pkt[3] = (SN & 0xFF);
									rtp_pkt[4] = 0;
									rtp_pkt[5] = 0;
									rtp_pkt[6] = 0;
									rtp_pkt[7] = 1;
									rtp_pkt[8] = 0;
									rtp_pkt[9] = 0;
									rtp_pkt[10] = 0;
									rtp_pkt[11] = 1;
									rtp_pkt._isBuffer = true;
									rtp_pkt.data = rtp_pkt;
								}

								var seq = (rtp_pkt[2] << 8 | rtp_pkt[3]) + 1;
								seq = seq.toUnsigned();
								if (seq <= 65535) {
									rtp_pkt[2] = (seq >>> 8);
									rtp_pkt[3] = (seq & 0xFF);
								}

								var time = (rtp_pkt[4] << 24 | rtp_pkt[5] << 16 | rtp_pkt[6] << 8 | rtp_pkt[7]) + rtp_pkt.length;
								time = time.toUnsigned();
								if (time <= 4294967295) {
									rtp_pkt[4] = (time >>> 24);
									rtp_pkt[5] = (time >>> 16 & 0xFF);
									rtp_pkt[6] = (time >>> 8 & 0xFF);
									rtp_pkt[7] = (time & 0xFF);
								}

								self.pipeline._src.outgoing.write(rtsp_hdr);
								self.pipeline._src.outgoing.write(rtp_pkt);

								const BIAS = 0x84;
								const CLIP = 32635;

								for (var i = 0; i < e.inputBuffer.length; ++i) {
									var pcm_16_sample = Math.min(1, data[i]) * 0x7FFF;
									var sign = (pcm_16_sample >> 8) & 0x80;
									if (sign != 0) pcm_16_sample = -pcm_16_sample;
									pcm_16_sample = pcm_16_sample + BIAS;
									if (pcm_16_sample > CLIP) pcm_16_sample = CLIP;
									var exponent = ulawencodeTable[(pcm_16_sample >> 7) & 0xFF];
									var mantissa = (pcm_16_sample >> (exponent + 3)) & 0x0F;
									rtp_pkt[i+12] = ~(sign | (exponent << 4) | mantissa);
								}

								self.pipeline._src.outgoing.write(rtsp_hdr);
								self.pipeline._src.outgoing.write(rtp_pkt);

							}
						}
						
					}).catch(function(err) {
						console.log('The following getUserMedia error occured: ' + err);
					});
				} else {
					console.log('getUserMedia not supported on your browser!');
				}
			}
			
			self.stopBackwardAudio = function() {
				self.isAudioCapture = false;
				self.audioCapture = null;
			}
			
			function wait(delayInMS) {
			     return new Promise(resolve => setTimeout(resolve, delayInMS));
			}
			
			function mergeTypedArrays(a, b) {
				if(!a && !b) throw 'Please specify valid arguments for parameters a and b.';  
				if(!b || b.length === 0) return a;
				if(!a || a.length === 0) return b;
				if(Object.prototype.toString.call(a) !== Object.prototype.toString.call(b))
				throw 'The types of the two arguments passed for parameters a and b do not match.';
				var c = new a.constructor(a.length + b.length);
				c.set(a);
				c.set(b, a.length);
				return c;
			}
			
			self.onRecord = null;

            self.ffmpeg_worker = null;
			
			self.startRecord = function( ms_length = 5000){
                isRecStarted = false;
				if (isRecording || isRecStopping) return;
				
                self.ffmpeg_worker = new Worker('/vxgwsplayer/ffmpeg/ffmpeg-worker-mp4.js');

				clearTimeout(record_wd);
				prevRecLen = 0;
				
				self.recordedData264	= new Uint8Array(self.rech264maxsize);  
				self.recordedData711	= new Uint8Array(self.recg711maxsize);  
				recData264Len = 0;
				recData711Len = 0;

				if (firstPackage != null) {
					self.recordedData264.set(firstPackage, recData264Len);
					recData264Len += firstPackage.length;
				}

				isRecording = true;
				isRecStopping = false;

				if (self.onRecord !== null) {
					var args = {};
					args.isRecording = isRecording;
					args.isRecStopping = isRecStopping;
					self.onRecord(args);
				}

				if (ms_length > 0) {
					setTimeout(function(){
						self.stopRecord();
					}, ms_length);
				}

				function recordWd () {
					clearTimeout(record_wd);
					if (!isRecording) return;
					if (!isRecStarted || recData264Len > prevRecLen) {
						prevRecLen = recData264Len;
						console.log('record watchdog: OK - record in process');
						record_wd = setTimeout(function(){recordWd();}, 5000);
					} else {
						if (record_try > 3) {
							console.warn('Early record save before expected time due record stream error or allocated memmory is over');
							self.stopRecord();
							record_try = 0;
						} else {
							console.log('record watchdog: may be error  (recdata:'+recData264Len+'<= prevrecorded:'+prevRecLen+'<= maxsize:'+self.rech264maxsize+'): - check in process ');
							record_try += 1;
							record_wd = setTimeout(function(){recordWd();}, 5000);
						}
					}
				}
				record_wd = setTimeout(function(){recordWd();}, 5000);
			}

			self.stopRecord = async  function stopRecord() {
				if (isRecording == false || isRecStopping == true) {
					return;
				}
				isRecording = false;
                isRecStarted = false;
				isRecStopping = true;
				if (self.onRecord !== null) {
					var args = {};
					args.isRecording = isRecording;
					args.isRecStopping = isRecStopping;
					self.onRecord(args);
				}

				//const OUT_FILE_NAME = self.defaultClipName + '.avi'; 
				const OUT_FILE_NAME = self.defaultClipName + '.mkv'; 
				//const OUT_FILE_NAME = self.defaultClipName + '.mov'; 
				var data = self.recordedData264.subarray(0, recData264Len);

				var outfile;

				self.ffmpeg_worker.onmessage = function(arg) {
					var msg = arg.data;
					switch (msg.type) {
						case "stdout":
							console.log("ffmpeg: stdout: " + msg.data);
							break;
						case "stderr":
							console.log("ffmpeg: stderr: " + msg.data);
							break;
						case "exit":
							console.log("Process exited with code " + msg.data);
							break;
						case 'done': {
							//const blob = new Blob([msg.data.MEMFS[0].data], {type: "video/avi"});
							const blob = new Blob([msg.data.MEMFS[0].data], {type: "video/mkv"});
							//const blob = new Blob([msg.data.MEMFS[0].data], {type: "video/mov"});

							let a = document.createElement("a");
							//document.body.appendChild(a);
							a.style.display = 'none';
                            let url = URL.createObjectURL(blob);
							a.href = url; 
							a.download = OUT_FILE_NAME;
							a.click();

                            window.URL.revokeObjectURL(url);

							delete self.recordedData264; self.recordedData264 = null;
							delete self.recordedData711; self.recordedData711 = null;
							recData264Len = 0;
							recData711Len = 0;

							isRecStopping = false;
							if (self.onRecord !== null) {
								var args = {};
								args.isRecording = isRecording;
								args.isRecStopping = isRecStopping;
								self.onRecord(args);
							}

                            setTimeout(function () {
                                if (self.ffmpeg_worker != null) {
                                    self.ffmpeg_worker.terminate();
                                    self.ffmpeg_worker = null;
                                }
                            }, 0);

						} break;
					}
				};

				if(self.recordedData711.length>0){
					var data711 = self.recordedData711.subarray(0, recData711Len);
					self.ffmpeg_worker.postMessage({
						type: 'run',
						TOTAL_MEMORY: 581510912, // 268435456,
//						arguments: ['-formats']
						//avi
						//arguments: ['-r','30', '-i', 'input.mp4', '-f', 'mulaw', '-ar', '8000', '-ac', '1', '-i', 'input.ulaw', '-vcodec', 'copy', '-acodec', 'copy', OUT_FILE_NAME],
						//mkv
						arguments: ['-i', 'input.mp4', '-f', 'mulaw', '-ar', '8000', '-ac', '1', '-i', 'input.ulaw', '-vcodec', 'copy', '-acodec', 'copy', OUT_FILE_NAME],
						//mov
						//arguments: ['-i', 'input.mp4', '-f', 'mulaw', '-ar', '8000', '-ac', '1', '-i', 'input.ulaw', '-vcodec', 'copy', '-acodec', 'copy', OUT_FILE_NAME],
						MEMFS: [{name: 'input.mp4', data: data}, {name: 'input.ulaw', data: data711 }],
					});
				}else{
					self.ffmpeg_worker.postMessage({
						type: 'run',
						TOTAL_MEMORY: 581510912, // 268435456,
						//avi
						//arguments: ['-r','30', '-i', 'input.mp4', '-vcodec', 'copy', '-acodec', 'copy', OUT_FILE_NAME],
						//mkv
						arguments: ['-i', 'input.mp4', '-vcodec', 'copy', '-acodec', 'copy', OUT_FILE_NAME],
						//mov
						//arguments: ['-i', 'input.mp4', '-vcodec', 'copy', '-acodec', 'copy', OUT_FILE_NAME],
						
						MEMFS: [{name: 'input.mp4', data: data}],
					});
				}
                /*
				if(self.recordedData711.length>0){
					var data711 = self.recordedData711.subarray(0, recData711Len);

					var file2 = { name:IN_FILE_NAME_ULAW, data:data711}
					const args = ['-i', IN_FILE_NAME_264, '-f', 'mulaw', '-ar', '8000', '-ac', '1', '-i', IN_FILE_NAME_ULAW, '-vcodec', 'copy', '-acodec', 'copy', OUT_FILE_NAME];
					var ret = await runFFmpeg(IN_FILE_NAME_264, data, args, OUT_FILE_NAME, [ file2 ] );
					
					data = null;
					data711 = null;
					
					ret.Core.PThread.ih();
					delete ret.Core;
					
					outfile = ret.file;
				}
				else{
					const args = ['-i', IN_FILE_NAME_264, '-vcodec', 'copy', '-acodec', 'copy', OUT_FILE_NAME];
					const ret = await runFFmpeg(IN_FILE_NAME_264, data, args, OUT_FILE_NAME );

					data = null;

					ret.Core.PThread.ih();
					delete ret.Core;

					outfile = ret.file;
				}

				let a = document.createElement("a");
				document.body.appendChild(a);
				a.style.display = 'none';
				a.href = URL.createObjectURL(new Blob([outfile.buffer], { type: 'video/mp4' }));
				a.download = OUT_FILE_NAME;
				a.click();

				delete self.recordedData264; self.recordedData264 = null;
				delete self.recordedData711; self.recordedData711 = null;
				recData264Len = 0;
				recData711Len = 0;
				
				isRecStopping = false;
				if (self.onRecord !== null) {
					var args = {};
					args.isRecording = isRecording;
					args.isRecStopping = isRecStopping;
					self.onRecord(args);
				}
                */
			}
			
			self.recordStatus = function() {
				if (isRecording == true) {
					var ret = {};
					ret.recording = true;
					ret.recStopping = false;
					ret.recData264Len = recData264Len;
					ret.recData711Len = recData711Len;
					return ret;
				} else if (isRecording == false && isRecStopping) {
					var ret = {};
					ret.recording	= false;
					ret.recStopping = true;
					ret.recData264Len = recData264Len;
					ret.recData711Len = recData711Len;
					return ret;
				} else {
					var ret = {};
					ret.recording	= false;
					ret.recStopping	= false;
					ret.recData264Len = recData264Len;
					ret.recData711Len = recData711Len;
					return ret;
				}
			}

			el_play.onclick = self.play;
			el_pause.onclick = self.pause;
			el_stop.onclick = self.stop;
			el_fullscreen.onclick = self.fullscreen;
			el_takescreenshot.onclick = self.takescreenshot;
			el_zoomUp.onclick = self.zoomUp;
			el_zoomDown.onclick = self.zoomDown;
			el_zoomPositionCursor.addEventListener('mousedown',self.zoomCursorDown,false);
			el_zoomPositionCursor.addEventListener('mousemove',self.zoomCursorMove,false);
			el_zoomPositionCursor.addEventListener('mouseleave',self.zoomCursorUp,false);
			el_zoomPositionCursor.addEventListener('mouseup',self.zoomCursorUp,false);
			el_zoomProgress.addEventListener('mousedown',self.zoomProgressDown,false);
			el_zoomProgress.addEventListener('mousemove',self.zoomProgressMove,false);
			el_zoomProgress.addEventListener('mouseleave',self.zoomProgressLeave,false);
			el_zoomProgress.addEventListener('mouseup',self.zoomProgressUp,false);
		}(id, options_);
		window.vxgwsplayer.players[id].playerDidLoad();
	}else{
		// console.warn(id + " -  already exists player");
	}

	return window.vxgwsplayer.players[id];
};

window.vxgwsplayer.webserverport = '8778';

window.vxgwsplayer.isFrame = function() {
	try { 
	    return window.self !== window.top; 
	} catch (e) { 
	    return true; 
	}
}

/*
window.vxgwsplayer.browserSupportsPluginPnacl = function() {
	return navigator.mimeTypes['application/x-pnacl'] !== undefined &&
		navigator.mimeTypes['application/vxg_media_player'] !== undefined;
}
*/

window.vxgwsplayer.showGlobalErrorMessage = function(id, html){
	var player = document.getElementById(id);
	var width=640;
	var height=480;
	width = parseInt(player.width,10) || width;
	height = parseInt(player.height,10) || height;
	player.style.width = width + 'px';
	player.style.height = height + 'px';
	player.innerHTML = html;
	return undefined;
}

window.vxgwsplayer.showNotInstalled = function(id){
	vxgwsplayer.showGlobalErrorMessage(id, ''
		+ '<div class="vxgwsplayer-unsupport">'
		+ '	<div class="vxgwsplayer-unsupport-content">'
		+ '	<a href="https://www.videoexpertsgroup.com/player_start/" ' + (vxgwsplayer.isFrame() ? 'target="_blank"' : '')+ '>Click here for install plugin</a>'
		+ '	<br/><br/> or visit in webstore <a href="https://chrome.google.com/webstore/detail/vxg-media-player/hncknjnnbahamgpjoafdebabmoamcnni" target="_blank">VXG Media Player</a>'
		+ '	</div>'
		+ '</div>');
}

window.vxgwsplayer.showAvailableInChrome = function(id){
	vxgwsplayer.showGlobalErrorMessage(id, ''
		+ '<div class="vxgwsplayer-unsupport">'
		+ '	<div class="vxgwsplayer-unsupport-content">'
		+ ' Available in <a href="https://www.google.com/chrome/" target="_blank">Chrome for Desktop PC only</a>'
		+ '	</div>'
		+ '</div>');
}

window.vxgwsplayer.showWebSocketFailed = function(id){
	vxgwsplayer.showGlobalErrorMessage(id, ''
		+ '<div class="vxgwsplayer-unsupport">'
		+ '	<div class="vxgwsplayer-unsupport-content">'
		+ ' Could not connect to plugin (WebSocket Error). Please try restart your browser.'
		+ '	</div> '
		+ '</div>');
}

window.vxgwsplayer.showNotInstalledInIncognitoMode = function(id){
	vxgwsplayer.showGlobalErrorMessage(id, ''
		+ '<div class="vxgwsplayer-unsupport">'
		+ '	<div class="vxgwsplayer-unsupport-content"> You have opened this page in incognito mode. Please open it in a regular tab, install the plugin and then come back.'
		+ '	<br/><br/> Also you can install <a href="https://chrome.google.com/webstore/detail/vxg-media-player/hncknjnnbahamgpjoafdebabmoamcnni" target="_blank">VXG Media Player</a> from webstore in regular tab.'
		+ '	</div>'
		+ '</div>');
}

window.vxgwsplayer.showNotStartedInIncognitoMode = function(id){
	vxgwsplayer.showGlobalErrorMessage(id, ''
		+ '<div class="vxgwsplayer-unsupport">'
		+ '	<div class="vxgwsplayer-unsupport-content"> You have opened this page in incognito mode. Please open it in a regular tab and then come back.'
		+ '	</div>'
		+ '</div>');
}

window.vxgwsplayer.startPlayerInNewTab = function(){
	console.log('start player');
	var params = "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes"
	var win = window.open('https://www.videoexpertsgroup.com/player_start/', "_blank", params)
	setTimeout(function(){
		win.close();
		location.reload();
		/*for(var id in window.vxgwsplayer.players){
			console.log("Restart player: " + id);
		}*/
	},1000);
}

window.vxgwsplayer.showNotStartedInIFrame = function(id){
	vxgwsplayer.showGlobalErrorMessage(id, ''
		+ '<div class="vxgwsplayer-unsupport">'
		+ '	<div class="vxgwsplayer-unsupport-content"> You have opened this page in frame.<br>'
		+ ' Please click on <a href="javascript:void(0);" onclick="window.vxgwsplayer.startPlayerInNewTab();">this link</a> for start Chrome App (VXG Media Player).'
		+ '	</div>'
		+ '</div>');
}

window.vxgwsplayer.showInitFailed = function(id, str){
	vxgwsplayer.showGlobalErrorMessage(id, ''
		+ '<div class="vxgwsplayer-unsupport">'
		+ '	<div class="vxgwsplayer-unsupport-content">'
		+ ' Chrome plugin init error. Try to update Video/Audio drivers. '
		+ str
		+ '	</div> '
		+ '</div>');
}

window.vxgwsplayer.Promise = function(){
	var completed = false;
	
	this.done = function(callback){
		this.done_callback = callback;
		if(this.completed){
			this.done_callback(this.err);
		}
		return this;
	}
	
	this.fail = function(callback){
		this.fail_callback = callback;
		if(this.completed){
			this.fail_callback(this.err);
		}
		return this;
	}
	
	this.resolve = function(result) {
		if(!this.completed){
			this.result = result;
			this.done_callback(result);
		}
		this.completed = true;
	}
	this.reject = function(err) {
		if(!this.completed){
			this.err = err;
			this.fail_callback(err);
		}
		this.completed = true;
	}
};

/*
window.vxgwsplayer.checkWebSocket = function(){
	var p = new vxgwsplayer.Promise();
	setTimeout(function(){
		var	testWebSocket = new WebSocket('ws://127.0.0.1:' + vxgwsplayer.webserverport + '/');
		testWebSocket.onerror=function(event){
			p.reject();
		}
		testWebSocket.onmessage = function(evt){
			console.log(evt);
		}
		testWebSocket.onopen=function(){
			testWebSocket.close();
			p.resolve();
		}
	},60000);
	return p;
}
*/

// helper funxtion
window.vxgwsplayer.getAbsolutePosition = function(element){
	var r = { x: element.offsetLeft, y: element.offsetTop };
	if (element.offsetParent) {
	var tmp = vxgwsplayer.getAbsolutePosition(element.offsetParent);
		r.x += tmp.x;
		r.y += tmp.y;
	}
	return r;
};

// init options
window.vxgwsplayer.initOptions = function(self, options){
	self.options = options || {};
	self.m = {};
	self.m.wsurl = self.player.getAttribute('wsurl') || self.options.wsurl || "";
	self.m.rtspurl = self.player.getAttribute('rtspurl') || self.options.rtspurl || "";
	self.m.user = self.player.getAttribute('user') || self.options.user || "";
	self.m.password = self.player.getAttribute('password') || self.options.password || "";

/*	
	var nmf_path = "media_player.nmf";
	var nmf_src = "pnacl/Release/media_player.nmf";
	self.m.nmf_path = self.player.getAttribute('nmf-path') || self.options.nmf_path || nmf_path;
	self.m.nmf_src = self.player.getAttribute('nmf-src') || self.options.nmf_src || nmf_src;
*/	
	self.m.autostart_parameter = self.player.hasAttribute('autostart') ? '1' : '0';
	self.m.audio_parameter = (self.player.hasAttribute('audio') && parseInt(self.player.getAttribute('audio', 10)) == 0) ? '0' : '1';
	self.m.mute_parameter = (self.player.hasAttribute('mute') && parseInt(self.player.getAttribute('mute', 10)) == 1) ? '1' : '0';
}

// generate embeded element for nacl player
/*
window.vxgwsplayer.generateEmbededElement = function(self){
	return ''
	+ '<embed class="vxgwsplayer-module" style="transform: scale(1); z-index: -1;" '
	+ '		autostart_parameter="' + self.m.autostart_parameter + '"'
	+ '		mute_parameter="' + self.m.mute_parameter + '" '
	+ '		audio_parameter="' + self.m.audio_parameter + '" ' 
	+ '		name="nacl_module" '
	+ '		id="' + self.id + '_nacl_module" '
	+ '		path="' + self.m.nmf_path + '" '
	+ '		src="' + self.m.nmf_src + '" '
	+ '		url="' + self.m.url + '" '
	+ '		type="application/x-pnacl" />';
}
*/

class audioBufferPCM {
    constructor(audioCtx, audioGain, sampleRate, bufferSize = 10) {
        this.ctx = audioCtx;
        this.gain = audioGain;
        this.sampleRate = sampleRate;
        this.bufferSize = bufferSize;
        this.chunks = [];
        this.isPlaying = false;
        this.startTime = 0;
        this.lastChunkOffset = 0;
    }
    clear() {
        this.chunks.splice(0, this.chunks.length);
    }
    createChunk(chunk) {
        var audioBuffer = this.ctx.createBuffer(1, chunk.length, this.sampleRate);
        audioBuffer.getChannelData(0).set(chunk);
        var source = this.ctx.createBufferSource();
        
        source.buffer = audioBuffer;
        source.connect(this.gain);
        source.onended = (e) => {
            this.chunks.splice(this.chunks.indexOf(source), 1);
            if (this.chunks.length == 0) {
                this.isPlaying = false;
                this.startTime = 0;
                this.lastChunkOffset = 0;
            }
        };
        return source;
    }
    addBuffer(data) {
        if (this.isPlaying && (this.chunks.length > this.bufferSize)) {
            console.warn("audio chunk discarded");
            return;
        } else if (this.isPlaying && (this.chunks.length <= this.bufferSize)) { // schedule & add right now
            var chunk = this.createChunk(data);
            chunk.start(this.startTime + this.lastChunkOffset);
            this.lastChunkOffset += chunk.buffer.duration;
            this.chunks.push(chunk);
            chunk = null;
        } else if ((this.chunks.length < (this.bufferSize / 2)) && !this.isPlaying) { // add & don't schedule
            var chunk = this.createChunk(data);
            this.chunks.push(chunk);
            chunk = null;
        } else { // add & schedule entire buffer
            this.isPlaying = true;
            var chunk = this.createChunk(data);
            this.chunks.push(chunk);
            chunk = null;
            this.startTime = this.ctx.currentTime;
            this.lastChunkOffset = 0;
            for (let i = 0; i < this.chunks.length; i++) {
                var chunk = this.chunks[i];
                chunk.start(this.startTime + this.lastChunkOffset);
                this.lastChunkOffset += chunk.buffer.duration;
            }
        }
    }
}

// init volumes
window.vxgwsplayer.initVolumeControls = function(self, onloadsettings){
	self.m.volume = self.options.volume || 0.7;
	var el_volumeMute = self.player.getElementsByClassName('vxgwsplayer-volume-mute')[0];
	var el_volumeDown = self.player.getElementsByClassName('vxgwsplayer-volume-down')[0];
	var el_volumeProgress = self.player.getElementsByClassName('vxgwsplayer-volume-progress')[0];
	var el_volumeUp = self.player.getElementsByClassName('vxgwsplayer-volume-up')[0];
	
	if(self.player.hasAttribute('volume')){
		self.m.volume = parseFloat(self.player.getAttribute('volume'));
		self.m.volume = Math.ceil(self.m.volume*10)/10;
		if(onloadsettings){
/*
			self.module.command('setvolume', self.m.volume.toFixed(1));
*/			
		}
	}else if(self.options.volume){
		console.warn("TODO volume");
	}

	self.m.mute = self.video.hasAttribute('muted') || self.options.mute || self.m.volume == 0 || self.m.mute_parameter == '1';
	if(self.m.mute){
		el_volumeDown.style.display='none';
		el_volumeProgress.style.display='none';
		el_volumeUp.style.display='none';
	}
	
	el_volumeProgress.className = el_volumeProgress.className.replace(/vol\d+/g,'vol' + Math.ceil(self.m.volume*10));

	self.isMute = function(){
		return self.m.mute;
	}

	self.applyVolume = function(){
		if(self.isMute()){
			el_volumeDown.style.display='none';
			el_volumeProgress.style.display='none';
			el_volumeUp.style.display='none';
			el_volumeProgress.className = el_volumeProgress.className.replace(/vol\d+/g,'vol0')
		}else{
			el_volumeDown.style.display='inline-block';
			el_volumeProgress.style.display='inline-block';
			el_volumeUp.style.display='inline-block';
			el_volumeProgress.className = el_volumeProgress.className.replace(/vol\d+/g,'vol' + Math.floor(self.m.volume*10));
		}
/*		
		self.module.command('setvolume', self.m.mute? '0': '' + self.m.volume.toFixed(1));
*/		
		if (self.isMute()){
		    self.video.setAttribute('muted', true);
		    self.video.muted = true;
		    if (self.audioGain != null && self.audioGain !== undefined && self.audioCtx != null && self.audioCtx !== undefined) {
			self.audioGain.gain.setValueAtTime(0, self.audioCtx.currentTime);
		    }
		} else {
		    self.video.removeAttribute('muted');
		    self.video.muted = false;		    
		    self.video.setAttribute('volume', self.m.volume.toFixed(1));
		    self.video.volume = self.m.volume.toFixed(1);
		    if (self.audioGain != null && self.audioGain !== undefined && self.audioCtx != null && self.audioCtx !== undefined) {
			self.audioGain.gain.setValueAtTime( self.m.volume, self.audioCtx.currentTime);
		    }
		}
//		self.video.volume = self.m.mute? '0': '' + self.m.volume.toFixed(1)
		if (self.audioCtx !== undefined || self.audioCtx != null) {
		    self.audioCtx.resume();
		}
	}

	self.mute = function(){
		self.restartTimeout();
		self.m.mute = !self.m.mute;
		self.applyVolume();
	}

	self.volume = function(val){
		if(val != undefined){
			val = val > 1 ? 1 : val;
			val = val < 0 ? 0 : val;
			self.m.volume = Math.ceil(val*10)/10;
			self.applyVolume();
		}else{
			return self.m.volume;
		}
	}

	self.volup = function(){
		self.restartTimeout();
		if(Math.round(self.m.volume*10) < 10){
			self.m.volume = self.m.volume + 0.1;
			self.applyVolume();
		}
	};

	self.voldown = function(){
		self.restartTimeout();
		if(Math.round(self.m.volume*10) > 0){
			self.m.volume = self.m.volume - 0.1;
			self.applyVolume();
		}
	};

	el_volumeMute.onclick = self.mute;
	el_volumeDown.onclick = self.voldown;
	el_volumeUp.onclick = self.volup;
}

document.addEventListener('DOMContentLoaded', function() {

	console.log("vxgwsplayer isFrame: " + window.vxgwsplayer.isFrame());
//	console.log("vxgwsplayer browserSupportsPluginPnacl: " + window.vxgwsplayer.browserSupportsPluginPnacl());

	// search all vxgwsplayers
	var els = document.getElementsByClassName("vxgwsplayer");
	for (var i = 0; i < els.length; i++) {
		if(els[i].id){
			vxgwsplayer(els[i].id);
		}else{
			console.error("Player has not id", els[i]);
		}
	}

	
	// TODO check ws
	// TODO start Chrome App
});
