API Docs for:
Show:

File: generic-playlist.js

/*******************************************************************************
 * Copyright (c) 2017 Genialist Software Ltd.
 * All rights reserved.
 ******************************************************************************/

var m_choose_subtitles_text = "Choose the Sub-Titles:";
var m_none_text = "None";

var m_resume_text_before = "Resume from ";
var m_resume_text_after = "";
var m_begining_text = "Play from beginning";
var m_choose_lang_text = "Choose the Language:";

var record_fullscreen = true;

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pPlaylistUtil = new (function () {
	
	this.playTimeCookieName = function(p_media) {
		return 'p-'+pLocation.path(p_media.doc_url || ((p_media.media)? p_media.media.doc_url : null) || p_media.request_url).substring(url_prefix.length);
	};
	
	this.highlight = function(p_element_id, p_set) {
		if (p_set===true)
			pElement.addClassName(p_element_id, 'playlist-highlight');
		else
			pElement.removeClassName(p_element_id, 'playlist-highlight-error playlist-highlight');
	};
	this.highlightError = function(p_element_id) {
		pElement.addClassName(p_element_id, 'playlist-highlight-error');
	};
	this.options = {
		OPTION_CHAPTERS: 			pROSE.newStringProp('pl-chapters', [ 'auto', '1', '0' ]),
		OPTION_AUDIO_TRACKS:		pROSE.newStringProp('pl-audioTracks', [ 'auto', '1', '0' ]),
		
		OPTION_YOUTUBE_SHOW_RELATED: pROSE.newBooleanProp('youtube-show-related'),

		OPTION_DEVICE_LOCAL: 		pROSE.newStringProp('device-local', (pLocation.isLocalhost())? 'auto' : 'never'),
		OPTION_DEVICE_TRANSCODE: 	pROSE.newStringProp('device-transcode', [ 'auto', '1', '0' ]),

		OPTION_PLUGIN_HLS_ENABLED:  pROSE.newBooleanProp('plugin-hlsjs-enabled', true),
		//OPTION_PLUGIN_VLC_ENABLED: { name: 'plugin-vlc-enabled', type: 'boolean', vdefault: false },//pDevice.isFirefox() },
		//OPTION_PLUGIN_WMP_ENABLED: 	pROSE.newBooleanProp('plugin-wmp-enabled'),

		OPTION_REPORT_TIME: 		pROSE.newBooleanProp('sync-report-time-videos', pDevice.isFirefox() || pDevice.isChrome()),
		OPTION_DEVICE_SCALE_VIDEOS: pROSE.newBooleanProp('scale-videos', true),
		OPTION_VIDEO_NOHTTPS: 		pROSE.newBooleanProp('videos-nohttps', pDevice.isIOS()),

		OPTION_WEBVTT: 				pROSE.newBooleanProp('webvtt'),

		OPTION_LOOP_PLAYLIST: 		pROSE.newBooleanProp('playlist-loop-playlist', true),
		OPTION_LOOP_TRACK: 			pROSE.newBooleanProp('playlist-loop-track'),
		OPTION_SHOW_VIDEOS: 		pROSE.newBooleanProp('playlist-show-video'),
		OPTION_SHUFFLE: 			pROSE.newBooleanProp('playlist-shuffle'),
		
		OPTION_NOSLEEP:				pROSE.newBooleanProp('nosleep', pDevice.isAndroid() || pDevice.isIOS()),
		OPTION_CANPLAY_PLAY:		pROSE.newBooleanProp('canplay-play'),
		
		OPTION_PREFER_VO:			pROSE.newBooleanProp('prefer-vo', true),
		OPTION_ONERROR_NEXT:		pROSE.newBooleanProp('playlist-onerror-next', true),
		
		OPTION_FULLSCREEN:			pROSE.newBooleanProp('fs'),
		
		OPTION_CHANGE_DOCTITLE:		pROSE.newBooleanProp('change-doctitle', true),
		
		OPTION_MAX_VIDEO_BITRATE:	{ name: 'vbitrate', type: 'int', vdefault: 1000 },
		
		OPTION_MOVIE_TRAILERS:		pROSE.newBooleanProp('trailers'),// !pDevice.isMobile())
		
		OPTION_PREFER_STREAMING:	pROSE.newBooleanProp('prefer-streaming', true)
	};
	
	//*** INIT OPTIONS
	for(var i_option in this.options) {
		var p = this.options[i_option];
		pROSE.getProp(p);
		p.get = (function() { return pROSE.getProp(this); }).bind(p);
		p.set = (function(s) { pROSE.setProp(this, s); }).bind(p);
		
		Object.defineProperty(this, i_option, { get: p.get, set: p.set });
	}
});

//keep for vlc
pPlaylistUtil.options_controls = [ pPlaylistUtil.options.OPTION_LOOP_PLAYLIST, pPlaylistUtil.options.OPTION_LOOP_TRACK, pPlaylistUtil.options.OPTION_SHOW_VIDEOS, pPlaylistUtil.options.OPTION_SHUFFLE ];

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const noSleep_media = 'data:video/mp4;base64,AAAAIGZ0eXBtcDQyAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACKBtZGF0AAAC8wYF///v3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0MiByMjQ3OSBkZDc5YTYxIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTEgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9MiBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0wIHRocmVhZHM9NiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MSBrZXlpbnQ9MzAwIGtleWludF9taW49MzAgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD0xMCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIwLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IHZidl9tYXhyYXRlPTIwMDAwIHZidl9idWZzaXplPTI1MDAwIGNyZl9tYXg9MC4wIG5hbF9ocmQ9bm9uZSBmaWxsZXI9MCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAA3//p+C7v8tDDSTjf97w55i3SbRPO4ZY+hkjD5hbkAkL3zpJ6h/LR1CAABzgB1kqqzUorlhQAAAAxBmiQYhn/+qZYADLgAAAAJQZ5CQhX/AAj5IQAD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';

//Detect iOS browsers < version 10
const oldIOS = typeof navigator !== 'undefined' && parseFloat(
  ('' + (/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0, ''])[1])
    .replace('undefined', '3_2').replace('_', '.').replace('_', '')
) < 10 && !window.MSStream

const noSleep = new (function() {
	var that = this, noSleepTimer, noSleepVideo;
	
    if (oldIOS) {
      //this.noSleepTimer = null
    } else {
      // Set up no sleep video element
      noSleepVideo = document.createElement('video')

      noSleepVideo.setAttribute('playsinline', '')
      noSleepVideo.setAttribute('src', noSleep_media)

      noSleepVideo.addEventListener('timeupdate', function (e) {
        if (noSleepVideo.currentTime > 0.5) {
        	noSleepVideo.currentTime = Math.random()
        }
      });
    }

  this.enable = function() {
    if (oldIOS) {
      that.disable()
      noSleepTimer = window.setInterval(function () {
        window.location.href = '/'
        window.setTimeout(window.stop, 0)
      }, 15000)
    } else {
      noSleepVideo.play()
    }
  };

  this.disable = function() {
    if (oldIOS) {
      if (noSleepTimer) {
        window.clearInterval(noSleepTimer)
        noSleepTimer = null
      }
    } else {
      noSleepVideo.pause()
    }
  };
});

function enableNoSleep() {
  noSleep.enable();
  document.removeEventListener('click', enableNoSleep, false);
}

function disableNoSleep() {
	noSleep.disable();
}

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pMedia = new (function() {
	
	this.externalPlay = function(m) {
		if (m && m.provided===true)
			if (!m.metadata_provider || ![ 'youtube', '(Unknown)', '(Radio)' ].includes(m.metadata_provider))
				return m.url && pLocation.path(m.url).indexOf(url_prefix)<0;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pTrackListUtil = new (function () {
	var that = this;
	
	function f_isVideo(t) { return t.type == 'v'; }
	function f_isAudio(t) { return t.type == 'a'; }
	function f_isExternalSubtitle(t) { return t.type == 's' && t.filename; }
	
	this.isSubtitle = function(t) { 
		return t.type == 's'; 
	};
	
	this.isSupportedSubtitle = function(t) { 
		var c = t.metadata_format.toLowerCase(); 
		return c.indexOf('vobsub')<0 && c.indexOf('dvdsub')<0 && c.indexOf('mp4s')<0;
	};
	
	this.tracks = function(media) {
		return (media.tracks && media.tracks != "null")? media.tracks : [];
	};
	
	this.getVideoCodec = function(l, p_default) {
		var t = (l || []).find(f_isVideo);
		return t? pString.substring_after(t.metadata_format, 'V_') : p_default;
	};
	
	this.getAudioCodec = function(l, p_default) {
		var t = (l || []).find(f_isAudio);
		return t? pString.substring_after(t.metadata_format, 'A_') : p_default;
	};
	
	this.countAudioTracks =  function(l) {
		return l? l.filter(f_isAudio).length : 0;
	};

	this.countSubtitleTracks = function(l, s) {
		if (s) return l? l.filter(that.isSubtitle).filter(that.isSupportedSubtitle).length : 0;
		return l? l.filter(that.isSubtitle).length : 0;
	};

	this.countVideoTracks =  function(l) {
		return l? l.filter(f_isVideo).length : 0;
	};

	this.countExternalSubtitleTracks = function(l) {
		return l? l.filter(f_isExternalSubtitle).length : 0;
	};

	this.shortName = function(t, d) {
		var r = "";
		if (t.name && t.name.toLowerCase() != "null")
			r += t.name;
		return r.length<1? (d || '(Unknown)') : r;
	};
	
	this.longName = function(t, l, d) {
		var r = that.shortName(t, d), sn = (l || []).find(function(tt) { return tt != t && tt.type == t.type && that.shortName(tt, d) == r})

		if (sn && t.metadata_lang) {
			var l = pLang.display(t.metadata_lang);
			if (l != r) {
				r += ((r != "")? " - ":"");
				r += l;
			}
			//r += ((r != "")? " - ":"");
			//r += pLang.display(t.metadata_lang) + ((t.forced === true)? " (forced)":"");
		}
		return r.length<1? '(Unknown)' : r;
	};
	
	this.longLangName = function(t) {
		if (t && t.metadata_lang)
			return pLang.display(t.metadata_lang);// + ((t.forced === true)? " (forced)":"");
	};
	
	/** @param ht Video or Audio track from <video> or <audio> element */
	this.displayAudioTrack = function(ht, i, m) {
		var t = that.tracks(m).filter(f_isAudio), a = (i>=0 && i<t.length)? t[i] : null;
		if (pString.v(ht.label)) {
			var l = pLang.display(pString.sv(ht.language, that.longLangName(a)))
			return ht.label + (pString.v(l)? ' - '+l:'');
		}
		return a? that.longName(a, t, '') : '(Unknown)';
	};
});

/******************************************************************************/
/***  Media Player for <video> elements                                     ***/
/******************************************************************************/

pConst.f_enable_fadeout_video_controls = function() {
	pElement.addClassName(pElement.x('vlc-foreground', true), 'fadeout');
	/*
	pElement.addClassName(pElement.x('vlc-fullscreen-close', true), 'fadeout');
	pElement.addClassName(pElement.x('vlc-fullscreen-title', true), 'fadeout');
	pElement.addClassName(pElement.x('video-controls', true), 'fadeout');*/ 
};

pConst.f_disable_fadeout_video_controls = function() {
	pElement.removeClassName(pElement.x('vlc-foreground', true), 'fadeout');
	/*
	pElement.removeClassName(pElement.x('vlc-fullscreen-close', true), 'fadeout');
	pElement.removeClassName(pElement.x('vlc-fullscreen-title', true), 'fadeout');
	pElement.removeClassName(pElement.x('video-controls', true), 'fadeout');*/ 
};

function pMediaPlayer(x) {
	var that = this, eh = new pEventHandler(), m_options = new pMap(), m_enabled = true, m_element = pElement.x(x), m_temp_hls, 
		m_controls;
	
	this.tracePrefix = 'VideoPlayer';
	
	/*** Indicates if HLS is enabled and supported or not. */
	function hls() {
		return pROSE.getProp(pPlaylistUtil.options.OPTION_PLUGIN_HLS_ENABLED) && Hls.isSupported();
	}
	
	var f = function() {
		pConst.f_disable_fadeout_video_controls(); 
		if (!m_element.paused)
			pDocument.setTimeout('video-controls', pConst.f_enable_fadeout_video_controls, 2000);
	}; 
	pElement.x('div-vlc-main').addEventListener('mousemove', f);
	pElement.x('div-vlc-main').addEventListener('touch', f);
	pElement.x(m_element).addEventListener('mousemove', f);
	pElement.x(m_element).addEventListener('touch', f);
	
	// "id" property
	Object.defineProperty(this, "id", { get: function() { return m_element.id; }});

	// "element" property
	Object.defineProperty(this, "element", { get: function() { return m_element; }});

	// "audioTracks" property
	Object.defineProperty(this, "audioTracks", { get: function() { return m_element.audioTracks; }});

	// "textTracks" property
	Object.defineProperty(this, "textTracks", { get: function() { return m_element.textTracks; }});
	
	// "videoTracks" property
	Object.defineProperty(this, "videoTracks", { get: function() { return m_element.videoTracks; }});
	
	//*** EXPOSE EVENT HANDLER
	function f_event(e) {
		//if (e_id == "ended")
		//alert(e.type);

		//console.log(e);
		if (e.type == "error") {
			if (e.srcElement && e.srcElement.error && e.srcElement.error.code == 4) {
				//pApplicationUI.errorDialog({ text: 'Failed to play audio media...' });//, icon: { src: '/resources/html/images/32x32/picture_error.png' } });
				eh.fireEvent("error", that, { id: 'error', msg: 'Failed to play video media', orgError: e.srcElement.error.code, src: that.source });//this);
				return;
			}
		}
		
		if (e.type == "timeupdate") {

			//*** RESUME
			var r = parseInt(m_options.getValue("resume-time", 0)) / 1000;
			if (r > 0) {
				//var i_start = i_resume_time;// / 1000;
				if (m_element.currentTime < r)
					m_element.currentTime = r;

				m_options.remove("resume-time");
			}

			var s = parseInt(m_options.getValue("start-time", 0)) / 1000;
			if (s > 0) {
				//var i_start = i_start_time;// / 1000;
				if (m_element.currentTime < s)
					m_element.currentTime = s;
			}

			s = parseInt(m_options.getValue("stop-time", 0)) / 1000;
			if (s > 0) {
				//var i_stop = i_stop_time;// / 1000;
				if (m_element.currentTime >= s)
					eh.fireEvent("ended", that);
					//this.doEnd(p);
			}
		}

		eh.fireEvent(e.type, that);
	};
	
	this.addEventListener = function(eid, f) {
		if (eid != "timeupdate")
			if (eh.countListeners(eid)==0)
				m_element.addEventListener(eid, f_event, false);
		eh.addEventListener(eid, f);
	};
	
	this.removeEventListener = function(eid, f) {
		eh.removeEventListener(eid, f);
		if (eid != "timeupdate")
			if (eh.countListeners(eid)==0)
				m_element.removeEventListener(eid, f_event, false);
	};

	// listen for timeupdate events
	m_element.addEventListener("timeupdate", f_event, false);

	// "currentTime" property (time in ms)
	Object.defineProperty(this, "currentTime", {
		get: function() { return m_element.currentTime; },
		set: function(v) { m_element.currentTime = v; }
	});
	
	m_controls = new pVideoControls(m_element, { play: 'v0-play' });

	this.play = function() {
		m_element.play();
		pConst.f_enable_fadeout_video_controls();
	};
	m_element.addEventListener('play', pConst.f_enable_fadeout_video_controls);
	m_element.addEventListener('canplay', pConst.f_enable_fadeout_video_controls);
	
	this.pause = function() {
		pConsole.info(that, "Pause...");
		m_element.pause();
	};
	m_element.addEventListener('pause', pConst.f_disable_fadeout_video_controls);
	
	this.togglePause = function() {
		if (m_element.paused)
			m_element.play();
		else
			m_element.pause();
	};
	
	this.load = function() {
		m_element.load();
	};

	// "source" property
	Object.defineProperty(this, "source", { get: function() { return m_element.src; }, set: function(p_src) {

		//*** SETUP NOSLEEP 
		if (pPlaylistUtil.OPTION_NOSLEEP) {
			if (p_src) enableNoSleep(); else disableNoSleep();
		}
		
		//*** SETUP CONTROLS
		if (p_src) {
			var p = pLocation.path(p_src.url);
			if (p.endsWith("/play.mp3"))
				p_src.url = p.substring(0, p.length-"/play.mp3".length);

			m_controls.start_time = parseInt(p_src.start_time || -1);
			m_controls.stop_time = parseInt(p_src.stop_time || -1);
			m_controls.time_range.enabled = true;
			m_controls.volume_range.enabled = true;
			
			//m_controls.resetTime();
			
			//alert(p_src.url);
		}
		else {
			m_controls.time_range.enabled = false;
			m_controls.volume_range.enabled = false;
		}
		m_controls.resetTime();
		//m_controls.time_range.value = 0;

		//*** SETUP SRC
		if (m_temp_hls)
			m_temp_hls.destroy();
		
		if (hls() === true) {
			if (p_src && p_src.hls === true) {
				pHTTPRequest.get(p_src.url).then(function() {
					m_temp_hls = new Hls();
					m_temp_hls.loadSource(p_src.url);
					m_temp_hls.attachMedia(m_element);
					//m_temp_hls.on(Hls.Events.MANIFEST_PARSED, m_play);
				});
			}
			else
				pElement.setSrc(m_element, p_src? p_src.url : null);
		}
		else
			pElement.setSrc(m_element, p_src? p_src.url : null);

		//*** SETUP OPTIONS
		m_options.clear();
		if (p_src)
			(p_src.options || []).forEach(function(o) {
				var pos = o.indexOf('=');
				if (pos>0)
					m_options.put(o.substring(0, pos), o.substring(pos+1));
			});

		//*** SETUP TITLE
		if (pDevice.isMobile())
			m_element.title = p_src? p_src.title : null;
	}});

	this.canTranscode = function() {
		return pDevice.isIOS() || hls();
	};

	this.canPlayLocalFiles = function() {
		return false;
	};

	this.capture = function() {
		var canvas = document.createElement('canvas');
		canvas.width = 640;
		canvas.height = 480;
		var ctx = canvas.getContext('2d');

		//draw image to canvas. scale to target dimensions
		ctx.drawImage(m_element, 0, 0, canvas.width, canvas.height);

		//convert to desired file format
		return canvas.toDataURL('image/jpeg'); // can also use 'image/png'
	};

	// "enabled" property
	Object.defineProperty(this, "enabled", { get: function() { return m_enabled; }, set: function(s) { if (typeof s == "boolean") m_enabled = s; }});

	// "type" property
	Object.defineProperty(this, "type", { get: function() { return "video" }});

	// "fullscreen" property
	Object.defineProperty(this, "fullscreen", { get: pDocument.isFullScreen, set: function(s) {
		if (typeof s == "boolean") {
			if (s === false)
				pDocument.exitFullScreen();//m_element);
			else
				pDocument.fullScreen('div-vlc-main');//m_element);
		}
	}});
	
	this.show = function() {
		pElement.addClassName('video-controls', 'visible');
		pDocument.show(m_element);
	};
	
	this.hide = function() {
		pElement.removeClassName('video-controls', 'visible');
		pDocument.hide(m_element);
		
		m_controls.time_range.enabled = false;
		m_controls.volume_range.enabled = false;
	};
};

/******************************************************************************/
/***  Media Player for <audio> elements                                     ***/
/******************************************************************************/

function pMediaPlayer_Audio(x) {
	var that = this, eh = new pEventHandler(), m_options = new pMap(), m_enabled = true, 
		element_next, element_prev, m_element = pElement.x(x), element_curr = m_element, element_next_curr, 
		m_controls, m_time_range, m_volume_range;
	
	this.tracePrefix = 'AudioPlayer';
	
	// "id" property
	Object.defineProperty(this, "id", { get: function() { return m_element.id; }});

	// "element" property
	Object.defineProperty(this, "element", { get: function() { return element_curr; }});

	if (pROSE.getProp('audio-cache-next')) {
		element_next = pElement.x(that.id + '-next');
		element_next_curr = element_next;
		element_prev = pElement.x(that.id + '-prev');
	}

	//*** EXPOSE EVENT HANDLER
	function f_event(e) {
		//console.log(e);
		//if (e.type == "ended")
		//	console.log(e.type);
		//alert(e.type);

		if (e.type == "error") {
			if (e.srcElement && e.srcElement.error && e.srcElement.error.code == 4) {
				//pApplicationUI.errorDialog({ text: 'Failed to play audio media...' });//, icon: { src: '/resources/html/images/32x32/picture_error.png' } });
				eh.fireEvent("error", that, { id: 'error', msg: 'Failed to play audio media', orgError: e.srcElement.error.code, src: that.source });//this);
				return;
			}
		}
		
		if (e.type == "timeupdate") {

			//*** RESUME
			var r = parseInt(m_options.getValue("resume-time", 0)) / 1000;
			if (r > 0) {
				//var i_start = r;// / 1000;
				if (element_curr.currentTime < r)
					element_curr.currentTime = r;

				m_options.remove("resume-time");
			}

			var s = parseInt(m_options.getValue("start-time", 0)) / 1000;
			if (s > 0) {
				//var i_start = i_start_time;// / 1000;
				//pConsole.debug('MediaPlayerAudio', "start: " + i_start + " - " + p.currentTime);
				if (element_curr.currentTime < s)
					element_curr.currentTime = s;
			}

			s = parseInt(m_options.getValue("stop-time", 0)) / 1000;
			if (s > 0) {
				//var i_stop = i_stop_time;// / 1000;
				//pConsole.info('MediaPlayerAudio', "start: " + i_stop + " - " + p.currentTime);
				if (element_curr.currentTime >= s)
					eh.fireEvent("ended", that);
					//this.doEnd(p);
			}
		}

		if (element_next)
			if (e.type == "error")
				if (that.id == element_next.id && element_curr != element_next)
					return;

		eh.fireEvent(e.type, that);
	}
	
	this.addEventListener = function(eid, f) {
		if (eid != "timeupdate")
			if (eh.countListeners(eid)==0) {
				m_element.addEventListener(eid, f_event, false);
				if (element_next)
					element_next.addEventListener(eid, f_event, false);
				if (element_prev)
					element_prev.addEventListener(eid, f_event, false);
			}
		eh.addEventListener(eid, f);
	};
	
	this.removeEventListener = function(eid, f) {
		eh.removeEventListener(eid, f);
		if (eid != "timeupdate")
			if (eh.countListeners(eid)==0) {
				m_element.removeEventListener(eid, f_event, false);
				if (element_next)
					element_next.removeEventListener(eid, f_event, false);
				if (element_prev)
					element_prev.removeEventListener(eid, f_event, false);
			}
	};

	// listen for timeupdate events
	m_element.addEventListener("timeupdate", f_event, false);
	if (element_next)
		element_next.addEventListener("timeupdate", f_event, false);
	if (element_prev)
		element_prev.addEventListener("timeupdate", f_event, false);

	// "muted" property
	Object.defineProperty(this, "muted", {
		get: function() { return element_curr.muted; },
		set: function(v) { m_element.muted = v;
			if (element_next)
				element_next.muted = v;
			if (element_prev)
				element_prev.muted = v;
		}
	});

	// "volume" property
	Object.defineProperty(this, "volume", {
		get: function() { return element_curr.volume; },
		set: function(v) { m_element.volume = v;
			if (element_next)
				element_next.volume = v;
			if (element_prev)
				element_prev.volume = v;
		}
	});

	// other properties (currentTime and duration in seconds, as for audio and video HTML elements)
	[ "buffered", "currentTime", "duration", "paused", "seekable" ].forEach(function(i) {
		Object.defineProperty(that, i, { get: function() { return element_curr[i]; }, set: function(v) { element_curr[i] = v; }});
	});

	this.play = function() {
		element_curr.play();
	}
	this.pause = function() {
		element_curr.pause();
	};
	this.togglePause = function() {
		if (element_curr.paused)
			element_curr.play();
		else
			element_curr.pause();
	};
	this.load = function() {
		element_curr.load();
	};

	function f_update_controls(p_src) {
		m_controls.start_time = p_src? parseInt(p_src.start_time || -1) : -1;
		m_stop_time = p_src? parseInt(p_src.stop_time || -1) : -1;
		m_time_range.value = 0;
		m_time_range.enabled = p_src != null;
		m_volume_range.enabled = p_src != null;
		if (p_src)
			pElement.removeClassName(m_controls.playButton, 'disabled');
		else {
			pElement.removeClassName(m_controls.playButton, 'paused');
			pElement.addClassName(m_controls.playButton, 'disabled');
		}
	};
	
	// "source" property
	var element_next_src;
	Object.defineProperty(this, "source", { get: function() { return m_element.src; }, set: function(p_src) {

		// after...
		if (!m_controls) {
			m_controls = new pVideoControls(that, { play: 'a0-play', time1: 'a0-time1', time2: 'a0-time2', volume: 'a0-volume-bar', seek: 'a0-seek-bar', mute: 'a0-mute' }),
			m_time_range = m_controls.time_range, m_volume_range = m_controls.volume_range; 
		}
		
		if (pPlaylistUtil.OPTION_NOSLEEP) {
			if (p_src) enableNoSleep(); else disableNoSleep();
		}
		
		if (element_next && p_src) {
			if (element_curr == m_element) {
				if ((element_next_src == p_src.url || element_next_src + '?undefined' == p_src.url)) {

					m_element.pause();

					element_curr = element_next;
					element_next_curr = m_element;

					//if (!pDevice.isIOS() && !pDevice.isAndroid())
					//	pDocument.show(element_next);
					//pDocument.hide(m_element);

					//element_curr.src = p_src.url;
					f_setOptions(p_src);
					element_curr.title = p_src.title;
					
					f_update_controls(p_src);
					return;
				}
			}

			if (element_curr == element_next) {
				if ((element_next_src == p_src.url || element_next_src + '?undefined' == p_src.url)) {

					element_next.pause();

					element_curr = m_element;
					element_next_curr = element_next;

					//if (!pDevice.isIOS() && !pDevice.isAndroid())
					//	pDocument.show(m_element);
					//pDocument.hide(element_next);

					//element_curr.src = p_src.url;
					f_setOptions(p_src);
					element_curr.title = p_src.title;
					
					f_update_controls(p_src);
					return;
				}
			}
		}

		f_update_controls(p_src);
		setTimeout(function() {
			pElement.setSrc(element_curr, p_src? p_src.url : null);
			f_setOptions(p_src);
			element_curr.title = p_src? p_src.title : null;
		}, 1);
	}});

	function f_setOptions(p_src) {
		m_options.clear();
		if (p_src)
			(p_src.options || []).forEach(function(o) {
				var pos = o.indexOf('=');
				if (pos>0)
					m_options.put(o.substring(0, pos), o.substring(pos+1));
			});
	};

	this.canTranscode = function() {
		return false;
	};

	this.canPlayLocalFiles = function() {
		return false;
	};

	// "enabled" property
	Object.defineProperty(this, "enabled", { get: function() { return m_enabled; }, set: function(s) { if (typeof s == "boolean") m_enabled = s; }});

	// "type" property
	Object.defineProperty(this, "type", { get: function() { return "audio" }});

	// "fullscreen" property
	Object.defineProperty(this, "fullscreen", { get: function() { return false; }, set: function(value) { }});

	this.setNextSource = function(p_src_current, p_src) {
		if (p_src_current.url == p_src.url || p_src_current.url == p_src.url+'?undefined')
			return;
		
		pConsole.info(that, "SetNextSource: " + p_src.url);
		if (element_next) {
			element_next_src = p_src.url;
			element_next_curr.src = p_src.url;
			element_next_curr.load();
		}
	};

	this.show = function() {
		//pDocument.show(element_curr);
	};

	this.hide = function() {
		if (m_controls) {
			m_controls.time_range.value = 0;
			m_controls.time_range.enabled = false;
			m_controls.volume_range.enabled = false;
			
			pElement.removeClassName(m_controls.playButton, 'paused');
			pElement.addClassName(m_controls.playButton, 'disabled');
			//pDocument.hide(element_curr);
		}
	};
};

/******************************************************************************/
/***  List of Medias (including current position, shuffling, length, ...)   ***/
/******************************************************************************/

function pMediaList() {
	var that = this, m_medias_play_count = 0; // after medias are cleared, this indicates how many have been played
	var m_shuffle = false, m_medias = [ "" ], m_shuffled = []; // shuffled list of media indexes (from m_medias list), eg [ 2, 4, 1, 3, ... ]
	var m_index = 0; // index of media being played (from m_medias list)
		
	function f_error(m) { return m.error; }

	this.clear = function() {
		m_medias = [ "" ];
		m_shuffled = [];
		m_index = 0;
		m_medias_play_count = 0;
	};
	
	this.add = function(p_item) {
		m_medias.push(p_item);

		//set position (starts with 1)
		p_item.position = m_medias.length-1;

		m_shuffled.push(that.length);
		if (m_shuffle === true)
			reshuffle();
	};
	this.getValue = function(n) {
		return m_medias[n];
	};
	this.findIndex = function(p_function) {
		return m_medias.findIndex(p_function);
	};
	this.error = function() {
		return m_medias.slice(1).every(f_error);
	};

	// "current" property
	Object.defineProperty(this, "current", { get: function() {
		if (m_index>0 && m_medias.length>1)
			return m_medias[m_index];
		//return null;
	}});

	this.reshuffle = function() {
		m_shuffled = pArray.shuffle(m_shuffled);
	};
	this.firstMediaIndex = function() {
		return m_shuffle? m_shuffled[0] : 1;
	};
	this.lastMediaIndex = function() {
		return m_shuffle? m_shuffled[m_shuffled.length-1] : m_medias.length-1;
	};
	this.nextMediaIndex = function() {
		return m_shuffle? m_shuffled[m_shuffled.indexOf(m_index)+1] : m_index+1;
	};
	this.previousMediaIndex = function() {
		return m_shuffle? m_shuffled[m_shuffled.indexOf(m_index)-1] : m_index-1;
	};
	// position of media being played in playing order (0: nothing, 1: first, "length": last)
	this.currentMediaPlayingPosition = function() {
		return m_shuffle? m_shuffled.indexOf(m_index)+1 : m_index;
	};

	// "index" property
	Object.defineProperty(this, "index", { get: function() { return m_index; }, set: function(n) { m_index = n; }});

	// "shuffle" property
	Object.defineProperty(this, "shuffle", {
		get: function() { return m_shuffle; },
		set: function(v) { m_shuffle = v; if (v) that.reshuffle(); }
	});

	// "length" property
	Object.defineProperty(this, "length", { get: function() { return m_medias.length-1 }});

	// "playCount" property
	Object.defineProperty(this, "playCount", { get: function() { return m_medias_play_count; }, set: function(n) { m_medias_play_count = n; } });
}

/******************************************************************************/
/***  Playlist Instance                                                     ***/
/******************************************************************************/

function pPlaylistInstance(p_id, p_players, p_controls) {
	var that = this, m_id = p_id, eh = new pEventHandler(), doc_title_new, play_by_user;
	
	this.medias = new pMediaList();
	this.controls = p_controls;
	this.tracePrefix = 'Playlist';
	this.doc_title_old = null;

	// "id" property
	Object.defineProperty(this, "id", { get: function() { return m_id; }});

	//*** EXPOSE EVENT HANDLER
	this.addEventListener = function(eid, f) {
		eh.addEventListener(eid, f);
	};
	this.removeEventListener = function(eid, f) {
		eh.removeEventListener(eid, f);
	};

	this.addMedia = function(p_item) {
		that.medias.add(p_item);
	};

	// "index" property
	Object.defineProperty(this, "index", { get: function() { return that.medias.index; }, set: function(n) { that.medias.index = n; }});

	// "shuffle" property
	Object.defineProperty(this, "shuffle", { get: function() { return that.medias.shuffle; }, set: function(p_set) { that.medias.shuffle = p_set; }});

	// "length" property
	Object.defineProperty(this, "length", { get: function() { return that.medias.length }});

	// "currentTime" property
	Object.defineProperty(this, "currentTime", {
		get: function() { return that.getPlayer().currentTime },
		set: function(v) { try { that.getPlayer().currentTime = v; } catch(e) { pConsole.error('Playlist', 'Failed to set time: ' + v, e); }}
	});

	var m_video = false;
	function f_ended() {
		if (pString.v(that.doc_title_old))
			document.title = that.doc_title_old;
		
		pConsole.info(that, "player event: ended...");
	
		var p = that.getPlayer();
		if (p)
			p.pause();
		//i_player.currentTime = 0;

		/** Report current time back to server **/
		f_reportTime();
		f_reportEnded();

		if (that.loopTrack === true)
			try {
				that.play();
				return;
			}
			catch (e) {
				console.log(e);
			}

		if (that.medias.currentMediaPlayingPosition()==that.medias.length) {
			if (that.loop === true)
				that.play(that.medias.firstMediaIndex());
			else {
				pConsole.info(that, 'Ended...');
				eh.fireEvent("ended", that);
			}
		}
		else
			that.next();
	}
	
	/** Report current time back to server **/
	var disable_reportTime
	function f_reportTime(p_time) {
		if (p_time==null) p_time = -1;

		if (disable_reportTime) {
			//pConsole.info(this, "Disabled Report Time: " + pDate.toLocaleString(new Date()));
			return;
		}

		var i_time = (p_time>=0)? p_time : ((that.getPlayer())? that.getPlayer().currentTime * 1000 : -1);
		if (i_time >= 0) {
			var m = that.medias.current;
			if (m) {
				//pConsole.info(this, "i_time: " + i_media.play_time);
				if (!m.play_time || m.play_time<=0 || Math.floor(m.play_time)!=Math.floor(i_time)) {
					var u = m.play_updated_url;
					if (u) {
						pConsole.debug(that, "Report Time: " + pDate.toLocaleString(new Date()) + ': ' + i_time);
						pHTTPRequest.post(u + "?action=play-update&time=" + i_time);
					}
				}
				m.play_time = i_time;
				
				//alert(pJSON.pretty(JSON.stringify(i_media)));
				if (!m.media || pMediaLibrary.getLIDfromURL(m.media.doc_url) != 'music.medias')
					pCookieManager.set(pPlaylistUtil.playTimeCookieName(m), ""+i_time);
			}
		}
	};
	
	/** Report end of media back to server **/
	function f_reportEnded() {
		var m = that.medias.current;
		if (m && m.play_ended_url)
			pHTTPRequest.get({ url: m.play_ended_url, responseType: "arraybuffer" });
	}
	
	var last_time, m_paused;
	function f_timed() {
		m_paused = false;

		//i_playlist.doControlTime(i_playlist.getPlayer());

		eh.fireEvent("timeupdate", that);

		/** Report current time back to server **/
		if (pROSE.getProp(pPlaylistUtil.options.OPTION_REPORT_TIME) != 'false') {
			var i_player = that.getPlayer()
			if (i_player) {
				var i_time = i_player.currentTime * 1000;
				if (null==last_time || 0 == last_time || (i_time - last_time) >= 3000) {
					last_time = i_time;
					f_reportTime(i_time);
				}
			}
		}
		//pConsole.debug('Playlist', "Timeupdate: " + i_playlist.getPlayer().currentTime);
	}
	
	this.doControlTime = function(i_player) {
		var i_media = that.medias.current;
		if (null==i_media)
			return;

		if (i_media.start_time > 0) {
			var i_start = i_media.start_time / 1000;
			if (i_player.currentTime < i_start)
				i_player.currentTime = i_start;
		}
		if (i_media.stop_time > 0) {
			var i_stop = i_media.stop_time / 1000;
			if (i_player.currentTime >= i_stop)
				f_ended(i_player);
		}
	};

	var m_player;
	this.getPlayer = function() {
		if (!m_player)
			pConsole.debug(that, 'No player chosen yet...');

		return m_player;
	};

	this.capture = function() {
		var i_player = that.getPlayer();
		if (i_player && i_player.capture)
			return i_player.capture();
		//return null;
	};

	function f_show(i_player) {
		if (i_player.show)
			i_player.show();
		else
			pDocument.show(i_player.element);
	}
	
	function f_hide(i_player) {
		if (i_player.hide)
			i_player.hide();
		else
			pDocument.hide(i_player.element);
	}
	
	function f_showPlayer(i_player, i_player_audio, i_player_video, i_media, i_show) {

		//*** NOTIFY
		if (i_show === true)
			eh.fireEvent("show-before", this);

		if (null==i_player_audio || (i_media.hasVideo===true && m_showVideos)) {
			if (i_player!=i_player_video) {
				if (null!=i_player) {
					i_player.pause();
					i_player.currentTime = 0;
					//if (i_player.fullscreen)
						record_fullscreen = false;
						i_player.fullscreen = false;
						

					f_hide(i_player);
				}
				if (i_show === true) {
					this.m_players.forEach(function(player) {
						if (player != i_player_video)
							f_hide(player);
					});
					f_show(i_player_video);
					eh.fireEvent("show-video", this);
				}
			}

			i_player = m_player = i_player_video;
			m_video = true;
		}
		else {
			if (i_player!=i_player_audio) {
				if (null!=i_player) {
					i_player.pause();
					i_player.currentTime = 0;
					//if (i_player.fullscreen)
						record_fullscreen = false;
						i_player.fullscreen = false;
						

					eh.fireEvent("hide-video", this);
					f_hide(i_player);
				}
				if (i_show === true) {
					this.m_players.forEach(function(player) {
						if (player != i_player_audio)
							f_hide(player);
					});
					if (!pDevice.isIPhone() && !pDevice.isAndroid() && !pDevice.isIPod()) //TODO: use screen size instead...
						f_show(i_player_audio);
				}
			}

			i_player = m_player = i_player_audio;//this.m_audio_player;
			m_video = false;
		}

		//*** NOTIFY
		if (i_show === true)
			eh.fireEvent("show-after", this);

		return i_player;
	};
	
	function f_checkPlayer(i_player, i_player_audio, i_player_video, i_media) {
		if (null==i_player_audio || (i_media.has_video===true && m_showVideos))
			return i_player_video;
		else
			return i_player_audio;
	};
	
	this.check = function(i_media) {
		
		//*** SELECT AUDIO PLAYER
		var i_player_audio = that.selectPlayer(i_media, 'audio');

		//*** SELECT VIDEO PLAYER
		var i_player_video = that.selectPlayer(i_media, 'video');

		if (null==i_player_audio && null==i_player_video)
			return null;//pConsole.warn('Playlist', "No enabled player...");

		//*** SHOW PLAYER
		var i_player = f_checkPlayer(that.getPlayer(), i_player_audio, i_player_video, i_media);

		//*** GET URL
		var i_mrl = f_createURL(i_media.url, server_nossl_port);// + "mode=web);
		var i_transcode = false;
		var i_mrl_done = false;
		var i_mime = null;
		if (i_media.mime) i_mime = i_media.mime.toLowerCase();
		var i_webvtt =pROSE.getProp(pPlaylistUtil.options.OPTION_WEBVTT);

		//pConsole.info(that, "Video Bit Rate: " + i_media.metadata_bitrate_video);

		//*** PLAY LOCAL FILE
		if (i_media.mrl_local && i_player.canPlayLocalFiles()) {
			i_mrl = i_media.mrl_local;

			if (that.autoscale===true) {
				if (i_media.width && i_media.height && (screen.width<i_media.width || screen.height<i_media.height))
					if (i_media.scaled && i_media.scaled.length>1 && i_media.scaled[0].mrl_local)
						i_mrl = i_media.scaled[0].mrl_local;
			}

			i_mrl = f_createURL(i_mrl, server_nossl_port);
			i_mrl_done = true;
		}

		//*** OR TRY TRANSCODING
		if (i_mrl_done === false)
			return that.m_do_transcode(i_player, i_media);
		
	};

	function f_createURL(p_url, p_port) {
		if (p_url.indexOf('file:')==0)
			return p_url;
		if (p_url.indexOf('http:')==0)
			return p_url;
		if (p_url.indexOf('https:')==0)
			return p_url;

		//if (pPlaylistUtil.OPTION_DEVICE_HTTPS_VIDEOS)
		//	return 'https://' + window.location.hostname + ":" + server_ssl_port + p_url;
		if (pPlaylistUtil.OPTION_VIDEO_NOHTTPS)
			return "http://" + window.location.hostname + ":" + server_nossl_port + p_url;
		return p_url;
	}

	function f_createTranscodeURL(media) {
		const i_webvtt = pPlaylistUtil.OPTION_WEBVTT;
		return f_createURL(media.url_transcode + ((i_webvtt === true)? "?subtitles=webvtt":""), server_nossl_port);
	}
	
	//*** Decides if transcoding is required and return appropriate URL
	var IOS_MIMES = [ 'video/mp2t', 'video/quicktime', 'video/mp4', 'video/x-m4v', 'video/3gpp' ];
	var WEB_MIMES = [ 'video/mp4', 'video/x-m4v', 'video/webm' ];
	this.m_do_transcode = function(i_player, i_media) {

		var i_transcode = that.transcode, r = { transcode: i_transcode, info: [], reason: [], player: true }, vbitrate = pPlaylistUtil.OPTION_MAX_VIDEO_BITRATE;
		
		r.info.push("Transcoding: " + i_transcode);
		
		//*** NEVER
		if (i_transcode === '0')
			return r;
		
		if (null!=i_media.canTranscode && i_media.canTranscode === false) {
			r.player = false;
			return r;
		}

		r.info.push("Video Bit Rate: " + i_media.metadata_bitrate_video);
		if (i_transcode === 'auto') {

			if (i_player.canTranscode()===true) {
				
				//0.9.11
				if (pTrackListUtil.countAudioTracks(i_media.tracks)>1) {
					r.reason.push("audio tracks: " + pTrackListUtil.countAudioTracks(i_media.tracks) + ">1");
					r.url = f_createTranscodeURL(i_media);
					return r;
				}
				if (pTrackListUtil.countExternalSubtitleTracks(i_media.tracks)>0) {
					r.reason.push("subtitle tracks: " + pTrackListUtil.countExternalSubtitleTracks(i_media.tracks) + ">0");
					r.url = f_createTranscodeURL(i_media);
					return r;
				}
				
				var t = pTrackListUtil.tracks(i_media);
				
				var i_vcodec = pTrackListUtil.getVideoCodec(t, '');
				r.info.push("Video Codec: " + i_vcodec);

				var i_acodec = pTrackListUtil.getAudioCodec(t, '');
				r.info.push("Audio Codec: " + i_acodec);
				
				var i_mime = (i_media.mime || '').toLowerCase();
				r.info.push("Mime: " + i_mime);
				
				if (pDevice.isIOS()) {
					if (IOS_MIMES.includes(i_mime))
						if (i_media.height && parseInt(i_media.height)<=1080) {
							if (i_media.metadata_bitrate_video && i_media.metadata_bitrate_video>0 && i_media.metadata_bitrate_video < vbitrate) {
								r.reason.push('mime is device compatible' + (pConsole.debugging()? ' (' + IOS_MIMES.join(', ') + ')':'')+': ' + i_mime);
								r.reason.push("video height is " + i_media.height + " (<=1080)");
								r.reason.push("bitrate video is " + i_media.metadata_bitrate_video + " (<"+vbitrate+")");
								return r;
							}
						}
					
					if (i_media.url_transcode != undefined) {
						r.url = f_createTranscodeURL(i_media);
						return r;
					}
				}
				
				var i_v = pVideoUtil.supports(i_mime + '; codecs="' + i_vcodec + '"'), i_a = pTrackListUtil.countAudioTracks(t)==0 || pAudioUtil.supports(i_mime.replace('video', 'audio') + '; codecs="'+i_acodec+'"');
				
				//r.reason.push('player '+(i_v? 'supports':'does not support')+' ' + i_mime + '; video codec="' + i_vcodec + '"');
				//r.reason.push('player '+(i_a? 'supports':'does not support')+' ' + i_mime.replace('video', 'audio') + '; audio codec="' + i_acodec + '"');
				
				if (i_v && i_a) {
					if (pLocation.isLocalhost()) {
						r.reason.push('player supports ' + i_mime + '; video codec="' + i_vcodec + '"');
						r.reason.push('player supports ' + i_mime.replace('video', 'audio') + '; audio codec="' + i_acodec + '"');
						
						r.reason.push("media is on localhost");// ("+window.location.hostname+")");
						return r;
					}
					
					if (i_media.height && parseInt(i_media.height)<=1080) {
						if (i_media.metadata_bitrate_video && i_media.metadata_bitrate_video>0 && i_media.metadata_bitrate_video < vbitrate) {
							r.reason.push('player supports ' + i_mime + '; video codec="' + i_vcodec + '"');
							r.reason.push('player supports ' + i_mime.replace('video', 'audio') + '; audio codec="' + i_acodec + '"');
							
							r.reason.push("video height is " + i_media.height + " (<=1080)");
							r.reason.push("bitrate video is " + i_media.metadata_bitrate_video + " (>0 && <"+vbitrate+")");
							return r;
						}
					}
				}
				/*else if (WEB_MIMES.includes(i_mime)) {
					r.reason.push('mime is device compatible' + (pConsole.debugging()? ' (' + WEB_MIMES.join(', ') + ')':'') + ': ' + i_mime);
					
					if (i_vcodec.indexOf('AVC')<0 && i_vcodec.indexOf('H264')<0) {
						r.reason.push('player '+(i_v? 'supports':'does not support')+' ' + i_mime + '; video codec="' + i_vcodec + '"');
						r.reason.push('player '+(i_a? 'supports':'does not support')+' ' + i_mime.replace('video', 'audio') + '; audio codec="' + i_acodec + '"');
						
						r.reason.push("video codec does not contain AVC abd H264: " + i_vcodec);
						r.url = f_createTranscodeURL(i_media);
						return r;
					}
					
					if (pLocation.isLocalhost) {
						r.reason.push("media is on localhost");
						return r;
					}
					
					if (i_media.height && parseInt(i_media.height)<=1080) {
						if (i_media.metadata_bitrate_video && i_media.metadata_bitrate_video>0 && i_media.metadata_bitrate_video < vbitrate) {
							r.reason.push("video height is " + i_media.height + " (<=1080)");
							r.reason.push("bitrate video is " + i_media.metadata_bitrate_video + " (>0 && <"+vbitrate+")");
							return r;
						}
					}
				}*/


				r.reason.push('player '+(i_v? 'supports':'does not support')+' ' + i_mime + '; video codec="' + i_vcodec + '"');
				r.reason.push('player '+(i_a? 'supports':'does not support')+' ' + i_mime.replace('video', 'audio') + '; audio codec="' + i_acodec + '"');
				
				if (i_media.url_transcode != undefined) {
					r.url = f_createTranscodeURL(i_media);
					return r;
				}
			}
		}
		// ALWAYS
		else if (i_transcode === '1') {
			if (i_player.canTranscode()===true)
				if (i_media.url_transcode != undefined) {
					r.url = f_createTranscodeURL(i_media);
					return r;
				}
		}
		
		return r;
	};
	
	this.selectPlayer = function(i_media, type) {
		if (i_media.metadata_provider) {
			const i_provider = i_media.metadata_provider.toLowerCase(), p = this.m_players.find(function(p) {
				if (p.enabled === true && p.type === type && p.providers)
					if (p.providers.includes(i_provider))
						return true;
			});
			if (p) 
				return p;
			/*for(var i=0 ; i<this.m_players.length ; i++)
				if (this.m_players[i].enabled === true && this.m_players[i].type === type && this.m_players[i].providers)
					if (this.m_players[i].providers.indexOf(i_provider)>=0)
						return this.m_players[i];*/
		}

		/*if (i_media.mrl_local) {
			var i_local = this.local;
			for(var i=0 ; i<this.m_players.length ; i++)
				if (this.m_players[i].enabled === true && this.m_players[i].type === type) {
					if (i_local === "always" || i_local === "auto" || i_local === "true")
						if (this.m_players[i].canPlayLocalFiles() === true)
							return this.m_players[i];
				}
		}*/

		/*for(var i=0 ; i<this.m_players.length ; i++)
			if (this.m_players[i].enabled === true && this.m_players[i].type === type) {
				if (this.transcode === "always" || this.local === "auto" || this.local === "true")
						if (this.m_players[i].canPlayLocalFiles() === true)
							return this.m_players[i];
				}
		*/
		return this.m_players.find(function(p) {
			if (p.enabled === true && p.type === type)
				return true;
		});
		/*
		for(var i=0 ; i<this.m_players.length ; i++)
			if (this.m_players[i].enabled === true && this.m_players[i].type === type)
				return this.m_players[i];

		return null;*/
	};
	
	this.canResume = function(i_media, p_askResume) {
		var i_play_time = null;
	
		// ask to resume...
		if (i_media.ms && (p_askResume === true || (p_askResume==null && this.askResume===true))) {
			i_play_time = pCookieManager.getCookie(pPlaylistUtil.playTimeCookieName(i_media));
			if (i_play_time)
				i_play_time = parseInt(i_play_time);
			else
				i_play_time = i_media.play_time;
		}

		if (i_play_time && i_play_time > 60000) {
			var r = i_play_time - (i_media.start_time || 0); //resume time
			if (r > 60000)
				return true;
		}
		
	};
		
	var m_mrl_options, m_asked = false, m_temp;
	this.play = function(n, p_incr, p_askResume, p_user, p_static) {
		if (n && n.doc_url)
			n = this.medias.findIndex(function(p_media) { return p_media.doc_url == n.doc_url || (p_media.media && p_media.media.doc_url == n.doc_url); });

		//console.log('n: ' + n);
		try {
			n = parseInt(n);
		}
		catch(exception) {
			n = this.index;
		}
		if (isNaN(n) || n<1 || n>this.medias.length)
			n = this.index;
		if (null == n)
			n = 1;

		//*** NO CHANGE...
		if (this.index == parseInt(n)) {
			this.getPlayer().play();
			this.updateMediaControlsPlay(n);
			return;
		}
		
		play_by_user = p_user;

		//*** DISABLE SOME FEATURES
		disable_reportTime = true;

		//*** SELECT MEDIA
		var i_media = this.medias.getValue(parseInt(n));
		if (null==i_media)
			return;
		if (i_media.media) {
			if (i_media.media.url && !i_media.media.url.startsWith('/'))
				i_media = i_media.media;
		}

		//*** SELECT AUDIO PLAYER
		var i_player_audio = this.selectPlayer(i_media, 'audio');

		//*** SELECT VIDEO PLAYER
		var i_player_video = this.selectPlayer(i_media, 'video');

		if (null==i_player_audio && null==i_player_video)
			pConsole.warn(this, "No enabled player...");

		//*** MOVE INDEX
		this.index = parseInt(n);

		//*** SHOW PLAYER
		var i_player = f_showPlayer.call(this, this.getPlayer(), i_player_audio, i_player_video, i_media, true);//false);
		pConsole.info(this, "Media Player: " + (i_player? i_player.tracePrefix : i_player));

		//*** GET URL
		var i_mrl = f_createURL(i_media.url, server_nossl_port), i_transcode = false, i_mrl_done = false, i_mime = (i_media.mime || '').toLowerCase(), i_webvtt =pROSE.getProp(pPlaylistUtil.options.OPTION_WEBVTT);

		//pConsole.info(this, "Video Bit Rate: " + i_media.metadata_bitrate_video);

		//*** PLAY LOCAL FILE
		if (i_media.mrl_local && i_player.canPlayLocalFiles()) {
			i_mrl = i_media.mrl_local;

			if (this.autoscale===true) {
				if (i_media.width && i_media.height && (screen.width<i_media.width || screen.height<i_media.height))
					if (i_media.scaled && i_media.scaled.length>1 && i_media.scaled[0].mrl_local)
						i_mrl = i_media.scaled[0].mrl_local;
			}

			i_mrl = f_createURL(i_mrl, server_nossl_port);
			i_mrl_done = true;
		}

		//*** OR TRY TRANSCODING
		if (i_mrl_done === false) {
			var r = this.m_do_transcode(i_player, i_media.media || i_media);
			if (r) {
				(r.info || []).forEach(function(i) { pConsole.info(that, i) });
				(r.reason || []).forEach(function(i) { pConsole.info(that, "Transcode: " + i) });
				if (r.url) {
					i_transcode = true;
					i_mrl = r.url;
					i_mrl_done = true;
				}
			}
		}

		if (pConsole.debugging())
			alert('To Play...\nvideo: ' +  m_video + '\nautoscale: ' + this.autoscale + '\ntranscode: ' + this.transcode + '\nurl: ' + i_mrl + '\nurl (decoded): ' + decodeURIComponent(i_mrl));
		
		pConsole.info(this, "Play " + i_mrl + ((i_transcode===true)? " (transcode)":" ") + "...");

		if (i_transcode===false && pDevice.isIOS()) {
			if (i_media.mime) {
				//alert(i_media.mime);
				if (null!=i_mime && i_mime.indexOf('video/')==0 && i_mime!='"video/mp2t' && i_mime!='video/quicktime' && i_mime!='video/mp4' && i_mime!='video/x-m4v' && i_mime!='video/3gpp') {
					pDocument.stopwait();
					location.href = i_mrl;
				}
			}
		}

		if (i_transcode === true)
			eh.fireEvent("transcode", this);

		var i_dialog = { id: 'playlist-resume', options: [] }, i_play_time = null, i_ask_options = this.askOptions;
		
		if (i_ask_options===true && i_media.askOptions)
			i_ask_options = i_media.askOptions;

		if (i_ask_options===true && (i_media.has_video && i_media.has_video === true) || (i_media.hasVideo && i_media.hasVideo === true)) {

			// ask to resume...
			if (i_media.ms && (p_askResume === true || (p_askResume==null && this.askResume===true))) {
				i_play_time = pCookieManager.getCookie(pPlaylistUtil.playTimeCookieName(i_media));
				if (i_play_time)
					i_play_time = parseInt(i_play_time);
				else
					i_play_time = i_media.play_time;
			}

			if (i_play_time && i_play_time > 60000) {
				var r = i_play_time - (i_media.start_time || 0); //resume time
				if (r > 60000) {
					i_dialog.options.push({ id: "resume", text: m_resume_text_before + pTime.display(r) + m_resume_text_after, value: "resume-true", vdefault: true });
					i_dialog.options.push({ id: "restart", text: m_begining_text, value: "resume-false" });
				}
			}

			pPlaylistControls.addDialogLangOptions(i_dialog, i_media, i_transcode, i_webvtt);
		}
		
		//0.9.9
		if (i_dialog.i_audio_done ==0 && i_dialog.i_subtitles_done == 0 && i_transcode===true)
			pHTTPRequest.get(i_mrl);

		if (i_dialog.options.length > 0 ) {

			//*** auto select audio and substitles
			//if (i_audio_done >0 || i_subtitles_done > 0)
			//	this.autoSelectLangs(i_dialog, i_option_snone, i_media);

			if (i_dialog.radio && i_dialog.radio === true)
				i_dialog.options.push('ok');
			i_dialog.options.push({ id: "cancel", value: "-1" });
			i_dialog.fcall = function(p_dialog, value) {
				if (value === "-1") {
					pDocument.stopwait(); m_vlc.close();
					return;
				}

				var i_options = pPlaylistControls.createLangOptions(p_dialog, value);//'';
				var i_resume = p_dialog.radio===true? value.includes('resume-true') : value === "resume-true";
				if (p_static===true)
					i_options = pURL.addBodyParameter(i_options, 'static', 1);

				m_mrl_options = i_options;

				var req = { player: m_temp.player, player_audio: m_temp.player_audio, 
					player_video: m_temp.player_video, 
					media: m_temp.media, play_time: m_temp.play_time,
					mrl: pURL.addQueryParameters(m_temp.mrl, i_options), 
					resume: i_resume, 
					transcode: m_temp.transcode, 
					incr: m_temp.incr };
			
				that.playImpl(req);

			};

			m_temp = { player: i_player, player_audio: i_player_audio, player_video: i_player_video, media: i_media, play_time: i_play_time, mrl: i_mrl, transcode: i_transcode, incr: p_incr };

			m_asked = true;
			pDialog.dialogQuestion(i_dialog);
		}
		else
			this.playImpl({ player: i_player, player_audio: i_player_audio, player_video: i_player_video, media: i_media, play_time: i_play_time, 
				mrl: pURL.addQueryParameters(i_mrl, m_mrl_options), resume: false, transcode: i_transcode, incr: p_incr });
		
	};
	this.playImpl = function(r) {//i_player, i_player_audio, i_player_video, i_media, i_play_time, i_mrl, i_resume, i_transcode, p_incr) {

		var p = r.player, m = r.media;
		
		try {
			m.error = false;
			
			var i_src = {
				url: r.mrl,
				options: [],
				title: (m.title)? m.title + ((r.transcode===true)? ' (transcoded)':'') : null ,
				hls: r.transcode && pLocation.param(r.mrl, 'type', 'hls')=='hls'
			};
			if (this.medias.length>1)
				if (i_src.title && !i_src.title.match(/[0-9]+\/[0-9]+ - .+/g))
					i_src.title = m.position+'/'+this.medias.length+' - ' + i_src.title;
			this.title = i_src.title;
			
			//*** CHANGE DOC TITLE
			if (pPlaylistUtil.OPTION_CHANGE_DOCTITLE) {
				that.doc_title_old = that.doc_title_old || document.title; // save title only first time... TODO: if title changes after renaming playlist, this will fail...
				document.title = doc_title_new = 'Playing: ' + i_src.title;
			}
			else {
				that.doc_title_old = null;
				doc_title_new = null;
			}

			if (r.resume) {
				i_src.options[i_src.options.length] = "resume-time=" + r.play_time;
				i_src.resume_time = r.play_time;
			}
			if (m.start_time && m.start_time > 0) {
				i_src.options[i_src.options.length] = "start-time=" + m.start_time;
				i_src.start_time = m.start_time;
			}
			if (m.stop_time && m.stop_time > 0) {
				i_src.options[i_src.options.length] = "stop-time=" + m.stop_time;
				i_src.stop_time =  m.stop_time;
			}

			p.source = i_src;
			
			//setTimeout(function() {
				if (p.setNextSource && this.index>0) {
					if (this.medias.currentMediaPlayingPosition()!=this.medias.length) {
						var n = this.medias.getValue(this.medias.nextMediaIndex());
						if (n)
							p.setNextSource(i_src, { url: f_createURL(n.url, server_nossl_port) });
					}
				}
				eh.fireEvent("title", this);
	
				//doRewind(p);
				//p.preload = "auto";
	
				//*** ENABLE SOME FEATURES
				disable_reportTime = false;
	
				that.medias.playCount = that.medias.playCount + 1;
				last_time = 0;
				p.load();
				p.play();
				
				if (pPlaylistUtil.OPTION_FULLSCREEN) {
					record_fullscreen = false;
					p.fullscreen = true;
					
				}
				
				for(var i=1;i<this.length+1;i++) {
					if (i!=this.index)
						this.updateMediaControlsPlayUnselected(i);
					//else
					//	updateMediaControlsPlay(i);
				}
				pPlaylistControls.updateChapters(m, p);
				pPlaylistControls.updateTracks(m, p);
	
				if (this.autoscroll===true)
					if (null!=m.controls && null!=m.controls.scrollTo)
						pDocument.scrollTo(m.controls.scrollTo);//'tr.'+this.index);
	
				// in case play event is not fired...
				this.updateMediaControlsPlay(this.index);
				//eh.fireEvent("play", i_playlist);
	
				//*** CALL PLAY INCR URL
				if (r.incr === true || this.callIncrPlayURL === true)
					if (m.url_incr)
						pHTTPRequest.post(m.url_incr);
		}
		catch (er) {
			console.log(er);
			alert("Failed: " + er + " " + er.stack);
		}
	};
	
	this.updateMediaControlsError = function(p_media_index) {
		var m = that.medias.getValue(p_media_index);
		if (m)
			if (m.controls) {
				pPlaylistUtil.highlightError(m.controls.scrollTo);
				pPlaylistUtil.highlightError(m.controls.highlight);
			}
	};

	// Handles error events from player
	this.f_error = function(e) {
		//console.log(e);
		var s = e.source || this;
		pConsole.info(that, pConsole.prefix(s, 'player') + " event: error...");
		
		//"this" is media player...
		if (!pString.v(this.source))
			return;
		//if (!pDocument.isShown(this))
		//	return;
		
		var p = pPlaylistManager.getValue(this.id);
		if (p) {
			if (p.m_player == this) {
				p.updateMediaControlsError(p.index);
		
				eh.fireEvent("error", p);
				
				var m = p.medias.current;
				m.error = true;
				
				if (pPlaylistUtil.OPTION_ONERROR_NEXT && p.medias.length>1)// && !play_by_user)
					p.next(p.medias.error!=true); //loop=false
				else if (e.msg && (e.src || this.source)) {
					pHTTPRequest.head({ 
						url: e.src || this.source,
						onerror: function() { pApplicationUI.errorDialog({ text: 'Failed to load media: network unavailable...' }); },
						f404: [ function() { pApplicationUI.errorDialog({ text: 'Failed to load media: media unavailable...' }) } ] 
					});
				}
				else if (e.msg) {
					pApplicationUI.errorDialog({ text: e.msg });//, icon: { src: '/resources/html/images/32x32/picture_error.png' } });
				}
			}
		}
	};

	this.togglePause = function() {
		var p = that.getPlayer();
		if (p)
			p.togglePause();
	};
	this.pause = function() {
		var p = that.getPlayer();
		if (p)
			p.pause();
	};
	// Handler pause events from player
	this.f_pause = function() {
		that.updateMediaControlsPause(that.index);
	
		eh.fireEvent("pause", that);
		m_paused = true;

		//*** CHANGE DOC TITLE
		if (doc_title_new && document.title == doc_title_new)
			document.title = doc_title_new + ' (Paused)';
	},
	this.updateMediaControlsPause = function(p_media_index) {
		var m = that.medias.getValue(p_media_index);
		if (m) {
			if (m.controls) {
				pDocument.show(m.controls.play);
				pDocument.hide(m.controls.play_unselected);
				pDocument.hide(m.controls.pause);
			}
	
			if (that.controls) {
				pDocument.enable(that.controls.play);
				pDocument.disable(that.controls.pause);
			}
		}
	};

	this.f_play = function() {
		that.updateMediaControlsPlay(that.index);
		
		eh.fireEvent("play", that);

		//*** CHANGE DOC TITLE
		if (doc_title_new)
			document.title = doc_title_new;
	};
	this.updateMediaControlsPlay = function(p_media_index) {
		var m = that.medias.getValue(p_media_index);
		if (m) {
			if (m.controls) {
				pDocument.hide(m.controls.play);
				pDocument.hide(m.controls.play_unselected);
				pDocument.show(m.controls.pause);
	
				pPlaylistUtil.highlight(m.controls.scrollTo, true);
				pPlaylistUtil.highlight(m.controls.highlight, true);
			}
	
			if (that.controls) {
				pDocument.enable(that.controls.previous)
				pDocument.enable(that.controls.next, that.length > 1 || eh.countListeners('unhandled-next')>0)
	
				pDocument.disable(that.controls.play);
				pDocument.enable(that.controls.pause);
	
				var p = that.getPlayer();
				if (that.controls.capture)
					that.controls.capture.forEach(function(c) {
						pDocument.enable(c, p.capture);
					});
			}
		}
	};
	this.updateMediaControlsPlayUnselected = function(p_media_index) {
		var m = that.medias.getValue(p_media_index);
		if (m)
			if (m.controls) {
				pDocument.hide(m.controls.play);
				pDocument.show(m.controls.play_unselected);
				pDocument.hide(m.controls.pause);
	
				pPlaylistUtil.highlight(m.controls.scrollTo, false);
				pPlaylistUtil.highlight(m.controls.highlight, false);
			}
	};

	this.captureArtwork = function() {
		var t = that.currentTime, m = that.medias.current;
		if (m && m.doc_url)
			pMediaLibrary.setArtworkImage(m.doc_url, { ms: t * 1000 }, null, true);
	};

	this.nextChapter = function() {
		var p = that.getPlayer(), m = that.medias.current;
		if (m) {
			const c = m.chapters;
			if (c && c.length>0) {
				//console.log('searching chapter...');
				for(var i=0 ; i<c.length-1; i++) {
					var i_time = p.currentTime * 1000;
					//console.log('time: ' + i_time);
					//console.log('chapter: ' + i + ' time: ' + i_time + ' - ' + i_chapters[i].startms + ' ' + i_chapters[i+1].startms);
					if (c[i].startms < i_time && i_time < c[i+1].startms) {
						//console.log('ok - chapter: ' + i + ' time: ' + i_time + ' - ' + i_chapters[i].startms + ' ' + i_chapters[i+1].startms);
						this.currentTime = c[i+1].startms / 1000;
						return true;
					}
				}
			}
		}
		return false;
	};

	this.next = function(loop) {

		// next chapter
		if (that.nextChapter())
			return;

		if (that.index>0) {
			if (that.medias.currentMediaPlayingPosition() == that.medias.length) {
				if ((null==loop || loop!=false) && that.loop) {
					
					if (that.medias.error())
						return;
					
					that.play(that.medias.firstMediaIndex());
				}
				else if (that.length == 1 && !loop)
					eh.fireEvent("unhandled-next", that.getPlayer());
			}
			else
				that.play(that.medias.nextMediaIndex());
		}
		else
			that.play(that.medias.firstMediaIndex());
	};

	this.previousChapter = function() {
		var p = that.getPlayer(), m = that.medias.current;
		if (m) {
			const c = m.chapters;
			if (c && c.length>0) {
				//console.log('searching chapter...');
				for(var i=0 ; i<c.length-1; i++) {
					var i_time = p.currentTime * 1000;
					//console.log('time: ' + i_time);
					//console.log('chapter: ' + i + ' time: ' + i_time + ' - ' + i_chapters[i].startms + ' ' + i_chapters[i+1].startms);
					if (c[i].startms < i_time && i_time < c[i+1].startms) {
						//console.log('ok - chapter: ' + i + ' time: ' + i_time + ' - ' + i_chapters[i].startms + ' ' + i_chapters[i+1].startms);
						if (i_time > c[i].startms + 5000)
							that.currentTime = c[i].startms / 1000;
						else if (i>0)
							that.currentTime = c[i-1].startms / 1000;
						return true;
					}
				}
			}
		}
		return false;
	};

	this.previous = function() {

		// previous chapter
		if (that.previousChapter())
			return;

		var p = that.getPlayer(), m = that.medias.current;
		if (p.currentTime * 1000 < Math.max(0, m.start_time || 0)+5000) {
			if (this.medias.currentMediaPlayingPosition()==1) {
				if (that.loop === true)
					that.play(that.medias.lastMediaIndex());
				else if (that.length == 1 && !that.loop)
					eh.fireEvent("unhandled-previous", that.getPlayer());
			}
			else
				that.play(that.medias.previousMediaIndex());
		}
		else {
			that.doRewind(p);
			p.play();
		}
	};
	this.doRewind = function(i_player) {
		var m = that.medias.current;
		if (m) {
			if (m.start_time >=0) {
				//alert("1: " + i_media.start_time / 1000 + " - " + i_player.currentTime);
				//i_player.preload = "auto";
				//i_player.load();
				i_player.currentTime = m.start_time / 1000;
				//alert("2: " + i_media.start_time / 1000 + " - " + i_player.currentTime);
			}
			else
				i_player.currentTime = 0;
		}
	};
	this.m_canplay = function() {
		pConsole.info(that, "player event: can play...");
	
		if (pPlaylistUtil.OPTION_CANPLAY_PLAY)
			that.play(); //TODO: ...
			
		pPlaylistControls.updateTracks(that.medias.current, that.getPlayer());
	};
	this.reset = function() {

		this.pause();

		var i_player = this.getPlayer();
		if (i_player)
			i_player.source = null;

		for(var i=1 ; i<this.length+1 ; i++) {
			var i_media = this.medias.getValue(i);
			if (null!=i_media.controls) {
				pDocument.hide(i_media.controls.play);
				pDocument.show(i_media.controls.play_unselected, 'inline');
				pDocument.hide(i_media.controls.pause);

				pPlaylistUtil.highlight(i_media.controls.scrollTo, false);
				pPlaylistUtil.highlight(i_media.controls.highlight, false);
			}
		}

		if (this.controls) {
			pDocument.disable(this.controls.previous);
			pDocument.disable(this.controls.next);
			pDocument.disable(this.controls.play);
			pDocument.disable(this.controls.pause);
			if (this.controls.capture)
				for(var i=0 ; i<this.controls.capture.length ; i++)
					pDocument.disable(this.controls.capture[i]);
		}

		this.m_player = null;
		this.index = null;
		if (this.autoclear)
			this.medias.clear();
		m_asked = false;
		m_mrl_options = '';
	};
	this.close = function() {

		try {
			//*** NOTIFY
			eh.fireEvent("close-before", that);
	
			//*** REPORT TIME TO SERVER
			f_reportTime();
			f_reportEnded();
	
			that.reset();
	
			//*** NOTIFY
			eh.fireEvent("close-after", that);
		}
		catch (exception) {
			alert(exception + ": " + exception.stack);
		}
	};
	
	this.getMedia = function() {
		return that.medias.current;
	};
		
	// "local" property
	//this.m_local = pCookieManager.getValue("device-local");
	this.m_local_user = false;
	Object.defineProperty(this, "local", { get: function() { return (this.m_local_user === true)? this.m_local : pROSE.getProp(pPlaylistUtil.options.OPTION_DEVICE_LOCAL); }, set: function(value) { if (typeof value == "string") { this.m_local = value.toLowerCase(); this.m_local_user = true; } } });

	// "autoscale" property
	this.m_autoscale = pROSE.getProp(pPlaylistUtil.options.OPTION_DEVICE_SCALE_VIDEOS);
	this.m_autoscale_user = false;
	Object.defineProperty(this, "autoscale", { get: function() { return this.m_autoscale; }, set: function(value) { this.m_autoscale = value; this.m_autoscale_user = true; } });

	// "autoscroll" property
	this.m_autoscroll = true;
	Object.defineProperty(this, "autoscroll", { get: function() { return this.m_autoscroll; }, set: function(value) { this.m_autoscroll = value; } });

	// "autoclear" property
	this.m_autoclear = true;
	Object.defineProperty(this, "autoclear", { get: function() { return this.m_autoclear; }, set: function(value) { this.m_autoclear = value; } });

	// "transcode" property
	this.m_transcode_user = false;
	Object.defineProperty(this, "transcode", { get: function() { return (this.m_transcode_user === true)? this.m_transcode : pPlaylistUtil.OPTION_DEVICE_TRANSCODE; }, set: function(value) { this.m_transcode = value; this.m_transcode_user = true; } });

	// "loopTrack" property
	this.m_loopTrack = false;
	Object.defineProperty(this, "loopTrack", { get: function() { return this.m_loopTrack; }, set: function(value) { this.m_loopTrack = value; } });

	// "loop" property
	var m_loop = true; 
	Object.defineProperty(this, "loop", { get: function() { return m_loop; }, set: function(v) { m_loop = v; } });

	// "showVideos" property
	var m_showVideos = false; 
	Object.defineProperty(this, "showVideos", { get: function() { return m_showVideos; }, set: function(v) { if (typeof v == "boolean") m_showVideos = v; } });

	// "askOptions" property
	var m_askOptions = true;
	Object.defineProperty(this, "askOptions", { get: function() { return m_askOptions; }, set: function(v) { if (typeof v == "boolean") m_askOptions = v; } });

	// "askResume" property
	Object.defineProperty(this, "askResume", { get: function() { return this.shuffle === false && this.medias.playCount===0; /*m_true*/ }});

	// "paused" property
	Object.defineProperty(this, "paused", { get: function() { return m_paused; }});

	// "callIncrPlayURL" property
	var m_callIncrPlayURL = false;
	Object.defineProperty(this, "callIncrPlayURL", { get: function() { return m_callIncrPlayURL }, set: function(v) { if (typeof v == "boolean") m_callIncrPlayURL = v; } });

	// Opens an URL and play it.
	this.open = function(url, p_askResume, p_title, p_start, p_static) {

		//*** NOTIFY
		eh.fireEvent("open-before", that);

		if (typeof url == "string") {

			var i_url = url + "&format=json"/*&urlformat=simple"*/;
			pConsole.info(that, 'Open url: ' + i_url);
			//alert(i_url);
			pHTTPRequest.get(i_url, function(i_request) {
				if (pROSE.handleHTTPResponse(i_request)===false)
					return;

				var i_obj = pJSON.parse(i_request.responseText);
				that.reset();

				i_obj.forEach(function(i_media) {
					if (p_title)
						i_media.title = p_title;
					that.addMedia(i_media);
				});
				that.play(p_start || 1, null, p_askResume, null, p_static);
			}, false, that);
		}
		else {
			that.reset();
			url.forEach(function(u) { that.addMedia(u); });
			that.play(p_start || 1, null, p_askResume, null, p_static);
		}

		//*** NOTIFY
		eh.fireEvent("open-after", that);
	};

	this.m_players = [];

	//*** REGISTER PLAYERS
	function f_registerPlayer(p) {
		/*abort 	Fires when the loading of an audio/video is aborted
		canplay 	Fires when the browser can start playing the audio/video
		canplaythrough 	Fires when the browser can play through the audio/video without stopping for buffering
		durationchange 	Fires when the duration of the audio/video is changed
		emptied 	Fires when the current playlist is empty
		ended 	Fires when the current playlist is ended
		error 	Fires when an error occurred during the loading of an audio/video
		loadeddata 	Fires when the browser has loaded the current frame of the audio/video
		loadedmetadata 	Fires when the browser has loaded meta data for the audio/video
		loadstart 	Fires when the browser starts looking for the audio/video
		pause 	Fires when the audio/video has been paused
		play 	Fires when the audio/video has been started or is no longer paused
		playing 	Fires when the audio/video is playing after having been paused or stopped for buffering
		progress 	Fires when the browser is downloading the audio/video
		ratechange 	Fires when the playing speed of the audio/video is changed
		seeked 	Fires when the user is finished moving/skipping to a new position in the audio/video
		seeking 	Fires when the user starts moving/skipping to a new position in the audio/video
		stalled 	Fires when the browser is trying to get media data, but data is not available
		suspend 	Fires when the browser is intentionally not getting media data
		timeupdate 	Fires when the current playback position has changed
		volumechange 	Fires when the volume has been changed
		waiting Fires when the video stops because it needs to buffer the next frame
		*/
		
		var f = function(e){ pConsole.info(that, 'player event: '+e.id+'...'); };

		p.addEventListener("abort", f);

		p.addEventListener("canplay", that.m_canplay);

		p.addEventListener("canplaythrough", that.m_canplay);//function(){ pConsole.info(pPlaylistManager.getValue(this.id), "player event: canPlayThrough...");  });
		p.addEventListener("durationchange", f);
		p.addEventListener("emptied", f);

		p.addEventListener("ended", that.f_ended, null);
		p.addEventListener("error", that.f_error, null);

		p.addEventListener("loadeddata", that.m_canplay, null);//function(){ pConsole.info(pPlaylistManager.getValue(this.id), "player event: loaded data..."); });
		p.addEventListener("loadedmetadata", f);
		p.addEventListener("loadstart", f);

		p.addEventListener("pause", that.f_pause, null);
		p.addEventListener("play", that.f_play, null);

		p.addEventListener("playing", f);
		//p.addEventListener("progress", f);
		p.addEventListener("ratechange", f);
		p.addEventListener("seeked", f);
		p.addEventListener("seeking", f);
		p.addEventListener("stalled", f);
		p.addEventListener("suspend", f);

		p.addEventListener("timeupdate", f_timed);

		p.addEventListener("volumechange", f);
		p.addEventListener("waiting", f);

		that.m_players.push(p);

		//*** Register playlist with player ID so it can be retrieved in event functions
		//*** where "this" is the player and not the playlist
		pPlaylistManager.add(p.id, that); //???
	};
	
	p_players.filter(pObject.isNotNull).forEach(function(p) {
		if (typeof p == "string") {
			if (p = pElement.x(p))
				f_registerPlayer(new pMediaPlayer(p));
		}
		else
			f_registerPlayer(p);
	});

	// "fullscreen" property
	Object.defineProperty(this, "fullscreen", { get: function() {
			return (that.getPlayer() || {}).fullscreen;
		}, set: function(s) {
			record_fullscreen = false;
			(that.getPlayer() || {}).fullscreen = s;
			
			if (!that.getPlayer()) {
				if (s === true)
					pDocument.fullScreen('div-vlc-main');
				else
					pDocument.exitFullScreen();
			}
			
		/*if (typeof s == "boolean") {
			var p = this.getPlayer();
			if (p)
				p.fullscreen = s;
		}*/
	}});
	
	this.f_chapters_menu = function() {
		var m = that.medias.current, c = (m || {}).chapters || [], o = pPlaylistUtil.OPTION_CHAPTERS;
		if (o=='1' || (o=='auto' && c.length>1))
			pApplicationMenu.menu('chapters', 
				c.length<1?
					[{ id:'none', text: pDocument.getStyleValue('.dq-chapters-none') }]
				:
					c.map(function(c, i) {
						//TODO: support nested chapters (hidden, enabled...)
						if (null==c.enabled || c.enabled === true)
							if (!c.hidden) {
								var n = c.name, r = pPlaylistControls.regex_names.find(function(r) { return r.regex.test(n) });
								
								return { text: pString.encodeHTML(r? r.transform(n, i) : n), fcall: function() { pPlaylistControls.seek(m_vlc, this.startms); }.bind(c) };
							}
					})
			);
		
	};
	
	this.f_audio_menu = function() {
		var p = that.getPlayer(), m = that.medias.current, a = []/*(m || {}).audioTracks || []*/, o = pPlaylistUtil.OPTION_AUDIO_TRACKS;
		//console.log(m.audioTracks);
		if (p && p.audioTracks && p.audioTracks.length>0) {
			for(var i=0 ; i<p.audioTracks.length ; i++)
				a[a.length] = p.audioTracks[i];
		}
		if (o=='1' || (o=='auto' && a.length>1))
			pApplicationMenu.menu('audio', 
				a.length<1?
					[{ id: 'none', text: pDocument.getStyleValue('.dq-audio-none') }]
				:
					a.map(function(a, i) {
						return { 
							id: ''+i, 
							text: pString.encodeHTML(pTrackListUtil.displayAudioTrack(a, i, m)), 
							fcall: function() { 
								for(var i=0 ; i<p.audioTracks.length ; i++)
									p.audioTracks[i].enabled = i == this.i;
							}.bind({ i: i }), 
							selected: p.audioTracks[i].enabled 
						};
					})
			);
		
	};
	
	//*** REPORT TIME BEFORE PAGE IS UNLOADED
	pDocument.addOnPageClose(function(e) {
		f_reportTime();
	});
};

/******************************************************************************/
/***  Registry of all playlists                                             ***/
/******************************************************************************/

const pPlaylistManager = new (function() {
	var that = this, m_playlists = new pMap();
	this.tracePrefix = 'PlaylistManager';
	
	this.create = function(p_id, p_players, p_controls) {
		var p = new pPlaylistInstance(p_id, p_players, p_controls);
		
		that.add(null, p);

		//pause after playlists in the same page
		p.addEventListener("play", function() {
			that.play(p.id);
		}, null);

		return p;
	};
	
	this.add = function(id, p_playlist) {
		m_playlists.put(id || p_playlist.id, p_playlist);
	};
	
	this.getValue = function(p_id) {
		//return this.playlists.getValue(p_id, null);

		var p = m_playlists.getValue(p_id);
		if (!p)
			alert(pConsole.error(that, 'Cannot find playlist: ' + p_id));

		return p;
	};
	
	this.play = function(p_id) { // pause all playlists or players except the one with the given id
		m_playlists.values().forEach(function(p) {
			if (p.id != p_id)
				p.pause();
		});
	};
	
	this.start = function() {
		return null!=m_playlists.values().find(function(p) {
			if (p.medias.length>0) {
				if (p.medias.playCount === 0)
					p.play(1, true);
				else
					p.togglePause();
				return true;
			}
		});	
	};

	//*** REGISTER KEYS
	pDocumenti.addOnKeyDown(function(e, k) {
		
		if (e.altKey || e.ctrlKey || e.shiftKey)
			return;
		
		pConsole.debug(that, 'KeyCode: ' + k);
		if (k == 32)
			return that.start();
		
		if (k == 40) //DOWN
			return null!=m_playlists.values().find(function(p) {
				if (p.medias.length>0) {
					if (p.medias.playCount === 0)
						p.play(1, true);
					else
						p.next();
					return true;
				}
			});
		
		if (k == 38) //UP
			return null!=m_playlists.values().find(function(p) {
				if (p.medias.length>0) {
					if (p.medias.playCount === 0)
						p.play(1, true);
					else
						p.previous();
					return true;
				}
			});
		
		if (k >= 97 && k <= 105) //1 to 9
			return null!=m_playlists.values().find(function(p) {
				if (p.medias.length>0) {
					p.play(k - 96, true);
					return true;
				}
			});
		
	});	
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

var pPlaylist = {
	sortTableString: function(p_playlist_table_id, title){
		pTable.sortString(p_playlist_table_id, columnIndex(p_playlist_table_id, title, 0));
		//sortTableString(p_playlist_table_id, columnIndex(p_playlist_table_id, title, 0));
	},
	sortTableTitle: function(p_playlist_table_id, title){
		pTable.sortTitle(p_playlist_table_id, columnIndex(p_playlist_table_id, title, 0));
		//sortTableTitle(p_playlist_table_id, columnIndex(p_playlist_table_id, title, 0));
	},
	sortTableInt: function(p_playlist_table_id, title){
		pTable.sortInt(p_playlist_table_id, columnIndex(p_playlist_table_id, title, 0));
		//sortTableInt(p_playlist_table_id, columnIndex(p_playlist_table_id, title, 0));
	},
	sortTableDuration: function(p_playlist_table_id, title){
		pTable.sortDuration(p_playlist_table_id, columnIndex(p_playlist_table_id, title, 0));
		//sortTableDuration(p_playlist_table_id, columnIndex(p_playlist_table_id, title, 0));
	}
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pPlaylistControls = new (function() {
	var that = this;
	
	const m_regex_names = Object.freeze([ { regex: /[a-z][a-z]:Chapter [0-9]+/i, transform: function(i_name) { return 'Chapter ' + i_name.substring(11); } },
		   { regex: /Chapter [0-9]+/i, transform: function(i_name) { return 'Chapter ' + i_name.substring(8); } },
		   { regex: /[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]/, transform: function(i_name, i) { return 'Chapter ' + (i+1) } }
		 ]);

	this.tracePrefix = 'PlaylistControls';
	
	// "regex_names" property
	Object.defineProperty(this, "regex_names", { get: function() { return m_regex_names; }});
	
	this.updateChapters = function(m, p) {
		pConsole.info(that, "Update Chapters...");
		
		var o = pPlaylistUtil.OPTION_CHAPTERS;
		if (o == '1' || (o == 'auto' && m.chapters && m.chapters.length>1))
			pDocument.show('v0-chapters', 'auto');
		else
			pDocument.hide('v0-chapters');
		
		pDocument.onResize();
	};
	
	this.updateTracks = function(m, p) {
		pConsole.info(that, "Update Audio Tracks...");
		
		var o = pPlaylistUtil.OPTION_AUDIO_TRACKS;
		if (o=='1' || (o == 'auto' && pTrackListUtil.countAudioTracks(m.tracks)>1 && p.audioTracks && p.audioTracks.length>1))
			pDocument.show('v0-audio', 'auto');
		else 
			pDocument.hide('v0-audio');

		pDocument.onResize();
	};
	
	this.seek = function(p, t) {
		p.currentTime = t / 1000;
		pConsole.info(that, "Seek to: " + p.currentTime);
	};
	
	this.langOptionText = function(n, l) {
		//var nn = pLang.nativex.getValue(l);
		//if (l && n.indexOf(nn)<0)
		//	return n + ' ('+nn+')';
		return n;
	};
	
	this.addDialogLangOptions = function(i_dialog, i_media, i_transcode, i_webvtt) {
		
		var i_tracks = i_media.tracks, i_langs = pApplicationUI.OPTION_PREFERRED_LANGS, sc = pTrackListUtil.countSubtitleTracks(i_tracks, true);
		i_dialog.i_audio_done = 0;

		if (i_transcode===true && i_webvtt===false && i_tracks && i_dialog.i_audio_done === 0) {
			var ac = pTrackListUtil.countAudioTracks(i_tracks);
			if (sc>0 || ac>1) {
				i_dialog.radio = true;
				i_dialog.options.push(m_choose_lang_text);
				
				var ll = [];
				i_tracks.forEach(function(t) {
					if (t.type == "a") {
						var n = pTrackListUtil.longLangName(t);
						if (ll.includes(n)) n = pTrackListUtil.longName(t);
						ll[ll.length] = n;
						
						i_dialog.options.push({
							audio: true,
							lang: t.metadata_lang,
							id: "langid-"+t.id, text: '<span class="lang">'+pPlaylistControls.langOptionText(n, t.metadata_lang)+'</span>', value: "langid-"+t.id, disabled: ac < 2, selected: ac < 2 });
						i_dialog.i_audio_done++;
					}
				});
			}
		}

		/*if (i_transcode===true && i_webvtt===false && i_media.metadata_lang && i_audio_done === 0) {
			var i_langs = i_media.metadata_lang.split("|");
			if (i_langs.length>1) {
				//i_dialog.radio = true;
				i_dialog.options[i_dialog.options.length] = { sep:true, text: "Language:" };
				for(var i=0 ; i<1 ; i++)
					i_dialog.options[i_dialog.options.length] = { id: "lang-"+i_langs[i], text: '<span class="lang">'+i_langs[i]+'</span>', value: "lang-"+i_langs[i],
						m_enabled: false, selected: true };

				i_audio_done++;
			}
		}*/

		i_dialog.i_subtitles_done = 0;
		var i_option_snone = null;

		if (i_transcode===true && i_webvtt===false && i_tracks && i_dialog.i_subtitles_done === 0) {
			if (sc>0) {
				i_dialog.radio = true;
				i_dialog.options.push(m_choose_subtitles_text);
				
				var ll = [];
				i_tracks.forEach(function(t) {
					if (t.type == "s") {
						var n = pTrackListUtil.longLangName(t);
						if (ll.includes(n)) n = pTrackListUtil.longName(t);
						ll[ll.length] = n;
						
						i_dialog.options.push({
							subtitles: true,
							lang: t.metadata_lang,
							id: "slangid-"+t.id, text: '<span class="lang">'+pPlaylistControls.langOptionText(n, t.metadata_lang)+'</span>', value: "slangid-"+t.id });
						i_dialog.i_subtitles_done++;
					}
				});
				i_option_snone = { id: "slang-none", text: '<span class="lang">'+m_none_text+'</span>', value: "slang-none", selected: true };
				i_dialog.options.push(i_option_snone);
			}
		}
		
		//*** auto select audio and substitles
		if (i_dialog.i_audio_done >0 || i_dialog.i_subtitles_done > 0)
			that.autoSelectLangs(i_dialog, i_option_snone, i_media);
	};
	
	this.createLangOptions = function(p_dialog, value) {
		var o = '';
		//var i_resume = (p_dialog.radio && p_dialog.radio===true)? value.indexOf('resume') >= 0 : value === "resume-true";
		if (p_dialog.radio===true) {
			value.forEach(function(v) {
				if (v.indexOf('lang-')==0)
					o = pURL.addBodyParameter(o, "lang", v.substring('lang-'.length));
				if (v.indexOf('langid-')==0)
					o = pURL.addBodyParameter(o, "lang-trackID", v.substring('langid-'.length));
				if (v.indexOf('slang-')==0)
					o = pURL.addBodyParameter(o, "subtitle-lang", v.substring('slang-'.length));
				if (v.indexOf('slangid-')==0)
					o = pURL.addBodyParameter(o, "subtitle-trackID", v.substring('slangid-'.length));
			});
			/*for(var i=0 ; i<value.length ; i++) {
				if (value[i].indexOf('lang-')==0)
					i_options = pURL.addBodyParameter(i_options, "lang", value[i].substring('lang-'.length));
				if (value[i].indexOf('langid-')==0)
					i_options = pURL.addBodyParameter(i_options, "lang-trackID", value[i].substring('langid-'.length));
				if (value[i].indexOf('slang-')==0)
					i_options = pURL.addBodyParameter(i_options, "subtitle-lang", value[i].substring('slang-'.length));
				if (value[i].indexOf('slangid-')==0)
					i_options = pURL.addBodyParameter(i_options, "subtitle-trackID", value[i].substring('slangid-'.length));
			}*/
		}
		return o;
	};

	this.autoSelectLangs = function(i_dialog, i_option_snone, i_media) {
		var i_langs = pApplicationUI.OPTION_PREFERRED_LANGS, o = i_dialog.options.filter(function(o) { return o.audio; });

		//*** auto select audio and subtitles
		var i_lang_ok = false;

		//*** select first audio from media country
		if (pPlaylistUtil.OPTION_PREFER_VO === true) {
			var i_lang_country = (i_media.country)? pLang.getLanguageByCountry(i_media.country) : null;
			if (i_lang_country)
				i_dialog.options.find(function(op) {
					if (op.audio === true) {
						if (op.lang === i_lang_country) {
							op.selected = true;
							i_lang_ok = true;
							return true;
						}
					}
				});
				/*
				for(var i=0 ; i<i_dialog.options.length && i_lang_ok === false ; i++) {
					var i_option = i_dialog.options[i];
					if (i_option.audio === true) {
						if (i_option.lang === i_lang_country) {
							i_option.selected = true;
							i_lang_ok = true;
						}
					}
				}*/
		}

		//*** select first audio from preferred lang
		o.forEach(function(o) {
			if (!i_lang_ok)
				i_langs.forEach(function(l) {
					if (pLang.getISOName(o.lang)==l)
						o.selected = i_lang_ok = true;
				});
		});
		/*for(var i=0 ; i<i_dialog.options.length && i_lang_ok === false ; i++) {
			var i_option = i_dialog.options[i];
			if (i_option.audio === true) {
				for(var j=0 ; j<i_langs.length && i_lang_ok === false ; j++)
					if (i_option.lang === i_langs[j]) {
						i_option.selected = true;
						i_lang_ok = true;
					}
			}
		}*/

		//*** select first audio
		if (!i_lang_ok && o.length>0)
			o[0].selected = true;
			/*i_dialog.options.find(function(op) {
				if (op.audio === true) {
					op.selected = true;
					return true;
				}
			});*/
			/*for(var i=0 ; i<i_dialog.options.length ; i++) {
				var i_option = i_dialog.options[i];
				if (i_option.audio === true) {
					i_option.selected = true;
					break;
				}
			}*/

		//*** check if audio if preferred lang
		i_lang_ok = false;
		o.forEach(function(o) {
			if (!i_lang_ok)
				i_langs.forEach(function(l) {
					if (pLang.getISOName(o.lang)==l && o.selected)
						i_lang_ok = true;
				});
		});
		/*for(var i=0 ; i<i_dialog.options.length && i_lang_ok != true ; i++) {
			var i_option = i_dialog.options[i];
			if (i_option.audio === true)
				for(var j=0 ; j<i_langs.length ; j++)
					if (i_option.lang === i_langs[j] && i_option.selected === true)
						i_lang_ok = true;
		}*/

		//*** if not
		if (i_lang_ok != true) {
			var done = false, o = i_dialog.options.filter(function(o) { return o.subtitles });
  
			//*** unselect all
			o.forEach(function(o) { o.selected = false; });
			if (i_option_snone)
				i_option_snone.selected = false;

			//*** select one subtitle from preferred langs
			o.forEach(function(o) {
				if (!done) {
					var iso = pLang.getISOName(o.lang);
					i_langs.forEach(function(l) {
						if (iso == l)
							o.selected = done = true;
					});
				}
			});
			if (!done && i_option_snone)
				i_option_snone.selected = true;
						
			//*** unselect all
			/*i_dialog.options.forEach(function(op) {
				if (op.subtitles === true)
					op.selected = false;
			});
			if (i_option_snone)
				i_option_snone.selected = false;

			//*** select one subtitle from preferred langs
			for(var i=0 ; i<i_dialog.options.length && i_subtitles_ok === false ; i++) {
				var i_option = i_dialog.options[i];
				if (i_option.subtitles === true)
					for(var j=0 ; j<i_langs.length && i_subtitles_ok === false ; j++)
						if (i_option.lang === i_langs[j]) {
							i_option.selected = true;
							i_subtitles_ok = true;
						}
			}
			if (i_subtitles_ok != true)
				if (i_option_snone)
					i_option_snone.selected = true;*/
		}
	};

});

/******************************************************************************/
/***  DETECT FULLSCREEN CHANGES on CHROME/Webkit  *****************************/
/******************************************************************************/

var f = function(e) {
	if (record_fullscreen != false)
		pPlaylistUtil.OPTION_FULLSCREEN = document.webkitFullscreenElement!=null;
	else
		record_fullscreen = true;
};
document.addEventListener('fullscreenchange', f);
document.addEventListener('webkitfullscreenchange', f);
document.addEventListener('mozfullscreenchange', f);

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
/*
const u_pause = '<i class="fas fa-pause"></i>';//'\u23F8';
const u_play = '<i class="fas fa-play"></i>';//'\u25B6';
const u_fullscreen = '<i class="fas fa-expand"></i>';//'\u25F3';
const u_exit_fullscreen = '<i class="fas fa-compress"></i>';
const u_muted = '<i class="fas fa-volume-off"></i>';//'\uD83D\uDD07';
const u_unmuted = '<i class="fas fa-volume-up"></i>';//'\uD83D\uDD0A';*/

var pVideoControls_img_loaded = false;

function pVideoControls(x, c) {
	var that = this, display_remaining_time = false, imgs = {}, video = x; 
	
	function seekable() {
		var s = video.seekable;
		return s && s.length>0 && isFinite(s.end(0));
	}
	
	c = c || {};
	
	// Buttons
	var playButton = pElement.x(c.play || "play-pause"); pElement.setInnerHTML(playButton, '');
	var muteButton = pElement.x(c.mute || "mute"); pElement.setInnerHTML(muteButton, ''); 
	var fullScreenButton = pElement.x(c.fullscreen || "full-screen"); pElement.setInnerHTML(fullScreenButton, ''); 
	
	// Sliders
	var seekBar = pElement.x(c.seek || "seek-bar");
	var volumeBar = pElement.x(c.volume || "volume-bar"); 
	
	var t1 = pElement.x(c.time1 || 'time1');
	
	this.start_time = 0;
	this.stop_time = 0;
	
	this.ms = c.ms;
	
	//*** PRELOAD ICONS
	if (!pVideoControls_img_loaded) {
		pVideoControls_img_loaded = true;
	
		if (pDevice.isMobile()) {
			const aa = [ "pause", "mute", "unmute", "exitfullscreen", "play", "fullscreen", "next", "previous", "loop-track", "loop-playlist", "show-video", "shuffled" ];
			aa.forEach(function(a) {
				var u = pDocument.getStyleValue("btn-player-button-"+a, "background-image");
				if (u) {
					pHTTPRequest.get(u);
					//imgs[a] = new Image();
					//imgs[a].src = u;
					//pConsole.info(that, "Pre-loading icon for " + a + ': ' + imgs[a].src + "...");
				}
			});
			aa.forEach(function(a) {
				var u = pDocument.getStyleValue("btn-player-button-"+a+'.disabled', "background-image");
				if (u) {
					pHTTPRequest.get(u);
					//imgs[a+'d'] = new Image();
					//imgs[a+'d'].src = u;
					//pConsole.info(that, "Pre-loading icon for " + a + ': ' + imgs[a+'d'].src + "...");
				}
			});
		}
	}

	//*** PLAY
	var timer_interval;
	if (playButton) {
		var f_video_play = function() {
			if (video.paused == true)
				video.play();
			else
				video.pause();
		};
		playButton.addEventListener("click", f_video_play);
		var f_play = function() {
			pElement.removeClassName(playButton, 'disabled');
			if (video.paused == true) {
				pElement.addClassName(playButton, 'paused');
				//this.playButton.innerHTML = u_play;
				
				if (timer_interval) {
					clearInterval(timer_interval);
					timer_interval = null;
				}
			}
			else
				pElement.removeClassName(playButton, 'paused');
				//this.playButton.innerHTML = u_pause;
		};
		video.addEventListener('play', f_play);
		video.addEventListener('pause', f_play);
	}
	//this.video.addEventListener('click', this.f_video_play);
	
	//*** FULLSCREEN
	if (fullScreenButton) {
		//pElement.setInnerHTML(this.fullScreenButton, u_fullscreen);
		fullScreenButton.title = 'Full screen';
		
		fullScreenButton.addEventListener("click", function() {
			var x = pElement.x('div-vlc-main');//this.video;
			if (x) {
				if (pDocument.isFullScreen())
					pDocument.exitFullScreen(x);
				else if (pDevice.isIOS())// && !pDevice.isIOS12())
					pDocument.fullScreen(video);
				else
					pDocument.fullScreen(x);
			}
		});
		var f = function() {
			if (pDocument.isFullScreen()) {
				pElement.addClassName(fullScreenButton, 'fullscreen');
				fullScreenButton.title = pDocument.getStyleValue('btn-exit-fullscreen-tooltip');
			}
			else {
				pElement.removeClassName(fullScreenButton, 'fullscreen');
				fullScreenButton.title = pDocument.getStyleValue('btn-fullscreen-tooltip');
			}
		}; 
		document.addEventListener("fullscreenchange", f);
		document.addEventListener("mozfullscreenchange", f);
		document.addEventListener("webkitfullscreenchange", f);
		
		pElement.x('div-vlc-header').addEventListener('dblclick', function() { pElement.click(fullScreenButton); });
		video.addEventListener('dblclick', function() { pElement.click(fullScreenButton); });
	}
	
	this.time_range = new pRange(seekBar);
	this.time_range.enabled = false;
	this.time_range.addEventListener('move', function(e) {
		var t = (that.start_time>=0? that.start_time : 0)/1000 + (e.value / 1000);
		if (seekBar) seekBar.title = pTime.display(t * 1000);
	});
	
	this.time_range.addEventListener("change", function() {
		//Calculate the new time
		//var d = this.start_time>=0? (this.stop_time - this.start_time)/1000 : this.video.duration;
		var t = ((that.start_time>0? that.start_time : 0) + that.time_range.value)/1000;

		// Update the video time
		video.currentTime = t;
	});
	
	function f_time() {
		//console.log(this.video.currentTime);
		// Calculate the slider value
		var c = video.currentTime * 1000 - (that.start_time>0? that.start_time : 0), d = (that.start_time>0 || that.stop_time>0)? (that.stop_time - that.start_time) : video.duration * 1000, v = c;//(100 / d) * c;

		// Update the slider value
		if (seekable()) {
			//pDocument.show(this.seekBar.parentElement);
			pElement.setInnerHTML(t1, pTime.display(c) + (display_remaining_time? ' / -' : ' / ') + pTime.display(display_remaining_time? d - c : d));
		}
		else {
			//pDocument.hide(this.seekBar.parentElement);
			pElement.setInnerHTML(t1, pTime.display(c));
		}
	  
		that.time_range.min = 0;
		that.time_range.max = d;
		that.time_range.value = v;
		
		var b = video.buffered;
		//console.log(b.length + ' ' + (b.length>0? b.end(0) : '0'));
		that.time_range.buffered = ((b && b.length>0)? b.end(b.length-1) : 0)*1000 - (that.start_time>0? that.start_time : 0);
				
		if (d<60000) {
			if (!timer_interval) {
				timer_interval = setInterval(f_time, 10);
				//console.log("set interval...");//this.video.currentTime);
			}
		}
		else if (timer_interval) {
			clearInterval(timer_interval);
			timer_interval = null;
		}
		
	};
	video.addEventListener("timeupdate", f_time);
	f_time();
	if (t1)
		t1.onclick = function() { display_remaining_time = !display_remaining_time; f_time(); };
	
	this.resetTime = function() {
		pElement.setInnerHTML(t1, pTime.display(0) + (display_remaining_time? ' / -' : ' / ') + pTime.display(0));
		
		that.time_range.value = 0;
		that.time_range.buffered = 0;
		if (seekBar) seekBar.title = pTime.display(0);
	};
	
	// Pause the video when the slider handle is being dragged
	//seekBar.addEventListener("mousedown", function() { video.pause(); });

	// Play the video when the slider handle is dropped
	//seekBar.addEventListener("mouseup", function() { video.play(); });
	
	//*** VOLUME
	if (muteButton) {
		muteButton.onclick = function(e) {
			if (video.muted == false)
				video.muted = true;
			else
				video.muted = false;
		};
	}
	
	this.volume_range = new pRange(volumeBar);
	this.volume_range.enabled = false;
	this.volume_range.min = 0;
	this.volume_range.value = video.volume * 100;
	
	//this.volume_range.max = 1;
	this.volume_range.addEventListener('change', function() {
		//console.log('volume: ' + this.volume_range.value / 100);
		video.volume = that.volume_range.value / 100;
	});
	function f_volume() {
		if (muteButton) {
			if (video.muted == false) {
				pElement.removeClassName(muteButton, 'muted');
				muteButton.title = "Mute";
			}
			else {
				pElement.addClassName(muteButton, 'muted');
				muteButton.title = "Unmute";
			}
		}
		
		volumeBar.value = video.volume * 100;
	};
	video.addEventListener('volumechange', f_volume);
	f_volume();		
};

/******************************************************************************/
/***  END OF FILE  ************************************************************/
/******************************************************************************/