PageSourceSearch

https://cashkidpodcast.com/player/new.js

js cashkidpodcast.com collected 2026-09-25 03:17:38 UTC 412,509 bytes, 15,059 lines download raw bytes

1(function (window){
2
3	var HAPUtils = function(){};
4
5	HAPUtils.isEmpty = function(str){
6		return str.replace(/^s+|s+$/g, '').length == 0;
7	}
8
9	HAPUtils.strip = function(str){
10		return str.replace(/^\s+|\s+$/g,"");
11	}
12
13	HAPUtils.isNumber = function(n){
14	    return !isNaN(parseFloat(n)) && isFinite(n);
15	}
16
17	HAPUtils.isMobile = function(){
18		return (/Android|webOS|iPhone|iPad|iPod|sony|BlackBerry/i.test(navigator.userAgent));
19	}
20
21	HAPUtils.isChrome = function(){
22		return !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime)
23	}
24
25	HAPUtils.isSafari = function() {
26		return isSafari = navigator.vendor.match(/apple/i) &&
27             !navigator.userAgent.match(/crios/i) &&
28             !navigator.userAgent.match(/fxios/i) &&
29             !navigator.userAgent.match(/Opera|OPT\//);
30	}
31
32	HAPUtils.isIE = function(){
33		var ie_check = getInternetExplorerVersion();
34		if (ie_check != -1)return true;
35		else return false;
36	}
37
38	function getInternetExplorerVersion(){
39	    var rv = -1;
40	    if(navigator.appName == 'Microsoft Internet Explorer'){
41			var ua = navigator.userAgent;
42			var re  = new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");
43			if(re.exec(ua) != null)rv = parseFloat( RegExp.$1 );
44	    }else if(navigator.appName == 'Netscape'){
45			var ua = navigator.userAgent;
46			var re  = new RegExp("Trident/.*rv:([0-9]{1,}[.0-9]{0,})");
47			if(re.exec(ua) != null)rv = parseFloat( RegExp.$1 );
48	    }
49	    return rv;
50	}
51
52	HAPUtils.htmlToElement = function(html) {
53	    var template = document.createElement('template');
54	    html = html.trim(); // Never return a text node of whitespace as the result
55	    template.innerHTML = html;
56	    return template.content.firstChild;
57	}
58
59
60	HAPUtils.insertAfter = function(newNode, referenceNode) {
61	    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
62	}
63
64	HAPUtils.isIOS = function(){
65		return navigator.userAgent.match(/(iPad|iPhone|iPod)/g);
66	}
67
68	HAPUtils.isAndroid = function() {
69		return navigator.userAgent.indexOf("Android") > -1;
70	}
71
72	HAPUtils.hasDownloadSupport = function(){  
73		return ("download" in document.createElement("a"));
74    }
75
76    HAPUtils.qualifyURL = function(url) {
77		var a = document.createElement('a');
78		a.href = url;
79		return a.href;
80	}
81
82	HAPUtils.relativePath = function(s){
83    	//https://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative
84		var r = new RegExp('^(?:[a-z]+:)?//', 'i');
85		return r.test(s);
86	}
87
88	HAPUtils.closestNumber = function(arr, goal){
89		var closest = arr.reduce(function(prev, curr) {
90	  		return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev);
91		});
92		return closest;
93	}
94
95	HAPUtils.b64DecodeUnicode = function(str) {
96	    // Going backwards: from bytestream, to percent-encoding, to original string.
97	    return decodeURIComponent(atob(str).split('').map(function(c) {
98	        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
99	    }).join(''));
100	};
101
102    HAPUtils.hasLocalStorage = function() {
103	    try{
104			return 'localStorage' in window && window['localStorage'] !== null;
105	    }catch(e){
106			return false;
107	    }
108	}
109
110	HAPUtils.volumeCanBeSet = function() {
111		var audio = document.createElement("audio");
112		if(!audio) return false;
113		audio.volume = 0;
114		return audio.volume == 0 ? true : false;
115	};
116
117	HAPUtils.arrayContainsAnotherArray = function(needle, haystack){
118		var i, len = needle.length;
119		for(i = 0; i < len; i++){
120		    if(haystack.indexOf(needle[i]) > -1)return true;
121		}
122		return false;
123	}
124
125	HAPUtils.randomiseArray = function(num) {
126		var arr = [], randomArr = [], i, j, randomIndex;
127		for(i = 0; i < num; i++){
128			arr[i] = i;
129		}
130		for(j = 0; j < num; j++){
131			randomIndex = Math.round(Math.random()*(arr.length-1));
132			randomArr[j] = arr[randomIndex];
133			arr.splice(randomIndex, 1);
134		}
135		return randomArr;
136	}
137
138	HAPUtils.valueLimit = function(val, min, max) {
139	    return val < min ? min : (val > max ? max : val);
140	}
141
142	HAPUtils.sortArray = function(arr, sortArr) {
143		var i, len = arr.length, result = [];
144		for(i=0; i<len; i++) {
145		   result[i] = arr[sortArr[i]];
146		}
147		return result;
148	}
149
150	HAPUtils.sortNumericArray = function(arr) {
151		arr.sort(function(a, b) {
152		   return a - b;
153		});
154	}
155
156	HAPUtils.shuffleArray = function(a) {
157	    for (let i = a.length - 1; i > 0; i--) {
158	        const j = Math.floor(Math.random() * (i + 1));
159	        [a[i], a[j]] = [a[j], a[i]];
160	    }
161	    return a;
162	}
163
164	HAPUtils.keysrt = function(arr, key, reverse) {
165		var order = 1;
166		if(reverse)order = -1;
167		return arr.sort(function(a, b) {
168			var x = a[key]; var y = b[key];
169			return order * ((x < y) ? -1 : ((x > y) ? 1 : 0));
170		});
171	}
172
173	HAPUtils.keysrt2 = function(arr, prop, key, reverse) {
174	    var sortOrder = 1;
175	    if(reverse)sortOrder = -1;
176	    return arr.sort(function(a, b) {
177	        var x = a[prop][key]; var y = b[prop][key];
178	        return sortOrder * ((x < y) ? -1 : ((x > y) ? 1 : 0));
179	    });
180	}
181
182	HAPUtils.keysrt3 = function(array, order, key) {
183  
184	  array.sort( function (a, b) {
185	    var A = a[key], B = b[key];
186	    
187	    if (order.indexOf(A) > order.indexOf(B)) {
188	      return 1;
189	    } else {
190	      return -1;
191	    }
192	    
193	  });
194	  
195	  return array;
196	};
197
198	HAPUtils.keysrt4 = function(arr, key, reverse) {
199		var order = 1;
200		if(reverse)order = -1;
201		return arr.sort(function(a, b) {
202			var x = a; var y = b;
203			return order * ((x < y) ? -1 : ((x > y) ? 1 : 0));
204		});
205	}
206
207
208	HAPUtils.parseXML = function(xml) {
209		if(window.ActiveXObject && window.GetObject) {
210			var dom = new ActiveXObject('Microsoft.XMLDOM');
211			dom.loadXML(xml);
212			return dom;
213		}
214		if(window.DOMParser){
215			return new DOMParser().parseFromString(xml, 'text/xml');
216		}else{
217			throw new Error('No XML parser available');
218		}
219	}
220
221	HAPUtils.formatTime = function(seconds) {
222		var sec_num = Math.round(seconds, 10); 
223	    var hours   = Math.floor(sec_num / 3600);
224	    var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
225	    var seconds = sec_num - (hours * 3600) - (minutes * 60);
226	    if(hours > 0){
227	    	if (hours   < 10) {hours   = "0"+hours;}
228		    if (minutes < 10) {minutes = "0"+minutes;}
229		    if (seconds < 10) {seconds = "0"+seconds;}
230		    return hours+':'+minutes+':'+seconds;
231	    }else{
232		    if (minutes < 10) {minutes = "0"+minutes;}
233		    if (seconds < 10) {seconds = "0"+seconds;}
234		    return minutes+':'+seconds;
235	    }
236	}	
237
238	HAPUtils.formatTimeWithMiliseconds = function(str){
239		var	hours = parseInt(str.split(':')[0]);  
240		var	minutes = parseInt(str.split(':')[1]);  
241		var	seconds = parseInt(str.split(':')[2]);  
242		var	millisesconds = parseInt(str.split(',')[1] || str.split('.')[1]);
243		var t = (hours*60*60) + (minutes*60) + seconds + (millisesconds/1000);  
244		t = Math.round(t*100)/100;  
245		return t;  
246	};
247
248	HAPUtils.toSeconds = function(n){
249		var a = n.split(/[.:,]+/),
250		seconds = (+a[0]) * 60 * 60 + (+a[1]) * 60 + (+a[2]); 
251		return Number(seconds);
252	}
253
254	HAPUtils.formatNumber = function(i) {
255		var s;
256		if(i < 9)s = "0" + (i + 1);
257		else s = i + 1;
258		return s;
259	} 
260
261	HAPUtils.nFormatter = function(num, digits) {
262	    var si = [
263		    { value: 1E18, symbol: "E" },
264		    { value: 1E15, symbol: "P" },
265		    { value: 1E12, symbol: "T" },
266		    { value: 1E9,  symbol: "G" },
267		    { value: 1E6,  symbol: "M" },
268		    { value: 1E3,  symbol: "k" }
269	    ], rx = /\.0+$|(\.[0-9]*[1-9])0+$/, i;
270
271	    for (i = 0; i < si.length; i++) {
272		    if (num >= si[i].value) {
273		        return (num / si[i].value).toFixed(digits).replace(rx, "$1") + si[i].symbol;
274		    }
275	    }
276 	    return num.toFixed(digits).replace(rx, "$1");
277	}
278
279	HAPUtils.hmsToSecondsOnly = function(str) {//This function handels "HH:MM:SS" as well as "MM:SS" or "SS".
280		var p = str.split(':'),
281			s = 0, m = 1;
282		while (p.length > 0) {
283			s += m * parseInt(p.pop());
284			m *= 60;
285		}
286		return s;
287	}
288
289	HAPUtils.canPlayMp3 = function() {
290		var a = document.createElement('audio');
291		return !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
292	}
293
294	HAPUtils.canPlayWav = function() {
295		var a = document.createElement('audio');
296		return !!(a.canPlayType && a.canPlayType('audio/wav;').replace(/no/, ''));
297	}
298
299	HAPUtils.canPlayAac = function() {
300		var a = document.createElement('audio');
301		return !!(a.canPlayType && a.canPlayType('audio/aac;').replace(/no/, ''));
302	}
303
304	HAPUtils.canPlayOgg = function() {
305		var a = document.createElement('audio');
306		return !!(a.canPlayType && a.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, ''));
307	}
308
309	HAPUtils.canPlayFlac = function() {
310		var a = document.createElement('audio');
311		return !!(a.canPlayType && a.canPlayType('audio/flac;').replace(/no/, ''));
312	}
313
314	HAPUtils.hasCanvas = function(){
315		return Boolean(document.createElement("canvas"));
316	};
317
318	HAPUtils.getElementOffsetTop = function(el) {
319	    var boundingClientRect = el.getBoundingClientRect();
320	    var bodyEl = document.body;
321	    var docEl = document.documentElement;
322	    var scrollTop = window.pageYOffset || docEl.scrollTop || bodyEl.scrollTop;
323	    var clientTop = docEl.clientTop || bodyEl.clientTop || 0;
324	    return Math.round(boundingClientRect.bottom - 100 + scrollTop - clientTop);
325	}
326
327	HAPUtils.getScrollTop = function(el) {
328	    var docEl = document.documentElement;
329  		return (window.pageYOffset || docEl.scrollTop) - (docEl.clientTop || 0);
330	}
331
332	HAPUtils.getEvents = function() {
333
334		var events = {};
335
336	   	if("ontouchstart" in window){
337			events.downEvent = "mousedown";
338			events.moveEvent = "mousemove";
339			events.upEvent = "mouseup";
340		}else{
341			if(window.PointerEvent){
342				events.downEvent = "pointerdown";
343				events.moveEvent = "pointermove";
344				events.upEvent = "pointerup";
345			}else{
346				events.downEvent = "mousedown";
347				events.moveEvent = "mousemove";
348				events.upEvent = "mouseup";
349			}
350		}
351
352  		return events;
353	}
354
355	HAPUtils.getUrlParameter = function(k) {
356		var p={};
357		window.location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi,function(s,k,v){p[k]=v})
358		return k?p[k]:p;
359	};
360
361	// Pass in the objects to merge as arguments.
362	// For a deep extend, set the first argument to `true`.
363	HAPUtils.extendObj = function() {
364
365		// Variables
366		var extended = {};
367		var deep = false;
368		var i = 0;
369		var length = arguments.length;
370
371		// Check if a deep merge
372		if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) {
373			deep = arguments[0];
374			i++;
375		}
376
377		// Merge the object into the extended object
378		var merge = function (obj) {
379			for ( var prop in obj ) {
380				if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) {
381					// If deep merge and property is an object, merge properties
382					if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) {
383						extended[prop] = HAPUtils.extendObj( true, extended[prop], obj[prop] );
384					} else {
385						extended[prop] = obj[prop];
386					}
387				}
388			}
389		};
390
391		// Loop through each object and conduct a merge
392		for ( ; i < length; i++ ) {
393			var obj = arguments[i];
394			merge(obj);
395		}
396
397		return extended;
398
399	};
400
401	HAPUtils.drawBars = function(ctx, ctx2, waveOptions, peaks, start, end) {
402
403		var absmax = 1 / waveOptions.barHeight;
404
405        if (waveOptions.normalize) {
406          var max = util.max(peaks);
407          var min = util.min(peaks);
408          absmax = -min > max ? -min : max;
409        } // Bar wave draws the bottom only as a reflection of the top,
410        // so we don't need negative values
411
412        var hasMinVals = [].some.call(peaks, function (val) {
413          return val < 0;
414        });
415        var height = waveOptions.height * waveOptions.pixelRatio;
416        var offsetY = 0;
417        var halfH = height / 2;
418
419        var peakIndexScale = hasMinVals ? 2 : 1;
420        var length = peaks.length / peakIndexScale;
421        var bar = waveOptions.barWidth * waveOptions.pixelRatio;
422        var gap = waveOptions.barGap === null ? Math.max(waveOptions.pixelRatio, ~~(bar / 2)) : Math.max(waveOptions.pixelRatio, waveOptions.barGap * waveOptions.pixelRatio);
423        var step = bar + gap;
424        var scale = length / waveOptions.width;
425        var first = start;
426        var last = end;
427        var i = first;
428
429
430
431
432        for (i; i < last; i += step) {
433          var peak = peaks[Math.floor(i * scale * peakIndexScale)] || 0;
434          var h = Math.round(peak / absmax * halfH);
435
436          if (h == 0 && waveOptions.barMinHeight) h = waveOptions.barMinHeight;
437
438          if(waveOptions.barRadius){
439          		drawRoundedRect(ctx, i + waveOptions.halfPixel, halfH - h + offsetY, bar + waveOptions.halfPixel, h * 2, waveOptions.barRadius)
440          }else{
441          		ctx.fillRect(i + waveOptions.halfPixel, halfH - h + offsetY, bar + waveOptions.halfPixel, h * 2, waveOptions.barRadius);
442          }
443
444          
445        }
446
447
448        i = first;
449
450        for (i; i < last; i += step) {
451          var peak = peaks[Math.floor(i * scale * peakIndexScale)] || 0;
452          var h = Math.round(peak / absmax * halfH);
453
454          if (h == 0 && waveOptions.barMinHeight) h = waveOptions.barMinHeight;
455
456          if(waveOptions.barRadius){
457
458          	drawRoundedRect(ctx2, i + waveOptions.halfPixel, halfH - h + offsetY, bar + waveOptions.halfPixel, h * 2, waveOptions.barRadius)
459
460          }else{
461
462          	ctx2.fillRect(i + waveOptions.halfPixel, halfH - h + offsetY, bar + waveOptions.halfPixel, h * 2, waveOptions.barRadius);
463          }
464        }
465
466    }
467
468    function drawRoundedRect(ctx, x, y, width, height, radius) {
469      if (height === 0) {
470        return;
471      }
472      // peaks are float values from -1 to 1. Use absolute height values in
473      // order to correctly calculate rounded rectangle coordinates
474      if (height < 0) {
475        height *= -1;
476        y -= height;
477      }
478      ctx.beginPath();
479      ctx.moveTo(x + radius, y);
480      ctx.lineTo(x + width - radius, y);
481      ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
482      ctx.lineTo(x + width, y + height - radius);
483      ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
484      ctx.lineTo(x + radius, y + height);
485      ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
486      ctx.lineTo(x, y + radius);
487      ctx.quadraticCurveTo(x, y, x + radius, y);
488      ctx.closePath();
489      ctx.fill();
490    }
491
492
493    HAPUtils.drawWave = function(ctx, ctx2, waveOptions, peaks, start, end) {
494
495    	var absmax = 1 / waveOptions.barHeight;
496
497        if (waveOptions.normalize) {
498          var max = util.max(peaks);
499          var min = util.min(peaks);
500          absmax = -min > max ? -min : max;
501        } 
502
503        var hasMinVals = [].some.call(peaks, function (val) {
504          return val < 0;
505        });
506        var height = waveOptions.height * waveOptions.pixelRatio;
507        var offsetY = 0;
508        var halfH = height / 2;
509
510        if (!hasMinVals) {
511          var reflectedPeaks = [];
512          var len = peaks.length;
513          var i = 0;
514
515          for (i; i < len; i++) {
516            reflectedPeaks[2 * i] = peaks[i];
517            reflectedPeaks[2 * i + 1] = -peaks[i];
518          }
519
520          peaks = reflectedPeaks;
521        } 
522
523        if (start !== undefined) {
524            drawLineToContext(ctx, waveOptions, peaks, absmax, halfH, offsetY, start, end);
525     		drawLineToContext(ctx2, waveOptions, peaks, absmax, halfH, offsetY, start, end);
526        } 
527
528        ctx.fillRect(0, halfH + offsetY - waveOptions.halfPixel, waveOptions.width, waveOptions.halfPixel, waveOptions.barRadius);
529
530        ctx2.fillRect(0, halfH + offsetY - waveOptions.halfPixel, waveOptions.width, waveOptions.halfPixel, waveOptions.barRadius);
531      
532    }
533
534    function drawLineToContext(ctx, waveOptions, peaks, absmax, halfH, offsetY, start, end) {
535
536      var length = peaks.length / 2;
537      var first = Math.round(length * start); 
538
539      var last = Math.round(length * 1) + 1;
540      var canvasStart = first;
541      var canvasEnd = last;
542      var scale = waveOptions.width / (canvasEnd - canvasStart - 1); // optimization
543
544      var halfOffset = halfH + offsetY;
545      var absmaxHalf = absmax / halfH;
546      ctx.beginPath();
547      ctx.moveTo((canvasStart - first) * scale, halfOffset);
548      ctx.lineTo((canvasStart - first) * scale, halfOffset - Math.round((peaks[2 * canvasStart] || 0) / absmaxHalf));
549      var i, peak, h;
550		
551      for (i = canvasStart; i < canvasEnd; i++) {
552        peak = peaks[2 * i] || 0;
553        h = Math.round(peak / absmaxHalf);
554        ctx.lineTo((i - first) * scale + waveOptions.halfPixel, halfOffset - h);
555      } 
556
557      var j = canvasEnd - 1;
558
559      for (j; j >= canvasStart; j--) {
560        peak = peaks[2 * j + 1] || 0;
561        h = Math.round(peak / absmaxHalf);
562        ctx.lineTo((j - first) * scale + waveOptions.halfPixel, halfOffset - h);
563      }
564
565      ctx.lineTo((canvasStart - first) * scale, halfOffset - Math.round((peaks[2 * canvasStart + 1] || 0) / absmaxHalf));
566      ctx.closePath();
567      ctx.fill();
568    }
569
570	window.HAPUtils = HAPUtils;
571
572}(window));	
573
574(function (){
575	var HAPEventDispatcher = function (){
576
577		var self = this;
578		
579	    self.events = {};
580
581	    self.addEventListener = function(name, handler) {
582	        if (self.events.hasOwnProperty(name)){
583	            self.events[name].push(handler);
584	        }else{
585	            self.events[name] = [handler];
586	        }
587	    };
588
589	    self.removeEventListener = function(name, handler) {
590	        /* This is a bit tricky, because how would you identify functions?
591	           This simple solution should work if you pass THE SAME handler. */
592	        if (!self.events.hasOwnProperty(name))return;
593
594	        var index = self.events[name].indexOf(handler);
595	        if (index != -1)self.events[name].splice(index, 1);
596	    };
597
598	    self.fireEvent = function(name, args) {
599	        if (!self.events.hasOwnProperty(name))return;
600
601	        if (!args || !args.length)args = [];
602
603	        var evs = self.events[name], l = evs.length;
604	        for (var i = 0; i < l; i++) {
605	            evs[i].apply(null, args);
606	        }
607	    };
608	    
609	};	
610	
611	window.HAPEventDispatcher = HAPEventDispatcher;
612}(window));
613
614(function (window){
615	"use strict"
616	var HAPPlaylistManager = function (data){
617		
618		var self = this,
619		loopState = data.loop,
620		randomPlay = data.random,
621		playlistItems,
622		lastInOrder = false,
623		queNextCounterState,
624		lastCounterState,
625		counter = -1,
626		lastPlayedFromPlaylistClick,//last played on click.
627		lastRandomCounter,//last played random media in random playlist.
628		randomPaused = false,//when random is playing and we interrupt it by click on the playlist.
629		randomArr = [],
630		playlistSelect = false;//prevent geting counter from randomArr on playlist click (get 'normal' counter instead)		
631
632		HAPEventDispatcher.call(this);
633		
634		//set counter to specific number or add it to the currect counter value		 
635		this.setCounter = function(value, _add) {
636			if (typeof _add === 'undefined') _add = true;
637			if(_add){
638				counter += parseInt(value, 10);
639			}else{
640				counter = parseInt(value, 10);
641			}
642			checkCounter();
643		}
644		this.getCounter = function() {
645			var i;
646			if(randomPlay){
647				if(!playlistSelect){
648					i = randomArr[counter];
649				}else{
650					i = counter;
651				}
652			}else{
653				i = counter;
654			}
655			return i;
656		}
657		this.queNext = function(type, a){
658
659			if(type == 'advance'){
660				queNextCounterState = true;
661				lastCounterState = counter;
662				self.advanceHandler(a)
663				counter = lastCounterState;
664				queNextCounterState = false;
665			}
666			else{//pick
667
668				queNextCounterState = true;
669				lastCounterState = counter;
670				self.setCounter(a, false)
671				counter = lastCounterState;
672				queNextCounterState = false;
673
674			}
675
676		}
677		this.advanceHandler = function(a) {
678			playlistSelect = false;//reset
679			if(randomPaused){
680				handleRandomPaused(a);
681			}else{
682				self.setCounter(a);
683			}
684		}
685		this.processPlaylistRequest = function(id) {
686			playlistSelect = false;//reset
687			if(randomPlay){
688				playlistSelect = true;
689				lastPlayedFromPlaylistClick = id;//always remember last played on each click.
690				if(!randomPaused){
691					lastRandomCounter = counter;
692					randomPaused = true;//needs to stay until random play comes back again! So that the above reference to last random counter doesnt get lost. (if we constantly clicking playlist)
693				}
694			}
695			self.setCounter(id, false);
696		}
697		this.setPlaylistItems = function(val, resetCounter) {
698			if(typeof resetCounter === 'undefined') resetCounter = true;
699			if(resetCounter)counter = -1;
700			playlistItems = val;
701			if(randomPlay) makeRandomList();
702		}
703		this.reSetCounter = function(num) {
704			if(typeof num === 'undefined'){
705				 counter = -1;
706			}else{//set counter to specific number
707				var n = parseInt(num,10);
708				if(playlistItems){
709					if(n > playlistItems - 1){
710						n = playlistItems - 1;
711					}else if(n < 0){
712						n = 0;
713					}
714					counter = n;
715				}else{
716					counter = -1;
717				}
718			}
719		}
720		this.setRandom = function(val) {
721			randomPlay = val;
722			if(randomPlay) makeRandomList();
723			randomChange();
724		}
725		this.setLooping = function(val) {
726			loopState = val;
727		}
728		this.getPosition = function(val) {
729			return randomArr.indexOf(val);
730		}
731		
732		//exiting randomPaused and going back to random mode
733		function handleRandomPaused(a) {
734			//just an exit out of randomPaused (because of a playlist click) and back to random again
735			randomPaused = false;//reset before because of the getCounter()
736			
737			if(lastRandomCounter + a > playlistItems - 1){
738				counter = playlistItems - 1;
739
740				var k = self.getCounter()
741
742				if(queNextCounterState)self.fireEvent('HAPPlaylistManager.QUE_NEXT_READY', [{counter:k}]);
743				else self.fireEvent('HAPPlaylistManager.COUNTER_READY', [{counter:k}]);
744
745				return;
746			} else if( lastRandomCounter + a < 0){
747				counter = 0;
748
749				var k = self.getCounter()
750
751				if(queNextCounterState)self.fireEvent('HAPPlaylistManager.QUE_NEXT_READY', [{counter:k}]);
752				else self.fireEvent('HAPPlaylistManager.COUNTER_READY', [{counter:k}]);
753
754				return;
755			}
756			self.setCounter(lastRandomCounter + a, false);
757		}
758		function randomChange() {//when random is turned on / off
759			if(randomPlay){
760				activeIndexFirst();
761				counter = 0;//we have to do it like this, because with (setCounter(0, false)) media starts to play from the beginning if its already playing. (when random requested)
762				//we need to say this on the every beginning of random to redirect the counter from wherever the currently is to 0, so that it becomes first index in randomArr. (after we have moved active index to beginning of randomArr)
763				
764			}else{
765				//we are not going through setCounter here because its just getting out of random mode, and its not changing counter, it just stays where it is (playing or not)
766				if(randomPaused){
767					counter = lastPlayedFromPlaylistClick;
768					randomPaused = false;//when random mode stops randomPaused stops also.
769				}else{
770					counter = randomArr[counter];//when we turn off random we need to set counter to the value of the current counter in randomArr, so if the counter is 1, and thats value 3 in randomArr for example, we want the active counter to stay 3, not 1, and next to go to 4, not 2.
771				}
772			}
773		}
774		function checkCounter() {
775			if(isNaN(counter)){
776				console.log('HAPPlaylistManager message: No active media, counter = ' + counter);
777				return;
778			}
779			//reset
780			lastInOrder = false;
781			
782			if(loopState == 'playlist' || loopState == 'single'){
783				if(randomPlay){
784					
785					if(counter > playlistItems - 1){//moving fowards
786						counter = randomArr[ playlistItems - 1];//remember counter for comparison
787						makeRandomList();
788						_firstIndexCheck();
789						counter = 0;
790						
791					}else if(counter < 0){//moving backwards
792						counter = randomArr[0];//remember counter for comparison
793						makeRandomList();
794						lastIndexCheck();
795						counter = playlistItems - 1;
796					}
797					
798				}else{//random off
799					if(counter > playlistItems - 1){
800						counter = 0;
801					}else if( counter < 0){
802						counter = playlistItems - 1;
803					}
804				}
805
806				var k = self.getCounter()
807
808				if(queNextCounterState)self.fireEvent('HAPPlaylistManager.QUE_NEXT_READY', [{counter:k}]);
809				else self.fireEvent('HAPPlaylistManager.COUNTER_READY', [{counter:k}]);
810				
811			}else if(loopState == 'off'){//looping off
812				
813				if(counter > playlistItems - 1){
814					counter = playlistItems - 1;
815					lastInOrder = true;//last item
816				}else if(counter < 0){
817					counter = 0;
818					lastInOrder = true;//last item
819				}
820				
821				if(!lastInOrder){
822					var k = self.getCounter()
823
824					if(queNextCounterState)self.fireEvent('HAPPlaylistManager.QUE_NEXT_READY', [{counter:k}]);
825					else self.fireEvent('HAPPlaylistManager.COUNTER_READY', [{counter:k}]);
826
827				}else{
828					self.fireEvent('PLAYLIST_END');
829				}
830			}
831			
832		}
833		function makeRandomList() {
834			randomArr = HAPUtils.randomiseArray(playlistItems);
835		}
836		function _firstIndexCheck () {
837			//check that first item in newly generated random array isnt equal to last active item.
838			if(randomArr[0] == counter){//if yes, put it at the last place in array.
839				var i = randomArr.splice(0,1);
840				randomArr.push(i);
841			}
842		}
843		function lastIndexCheck() {
844			if(randomArr[playlistItems - 1] == counter){//if yes, put it at the first place in array.
845				var i = randomArr.splice(playlistItems - 1,1);
846				randomArr.unshift(i);
847			}
848		}
849		function activeIndexFirst() {//when going into random (playing or not) put currently active index on the first place of random array.
850			var i,len = randomArr.length, j;
851			for(i = 0; i < len; i++){
852				if(randomArr[i] == counter){
853					if(i == 0){//if its already on the first place no need for action.
854						break;
855					}
856					j = randomArr.splice(i,1);
857					randomArr.unshift(parseInt(j,10));
858					break;
859				}
860			}
861		} 
862
863	};
864
865	window.HAPPlaylistManager = HAPPlaylistManager;	
866
867}(window));
868
869(function (window){
870	"use strict"	
871	var HAPSCLoader = function (data){
872			
873		var self = this,
874		settings = data.settings,
875		soundCloudToken,
876        soundCloudRefreshToken,
877        soundCloudExpiredTimeInSeconds,
878        action,
879        storage_key = 'hap_sc_at' + settings.instanceName,
880        current_track_id,
881        current_url,
882        playlistProcessData = [],
883        defaultData,
884        nextPageToken = [],
885        resultLimit,
886        xhrRequest
887
888        HAPEventDispatcher.call(this);
889
890		function getToken(){
891
892            var apk = settings.scak[Math.floor(Math.random() * settings.scak.length)];
893
894            if(xhrRequest){
895				xhrRequest.abort();
896				xhrRequest = null;
897			}
898
899			var url = settings.sourcePath + 'includes/sc/sc2.php';
900
901			var params = 'action=sc_get_auth&sck='+apk;
902
903			xhrRequest = new XMLHttpRequest();
904			xhrRequest.onreadystatechange = function() {
905				if (xhrRequest.readyState == 4) {
906
907					var d = JSON.parse(xhrRequest.response);
908
909					soundCloudToken = d.access_token;
910	                soundCloudRefreshToken = d.refresh_token;
911	                d.soundCloudExpiredTimeInSeconds = d.expires_in + getCurrentTimeInSeconds() - 10;
912	               
913	                localStorage.setItem(storage_key, JSON.stringify(d));
914
915	                if(action == 'url_request')scRequest()
916	                else if(action == 'download')download()
917	                else if(action == 'get_stream_url')getStreamUrl()
918						
919			    }
920			}
921			xhrRequest.onerror = function(e) { 
922			    console.log('Error processSC ' + e);
923			};
924			xhrRequest.open('POST', url);
925			xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
926			xhrRequest.send(params);
927
928        }
929
930        this.get_stream_url = function(id){
931
932            current_track_id = id
933
934            getStreamUrl()
935        }
936
937        function getStreamUrl(){
938
939        	if(xhrRequest){
940				xhrRequest.abort();
941				xhrRequest = null;
942			}
943
944			var url = settings.sourcePath + 'includes/sc/sc2.php';
945
946			var params = 'action=sc_get_stream_url&track_id='+current_track_id+'&access_token='+soundCloudToken;
947
948			xhrRequest = new XMLHttpRequest();
949			xhrRequest.onreadystatechange = function() {
950				if (xhrRequest.readyState == 4) {
951
952					var d = JSON.parse(xhrRequest.response);
953					
954					if(d.location){
955
956	                	self.fireEvent('HAPSCLoader.GET_STREAM_URL', [{location: d.location}]);		
957
958	                }else{
959
960	                    console.log('stream url not valid?', d)
961	                }
962			    }
963			}
964			xhrRequest.onerror = function(e) { 
965			    console.log('Error processSC: ' + e);
966			};
967			xhrRequest.open('POST', url);
968			xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
969			xhrRequest.send(params);
970
971        }
972
973        this.resume = function(){
974
975        	if(checkToken()){
976                getStreamUrl()
977            }else{
978                action = 'get_stream_url'
979                getToken()
980            }
981        }
982
983        this.get_dl = function(track_id, title){
984        	getDlUrl(track_id, title)
985        }
986
987        function getDlUrl(track_id, title){
988
989        	/*console.log(track_id)
990
991        	title+='.mp3'
992
993        	var hap_dl_iframe = document.querySelector('<iframe class="hap_dl_iframe"/>')
994        	hap_dl_iframe.style.display = 'none'
995        	hap_dl_iframe.appendTo('body');
996
997        	hap_dl_iframe.setAttribute('src',settings.sourcePath + "includes/sc/dl.php?path="+track_id+"&name="+title);
998
999			return
1000*/
1001
1002			if(xhrRequest){
1003				xhrRequest.abort();
1004				xhrRequest = null;
1005			}
1006
1007			var url = settings.sourcePath + 'includes/sc/sc2.php'
1008
1009			var params = 'action=sc_get_dl_url&track_id='+track_id+'&title='+title+'&access_token='+soundCloudToken;
1010
1011			xhrRequest = new XMLHttpRequest();
1012			xhrRequest.onreadystatechange = function() {
1013				if (xhrRequest.readyState == 4) {
1014
1015					console.log(xhrRequest.response)
1016					
1017			    }
1018			}
1019			xhrRequest.onerror = function(e) { 
1020			    console.log(jqXHR.responseText);
1021			};
1022			xhrRequest.open('POST', url);
1023			xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
1024			xhrRequest.send(params);
1025
1026
1027
1028
1029           /* var postData = [
1030                {name: 'action', value: 'sc_get_dl_url'},
1031                {name: 'track_id', value: track_id},
1032                {name: 'title', value: title},
1033                {name: 'access_token', value: soundCloudToken},//
1034            ];
1035
1036            $.ajax({
1037                url: settings.sourcePath + 'includes/sc/sc2.php',
1038                data: postData,
1039            }).done(function(response) {
1040
1041                console.log(response)
1042
1043
1044            }).fail(function(jqXHR, textStatus, errorThrown) {
1045                console.log(jqXHR.responseText);
1046            }); */
1047
1048        }
1049        
1050        function checkToken() {
1051
1052            var ls = localStorage.getItem(storage_key);
1053
1054            if(ls){
1055                var d = JSON.parse(ls);
1056
1057                soundCloudToken = d.access_token;
1058
1059                return getCurrentTimeInSeconds() < d.soundCloudExpiredTimeInSeconds;
1060
1061            }else{
1062                return false;
1063            }
1064
1065        }
1066
1067        function getCurrentTimeInSeconds() {
1068             return Date.now() * 0.001;
1069        }
1070       
1071        function scRequest(){
1072
1073        	if(xhrRequest){
1074				xhrRequest.abort();
1075				xhrRequest = null;
1076			}
1077
1078			var url = settings.sourcePath + 'includes/sc/sc2.php';
1079
1080			var params = 'action=sc_get_request&access_token='+soundCloudToken+'&url='+current_url;
1081
1082			xhrRequest = new XMLHttpRequest();
1083			xhrRequest.onreadystatechange = function() {
1084				if (xhrRequest.readyState == 4) {
1085
1086					var d = JSON.parse(xhrRequest.response)
1087
1088					if(d.location){
1089	                    scRequest2(d.location)
1090	                }else{
1091	                    console.log('sc request fail?')
1092	                    self.fireEvent('HAPSCLoader.END_LOAD', [{data: []}]);	
1093	                }
1094					
1095			    }
1096			}
1097			xhrRequest.onerror = function(e) { 
1098			    console.log('Error processSC: ' + e);
1099			};
1100			xhrRequest.open('POST', url);
1101			xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
1102			xhrRequest.send(params);
1103
1104        }
1105
1106        function scRequest2(curl){
1107
1108        	if(xhrRequest){
1109				xhrRequest.abort();
1110				xhrRequest = null;
1111			}
1112
1113			var url = settings.sourcePath + 'includes/sc/sc2.php';
1114
1115			var params = 'action=sc_get_request&access_token='+soundCloudToken+'&url='+curl;
1116
1117			xhrRequest = new XMLHttpRequest();
1118			xhrRequest.onreadystatechange = function() {
1119				if (xhrRequest.readyState == 4) {
1120
1121					var data = JSON.parse(xhrRequest.response);
1122
1123					playlistProcessData = []
1124
1125	                if(data.kind == "track") {
1126
1127	                    playlistProcessData.push(getSoundCloudTrack(data));
1128
1129	                }else if(data.kind == "playlist"){
1130	                    //no pagination, returns some tracks but not all?
1131
1132	                    var i, len = data.tracks.length, obj;
1133	                    for(i=0; i<len; i++) {
1134	                        obj = getSoundCloudTrack(data.tracks[i]);
1135
1136	                        if(i < resultLimit)playlistProcessData.push(obj); 
1137	                        else nextPageToken.push(obj); 
1138
1139	                    }
1140
1141	                }else if(data.collection){
1142	                    //has pagination
1143	                    /*var i, len = data.collection.length, end;
1144	                    if(soundCloudResultArr.length + len >= resultLimit){
1145	                        len = resultLimit - soundCloudResultArr.length;
1146	                        end = true;
1147	                    }
1148	                
1149	                    for(i=0; i<len; i++) {
1150	                        getSoundCloudTrack(data.collection[i]);
1151	                    }*/
1152
1153	                }else if(data.username) {
1154	                    //data username goes to data collection, always?
1155	                    /*var sc_uri = data.uri;
1156	                    if(/likes/.test(path)) {
1157	                        sc_uri += '/likes';
1158	                    }else if(/favorites/.test(path)) {
1159	                        sc_uri += '/favorites';
1160	                    }else{
1161	                        sc_uri += '/tracks';
1162	                    }
1163	                    getSoundCloudPage(false, sc_uri);
1164	                    return;*/
1165
1166	                //}else if($.isArray(data)) {
1167	                }else if (data instanceof Array) {
1168	                    //username/tracks returns this, always returns 50 tracks?, no pagination
1169	                    /*var i, len = data.length;
1170	                    if(soundCloudResultArr.length + len >= resultLimit){
1171	                        len = resultLimit - soundCloudResultArr.length;
1172	                        end = true;
1173	                    }
1174	                    for(i=0; i<len; i++) {
1175	                        getSoundCloudTrack(data[i]);
1176	                    }*/
1177	                }
1178
1179	                //console.log(playlistProcessData)
1180	                
1181	                self.fireEvent('HAPSCLoader.END_LOAD', [{data: playlistProcessData, nextPageToken: nextPageToken}]);
1182						
1183			    }
1184			}
1185			xhrRequest.onerror = function(e) { 
1186			    console.log('Error processSC: ' + e);
1187			    self.fireEvent('HAPSCLoader.END_LOAD', [{data: []}]);	
1188			};
1189			xhrRequest.open('POST', url);
1190			xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
1191			xhrRequest.send(params);
1192
1193        }
1194
1195        this.request = function(data, limit){
1196            action = 'url_request'
1197
1198            nextPageToken = []
1199            resultLimit = limit
1200            defaultData = data
1201
1202            var s = 'https://api.soundcloud.com/resolve?url=' + data.path
1203
1204            current_url = s
1205
1206            if(checkToken()){
1207                scRequest()
1208            }
1209            else{
1210                getToken()
1211            }
1212
1213        }
1214
1215        function getSoundCloudTrack(track){
1216
1217            if(track.streamable && track.stream_url){
1218
1219            	var obj = HAPUtils.extendObj(true, {}, defaultData);
1220
1221	            obj.type = 'audio';
1222	            obj.origtype = 'soundcloud'
1223	            obj.id = track.id;
1224
1225	            if(track.duration)obj.duration = track.duration / 1000;//miliseconds
1226	             
1227	            if(track.downloadable && track.download_url){
1228	                obj.download = track.download_url;
1229	            }
1230
1231	            if(!obj.title && track.title)obj.title = track.title;
1232	            if(!obj.description && track.description)obj.description = track.description;
1233	            if(!obj.thumb && track.artwork_url){
1234	                obj.thumb = track.artwork_url;
1235	            }
1236	            if(track.created_at)obj.date = track.created_at;
1237	            obj.favoritingsCount = parseInt(track.favoritings_count?track.favoritings_count:0,10);
1238	            obj.playbackCount = parseInt(track.playback_count?track.playback_count:0,10);
1239	            obj.hotness = obj.favoritingsCount + obj.playbackCount;
1240	            if(track.permalink_url){
1241	                //if(!obj.share)obj.share = track.permalink_url;
1242	                if(settings.createLinkIconsInPlaylist)if(!obj.link)obj.link = track.permalink_url;
1243	            }
1244
1245	            obj.sc_data = track;
1246
1247	            return obj;
1248
1249            }
1250        }
1251
1252	};
1253
1254	window.HAPSCLoader = HAPSCLoader;
1255
1256}(window));
1257
1258(function (window){
1259	"use strict"	
1260	var HAPCirclePlayer = function (data){
1261			
1262		var self = this,
1263		parent = data.parent,
1264		circlePlayer = parent.querySelector('.hap-circle-player'),
1265		loadCanvas = parent.querySelector('.hap-load-canvas'),
1266		progressCanvas = parent.querySelector('.hap-progress-canvas'),
1267		strokeSize = parseInt(circlePlayer.getAttribute('data-stroke-size'),10);
1268
1269		if(loadCanvas){
1270			var loadColor = loadCanvas.getAttribute('data-color'), 
1271			loadCtx = loadCanvas.getContext('2d'),
1272			circleSize = loadCanvas.offsetWidth
1273		}
1274		if(progressCanvas){
1275			var progressColor = progressCanvas.getAttribute('data-color'),
1276			progressCtx = progressCanvas.getContext('2d'),
1277			circleSize = progressCanvas.offsetWidth
1278		}
1279
1280		var circ = Math.PI * 2,
1281		quart = Math.PI / 2,
1282		circleRadius = circleSize/2
1283
1284		this.drawSeekbar = function(loadPercent,t,d){
1285			if(loadCanvas)drawLoadbar(loadPercent);
1286			if(progressCanvas)drawProgressbar(t/d);
1287		}
1288
1289		this.clear = function(){
1290			if(progressCanvas)progressCtx.clearRect(0, 0, circleSize, circleSize);
1291			if(loadCanvas)loadCtx.clearRect(0, 0, circleSize, circleSize);
1292		}
1293
1294		this.setProgress = function(point){
1295
1296			var rect = circlePlayer.getBoundingClientRect()
1297
1298			var x = point.pageX - rect.left - circleSize/2,
1299				y = point.pageY - rect.top - circleSize/2,
1300				mAngle = Math.atan2(y, x);
1301					
1302			if (mAngle > -1 * Math.PI && mAngle < -0.5 * Math.PI) {
1303				mAngle = 2 * Math.PI + mAngle;
1304			}
1305			
1306			var v = Math.max(0, Math.min((mAngle + Math.PI / 2) / 2 * Math.PI * 10))/100;
1307			
1308			if(progressCanvas){
1309				progressCtx.clearRect(0, 0, circleSize, circleSize);
1310				drawProgressbar(v);
1311			}
1312
1313			return v;
1314
1315		}
1316
1317		this.trackTooltip = function(e){
1318
1319			var rect = circlePlayer.getBoundingClientRect()
1320
1321			var x1 = e.pageX - rect.left,
1322				y1 = e.pageY - rect.top,
1323				x = x1 - circleSize/2,
1324				y = y1 - circleSize/2,
1325				mAngle = Math.atan2(y,x);
1326			
1327			if (mAngle > -1 * Math.PI && mAngle < -0.5 * Math.PI){
1328				mAngle = 2 * Math.PI + mAngle;
1329			}
1330			var v = Math.max(0, Math.min((mAngle + Math.PI / 2) / 2 * Math.PI * 10))/100;
1331			
1332			return v;
1333		
1334		}
1335
1336		function drawProgressbar(percent){
1337			if(progressCanvas){
1338				progressCtx.clearRect(0, 0, circleSize, circleSize);
1339				progressCtx.beginPath();      
1340				progressCtx.arc(circleSize/2, circleSize/2,circleRadius-strokeSize/2,-(quart),((circ) * percent) - quart,false);
1341				progressCtx.strokeStyle = progressColor;
1342				progressCtx.lineCap = 'butt';
1343				progressCtx.lineWidth = strokeSize;
1344				progressCtx.stroke();
1345			}
1346		}
1347		
1348		function drawLoadbar(percent){
1349			if(loadCanvas){
1350				loadCtx.clearRect(0, 0, circleSize, circleSize);
1351				loadCtx.beginPath();    
1352				loadCtx.arc(circleSize/2, circleSize/2,circleRadius-strokeSize/2,-(quart),((circ) * percent) - quart,false);
1353				loadCtx.strokeStyle = loadColor;
1354				loadCtx.lineCap = 'butt';
1355				loadCtx.lineWidth = strokeSize;
1356				loadCtx.stroke();
1357			}
1358		}
1359
1360	};	
1361
1362	window.HAPCirclePlayer = HAPCirclePlayer;
1363
1364}(window));
1365
1366(function (window){
1367	"use strict"	
1368	var HAPAdManager = function(settings, parent, cmd, progressBg, progressLevel, mediaTimeAd){
1369			
1370		var self = this,
1371		isIOS = HAPUtils.isIOS(),
1372		adPreOn,
1373		adAudio,
1374	    adMidTimeoutID,
1375	    adMidOn,
1376	    adEndOn,
1377	    adCounter = 0,
1378	    currMediaData = cmd,
1379	    volume = settings.volume
1380
1381	    adAudio = new Audio()
1382	    adAudio.preload = "auto";
1383		adAudio.addEventListener("loadedmetadata", adPlay);
1384
1385		HAPEventDispatcher.call(this);
1386
1387	    //ad pre
1388
1389		this.initAd = function(type){
1390
1391			if(type == 'pre'){
1392
1393				adPreOn = true;
1394				adCounter = 0;
1395				adAudio.src = currMediaData.adPre[adCounter];	
1396			}
1397
1398			else if(type == 'mid'){
1399
1400				adMidOn = true;
1401				adCounter = 0;
1402				adAudio.src = currMediaData.adMid[adCounter];
1403
1404				if(isIOS)adAudio.load()
1405
1406
1407			}
1408			else if(type == 'end'){
1409
1410				adEndOn = true;
1411				adCounter = 0;
1412				adAudio.src = currMediaData.adEnd[adCounter];
1413
1414			}
1415
1416			adAudio.volume = volume;
1417
1418			var promise = adAudio.play();
1419			if (promise !== undefined) {
1420				promise.then(function(){
1421
1422					if(adPreOn){
1423						self.fireEvent('HAPAdManager.AD_PLAY_ACTION');
1424					}
1425
1426			    }).catch(function(error){
1427				});
1428			}
1429
1430			adAudio.addEventListener("ended", adEnded);
1431
1432		}
1433
1434		this.forceAdMidAudio = function(){
1435			if(adAudio){
1436				adAudio.src = settings.sourcePath + 'data/m.mp3';
1437				adAudio.play();
1438			}
1439		}
1440		
1441		function adPlay() {
1442
1443			if(adPreOn){
1444
1445				self.fireEvent('HAPAdManager.ADPRE_PLAY');
1446
1447				parent.fireEvent("adPrePlay", [{instance:parent, instanceName:settings.instanceName, media:currMediaData.adPre}]);
1448
1449				adAudio.addEventListener("timeupdate", adTimeUpdate);
1450				adTimeUpdate();
1451
1452			}
1453			else if(adMidOn){
1454
1455				adAudio.addEventListener("timeupdate", adTimeUpdate);
1456				adTimeUpdate();
1457
1458				self.fireEvent('HAPAdManager.ADMID_PLAY');
1459
1460				parent.fireEvent("adMidPlay", [{instance:parent, instanceName:settings.instanceName, media:currMediaData.adMid}]);
1461
1462			}
1463			else if(adEndOn){
1464
1465				self.fireEvent('HAPAdManager.ADEND_PLAY');
1466
1467				adAudio.addEventListener("timeupdate", adTimeUpdate);
1468				adTimeUpdate();
1469
1470		    	parent.fireEvent("adEndPlay", [{instance:parent, instanceName:settings.instanceName, media:currMediaData.adMid}]);
1471
1472			}
1473
1474		}
1475
1476	    function adEnded() {
1477
1478	    	if(adPreOn){
1479
1480		    	//we can have multiple ads
1481		    	adCounter++;
1482				if(adCounter < currMediaData.adPre.length){
1483
1484					adAudio.src = currMediaData.adPre[adCounter];
1485					var promise = adAudio.play();
1486					if (promise !== undefined) {
1487						promise.then(function(){
1488					    }).catch(function(error){
1489						});
1490					}
1491
1492				}else{
1493
1494			    	adAudio.removeEventListener("timeupdate", adTimeUpdate);
1495			    	adAudio.removeEventListener("ended", adEnded);
1496
1497			    	if(progressLevel)progressLevel.style.width = 0;//reset do its not seen as full width after ad seekbar disapears
1498			    	
1499					self.fireEvent('HAPAdManager.ADPRE_ENDED');
1500
1501					adPreOn = false;
1502
1503				}
1504
1505			}
1506			else if(adMidOn){
1507
1508				//we can have multiple ads
1509		    	adCounter++;
1510				if(adCounter < currMediaData.adMid.length){
1511
1512					adAudio.src = currMediaData.adMid[adCounter];
1513					var promise = adAudio.play();
1514					if (promise !== undefined) {
1515						promise.then(function(){
1516					    }).catch(function(error){
1517						});
1518					}
1519
1520				}else{
1521
1522					adAudio.removeEventListener("timeupdate", adTimeUpdate);
1523				    adAudio.removeEventListener("ended", adEnded);
1524
1525					self.fireEvent('HAPAdManager.ADMID_ENDED');
1526
1527			    	adMidOn = false;
1528
1529			    	parent.fireEvent("adMidEnded", [{instance:parent, instanceName:settings.instanceName, media:currMediaData.adMid}]);
1530
1531			        startAdMidTimer();
1532
1533			    }
1534
1535			}
1536
1537			else if(adEndOn){
1538
1539				//we can have multiple ads
1540		    	adCounter++;
1541				if(adCounter < currMediaData.adEnd.length){
1542
1543					adAudio.src = currMediaData.adEnd[adCounter];
1544					var promise = adAudio.play();
1545					if (promise !== undefined) {
1546						promise.then(function(){
1547					    }).catch(function(error){
1548						});
1549					}
1550
1551				}else{
1552
1553			    	adAudio.removeEventListener("timeupdate", adTimeUpdate);
1554			    	adAudio.removeEventListener("ended", adEnded);
1555
1556			    	adEndOn = false;
1557
1558			    	self.fireEvent('HAPAdManager.ADEND_ENDED');
1559
1560			    }
1561
1562			}
1563	    }
1564
1565	    function startAdMidTimer(){
1566
1567	    	if(adMidTimeoutID)clearTimeout(adMidTimeoutID);
1568	    	var interval = currMediaData.adMidInterval || 10000; 
1569
1570			adMidTimeoutID = setTimeout(function(){
1571				adMidTimeoutID = null;
1572				if(parent.getMediaPlaying()){
1573					self.initAd('mid')
1574				}
1575	        }, parseInt(interval,10) + settings.dataInterval);
1576		}
1577
1578		this.adMidStartHandler = function(){
1579			startAdMidTimer();
1580		}
1581
1582		this.adMidPlayHandler = function(){
1583
1584			//if we paused audio and ad timer fired later while audio paused, we want to resume ad timer now
1585			if(!adMidTimeoutID){
1586
1587				//console.log('audio resumed, but no ad timeout')
1588				
1589				startAdMidTimer();
1590			
1591			}
1592		}
1593
1594		this.clearAdMidTimeout = function(){
1595			if(adMidTimeoutID){
1596				clearTimeout(adMidTimeoutID);
1597				adMidTimeoutID = null;
1598			}
1599		}
1600
1601		this.toggleAdAudio = function(){
1602
1603		    if(adAudio.paused) {
1604			    var promise = adAudio.play();
1605				if (promise !== undefined) {
1606					promise.then(function(){
1607
1608						if(adPreOn){
1609							self.fireEvent('HAPAdManager.AD_PLAY_ACTION');
1610						}
1611
1612				    }).catch(function(error){
1613				    });
1614				}
1615		    }else{
1616			    adAudio.pause();
1617
1618			    if(adPreOn){
1619					self.fireEvent('HAPAdManager.AD_PAUSE_ACTION');
1620				}
1621		    }
1622		}
1623
1624	    //ad time update
1625
1626	    function adTimeUpdate(){
1627	    	var t = adAudio.currentTime, d = adAudio.duration;
1628	    	parent.displayAdTime(d,t)
1629		}
1630
1631		this.isAdOn = function(){
1632			return (adPreOn || adMidOn || adEndOn);
1633		}
1634
1635		this.isAdPreOn = function(){
1636			return adPreOn;
1637		}
1638
1639		this.isAdMidOn = function(){
1640			return adMidOn;
1641		}
1642
1643		this.isAdEndOn = function(){
1644			return adEndOn;
1645		}
1646
1647		this.setAdData = function(cmd){
1648			currMediaData = cmd;
1649			adCounter = 0;
1650		}
1651
1652		this.setVolume = function(vol){
1653    		volume = vol;
1654	        if(adAudio)adAudio.volume = volume;
1655    	}
1656
1657		this.cleanAds = function(){
1658
1659			if(adAudio){
1660
1661				adAudio.removeEventListener("timeupdate", adTimeUpdate);
1662				adAudio.removeEventListener("ended", adEnded);
1663
1664				adAudio.pause();
1665				adAudio.src = '';
1666
1667			}
1668				
1669			adPreOn = false;
1670
1671			if(adMidTimeoutID){
1672				clearTimeout(adMidTimeoutID);
1673				adMidTimeoutID = null;
1674			}
1675
1676			adMidOn = false;
1677
1678			adEndOn = false;
1679
1680		}
1681
1682	};	
1683
1684	window.HAPAdManager = HAPAdManager;
1685
1686}(window));
1687
1688(function (window){
1689	"use strict"
1690	var HAPYoutubeLoader = function (data){
1691		
1692		var self = this,
1693		settings = data,
1694		type,
1695		processData,
1696		finishData = [],
1697		deeplinkCounter,
1698		maxResults = 50,
1699		requestUrl,
1700		nextPageToken,
1701		limit,
1702		order,
1703		orderArr = ['date','rating','relevance','title','videoCount','viewCount'],//only valid for search?
1704		appId = data.youtubeAppId || data.ytk,
1705		xhrRequest;
1706
1707		HAPEventDispatcher.call(this);
1708
1709		this.resumeLoad = function(nextPageToken){
1710			finishData = [];
1711
1712			if(nextPageToken){
1713				requestUrl = nextPageToken.substr(0,nextPageToken.lastIndexOf('&pageToken='));//remove last page token
1714				process(nextPageToken);
1715			}else{
1716				self.fireEvent('HAPYoutubeLoader.END_LOAD', [{data:finishData, nextPageToken:nextPageToken}]);		
1717			}
1718		}
1719		this.setData = function(data){
1720
1721			if(!appId){
1722				alert('Youtube API key missing! Set API key in settings.');
1723				return;
1724			}
1725
1726			finishData = [];
1727			processData = data;
1728			deeplinkCounter = 0;
1729			limit = processData.limit || 400;
1730			maxResults = 50;
1731			if(limit < maxResults)maxResults = limit;
1732			order = processData.sort || 'relevance';
1733			type = processData.type;
1734			nextPageToken = null;
1735
1736			//build snippet fields
1737			var s = '';
1738			if(settings.playlistItemContent.indexOf('title')>-1)s+='title,';
1739			if(settings.playlistItemContent.indexOf('description')>-1)s+='description,';
1740			if(settings.playlistItemContent.indexOf('date')>-1)s+='publishedAt,';
1741			if(settings.playlistItemContent.indexOf('thumb')>-1)s+='thumbnails,';
1742			if(s.length>0){
1743				s = s.substr(0,s.length-1)//remove comma
1744				var snippetTitle = ',snippet';
1745				var snippet = ',snippet('+s+')';
1746			}else{
1747				var snippetTitle = '';
1748				var snippet = '';
1749			}
1750
1751			if(type == 'youtube_single' || type == 'youtube_single_list'){
1752
1753				if(type == 'youtube_single_list')processData.path = processData.path.replace(/\s+/g, '');
1754
1755				//build contentDetails fields
1756				if(settings.playlistItemContent.indexOf('duration')>-1){
1757					var contentDetailsTitle = ',contentDetails';
1758					var contentDetails = ',contentDetails(duration)';
1759				}else{
1760					var contentDetailsTitle = '';
1761					var contentDetails = '';
1762				}
1763
1764				//video, https://developers.google.com/youtube/v3/docs/videos
1765				//requestUrl = 'https://www.googleapis.com/youtube/v3/videos?id='+processData.path+'&key='+appId+'&part=id'+snippetTitle+',status'+contentDetailsTitle+'&fields=items(id,status(privacyStatus)'+snippet+contentDetails+')';
1766
1767				//without privacy status
1768				requestUrl = 'https://www.googleapis.com/youtube/v3/videos?id='+processData.path+'&key='+appId+'&part=id'+snippetTitle+contentDetailsTitle+'&fields=items(id'+snippet+contentDetails+')';
1769
1770			}else{
1771
1772				if(type == 'youtube_playlist'){
1773
1774					//no duration in contentDetails here
1775
1776					//https://developers.google.com/youtube/v3/docs/playlistItems/list
1777					//requestUrl = 'https://www.googleapis.com/youtube/v3/playlistItems?playlistId='+processData.path+'&maxResults='+maxResults+'&key='+appId+'&part=status'+snippetTitle+',contentDetails&fields=items(status(privacyStatus)'+snippet+',contentDetails(videoId))';
1778
1779					//without privacy status
1780					requestUrl = 'https://www.googleapis.com/youtube/v3/playlistItems?playlistId='+processData.path+'&maxResults='+maxResults+'&key='+appId+'&part=contentDetails'+snippetTitle+'&fields=items(contentDetails(videoId)'+snippet+'),nextPageToken';
1781
1782				}
1783			}
1784
1785			process(requestUrl);
1786		
1787		}
1788		function process(url) {
1789
1790			if(xhrRequest){
1791				xhrRequest.abort();
1792				xhrRequest = null;
1793			}
1794
1795			xhrRequest = new XMLHttpRequest();
1796			xhrRequest.onreadystatechange = function() {
1797				if (xhrRequest.readyState == 4) {
1798
1799					var response = JSON.parse(xhrRequest.responseText)
1800
1801					if(response.error && response.error.message){
1802						console.log(response.error.message);
1803						return;
1804					} 
1805
1806					var i, len = response.items.length, item;
1807
1808					nextPageToken = response.nextPageToken;
1809					if(nextPageToken)nextPageToken = requestUrl+'&pageToken='+nextPageToken;//we have to save whole nextPageToken if we load from cache and then load more on total scroll
1810
1811					for(i=0; i < len; i++){
1812						if(finishData.length == limit)break;
1813
1814						item = response.items[i];
1815
1816						if(type=='youtube_playlist' || type=='youtube_single' || type=='youtube_single_list'){
1817							
1818							if(item.status){
1819								if(item.status.privacyStatus == 'public')finishData.push(getItemData(item, type));
1820							}else if(item.snippet){
1821								if(item.snippet.title != 'Private video')finishData.push(getItemData(item, type));
1822							}
1823						}
1824					}
1825
1826					if(type=='youtube_single' || type=='youtube_single_list'){
1827
1828						self.fireEvent('HAPYoutubeLoader.END_LOAD', [{data:finishData, nextPageToken:nextPageToken}]);		
1829
1830				    }else{
1831
1832						if(finishData.length < limit){
1833
1834							if(nextPageToken){
1835								//adjust nextPageToken - when we request more than youtube can retrive (50) in one go, for example: 70 videos, in first request we will get 50 videos, then next page token will retrieve 50 videos again, but we only need next 20 videos! so we will loose leftover 30 videos from next page token. In this example we need to adjust maxResults to 20.
1836
1837								if(finishData.length + maxResults > limit){
1838									maxResults = limit - finishData.length;
1839									var t1 = nextPageToken.substr(0,requestUrl.indexOf('&maxResults=')+12),
1840									t2 = nextPageToken.substr(requestUrl.indexOf('&key='));
1841									nextPageToken = t1+maxResults.toString()+t2;
1842								}
1843
1844								if(type=='youtube_playlist'){
1845									var url = nextPageToken;
1846								}
1847
1848								process(url);
1849
1850							}else{
1851
1852								self.fireEvent('HAPYoutubeLoader.END_LOAD', [{data:finishData, nextPageToken:nextPageToken}]);	
1853								
1854							}
1855						}else{
1856
1857							self.fireEvent('HAPYoutubeLoader.END_LOAD', [{data:finishData, nextPageToken:nextPageToken}]);		
1858							
1859						}	
1860					}
1861						
1862					
1863			    }
1864			}
1865			xhrRequest.onerror = function(e) { 
1866			    console.log('Error processYT: ' + e);
1867				
1868			};
1869			xhrRequest.open('GET', url);
1870			xhrRequest.send();
1871
1872		}
1873
1874		function getItemData(item, type) {
1875
1876			var obj = HAPUtils.extendObj(true, {}, processData);
1877			obj.type = 'youtube';
1878
1879			if(type=='youtube_single' || type=='youtube_single_list'){
1880				obj.mp3 = item.id;
1881			}else if(type=='youtube_playlist'){
1882				obj.mp3 = item.contentDetails.videoId;
1883			}
1884
1885			if(item.snippet){
1886				//obj.snippet = item.snippet;	
1887				if(!obj.title && item.snippet.title)obj.title = item.snippet.title;
1888				if(!obj.description && item.snippet.description)obj.description = item.snippet.description;
1889				if(item.snippet.publishedAt)obj.date = item.snippet.publishedAt;
1890				if(!item.thumb && item.snippet.thumbnails){
1891					/*
1892					default 120x90
1893					medium 320x180
1894					high 480x360
1895					standard 640x480
1896					maxres 1280x720
1897					*/
1898					if(item.snippet.thumbnails.medium)obj.thumb = item.snippet.thumbnails.medium.url;
1899					else if(item.snippet.thumbnails.high)obj.thumb = item.snippet.thumbnails.high.url;
1900					else if(item.snippet.thumbnails.default)obj.thumb = item.snippet.thumbnails.default.url;
1901				}
1902			}
1903
1904			if(item.contentDetails){
1905				if(item.contentDetails.duration)obj.duration = ytDur2Sec(item.contentDetails.duration);
1906			} 
1907		
1908			return obj;
1909		}
1910
1911		function ytDur2Sec(duration) {
1912
1913		    var a = duration.match(/\d+/g);
1914
1915		    if (duration.indexOf('M') >= 0 && duration.indexOf('H') == -1 && duration.indexOf('S') == -1) {
1916		        a = [0, a[0], 0];
1917		    }
1918
1919		    if (duration.indexOf('H') >= 0 && duration.indexOf('M') == -1) {
1920		        a = [a[0], 0, a[1]];
1921		    }
1922		    if (duration.indexOf('H') >= 0 && duration.indexOf('M') == -1 && duration.indexOf('S') == -1) {
1923		        a = [a[0], 0, 0];
1924		    }
1925
1926		    duration = 0;
1927
1928		    if (a.length == 3) {
1929		        duration = duration + parseInt(a[0]) * 3600;
1930		        duration = duration + parseInt(a[1]) * 60;
1931		        duration = duration + parseInt(a[2]);
1932		    }
1933
1934		    if (a.length == 2) {
1935		        duration = duration + parseInt(a[0]) * 60;
1936		        duration = duration + parseInt(a[1]);
1937		    }
1938
1939		    if (a.length == 1) {
1940		        duration = duration + parseInt(a[0]);
1941		    }
1942		    return duration;
1943		}
1944
1945		this.getNextPageToken = function(){
1946			return nextPageToken;
1947		}
1948	
1949	};	
1950
1951	window.HAPYoutubeLoader = HAPYoutubeLoader;
1952
1953}(window));
1954
1955
1956(function (window){
1957	"use strict"	
1958	var HAPPlaybackRateSlider = function (data){
1959
1960		var self = this,
1961		_doc = document,
1962		_window = window,
1963		isMobile = HAPUtils.isMobile(),
1964		wrapper = data.wrapper,
1965		isPressed,
1966		isVertical = data.isVertical,
1967		prop = isVertical ? 'height' : 'width',
1968		tooltip = data.tooltip,
1969		e_holder = data.e_holder,
1970		sliderBg = data.sliderBg,
1971		sliderLevel = data.sliderLevel,
1972		seekbarHolder = data.seekbarHolder,
1973		seekbar = data.seekbar,
1974		rangeSize,
1975		events = HAPUtils.getEvents(),
1976		min = Number(data.settings.playbackRateMin), 
1977		max = Number(data.settings.playbackRateMax),
1978		mobileSelect
1979
1980		HAPEventDispatcher.call(this);
1981
1982		if(isMobile && data.settings.useMobileSelect){
1983
1984			var i, len = max+.1, p, o, select = '<select size="5" class="hap-mobile-playback-rate">'
1985
1986			for (i = min; i <= len; i+=.1){
1987				p = parseFloat(i.toFixed(1))
1988
1989				o = '<option value="'+p+'">'+p+'</option>'
1990
1991				select += o
1992			}
1993			
1994			select += '</select>'	
1995
1996			seekbarHolder.innerHTML = select
1997
1998			mobileSelect = seekbarHolder.querySelector('.hap-mobile-playback-rate')
1999
2000		}else{
2001
2002			seekbar.addEventListener(events.downEvent,function(e){
2003				onDragStart(e);
2004			});
2005
2006			if("ontouchstart" in window){
2007				seekbar.addEventListener('touchstart',function(e){
2008					if(e.preventDefault) e.preventDefault();
2009
2010					onDragStart(e);
2011				});
2012			}
2013
2014		}
2015
2016		function onDragStart(e) {
2017			if(!isPressed){					
2018				isPressed = true;
2019				_doc.addEventListener(events.moveEvent, onDragMove)
2020				_doc.addEventListener(events.upEvent, onDragRelease);		
2021
2022				if("ontouchstart" in window){
2023					_doc.addEventListener('touchmove', onDragMove)
2024					_doc.addEventListener('touchend', onDragRelease);	
2025				}
2026			}
2027		}
2028					
2029		function onDragMove(e) {	
2030			var point;
2031			if(e.type == 'touchmove'){
2032				var touches = e.changedTouches;
2033
2034				if(touches.length > 1) {
2035					return false;
2036				}
2037				point = touches[0];	
2038				e.preventDefault();				
2039			} else {
2040				point = e;
2041				e.preventDefault();		
2042			}
2043			moveTo(point);
2044
2045			if(tooltip)mouseMoveHandlerTooltip(point);
2046			
2047		}
2048		
2049		function onDragRelease(e) {
2050			if(isPressed){	
2051				isPressed = false;			
2052				_doc.removeEventListener(events.moveEvent, onDragMove)
2053				_doc.removeEventListener(events.upEvent, onDragRelease);	
2054
2055				if("ontouchstart" in window){
2056					_doc.removeEventListener('touchmove', onDragMove)
2057					_doc.removeEventListener('touchend', onDragRelease);	
2058				}
2059				
2060				var point;
2061				if(e.type == 'touchend'){
2062					var touches = e.changedTouches;
2063			
2064					if(touches.length > 1) {
2065						return false;
2066					}
2067					point = touches[0];	
2068					e.preventDefault();				
2069				} else {
2070					point = e;
2071					e.preventDefault();		
2072				}
2073				moveTo(point);
2074
2075				if(tooltip)tooltip.style.display = 'none';
2076
2077			}
2078			
2079		}	
2080
2081		function moveTo(v) {
2082
2083			var rect = sliderBg.getBoundingClientRect()
2084
2085			var value;
2086			if(isVertical){
2087				value = Math.max(0, Math.min(1, (v.pageY - rect.top) / rangeSize));
2088				value = 1 - value;//reverse for up dir
2089			}else{
2090				value = Math.max(0, Math.min(1, (v.pageX - rect.left) / rangeSize));
2091			} 
2092
2093			self.setValue(value);
2094		}
2095
2096		this.setValue = function(percent){
2097
2098			if(!HAPUtils.isNumber(rangeSize) || rangeSize == 0){
2099				e_holder.classList.add('hap-measure')
2100				rangeSize = isVertical ? sliderBg.offsetHeight : sliderBg.offsetWidth;
2101				e_holder.classList.remove('hap-measure')
2102			}
2103		
2104			if(prop == 'width')sliderLevel.style.width = percent*rangeSize+'px';
2105			else sliderLevel.style.height = percent*rangeSize+'px';
2106
2107			//convert to playback rate
2108			var val = min+(max-min)*percent;
2109			val = Math.round( val * 10) / 10;//one decimal
2110
2111			self.fireEvent("HAPPlaybackRateSlider.RANGE_CHANGE", [{value:val}]);
2112
2113		}
2114
2115		this.setVisual = function(v){
2116
2117			//convert to percent
2118			var percent = (v - min) / (max-min);
2119
2120			if(!HAPUtils.isNumber(rangeSize) || rangeSize == 0){
2121				e_holder.classList.add('hap-measure')
2122				rangeSize = isVertical ? sliderBg.offsetHeight : sliderBg.offsetWidth;
2123				e_holder.classList.remove('hap-measure')
2124			}
2125
2126			if(prop == 'width')sliderLevel.style.width = percent*rangeSize+'px';
2127			else sliderLevel.style.height = percent*rangeSize+'px';
2128
2129		}
2130
2131		//############################################//
2132		/* tooltip */
2133		//############################################//
2134
2135		if(!isMobile && tooltip){
2136			seekbar.addEventListener('mouseover', function mouseOverHandlerSeek(){
2137				if(isPressed)return;
2138				seekbar.addEventListener(events.moveEvent, mouseMoveHandlerTooltip)
2139				seekbar.addEventListener('mouseout', mouseOutHandler);
2140
2141				_doc.addEventListener('mouseout', mouseOutHandler);
2142			});
2143			function mouseOutHandler() {
2144				seekbar.removeEventListener(events.moveEvent, mouseMoveHandlerTooltip)
2145				seekbar.removeEventListener('mouseout', mouseOutHandler);
2146
2147				_doc.removeEventListener('mouseout', mouseOutHandler);
2148				tooltip.style.display = 'none';
2149			}
2150		}
2151
2152		function mouseMoveHandlerTooltip(e){
2153
2154			var rect = sliderBg.getBoundingClientRect()
2155
2156			var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
2157			scrollTop = window.pageYOffset || document.documentElement.scrollTop;
2158
2159			if(!isVertical)var s = e.pageX - rect.left - scrollLeft;
2160			else var s = e.pageY - rect.top - scrollTop;
2161			if(s < 0) s = 0;
2162			else if(s > rangeSize) s = rangeSize;
2163			var newPercent = Math.max(0, Math.min(1, s / rangeSize));
2164			if(!HAPUtils.isNumber(newPercent))return false;
2165			if(isVertical)newPercent = 1 - newPercent;//reverse for up dir
2166			
2167			var val = min+(max-min)*newPercent;
2168			val = Math.round( val * 10) / 10;
2169
2170			tooltip.style.display = 'block';
2171
2172			tooltip.innerHTML = val;
2173
2174			var main = wrapper.getBoundingClientRect(),
2175			element = seekbar.getBoundingClientRect();
2176
2177			var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
2178			scrollTop = window.pageYOffset || document.documentElement.scrollTop;
2179
2180			if(isVertical){
2181				var l = parseInt(element.left - main.left - tooltip.offsetWidth/2 + seekbar.offsetWidth/2),
2182					t = parseInt(e.pageY - scrollTop - main.top - tooltip.offsetHeight - 10);
2183
2184				//restrain
2185				if(t < element.top - main.top - tooltip.offsetHeight - 10)t = element.top - main.top - tooltip.offsetHeight - 10;
2186				else if(t > element.top - main.top + seekbar.offsetHeight - tooltip.offsetHeight) t = element.top - main.top + seekbar.offsetHeight - tooltip.offsetHeight;
2187
2188			}else{
2189				var l = parseInt(e.pageX - scrollLeft - main.left - tooltip.offsetWidth/2),
2190					t = parseInt(element.top - main.top - tooltip.offsetHeight);
2191
2192				//restrain
2193				if(l < element.left - main.left)l = element.left - main.left;
2194				else if(l > element.left - main.left + seekbar.offsetWidth - tooltip.offsetWidth) l = element.left - main.left + seekbar.offsetWidth - tooltip.offsetWidth;
2195			}
2196
2197			//keep within component
2198			//if(t + main.top < 0)t = parseInt(element.top - main.top + tooltip.offsetHeight + 15);//position below
2199			//if(t < 0) t = 0;
2200
2201			tooltip.style.left = l+'px';
2202			tooltip.style.top = t+'px';
2203			
2204		}
2205
2206		//init
2207
2208		this.setVisual(data.settings.playbackRate)
2209
2210	}
2211
2212	window.HAPPlaybackRateSlider = HAPPlaybackRateSlider;
2213
2214}(window));
2215
2216
2217(function (window){
2218	"use strict"	
2219	var HAPRangeSlider = function (data){
2220
2221		var self = this,
2222		_doc = document,
2223		_window = window,
2224		isPressed,
2225		events = HAPUtils.getEvents(),
2226		range_handle_a = data.range_handle_a,
2227		range_handle_b = data.range_handle_b,
2228		elem
2229
2230		HAPEventDispatcher.call(this);
2231
2232		range_handle_a.addEventListener(events.downEvent,function(e){
2233			elem = e.currentTarget
2234			elem.style.zIndex = 1;//make last dragged z index higher, so we cannot drag into corner one over another
2235			range_handle_b.style.zIndex = 0;
2236			onDragStart(e);
2237		});
2238
2239		range_handle_b.addEventListener(events.downEvent,function(e){
2240			elem = e.currentTarget
2241			elem.style.zIndex = 1;
2242			range_handle_a.style.zIndex = 0;
2243			onDragStart(e);
2244		});
2245
2246		if("ontouchstart" in window){
2247
2248			range_handle_a.addEventListener('touchstart',function(e){
2249				if(e.preventDefault) e.preventDefault();
2250
2251				elem = e.currentTarget
2252				elem.style.zIndex = 1;//make last dragged z index higher, so we cannot drag into corner one over another
2253				range_handle_b.style.zIndex = 0;
2254				onDragStart(e);
2255			});
2256
2257			range_handle_b.addEventListener('touchstart',function(e){
2258				if(e.preventDefault) e.preventDefault();
2259
2260				elem = e.currentTarget
2261				elem.style.zIndex = 1;
2262				range_handle_a.style.zIndex = 0;
2263				onDragStart(e);
2264			});
2265				
2266		}
2267
2268		function onDragStart(e) {
2269			if(!isPressed){					
2270				
2271				isPressed = true;
2272				_doc.addEventListener(events.moveEvent, onDragMove);
2273				_doc.addEventListener(events.upEvent, onDragRelease);	
2274
2275				if("ontouchstart" in window){
2276					_doc.addEventListener('touchmove', onDragMove);
2277					_doc.addEventListener('touchend', onDragRelease);	
2278				}	
2279			}
2280		}
2281					
2282		function onDragMove(e) {	
2283			var point;
2284			if(e.type == 'touchmove'){
2285				var touches = e.changedTouches;
2286
2287				if(touches.length > 1) {
2288					return false;
2289				}
2290				point = touches[0];	
2291				e.preventDefault();				
2292			} else {
2293				point = e;
2294				e.preventDefault();		
2295			}
2296			
2297			self.fireEvent("HAPRangeSlider.RANGE_CHANGE", [{point:point, elem: elem, event:point}]);
2298			
2299		}
2300		
2301		function onDragRelease(e) {
2302			if(isPressed){	
2303				isPressed = false;			
2304				_doc.removeEventListener(events.moveEvent, onDragMove)
2305				_doc.removeEventListener(events.upEvent, onDragRelease);	
2306
2307				if("ontouchstart" in window){
2308					_doc.removeEventListener('touchmove', onDragMove);
2309					_doc.removeEventListener('touchend', onDragRelease);	
2310				}	
2311				
2312				var point;
2313				if(e.type == 'touchend'){
2314					var touches = e.changedTouches;
2315
2316					if(touches.length > 1) {
2317						return false;
2318					}
2319					point = touches[0];	
2320					e.preventDefault();				
2321				} else {
2322					point = e;
2323					e.preventDefault();		
2324				}
2325				
2326				self.fireEvent("HAPRangeSlider.RANGE_CHANGE", [{point:point, elem: elem}]);
2327			}
2328			
2329		}
2330
2331		this.isDrag = function(){
2332			return isPressed;
2333		}	
2334
2335	}
2336
2337	window.HAPRangeSlider = HAPRangeSlider;
2338
2339}(window));
2340
2341(function (window){
2342	"use strict"
2343	var HAPLyrics = function (data){
2344			
2345		var self = this,	
2346
2347		settings = data.settings,
2348		lyricsArr = [],
2349	    lineElements = [],
2350	    enabled = true,
2351	    xhr,
2352	    autoScroll = settings.lyricsAutoScroll,
2353	    scrollContainer = data.scrollContainer,
2354	    wrapContainer = data.wrapContainer
2355
2356	    this.load = function(url){
2357
2358	    	enabled = true;
2359
2360	    	if(window.location.protocol == 'file:'){
2361				console.log('Getting lyrics requires server connection.');
2362				return false;
2363			}
2364
2365	    	if(xhr){
2366				xhr.abort();
2367				xhr = null;
2368			}
2369
2370	    	xhr = new XMLHttpRequest();
2371			xhr.onreadystatechange = function() {
2372
2373				if (xhr.readyState == 4) {
2374					if(!enabled)return
2375
2376					if(url.indexOf('.lrc') > -1){
2377						var type = 'lrc'
2378					}else if(url.indexOf('.vtt') > -1){
2379						var type = 'vtt'
2380					}else if(url.indexOf('.srt') > -1){
2381						var type = 'srt'
2382					}else{
2383						var type = 'lrc'//try to guess
2384					}
2385
2386					lyricsArr = []
2387
2388					parseLyrics(type, xhr.responseText);
2389					createLyrics()
2390					
2391			    }
2392			}
2393			xhr.onerror = function(e) { 
2394			    console.log(e);
2395			};
2396			xhr.open('GET', url);
2397			xhr.send();
2398
2399	    }
2400				
2401	    function createLyrics(){
2402
2403	        var n, len = lyricsArr.length, start, end, lineElement
2404            for(n=0; n<len; n++) {
2405
2406            	start = lyricsArr[n].start
2407
2408            	if(n<len-1)end = lyricsArr[n+1].start
2409            	else end = lyricsArr[n].start
2410
2411             	lineElement = document.createElement("div");
2412		        lineElement.className = data.itemClass;
2413		        data.scrollContainer.appendChild(lineElement);
2414		        lineElements.push(lineElement);
2415
2416		        lineElement.dataset.start = start;
2417		        lineElement.dataset.end = end;
2418
2419		        lineElement.innerHTML = lyricsArr[n].text;
2420
2421
2422		        if(settings.useSeekOnLyrics){
2423		        	lineElement.addEventListener('click', function(e){
2424		        		var time = Number(e.currentTarget.getAttribute('data-start'))
2425		        		var event = new CustomEvent("HAPLyrics.LYRICS_CLICKED", {"detail": time});
2426            			document.dispatchEvent(event);
2427		        	});
2428		        }
2429            
2430            }
2431
2432            var event = new CustomEvent("HAPLyrics.LYRICS_READY", {"detail": lyricsArr});
2433            document.dispatchEvent(event);
2434
2435		}
2436
2437		this.setAutoScroll = function(v){
2438	    	autoScroll = v;
2439	    }
2440
2441		this.setData = function(arr){
2442			lyricsArr = arr;
2443			createLyrics()
2444		}
2445
2446		this.synchronize = function(currentTime) {
2447
2448		    // Highlight the current transcript item.
2449
2450		    var div_list = scrollContainer.querySelectorAll('.hap-lyrics-item').forEach(div => {
2451
2452				var start = parseFloat(div.getAttribute('data-start')),
2453				end = parseFloat(div.getAttribute('data-end'))
2454		
2455				if (currentTime >= start && currentTime <= end) {
2456
2457			  		// If this item isn't already highlighted, it should be
2458			  		if (!div.classList.contains('hap-lyrics-item-active')) {
2459			  			// remove all previous highlights before adding one to current 
2460				        scrollContainer.querySelectorAll('.hap-lyrics-item').forEach(function(el){
2461					        el.classList.remove('hap-lyrics-item-active');
2462					    })   	
2463					    div.classList.add('hap-lyrics-item-active');
2464					    scrollContainer.movingHighlight = true;
2465
2466			    	}
2467
2468			    	return false;
2469				}
2470
2471			});
2472
2473			scrollContainer.currentHighlight = scrollContainer.querySelector('.hap-lyrics-item-active');
2474			if (!scrollContainer.currentHighlight) {
2475				scrollContainer.currentHighlight = null;
2476			}
2477
2478			if(autoScroll){
2479
2480				if (scrollContainer.currentHighlight) {
2481
2482					var rect = scrollContainer.currentHighlight.getBoundingClientRect()
2483
2484					var newTop = Math.floor(wrapContainer.scrollTop +
2485						rect.top -
2486						(wrapContainer.offsetHeight / 2 +
2487						rect.height / 2));
2488
2489					if (newTop != Math.floor(wrapContainer.scrollTop)) {
2490						// only scroll once after moving a highlight
2491						if (scrollContainer.movingHighlight) {
2492							//wrapContainer.scrollTop(newTop);
2493							scrollContainer.currentHighlight.scrollIntoView({behavior: 'smooth', block: 'nearest' });
2494
2495	                		scrollContainer.movingHighlight = false;
2496			            }
2497					}
2498				}
2499
2500			}
2501		}
2502	  
2503		function parseLyrics(type, data) {
2504
2505	    	if(type == 'lrc'){
2506			
2507		    	var lrcs = data.split('\n');//Use Enter to split into an array
2508			    var i, len = lrcs.length;
2509
2510			    for(i=0;i<len;i++) {//Traversing the lyrics array
2511
2512			    	lrcs[i] = lrcs[i].replace(/(^\s*)|(\s*$)/g, ""); //Remove spaces before and after
2513			        var t = lrcs[i].substring(lrcs[i].indexOf("[") + 1, lrcs[i].indexOf("]"));//Fetch the contents of []
2514			        var s = t.split(":");//Separation: before and after text
2515
2516			        if(isNaN(parseInt(s[0]))) {//Not a number
2517			        	//id tags
2518
2519		            }else {//is a value
2520
2521			            var arr = lrcs[i].match(/\[(\d+:.+?)\]/g);//Extract time field, there may be multiple
2522			            var start = 0;
2523			            var k, klen = arr.length;
2524			            for(k=0;k<klen;k++){
2525			                start += arr[k].length; //Calculate the lyrics position
2526			            }
2527
2528			            var content = lrcs[i].substring(start);//Get lyrics content
2529
2530			     	 	if(!HAPUtils.isEmpty(content)){
2531				            for(k=0;k<klen;k++){
2532				                var t = arr[k].substring(1, arr[k].length-1);//Fetch the contents of []
2533				                var s = t.split(":");//Separation: before and after text
2534				                lyricsArr.push({//object{t: time, c: lyrics} added to ms array
2535				                    start: (parseFloat(s[0])*60+parseFloat(s[1])).toFixed(3),
2536				                    text: content
2537				                });
2538				            }
2539				        }
2540			    	}
2541			    }
2542
2543		    }else if(type == 'vtt' || type == 'srt'){
2544
2545				var srt = data.replace(/\r\n|\r|\n/g, '\n');
2546
2547			    srt = HAPUtils.strip(srt);
2548
2549			    var sub = srt.split('\n\n'), s, st, z = 0, j, number, start, end, text;
2550
2551			    for(s in sub) {
2552
2553			        st = sub[s].split('\n');
2554			      
2555			        if(st == "WEBVTT")continue;
2556
2557			        if(st.length >= 2) {
2558			            //number = st[0];
2559
2560			            if(st[0] == "WEBVTT")continue;
2561
2562				        if(st.length > 2){
2563
2564			            	start = HAPUtils.strip(st[1].split(' --> ')[0]);
2565				            end = HAPUtils.strip(st[1].split(' --> ')[1]);
2566				            text = st[2];
2567
2568				            if(st.length > 3) {
2569				                for(j=3; j<st.length;j++){
2570				                   text += '\n'+st[j];
2571				                }
2572				            }
2573
2574			            }else{
2575			            	start = HAPUtils.strip(st[0].split(' --> ')[0]);
2576				            end = HAPUtils.strip(st[0].split(' --> ')[1]);
2577				            text = st[1];
2578			            }
2579
2580				        lyricsArr[z] = {};
2581			            lyricsArr[z].start = HAPUtils.formatTimeWithMiliseconds(start);
2582			            lyricsArr[z].end = HAPUtils.formatTimeWithMiliseconds(end);
2583			            lyricsArr[z].text = text;
2584
2585				        z++;
2586
2587			        }
2588			    }
2589
2590			}
2591
2592		    //console.log(lyricsArr)
2593		 
2594		}
2595
2596		this.deactivate = function(){
2597
2598			if(xhr){
2599				xhr.abort();
2600				xhr = null;
2601			}
2602			enabled = false;
2603
2604			data.scrollContainer.innerHTML = ''
2605		}
2606		
2607	};	
2608	
2609	window.HAPLyrics = HAPLyrics;
2610
2611}(window));	
2612
2613(function (window){
2614	"use strict"
2615	var HAPDialog = function (wrap, data, settings){
2616
2617		var self = this,
2618		wrapper = wrap,
2619		elements = data,
2620	    activeDrag,
2621	    events = HAPUtils.getEvents(),
2622	    _doc = document,
2623	    activeResize
2624
2625		function resizeDialog(element, w, h, save){
2626
2627			if(element == 'lyrics'){
2628				var dialog = getResizeItem2('lyrics')
2629				var storage_key = 'hap_lyrics_dialog_'+settings.instanceName
2630			}
2631			else if(element == 'video'){
2632				var dialog = getResizeItem2('video')
2633				var storage_key = 'hap_video_dialog_'+settings.instanceName
2634			}
2635
2636			//not bigger than screen
2637			var wv = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
2638			wh = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
2639
2640			if(w > wv) w = wv;
2641			if(h > wh) h = wh;
2642
2643			dialog.style.width = w + 'px';
2644			dialog.style.height = h + 'px';
2645				
2646			var innerHeight = h - 75;//header + padding bottom height
2647
2648			dialog.querySelector('.hap-dialog-content').style.height = innerHe
2648ight + 'px';
2649
2650			if(element == 'lyrics'){
2651
2652				dialog.querySelector('.hap-lyrics-wrap').style.height = innerHeight + 'px';
2653
2654			}else if(element == 'video'){
2655
2656				dialog.querySelector('.hap-video-wrap').style.height = innerHeight + 'px';
2657				dialog.querySelector('.hap-video-wrap').style.width = w + 'px';
2658			}
2659
2660			//save
2661			if(localStorage && save){
2662				if(localStorage.getItem(storage_key)){
2663					var l = JSON.parse(localStorage.getItem(storage_key))
2664				}else{
2665					var l = {}
2666				}
2667
2668				var pos = dialog.getBoundingClientRect()
2669				l.pos = pos
2670
2671				localStorage.setItem(storage_key, JSON.stringify(l));
2672			}
2673
2674		}
2675
2676		function getDragItem(target){
2677
2678			var i, len = elements.length, found
2679
2680			for (i = 0;i < len; i++) {
2681			    if(target == elements[i].itemHandle){
2682			    	activeDrag = elements[i]
2683
2684			    	found = true
2685			    	break;
2686			    }
2687			}	
2688			return found;
2689		}
2690
2691		function getResizeItem(target){
2692
2693			var i, len = elements.length, found
2694
2695			for (i = 0;i < len; i++) {
2696			    if(target == elements[i].itemResizeHandle){
2697			    	activeResize = elements[i]
2698			    	found = true
2699			    	break;
2700			    }
2701			}	
2702			return found;
2703		}
2704
2705		function getResizeItem2(element){
2706
2707			var i, len = elements.length, item
2708
2709			for (i = 0;i < len; i++) {
2710			    if(element == elements[i].element){
2711			    	item = elements[i].itemDialog;
2712			    	break;
2713			    }
2714			}	
2715			return item;
2716		}
2717
2718		//drag
2719
2720	    function onDragStart(e) {
2721
2722	    	if (getDragItem(e.target)) {
2723
2724				if(e.type == 'touchstart'){
2725
2726					var touches = e.changedTouches;
2727
2728					if(touches.length > 1) {
2729						return false;
2730					}
2731						
2732					activeDrag.initialX = touches[0].pageX - activeDrag.xOffset;
2733		        	activeDrag.initialY = touches[0].pageY - activeDrag.yOffset;
2734
2735					e.preventDefault();		
2736				}else{
2737					e.preventDefault();		
2738
2739					activeDrag.initialX = e.pageX - activeDrag.xOffset;
2740		       		activeDrag.initialY = e.pageY - activeDrag.yOffset;	
2741
2742				}
2743
2744		        _doc.addEventListener(events.moveEvent, onDragMove)
2745		        _doc.addEventListener(events.upEvent, onDragRelease);
2746
2747		        if("ontouchstart" in window){
2748					_doc.addEventListener('touchmove', onDragMove);
2749					_doc.addEventListener('touchend', onDragRelease);	
2750				}	
2751
2752	        }
2753
2754	    }
2755
2756	    function onDragMove(e) {
2757	    	
2758	        if (activeDrag) {
2759
2760				if(e.type == 'touchmove'){
2761					var touches = e.changedTouches;
2762
2763					if(touches.length > 1) {
2764						return false;
2765					}
2766					e.preventDefault();		
2767
2768					activeDrag.currentX = touches[0].pageX - activeDrag.initialX;
2769		            activeDrag.currentY = touches[0].pageY - activeDrag.initialY;
2770
2771				} else {
2772					e.preventDefault();		
2773
2774					activeDrag.currentX = e.pageX - activeDrag.initialX;
2775		            activeDrag.currentY = e.pageY - activeDrag.initialY;
2776				}
2777
2778				activeDrag.xOffset = activeDrag.currentX;
2779		        activeDrag.yOffset = activeDrag.currentY;
2780
2781		        activeDrag.itemDialog.style.left = activeDrag.currentX + "px";
2782    			activeDrag.itemDialog.style.top = activeDrag.currentY + "px";
2783	      		
2784
2785		    }
2786		    
2787	    }
2788
2789	    function onDragRelease(e) {
2790
2791	    	if(activeDrag){
2792
2793	    		_doc.removeEventListener(events.moveEvent, onDragMove)
2794	    		_doc.removeEventListener(events.upEvent, onDragRelease);	
2795
2796	    		if("ontouchstart" in window){
2797					_doc.removeEventListener('touchmove', onDragMove);
2798					_doc.removeEventListener('touchend', onDragRelease);	
2799				}
2800
2801				activeDrag.initialX = activeDrag.currentX;
2802	        	activeDrag.initialY = activeDrag.currentY;
2803
2804            	if(activeDrag.itemDialog.classList.contains('hap-lyrics-holder')){
2805
2806					if(localStorage){
2807						if(localStorage.getItem('hap_lyrics_dialog_'+settings.instanceName)){
2808							var l = JSON.parse(localStorage.getItem('hap_lyrics_dialog_'+settings.instanceName))
2809						}else{
2810							var l = {}
2811						}
2812
2813						l.currentX = activeDrag.currentX;
2814						l.currentY = activeDrag.currentY;
2815
2816						localStorage.setItem('hap_lyrics_dialog_'+settings.instanceName, JSON.stringify(l));
2817					}
2818
2819				}
2820				else if(activeDrag.itemDialog.classList.contains('hap-video-holder')){
2821
2822					if(localStorage){
2823						if(localStorage.getItem('hap_video_dialog_'+settings.instanceName)){
2824							var l = JSON.parse(localStorage.getItem('hap_video_dialog_'+settings.instanceName))
2825						}else{
2826							var l = {}
2827						}
2828
2829						l.currentX = activeDrag.currentX;
2830						l.currentY = activeDrag.currentY;
2831
2832						localStorage.setItem('hap_video_dialog_'+settings.instanceName, JSON.stringify(l));
2833					}
2834
2835				}
2836
2837				activeDrag = null;
2838				        
2839	    	}
2840
2841	    }
2842
2843	    this.setTranslateInit = function(selector, xPos, yPos) {//restore position
2844
2845	    	var i, len = elements.length, el
2846			for (i = 0;i < len; i++) {
2847				//only if draggable (is dialog)
2848			    if(elements[i].itemDialog.classList.contains('hap-dialog') && elements[i].itemDialog.classList.contains(selector)){
2849			    	el = elements[i]
2850			    	break;
2851			    }
2852			}
2853
2854			if(el){
2855				//start in center like fixed
2856				var player_bound = wrapper.getBoundingClientRect()
2857				
2858				el.itemDialog.classList.add('hap-dialog-force-hide')
2859
2860				var x = window.innerWidth / 2;
2861				var y = window.innerHeight / 2;
2862				var rect = el.itemDialog.getBoundingClientRect()
2863
2864				xPos = x - rect.width / 2// - player_bound.left;
2865				yPos = y - rect.height / 2 //- player_bound.top;
2866
2867				if(isNaN(xPos))xPos = 0;
2868				if(isNaN(yPos))yPos = 0;
2869
2870				if(xPos < 0)xPos = 0;
2871				if(yPos < 0)yPos = 0;
2872
2873		        el.itemDialog.style.left = xPos + "px";
2874	    		el.itemDialog.style.top = yPos + "px";
2875
2876		        el.initialX = xPos;
2877		        el.initialY = yPos;
2878
2879		        el.currentX = xPos;
2880		        el.currentY = yPos;
2881
2882		        el.xOffset = xPos;
2883		        el.yOffset = yPos;
2884
2885		        el.dragInited = true
2886
2887		        el.itemDialog.classList.remove('hap-dialog-force-hide')
2888
2889		    }
2890
2891	    }
2892
2893	    //resize
2894
2895		function onDialogResizeStart(e) {
2896	
2897			if (getResizeItem(e.target)) {
2898
2899				var point;
2900				if(e.type == 'touchstart'){
2901					var touches = e.changedTouches;
2902
2903					if(touches.length > 1) {
2904						return false;
2905					}
2906					point = touches[0];	
2907					e.preventDefault();		
2908				}else{
2909					point = e;
2910					e.preventDefault();						
2911				}
2912	
2913				activeResize.startMouseX = point.pageX
2914	            activeResize.startMouseY = point.pageY
2915				activeResize.dragStartWidth = activeResize.itemDialog.offsetWidth
2916				activeResize.dragStartHeight = activeResize.itemDialog.offsetHeight
2917		
2918				_doc.addEventListener(events.moveEvent, onDialogResizeMove)
2919				_doc.addEventListener(events.upEvent, onDialogResizeRelease);		
2920
2921				if("ontouchstart" in window){
2922					_doc.addEventListener('touchmove', onDialogResizeMove);
2923					_doc.addEventListener('touchend', onDialogResizeRelease);	
2924				}
2925			}
2926
2927		}
2928					
2929		function onDialogResizeMove(e) {	
2930
2931			var point;
2932			if(e.type == 'touchmove'){
2933				var touches = e.changedTouches;
2934
2935				if(touches.length > 1) {
2936					return false;
2937				}
2938				point = touches[0];	
2939				e.preventDefault();				
2940			} else {
2941				point = e;
2942				e.preventDefault();		
2943			}
2944
2945			var w = parseInt(activeResize.dragStartWidth + (point.pageX - activeResize.startMouseX),10),
2946			h = parseInt(activeResize.dragStartHeight + (point.pageY - activeResize.startMouseY),10)
2947
2948			if(w < activeResize.itemResizeMinW) w = activeResize.itemResizeMinW;
2949			if(h < activeResize.itemResizeMinH) h = activeResize.itemResizeMinH;
2950
2951			resizeDialog(activeResize.element, w, h, true)
2952
2953		}
2954		
2955		function onDialogResizeRelease(e) {
2956			if(activeResize){	
2957				activeResize = null;			
2958				_doc.removeEventListener(events.moveEvent, onDialogResizeMove)
2959				_doc.removeEventListener(events.upEvent, onDialogResizeRelease);
2960
2961				if("ontouchstart" in window){
2962					_doc.removeEventListener('touchmove', onDialogResizeMove);
2963					_doc.removeEventListener('touchend', onDialogResizeRelease);	
2964				}	
2965			}
2966			
2967		}	
2968
2969		//init
2970
2971		if(settings.clearDialogCacheOnStart || settings.isPopup){
2972			if(localStorage){
2973				localStorage.removeItem('hap_lyrics_dialog_'+settings.instanceName)
2974				localStorage.removeItem('hap_video_dialog_'+settings.instanceName)
2975			}
2976		}
2977
2978
2979
2980		var i, len = elements.length, item
2981		for (i = 0;i < len; i++) {
2982			item = elements[i]
2983		    item.xOffset = 0
2984		    item.yOffset = 0 
2985
2986		    item.itemHandle.addEventListener(events.downEvent,function(e){
2987				onDragStart(e);
2988			});
2989
2990			if("ontouchstart" in window){
2991
2992				item.itemHandle.addEventListener('touchstart',function(e){
2993					if(e.preventDefault) e.preventDefault();
2994					onDragStart(e);
2995				});
2996
2997			}
2998
2999			if(item.itemResizeHandle){
3000				item.itemResizeHandle.addEventListener(events.downEvent,function(e){
3001					onDialogResizeStart(e);
3002				});
3003
3004				if("ontouchstart" in window){
3005
3006					item.itemResizeHandle.addEventListener('touchstart',function(e){
3007						if(e.preventDefault) e.preventDefault();
3008						onDialogResizeStart(e);
3009					});
3010
3011				}
3012			}
3013
3014			if(item.element == 'lyrics'){
3015
3016				//autoscroll checkbox
3017
3018				item.itemDialog.querySelector('.hap-lyrics-autoscroll').addEventListener('change', function(e){
3019
3020					var lyricsAutoScroll = e.currentTarget.checked;
3021
3022					var event = new CustomEvent("HAPDialog.LYRICS_AUTOSCROLL_CHANGE", {"detail": lyricsAutoScroll});
3023            		document.dispatchEvent(event);
3024
3025			        //save
3026					if(localStorage){
3027				        if(localStorage.getItem('hap_lyrics_dialog_'+settings.instanceName)){
3028							var l = JSON.parse(localStorage.getItem('hap_lyrics_dialog_'+settings.instanceName))
3029						}else{
3030							var l = {}
3031						}
3032						l.autoScroll = lyricsAutoScroll
3033
3034						localStorage.setItem('hap_lyrics_dialog_'+settings.instanceName, JSON.stringify(l));
3035					}
3036				})
3037
3038				if(localStorage && localStorage.getItem('hap_lyrics_dialog_'+settings.instanceName)){
3039
3040					var l = JSON.parse(localStorage.getItem('hap_lyrics_dialog_'+settings.instanceName))
3041					console.log(l)
3042					if(l.pos){
3043						var w = l.pos.width, h = l.pos.height
3044						resizeDialog('lyrics', w, h)
3045					}else{
3046						var width = window.getComputedStyle(wrapper.querySelector(".hap-lyrics-holder")).getPropertyValue("width");
3047					}
3048
3049					if(l.currentX && l.currentY){//restore position
3050						self.setTranslateInit('hap-lyrics-holder', l.currentX, l.currentY)
3051					}
3052
3053					var checked = l.autoScroll ? true : false;
3054					item.itemDialog.querySelector('.hap-lyrics-autoscroll').checked = true;
3055
3056					var event = new CustomEvent("HAPDialog.LYRICS_AUTOSCROLL_CHANGE", {"detail": l.autoScroll});
3057		            document.dispatchEvent(event);
3058
3059				}else{
3060					if(settings.lyricsAutoScroll){
3061						item.itemDialog.querySelector('.hap-lyrics-autoscroll').checked = true;
3062
3063						var event = new CustomEvent("HAPDialog.LYRICS_AUTOSCROLL_CHANGE", {"detail": settings.lyricsAutoScroll});
3064		            	document.dispatchEvent(event);
3065					}
3066					self.setTranslateInit('hap-lyrics-holder', 0, 0)
3067				}
3068
3069			}
3070			else if(item.element == 'video'){
3071
3072				if(localStorage && localStorage.getItem('hap_video_dialog_'+settings.instanceName)){
3073					var l = JSON.parse(localStorage.getItem('hap_video_dialog_'+settings.instanceName))
3074
3075					if(l.pos){
3076						var w = l.pos.width, h = l.pos.height
3077						resizeDialog('video', w, h)
3078					}
3079
3080					if(l.currentX && l.currentY){//restore position
3081						self.setTranslateInit('hap-video-holder', l.currentX, l.currentY)
3082					}
3083
3084				}else{
3085					self.setTranslateInit('hap-video-holder', 0, 0)//restore layout if we open in popup
3086				}
3087
3088			}
3089
3090		}
3091		
3092	};	
3093	
3094	window.HAPDialog = HAPDialog;
3095
3096}(window));		
3097
3098
3099(function (window){
3100	"use strict"
3101	var HAPVolumeSlider = function (data){
3102			
3103		var self = this,	
3104		isMobile = HAPUtils.isMobile(),
3105		_window = window,
3106		settings = data.settings,
3107		tooltip = data.tooltip,
3108		parent = data.parent,
3109
3110		volumeToggle = data.container.querySelector('.hap-volume-toggle'),
3111		volumeSeekbar = data.container.querySelector('.hap-volume-seekbar'),
3112		volumeBg = data.container.querySelector('.hap-volume-bg'),
3113		volumeLevel = data.container.querySelector('.hap-volume-level'),
3114
3115		_doc = document,
3116		events = HAPUtils.getEvents(),
3117
3118		volume = data.volume,
3119		volumeBarDown,
3120		volumeVertical,
3121		volumeSize
3122
3123		HAPEventDispatcher.call(this);
3124
3125		if(volumeSeekbar){
3126			volumeVertical = volumeSeekbar.classList.contains('hap-volume-vertical'),
3127			volumeSize = volumeVertical ? volumeBg.clientHeight : volumeBg.clientWidth;
3128
3129			volumeSeekbar.addEventListener(events.downEvent,function(e){
3130				if(data.parent.isCrossFade()) return false;
3131				onDragStartVolume(e);
3132			});
3133
3134			if("ontouchstart" in window){
3135
3136				volumeSeekbar.addEventListener('touchstart',function(e){
3137					if(e.preventDefault) e.preventDefault();
3138					if(data.parent.isCrossFade()) return false;
3139					onDragStartVolume(e);
3140				});
3141
3142			}
3143		}
3144
3145		if(!volumeSeekbar || volumeToggle && volumeToggle.classList.contains('hap-volume-toggable')){
3146			if(volumeToggle){
3147				volumeToggle.addEventListener('click', function(){
3148					self.fireEvent("HAPVolumeSlider.TOGGLE_MUTE");
3149				})
3150			}
3151		}
3152
3153		function onDragStartVolume(e) {
3154			if(!volumeBarDown){					
3155				var point;
3156				if(e.type == 'touchstart'){
3157					var touches = e.changedTouches;
3158
3159					if(touches.length > 1) {
3160						return false;
3161					}
3162					point = touches[0];	
3163					e.preventDefault();		
3164				}else{
3165					point = e;								
3166					e.preventDefault();						
3167				}
3168				volumeBarDown = true;
3169				_doc.addEventListener(events.moveEvent, onDragMoveVolume)
3170				_doc.addEventListener(events.upEvent, onDragReleaseVolume);		
3171
3172				if("ontouchstart" in window){
3173					_doc.addEventListener('touchmove', onDragMoveVolume);
3174					_doc.addEventListener('touchend', onDragReleaseVolume);	
3175				}	
3176			}
3177		}
3178					
3179		function onDragMoveVolume(e) {	
3180			var point;
3181			if(e.type == 'touchmove'){
3182				var touches = e.changedTouches;
3183
3184				if(touches.length > 1) {
3185					return false;
3186				}
3187				point = touches[0];	
3188				e.preventDefault();				
3189			} else {
3190				point = e;
3191				e.preventDefault();		
3192			}
3193			volumeTo(point);
3194
3195			if(tooltip)mouseMoveHandlerVolumeTooltip(point);
3196			
3197		}
3198		
3199		function onDragReleaseVolume(e) {
3200			if(volumeBarDown){	
3201				volumeBarDown = false;			
3202				_doc.removeEventListener(events.moveEvent, onDragMoveVolume)
3203				_doc.removeEventListener(events.upEvent, onDragReleaseVolume);	
3204
3205				if("ontouchstart" in window){
3206					_doc.removeEventListener('touchmove', onDragMoveVolume);
3207					_doc.removeEventListener('touchend', onDragReleaseVolume);	
3208				}
3209				
3210				var point;
3211				if(e.type == 'touchend'){
3212					var touches = e.changedTouches;
3213
3214					if(touches.length > 1) {
3215						return false;
3216					}
3217					point = touches[0];	
3218					e.preventDefault();				
3219				} else {
3220					point = e;
3221					e.preventDefault();		
3222				}
3223				volumeTo(point);
3224
3225				if(tooltip)tooltip.style.display = 'none';
3226
3227			}
3228			
3229		}	
3230
3231		function volumeTo(v) {
3232			var rect = volumeBg.getBoundingClientRect()
3233
3234			var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
3235			scrollTop = window.pageYOffset || document.documentElement.scrollTop;
3236
3237			volumeSize = volumeVertical ? rect.height : rect.width;
3238
3239			if(volumeVertical){
3240				volume = Math.max(0, Math.min(1, (v.pageY - rect.top - scrollTop) / volumeSize));
3241				volume = 1 - volume;//reverse for up dir
3242			}else{
3243				volume = Math.max(0, Math.min(1, (v.pageX - rect.left - scrollLeft) / volumeSize));
3244			} 
3245			self.setValue(volume);
3246		}
3247
3248		this.setValue = function(volume){
3249
3250			self.setVisual(volume)
3251
3252			self.fireEvent("HAPVolumeSlider.VOLUME_CHANGE", [{volume:volume}]);
3253
3254		}
3255
3256		this.setVisual = function(v){
3257
3258			volume = v;
3259
3260			if(volumeBg){
3261
3262				var rect = volumeBg.getBoundingClientRect()
3263
3264				volumeSize = volumeVertical ? rect.height : rect.width;
3265
3266				var prop = volumeVertical ? 'height' : 'width';
3267
3268				if(prop == 'width')volumeLevel.style.width = volume * volumeSize+'px';
3269				else volumeLevel.style.height = volume * volumeSize+'px';
3270
3271			}
3272
3273			if(volumeToggle){
3274
3275				volumeToggle.querySelectorAll('.hap-btn').forEach(function(el){
3276					el.style.display = 'none';
3277				})
3278
3279				volumeToggle.querySelectorAll('.hap-sticky-btn').forEach(function(el){
3280					el.style.display = 'none';
3281				})
3282
3283				if(volume == 0){
3284					volumeToggle.querySelector('.hap-btn-volume-off').style.display = 'block';
3285				}else if(volume > 0 && volume < 0.5){	
3286					volumeToggle.querySelector('.hap-btn-volume-down').style.display = 'block';
3287				}else{
3288					volumeToggle.querySelector('.hap-btn-volume-up').style.display = 'block';
3289				}
3290
3291			}
3292
3293		}
3294
3295		//############################################//
3296		/* volume tooltip */
3297		//############################################//
3298
3299		if(tooltip && !isMobile && volumeSeekbar){
3300			volumeSeekbar.addEventListener('mouseover', function mouseOverHandlerSeek(){
3301				if(volumeBarDown)return;
3302
3303				volumeSeekbar.addEventListener(events.moveEvent, mouseMoveHandlerVolumeTooltip)
3304				volumeSeekbar.addEventListener('mouseout', mouseOutHandlerVolume);
3305
3306				_doc.addEventListener('mouseout', mouseOutHandlerVolume);
3307			});
3308		}
3309
3310		function mouseOutHandlerVolume() {
3311			volumeSeekbar.removeEventListener(events.moveEvent, mouseMoveHandlerVolumeTooltip)
3312			volumeSeekbar.removeEventListener('mouseout', mouseOutHandlerVolume);
3313
3314			_doc.removeEventListener('mouseout', mouseOutHandlerVolume);
3315
3316			tooltip.style.display = 'none';
3317		}	
3318		
3319		function mouseMoveHandlerVolumeTooltip(e){
3320
3321			let volumeBg_rect = volumeBg.getBoundingClientRect();
3322
3323			var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
3324			scrollTop = window.pageYOffset || document.documentElement.scrollTop;
3325
3326			if(!volumeVertical)var s = e.pageX - volumeBg_rect.left - scrollLeft;
3327			else var s = e.pageY - volumeBg_rect.top - scrollTop;
3328			if(s<0) s=0;
3329			else if(s>volumeSize) s=volumeSize;
3330
3331			var newPercent = Math.max(0, Math.min(1, s / volumeSize));
3332			if(!HAPUtils.isNumber(newPercent))return false;
3333			if(volumeVertical)newPercent = 1 - newPercent;//reverse for up dir
3334			var value = parseInt(newPercent * 100, 10);
3335
3336			tooltip.innerHTML = value + ' %';
3337
3338			tooltip.style.display = 'block';
3339
3340			var main = data.container.getBoundingClientRect(),
3341			element = volumeSeekbar.getBoundingClientRect();
3342
3343			if(volumeVertical){
3344				var l = parseInt(element.left + element.width/2 - main.left- tooltip.offsetWidth/2),
3345					t = parseInt(e.pageY - scrollTop - main.top - tooltip.offsetHeight - 20);
3346
3347					//restrain
3348					if(t < element.top - main.top - tooltip.offsetHeight - 10)t = element.top - main.top - tooltip.offsetHeight - 10;
3349					else if(t > element.top - main.top + volumeSeekbar.offsetHeight - tooltip.offsetHeight) t = element.top - main.top + volumeSeekbar.offsetHeight - tooltip.offsetHeight;
3350
3351			}else{
3352
3353				var l = parseInt(e.pageX - scrollLeft - main.left - tooltip.offsetWidth/2),
3354					t = parseInt(element.top - main.top - tooltip.offsetHeight) - 15;
3355
3356					//restrain
3357					if(l < element.left - main.left)l = element.left - main.left;
3358					else if(l > element.left - main.left + volumeSeekbar.offsetWidth - tooltip.offsetWidth) l = element.left - main.left + volumeSeekbar.offsetWidth - tooltip.offsetWidth;
3359				
3360			}
3361
3362			if(l < 0 || t < 0){//if we have a seekbar that shows on hover it will hide when we hold and press outside seekbar.
3363				//tooltip.style.display = 'none';
3364				//return;
3365			} 
3366
3367			if(t + main.top < 0)t = parseInt(element.top - main.top + tooltip.offsetHeight + 20);//position below
3368			//if(t < 0) t = 0;
3369			
3370			tooltip.style.top = t+'px'
3371			tooltip.style.left = l+'px'
3372			
3373		}
3374
3375
3376		//init
3377		setTimeout(function(){
3378			self.setVisual(volume);
3379		},200)
3380		
3381	};	
3382	
3383	window.HAPVolumeSlider = HAPVolumeSlider;
3384
3385}(window));		
3386
3387
3388(function() {
3389
3390	var hap = function(delem, settings) {
3391
3392	"use strict"
3393
3394	var self = this;
3395
3396    HAPEventDispatcher.call(this);
3397
3398	//############################################//
3399	/* settings */
3400	//############################################//
3401
3402	var defaults = {
3403		//scripts
3404		gtag_js: "//www.googletagmanager.com/gtag/js?id=",
3405		sharemanager_js: "js/sharemanager.js",
3406		perfectScrollbar_js: 'js/perfect-scrollbar.min.js',
3407		imaLoader_js: 'js/imaloader.js',
3408		sortable_js: 'js/sortable.min.js',
3409	    ima_js: '//imasdk.googleapis.com/js/sdkloader/ima3.js',
3410	    md5_js: "https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.10.0/js/md5.min.js",
3411		mediaId:null,
3412		mediaTitle:null,
3413		resumeTime:null,//we use this to set audio start time, we cant use start alone in querystring because query playlist does not exist (unless we have other required query string parameters for playlist, then we can use start)
3414		sourcePath:"",
3415		instanceName: "",
3416		queryInstance: "",
3417		playlistItemContent:"",
3418		playlistTitleOrder:"title,artist",
3419		statisticsContent:"plays,likes,downloads",
3420		dataInterval: 250,
3421		volume:0.5,
3422		activeItem:0,
3423		numberTitleSeparator:".&nbsp;",
3424		artistTitleSeparator:"&nbsp;-&nbsp;",
3425		embedSrc: 'Embed url goes here',
3426		useNumbersInPlaylist:false,
3427		preload:"auto",
3428		autoPlay:false,
3429		loopState:"playlist",
3430		playbackRate:1,
3431		addResizeEvent:true,
3432		sortableTracksSet:false,
3433		togglePlaybackOnPlaylistItem:false,
3434		pauseAudioDuringAds:true,
3435		randomPlay:false,
3436		usePlaylist:true,
3437		waveOptions: {
3438			barHeight: 1,
3439			halfPixel: 0.5 / 1,
3440	  		barMinHeight: null,
3441		    normalize: false,
3442		    partialRender: false,
3443		    pixelRatio:1,
3444		    barWidth: 1,
3445            barRadius: 0,
3446            barGap: 5,
3447		},
3448		demoPeakArr: [
3449
3450		'0.05,-0.11,0.27,-0.57,0.19,-0.12,0.14,-0.12,0.13,-0.07,0.11,-0.11,0.12,-0.14,0.31,-0.16,0.29,-0.09,0.15,-0.24,0.18,-0.45,0.3,-0.16,0.24,-0.1,0.1,-0.2,0.16,-0.13,0.12,-0.13,0.34,-0.13,0.44,-0.22,0.54,-0.32,0.48,-0.24,0.57,-0.51,0.54,-0.42,0.22,-0.44,0.31,-0.37,0.58,-0.57,0.58,-0.69,0.75,-0.41,0.41,-0.22,0.66,-0.68,0.64,-0.36,0.42,-0.41,0.34,-0.56,0.37,-0.38,0.33,-0.49,0.38,-0.37,0.41,-0.4,0.55,-0.58,0.4,-0.33,0.35,-0.43,0.16,-0.26,0.26,-0.33,0.38,-0.3,0.23,-0.56,0.52,-0.39,0.41,-0.89,0.49,-0.36,0.43,-0.42,0.35,-0.36,0.48,-0.28,0.34,-0.35,0.78,-0.18,0.63,-0.43,0.13,-0.34,0.18,-0.44,0.35,-0.14,0.22,-0.36,0.26,-0.13,0.28,-0.31,0.31,-0.32,0.29,-0.33,0.26,-0.27,0.36,-0.21,0.16,-0.28,0.55,-0.34,0.38,-0.42,0.53,-0.71,0.56,-0.65,0.81,-0.55,0.55,-0.46,0.67,-0.71,0.96,-0.89,0.88,-0.54,0.65,-0.51,0.55,-0.47,0.79,-0.34,0.66,-0.45,0.57,-0.73,0.66,-0.71,0.59,-0.77,0.64,-0.32,0.65,-0.22,0.5,-0.7,0.62,-0.67,0.52,-0.57,0.8,-0.5,0.69,-0.67,0.79,-0.63,0.63,-0.8,0.78,-0.59,0.73,-0.6,0.65,-0.72,0.8,-0.65,0.84,-0.74,0.64,-0.56,0.75,-0.81,0.42,-0.19,0.25,-0.15,0.1,-0.04,0.07,-0.11,0.08,-0.06',
3451
3452		'0.09,-0.09,0.16,-0.12,0.14,-0.28,0.18,-0.31,0.26,-0.29,0.34,-0.3,0.32,-0.2,0.44,-0.22,0.34,-0.28,0.22,-0.19,0.18,-0.29,0.26,-0.21,0.3,-0.14,0.23,-0.17,0.2,-0.36,0.26,-0.19,0.28,-0.2,0.16,-0.19,0.33,-0.15,0.27,-0.21,0.3,-0.35,0.23,-0.3,0.55,-0.35,0.49,-0.32,0.61,-0.35,0.28,-0.2,0.27,-0.26,0.41,-0.38,0.39,-0.3,0.29,-0.26,0.37,-0.44,0.22,-0.22,0.52,-0.25,0.24,-0.31,0.35,-0.26,0.53,-0.32,0.33,-0.32,0.36,-0.25,0.42,-0.14,0.46,-0.29,0.28,-0.2,0.22,-0.28,0.25,-0.29,0.3,-0.34,0.41,-0.36,0.3,-0.48,0.64,-0.49,0.21,-0.29,0.36,-0.47,0.33,-0.39,0.36,-0.63,0.17,-0.47,0.43,-0.58,0.41,-0.58,0.54,-0.31,0.47,-0.55,0.5,-0.45,0.78,-0.44,0.13,-0.51,0.46,-0.58,0.48,-0.19,0.51,-0.18,0.39,-0.03,0.28,-0.16,0.17,-0.3,0.24,-0.22,0.27,-0.28,0.35,-0.39,0.63,-0.3,0.55,-0.46,0.84,-0.51,0.62,-0.86,0.59,-0.59,0.59,-0.61,0.7,-0.42,0.64,-0.43,0.6,-0.44,0.58,-0.62,0.58,-0.5,0.58,-0.55,0.62,-0.54,0.57,-0.36,0.54,-0.71,0.49,-0.39,0.43,-0.47,0.7,-0.66,0.78,-0.37,0.57,-0.57,0.31,-0.58,0.41,-0.72,0.53,-0.66,0.57,-0.49,0.51,-0.67,0.45,-0.6,0.41,-0.7,0.81,-0.56,0.66,-0.55,0.46,-0.75,0.52,-0.54,0.37,-0.33',
3453
3454		'0.32,-0.57,0.34,-0.39,0.36,-0.17,0.35,-0.37,0.42,-0.28,0.71,-0.31,0.42,-0.31,0.51,-0.28,0.33,-0.39,0.52,-0.69,0.35,-0.56,0.79,-0.94,0.55,-0.55,0.58,-0.41,0.35,-0.5,0.19,-0.48,0.3,-0.54,0.14,-0.2,0.11,-0.1,0.35,-0.45,0.28,-0.32,0.4,-0.28,0.3,-0.45,0.22,-0.22,0.29,-0.44,0.28,-0.5,0.5,-0.43,0.29,-0.5,0.51,-0.22,0.29,-0.38,0.32,-0.54,0.2,-0.32,0.97,-0.52,0.41,-0.42,0.44,-0.48,0.75,-0.39,0.31,-0.12,0.13,-0.12,0.27,-0.56,0.41,-0.72,0.35,-0.17,0.28,-0.78,0.24,-0.74,0.23,-0.56,0.43,-0.69,0.53,-0.27,0.2,-0.44,0.37,-0.22,0.37,-0.21,0.26,-0.32,0.39,-0.23,0.19,-0.43,0.41,-0.25,0.59,-0.47,0.36,-0.52,0.33,-0.18,0.16,-0.17,0.1,-0.58,0.51,-0.46,0.47,-0.4,0.38,-0.52,0.33,-0.44,0.48,-0.34,0.23,-0.51,0.42,-0.73,0.41,-0.32,0.61,-0.18,0.43,-0.37,0.38,-0.31,0.46,-0.2,0.36,-0.53,0.38,-0.76,0.26,-0.51,0.33,-0.69,0.55,-0.34,0.17,-0.
345411,0.71,-0.32,0.37,-0.24,0.48,-0.56,0.29,-0.55,0.26,-0.67,0.41,-0.41,0.39,-0.32,0.39,-0.23,0.35,-0.51,0.55,-0.39,0.47,-0.4,0.35,-0.35,0.4,-0.74,0.35,-0.23,0.2,-0.59,0.52,-0.16,0.44,-0.37,0.2,-0.57,0.18,-0.12,0.04,-0.1,0.03,-0.02,0.01,-0.01,0,0,0,0'
3455		],
3456		playlistScrollType: 'browser',
3457	 	useVideoControls: false,
3458		usePlaylistScroll:false,
3459		useKeyboardNavigationForPlayback:false,
3460		variationToggleSpeed: 300,
3461		keyboardControls: [],
3462		togglePlaybackOnMultipleInstances:true,
3463		useTitleScroll:false,
3464		clearDialogCacheOnStart:true,
3465		useSeekOnLyrics:true,
3466		lyricsAutoOpen:false,
3467		videoAutoOpen:false,
3468		titleScrollSpeed: 1,
3469        titleScrollSeparator: " *** ",
3470        continousKey:"hap-continous-key",
3471        openPlayerInMainWindowAfterPopupClose:true,
3472        lyricsAutoScroll:true,
3473        useMediaSession:true,
3474        crossFadeTime: 5,
3475        s3ThumbExtension: 'jpg',
3476        s3UrlExpireTime: '+15 seconds',
3477        s3Region: 'us-east-1', 
3478        cf_expire: 300,
3479        createDownloadIconsInPlaylist: false,
3480        createLinkIconsInPlaylist: false,
3481        limitDescriptionReadMoreText:"Read more",
3482        limitDescriptionReadLessText:"Read less",
3483        useShare:true,
3484        percentToCountAsPlay: 25,
3485        scrollToPlayer:0,
3486        linkIconTitle:"Purchase",
3487        downloadIconTitle:"Download",
3488        tooltipStatPlays: "Plays",
3489        tooltipStatLikes: "Likes",
3490        tooltipStatDownloads: "Downloads",
3491        artworkSize: [100,200,250,340,460,600],
3492        soundCloudThumbQuality: "t300x300.jpg",
3493        playlistSelectorPlaceholderThumb:'media/playlist-placeholder.jpg',
3494        playlistContentClass: 'hap-playlist-default',
3495        playbackRateMin: 0.5, 
3496		playbackRateMax: 2,
3497		playbackRateArr:[0.5,1,1.2,1.5,2],
3498		playlistItemClickSelectorArr: ['hap-playlist-thumb', 'hap-thumbimg', 'hap-playlist-title-wrap', 'hap-playlist-title', 'hap-playlist-artist'],
3499		sortOrder: "",
3500		modifierKey: "", 
3501		getId3Image: true,
3502		useContinousPlayback:false,
3503		playlistItemMultilineWidth:600,
3504		searchDescriptionInPlaylist:true,
3505		hideYoutubeAfterStart:false,
3506		defaultSongArtist: "DATA NOT AVAILABLE",
3507        defaultSongTitle: "DATA NOT AVAILABLE",
3508        cors: 'https://cors-anywhere.herokuapp.com',
3509        seekTime:10,
3510        //pagination
3511		paginationPreviousBtnTitle:"Previous",
3512		paginationPreviousBtnText:"Prev",
3513		paginationNextBtnTitle:"Next",
3514		paginationNextBtnText:"Next",
3515
3516		useTitleAnimationInSticky:true,
3517
3518		downloadIcon: "<svg aria-hidden='true' focusable='false' role='img' viewBox='0 0 512 512'><path d='M216 0h80c13.3 0 24 10.7 24 24v168h87.7c17.8 0 26.7 21.5 14.1 34.1L269.7 378.3c-7.5 7.5-19.8 7.5-27.3 0L90.1 226.1c-12.6-12.6-3.7-34.1 14.1-34.1H192V24c0-13.3 10.7-24 24-24zm296 376v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V376c0-13.3 10.7-24 24-24h146.7l49 49c20.1 20.1 52.5 20.1 72.6 0l49-49H488c13.3 0 24 10.7 24 24zm-124 88c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20zm64 0c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20z'></path></svg>",
3519
3520		linkIcon: "<svg aria-hidden='true' focusable='false' role='img' viewBox='0 0 576 512'><path d='M504.717 320H211.572l6.545 32h268.418c15.401 0 26.816 14.301 23.403 29.319l-5.517 24.276C523.112 414.668 536 433.828 536 456c0 31.202-25.519 56.444-56.824 55.994-29.823-.429-54.35-24.631-55.155-54.447-.44-16.287 6.085-31.049 16.803-41.548H231.176C241.553 426.165 248 440.326 248 456c0 31.813-26.528 57.431-58.67 55.938-28.54-1.325-51.751-24.385-53.251-52.917-1.158-22.034 10.436-41.455 28.051-51.586L93.883 64H24C10.745 64 0 53.255 0 40V24C0 10.745 10.745 0 24 0h102.529c11.401 0 21.228 8.021 23.513 19.19L159.208 64H551.99c15.401 0 26.816 14.301 23.403 29.319l-47.273 208C525.637 312.246 515.923 320 504.717 320zM408 168h-48v-40c0-8.837-7.163-16-16-16h-16c-8.837 0-16 7.163-16 16v40h-48c-8.837 0-16 7.163-16 16v16c0 8.837 7.163 16 16 16h48v40c0 8.837 7.163 16 16 16h16c8.837 0 16-7.163 16-16v-40h48c8.837 0 16-7.163 16-16v-16c0-8.837-7.163-16-16-16z'></path></svg>",
3521
3522		statDownloadIcon: "<svg aria-hidden='true' focusable='false' role='img' viewBox='0 0 512 512'><path d='M504 256c0 137-111 248-248 248S8 393 8 256 119 8 256 8s248 111 248 248zM212 140v116h-70.9c-10.7 0-16.1 13-8.5 20.5l114.9 114.3c4.7 4.7 12.2 4.7 16.9 0l114.9-114.3c7.6-7.6 2.2-20.5-8.5-20.5H300V140c0-6.6-5.4-12-12-12h-64c-6.6 0-12 5.4-12 12z'></path></svg>",
3523
3524		statLikeIcon: "<svg aria-hidden='true' focusable='false' role='img' viewBox='0 0 512 512'>
3524<path d='M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z'></path></svg>",
3525
3526		statPlayIcon: "<svg aria-hidden='true' focusable='false' role='img' viewBox='0 0 512 512'><path d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm115.7 272l-176 101c-15.8 8.8-35.7-2.5-35.7-21V152c0-18.4 19.8-29.8 35.7-21l176 107c16.4 9.2 16.4 32.9 0 42z'></path></svg>",
3527
3528		iconClose: "<svg aria-hidden='true' focusable='false' role='img' viewBox='0 0 320 512'><path d='M317.7 402.3c3.125 3.125 3.125 8.188 0 11.31c-3.127 3.127-8.186 3.127-11.31 0L160 267.3l-146.3 146.3c-3.127 3.127-8.186 3.127-11.31 0c-3.125-3.125-3.125-8.188 0-11.31L148.7 256L2.344 109.7c-3.125-3.125-3.125-8.188 0-11.31s8.188-3.125 11.31 0L160 244.7l146.3-146.3c3.125-3.125 8.188-3.125 11.31 0s3.125 8.188 0 11.31L171.3 256L317.7 402.3z'/></svg>"
3529
3530	}
3531
3532	var settings = HAPUtils.extendObj(true, {}, defaults, settings);
3533
3534
3535
3536	//############################################//
3537	/* url params */
3538	//############################################//
3539
3540	//check url params, overwrite settings
3541	//check playlist from url params
3542
3543	var urlParams = HAPUtils.getUrlParameter(),
3544	queryPlaylistArr = [], 
3545	queryPlaylist = [];
3546
3547	var supportedPlaylistKeyArr = ['type','noapi','video','audio-preview','path','path','version','sid','lyrics','limit','thumb','thumb-small','thumb-default','thumb-alt','title','description','artist','album','download','duration','date','id3','start','end','link','target','rel','load-more','active-accordion']
3548
3549	if(urlParams['hap-query-instance']){
3550		if(decodeURIComponent(urlParams['hap-query-instance']) == settings.instanceName)checkUrlParams();//on if share url is for this instance
3551	}else{
3552		checkUrlParams();//we have single player or down want to check instance name
3553	}
3554	
3555	function checkUrlParams(){
3556
3557		Object.keys(urlParams).forEach(function(key) {
3558			if(key.indexOf('hap-')==0){//url params start with 'hap-'
3559				var k = key.substr(4),//remove hap-
3560				camelCased = k.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
3561				var value = decodeURIComponent(urlParams[key]).replace(/\+/g, ' ');//+ to space
3562				if(value){
3563					if(settings.hasOwnProperty(camelCased)){//update settings 
3564						if(value === "true")value = true;
3565						else if(value === "false")value = false;
3566						settings[camelCased] = value;//only accept if property already exist
3567					}else{//check for playlist
3568						value = value.split(",").map(function(item){
3569						    return item.trim();
3570						});
3571						//check if we have valid key!
3572						//if(supportedPlaylistKeyArr.indexOf(k) > -1){
3573							queryPlaylistArr.push({key:camelCased, value: value})
3574					//	}
3575					}
3576				}
3577			}
3578		});
3579
3580		//playlist from url params
3581		if(queryPlaylistArr.length){
3582
3583			var i, j, len = queryPlaylistArr.length, len2 = queryPlaylistArr[0].value.length, item, obj, value;
3584			for(j = 0;j<len2;j++){
3585				obj = {};
3586				for(i = 0;i<len;i++){
3587					item = queryPlaylistArr[i];
3588					value = item.value[j];
3589					if(value === "true")value = true;
3590					else if(value === "false")value = false;
3591					obj[item.key] = value;
3592					if(item.key === 'type')obj['origtype'] = value;//for stats
3593				}
3594				queryPlaylist.push(obj);
3595			}
3596		}
3597
3598	}
3599
3600	urlParams = null;
3601
3602
3603	//############################################//
3604	/* elements */
3605	//############################################//
3606
3607	
3608
3609
3610	
3611
3612
3613
3614
3615	var wrapper = delem;
3616	wrapper.style.display = 'block';
3617
3618	var playerOuter = wrapper.querySelector('.hap-player-outer');
3619	if(playerOuter && settings.hidePlayerUntilMusicStart)playerOuter.classList.add('hap-music-player-force-hidden');
3620
3621
3622	var class_list = '';
3623	if(wrapper.getAttribute('class') != undefined){
3624		class_list = wrapper.getAttribute('class').split(/(\s+)/);
3625	}
3626
3627	if(settings.customClass){
3628		if(settings.customClass.indexOf('|')>-1){
3629			var cc = settings.customClass.split(','), i, len = cc.length, clas;
3630			for(i=0;i<len;i++){
3631				clas = cc[i].split('|');
3632				wrapper.querySelector(clas[0]).classList.add(clas[1]);
3633			}
3634		}else{
3635			wrapper.classList.add(settings.customClass);
3636		}
3637	}
3638	
3639
3640	if(settings.markup)	wrapper.insertAdjacentHTML("beforeend",settings.markup);
3641
3642
3643
3644
3645
3646
3647
3648
3649	var playlistList = settings.playlistList ? document.querySelector(settings.playlistList) : null,
3650	playlistHolder = wrapper.querySelector('.hap-playlist-holder'),
3651	playlistInner = wrapper.querySelector('.hap-playlist-inner'),
3652	playlistInnerWrap = wrapper.querySelector('.hap-playlist-inner-wrap'),
3653	playlistContent = wrapper.querySelector('.hap-playlist-content'),
3654	playlistFilterMsg = wrapper.querySelector('.hap-playlist-filter-msg'),
3655	sortAlpha = wrapper.querySelector('.hap-sort-alpha'),
3656	popupToggle = wrapper.querySelector('.hap-popup-toggle'),
3657	progressBg = wrapper.querySelector('.hap-progress-bg'),
3658	loadLevel = wrapper.querySelector('.hap-load-level'),
3659	progressLevel = wrapper.querySelector('.hap-progress-level'),
3660
3661	seekbarWave = wrapper.querySelector('.hap-seekbar-wave'),
3662	seekbarWaveProgress = wrapper.querySelector('.hap-seekbar-wave-progress'),
3663
3664	volumeToggle = wrapper.querySelector('.hap-volume-toggle'),
3665	volumeSeekbar = wrapper.querySelector('.hap-volume-seekbar'),
3666	volumeBg = wrapper.querySelector('.hap-volume-bg'),
3667	volumeLevel = wrapper.querySelector('.hap-volume-level'),
3668
3669	lyricsHolder = wrapper.querySelector('.hap-lyrics-holder'),
3670	lyricsToggle = wrapper.querySelector('.hap-lyrics-toggle'),
3671
3672	videoHolder = wrapper.querySelector('.hap-video-holder'),
3673	videoWrap = wrapper.querySelector('.hap-video-wrap'),
3674	videoToggle = wrapper.querySelector('.hap-video-toggle'),
3675
3676	playerHolder = wrapper.querySelector('.hap-player-holder'),
3677	playerThumb = wrapper.querySelector('.hap-player-thumb'),
3678	shareHolder = wrapper.querySelector('.hap-share-holder'),
3679	playbackRateHolder = wrapper.querySelector('.hap-playback-rate-holder'),
3680	playbackRateToggle = wrapper.querySelector('.hap-playback-rate-toggle'),
3681	playbackRateToggleNum = wrapper.querySelector('.hap-playback-rate-toggle-num'),
3682	playerTitle = wrapper.querySelector('.hap-player-title'),
3683	playerArtist = wrapper.querySelector('.hap-player-artist'),
3684
3685	playlistTitle = wrapper.querySelector('.hap-playlist-main-title'),
3686	playlistDescription = wrapper.querySelector('.hap-playlist-main-description'),                 
3687
3688	mediaTimeCurrent = wrapper.querySelector('.hap-media-time-current'),
3689	mediaTimeTotal = wrapper.querySelector('.hap-media-time-total'),
3690	mediaTimeAd = wrapper.querySelector('.hap-media-time-ad'),
3691	adSkipBtn = wrapper.querySelector('.hap-ad-skip'),
3692	playbackToggle = wrapper.querySelector('.hap-playback-toggle'),
3693	loopToggle = wrapper.querySelector('.hap-loop-toggle'),
3694	randomToggle = wrapper.querySelector('.hap-random-toggle'),
3695	rangeToggle = wrapper.querySelector('.hap-range-toggle'),
3696	rangeHolder = wrapper.querySelector('.hap-range-holder'),
3697	shareToggle = wrapper.querySelector('.hap-share-toggle'),
3698	preloader = wrapper.querySelector('.hap-preloader'),
3699
3700	embedToggle = wrapper.querySelector('.hap-embed-toggle'),
3701	embedHolder = wrapper.querySelector('.hap-embed-holder'),
3702
3703	pwdHolder = wrapper.querySelector('.hap-pwd-holder'),
3704	pwdField = wrapper.querySelector('.hap-pwd-field'),
3705	pwdErrorMsg = wrapper.querySelector('.hap-pwd-error') ? wrapper.querySelector('.hap-pwd-error').innerHTML : '',
3706
3707	playbackToggleIndicator = wrapper.querySelector('.hap-playback-toggle-indicator'),
3708
3709	//fixed
3710	externalPlaybackBtn = wrapper.querySelector('.hap-playback-toggle-ex'),
3711	externalPlayerToggleBtn = wrapper.querySelector('.hap-player-toggle-ex'),
3712
3713	waveformLoader = wrapper.querySelector('.hap-waveform-loader'),
3714
3715	//restrict download
3716	redirectLoginHolderDownload = wrapper.querySelector('.hap-redirect-login-holder-download'),
3717	redirectLoginHolderWatch = wrapper.querySelector('.hap-redirect-login-holder-watch'),
3718	redirectLoginHolder,
3719
3720	globalDownloadBtn
3721
3722
3723	
3724
3725	//############################################//
3726	/* vars */
3727	//############################################//
3728	
3729	var isMobile = HAPUtils.isMobile(),
3730	isIOS = HAPUtils.isIOS(),
3731	hasLocalStorage = HAPUtils.hasLocalStorage(),
3732
3733	autoPlay = settings.autoPlay,
3734	initialAutoPlay = autoPlay,
3735    playlistItemContent = settings.playlistItemContent.replace(/\s+/g, '').split(','),
3736    playlistTitleOrder = settings.playlistTitleOrder.replace(/\s+/g, '').split(','),
3737    statisticsContent = settings.statisticsContent ? settings.statisticsContent.replace(/\s+/g, '').split(',') : '',
3738    useContinousPlayback = settings.useContinousPlayback && hasLocalStorage,
3739    continousPlaybackTrackAllSongs = useContinousPlayback && settings.continousPlaybackTrackAllSongs,
3740    useMediaSession = settings.useMediaSession && 'mediaSession' in navigator && window.location.protocol != 'file:',
3741 	useGa = settings.useGa && settings.gaTrackingId,
3742	sortOrder = settings.sortOrder
3743
3744    if(settings.playlistContent)playlistContent = document.querySelector(settings.playlistContent)//custom playlist element
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754	var componentInited,
3755	playlistTransitionOn,
3756	_body = document.body,
3757	_window = window,
3758	_doc = document,
3759	windowResizeTimeoutID,
3760	windowResizeTimeout = 150,
3761	hasDownloadSupport = HAPUtils.hasDownloadSupport(),
3762	isAndroid = HAPUtils.isAndroid(),
3763	mp3Support = HAPUtils.canPlayMp3(),
3764	wavSupport = HAPUtils.canPlayWav(),
3765	aacSupport = HAPUtils.canPlayAac(),
3766	flacSupport = HAPUtils.canPlayFlac(),
3767	isChrome = HAPUtils.isChrome(),
3768	isSafari = HAPUtils.isSafari(),
3769	proxy = settings.sourcePath + 'includes/ba-simple-proxy.php', 
3770	bsf_match = 'ebsfm:',
3771	loopStateArr = ['playlist','single','off'],
3772	loopStateCounter = loopStateArr.indexOf(settings.loopState),
3773	nonProcessMedia = ['audio', 'hls', 's3_audio'],
3774	allowedCrossFadeMedia = ['audio', 's3_audio'],
3775	singleMediaSourcesArr = ['audio', 'youtube_single', 'hls'],
3776	mediaPrefixUrl,
3777
3778	//pagnation
3779    paginationInited, 
3780	lastActivePaginationBtn,
3781	paginationWrap,//pagination buttons go here
3782	paginationTotalPages,
3783	paginationCurrentPage,
3784	paginationArr = [],//save playlist items per page
3785
3786
3787	playlistScroll,
3788
3789	burl_request,
3790	bUrl,
3791	bUrlIntervalID,
3792	useBlob = settings.useBlob && window.location.protocol != "file:",
3793	
3794	//audio
3795	id3Counter,
3796	id3Start,
3797	audio,
3798	//audio = audio,
3799	activeAudio,
3800	audioReady,
3801	audioInited,
3802	startEventDone,//lot of delay on ios
3803
3804	//crossfade
3805	audio2,
3806	audioUp2Js2,
3807	crossFadeOn,
3808	fadeOutAudioComplete,
3809	isCancelCrossFade,
3810	comingFromCrossFade,
3811	nextDataCheck,
3812
3813	//add more on total scroll
3814	addMoreOnTotalScroll,
3815	addMoreLimit,
3816	addMoreNumResults,
3817	addMoreOffset,
3818	addMoreSortOrder,
3819	addMoreSortDirection,//asc, desc
3820
3821	waveformCrossfadeTime = 300,
3822
3823	//video
3824	videoSync,
3825	videoOpened,
3826	syncComingFromVideo,//video sync audio (if video has controls)
3827
3828	//playback position
3829	lastPositionDataPath,
3830
3831	//load more
3832	nextPageToken,
3833	loadMoreProcess,
3834	loadMoreType,
3835 	loadMoreOnTotalScroll,
3836 	useId3,
3837 	useID3Cors,
3838 	allResults = 999999999,
3839
3840	//soundcloud
3841	soundCloudInited,
3842	resultLimit,
3843	soundCloudResultArr = [],
3844
3845	//hls
3846	hls,
3847	hlsSupport,
3848	hlsInited,
3849
3850	//ads
3851	_HAPAdManager,
3852	iosAdMidFixStart, 
3853	showAds = true,
3854	viewSongWithoutAdsUserRoles,
3855	currentUserRoles,
3856
3857	//user roles
3858	downloadVideoUserRoles,
3859	allowedViewVideoUserRole,
3860	allowedDownloadVideoUserRole = true,
3861	mediaForcePause,
3862
3863	redirectLoginOpened,
3864
3865	//ima
3866	_IMALoader,
3867	imaLoaded,
3868	imaStarted,
3869	isIma,
3870	isImaOn,
3871
3872	adContent,
3873
3874	//dialog
3875	_dialog,
3876
3877	_lyrics,
3878	lyricsReady,
3879	lyricsOpened,
3880
3881	s3UrlReady,
3882	scUrlReady,
3883
3884	//
3885	_scLoader,
3886
3887	pwdAction,
3888	pwdDownloadData,
3889
3890	//share
3891	_HAPShareManager,
3892
3893	//youtube
3894	youtubeIframe,
3895	_HAPYoutubeLoader,
3896	youtubeHolder,
3897	youtubePlayer,
3898	youtubeInited,
3899	youtubeReady,
3900	youtubeStarted,
3901	youtubeEndFix,//end event fires multiple with end seconds?
3902	youtubeBlocker,
3903	youtubePlayed,//add autoplay after this 
3904
3905	playlistScrollInit,
3906	playlistClick,//scroll to active playlist item
3907
3908	//global playlist otions
3909	playerThumbInited,
3910	globalThumb,
3911	globalDownload,
3912	globalStartTime,
3913	globalStartTimeMediaId,
3914	globalEndTime,
3915	globalAdPre,
3916	globalAdMid,
3917	globalAdMidInterval,
3918	globalAdEnd,
3919	globalPwd,
3920	globalVast,
3921	globalLockTime,
3922	globalLockVideoUserRoles,
3923	
3924	soundCloud_id,
3925
3926	//addTrack helpers
3927	endInsert,//insert at playlist end 
3928	insertPosition, 
3929	addTrackProcess,
3930	addTrackPlayit,
3931	
3932	playlistProcessCounter = -1,
3933	playlistDataReady,
3934	playlistDataArr = [],
3935	lastPlaylistDataArr = [],//for load more
3936	playlistProcessData = [],
3937	playlistProcessDataUrl = [],
3938	playlistData,
3939	playlistLength=0,
3940	activeMediaId = null,//active item from media id
3941	playlistInitLoad,
3942	lastPlaylist,
3943	playlistId,
3944	playerId,
3945	activePlaylistItem,
3946	mediaStarted,
3947	mediaType,
3948	mediaTitle,
3949	mediaPath,
3950	nextMediaData,
3951	mediaCounter,
3952	mediaPlaying = false,
3953	isEnqueue,
3954	countryData,
3955	currMediaData,
3956	dataInterval = settings.dataInterval,
3957	dataIntervalID, 
3958	seekBarDown, 
3959	seekBarSize,
3960	events = HAPUtils.getEvents(), 
3961	comingFromPwd,
3962
3963	xhrRequest,
3964	xhrRequest_pag,
3965
3966	_HAPPlaybackRateSlider,
3967	_HAPRangeSlider,
3968	_volumeSlider,
3969	lastVolume = 0.5,
3970
3971	//scripts
3972	hls_js = "https://cdn.jsdelivr.net/npm/hls.js@latest",
3973	youtube_js = "https://www.youtube.com/iframe_api",
3974
3975	//variation
3976	activeVariation,
3977	hasVariation,
3978
3979	//ws
3980	waveformLoading,
3981	waveformLoadingInterval,
3982	waveformDataAws,
3983	waveformDataAws2,
3984
3985	peakWriteRequired,
3986	xhrRequest_remoteDownload,
3987	xhrRequest_loadPeaks,
3988	xhrRequest_writePeaks,
3989
3990	//accordion
3991	lastOpenedAccordion,
3992    accordionLoading,
3993	accordionMode,
3994	accordionHolder,//playlistContent for accordion 
3995	accordionInited,
3996	accordionActiveId,//active accordion item
3997	accordioPlaylistId,//currently playing accordion playlist
3998	playlistDataAccordion = [],//save playlist data for each accordion item
3999	accordionUseId3,//use id3 tags in accordion
4000
4001	//fixed
4002	_HAPStickyPlayer,
4003	seekingFixed,
4004
4005	//inline seekbar
4006	inlineSeekbar,
4007
4008	//wave seekbar
4009	seekbarWaveCanvasBg,
4010	seekbarWaveCanvasProgress,
4011	seekbarWaveCtx,
4012	seekbarWaveCtx2,
4013	peakData,
4014
4015	waveOptions = settings.waveOptions
4016
4017	if(settings.waveBgColor != undefined)waveOptions.waveColor = settings.waveBgColor;
4018	if(settings.waveProgressColor != undefined)waveOptions.progressColor = settings.waveProgressColor;
4019	if(settings.waveBarWidth != undefined)waveOptions.barWidth = Number(settings.waveBarWidth);
4020	if(settings.waveBarRadius != undefined)waveOptions.barRadius = Number(settings.waveBarRadius);
4021	if(settings.waveBarGap != undefined)waveOptions.barGap = Number(settings.waveBarGap);
4022
4023
4024	//only click, not mouseenter, leave
4025	const onHandler = (element, type, selector, handler) => {
4026		element.addEventListener(type, (event) => {
4027			if (event.target.closest(selector)) {
4028			    handler(event);
4029		    }
4030	    });
4031	};
4032
4033
4034
4035
4036	//############################################//
4037	/* setup */
4038	//############################################//
4039
4040
4041
4042
4043
4044	
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057	audio = document.createElement("audio")
4058	audio.setAttribute('preload',settings.preload)
4059	activeAudio = audio;
4060
4061
4062
4063
4064	if(playerThumb)playerThumb.setAttribute('role', 'img')
4065
4066
4067	if(playlistContent && settings.playlistContentClass){
4068		playlistContent.classList.add(settings.playlistContentClass)
4069	}
4070	
4071
4072
4073
4074	if(wrapper.classList.contains('hap-wrapper-popup'))settings.isPopup = true;
4075	else settings.isPopup = false;
4076
4077
4078	//if(class_list.indexOf('hap-fixed') > -1 || class_list.indexOf('hap-fixed2') > -1)settings.useStickyPlayer = false;
4079
4080	if(typeof HAPStickyPlayer !== 'function'){
4081		settings.useStickyPlayer = false;
4082		//console.log('missing sticky.js script tag, sticky player cannot be used!')
4083	}
4084
4085
4086	if(settings.useStickyPlayer && playbackToggleIndicator)playlistContent.prepend(playbackToggleIndicator)//so its available in fixed player
4087
4088
4089
4090
4091	if(seekbarWave)settings.useWaveSeekbar = true
4092
4093
4094	if(settings.viewSongWithoutAdsForLoggedInUser && settings.isUserLoggedIn){
4095		showAds = false;
4096	}else{
4097		showAds = true;
4098	}
4099
4100	if(settings.currentUserRoles)currentUserRoles = settings.currentUserRoles.split(',');
4101
4102	if(settings.downloadVideoUserRoles)downloadVideoUserRoles = settings.downloadVideoUserRoles.split(',');
4103
4104	if(currentUserRoles && downloadVideoUserRoles){
4105		allowedDownloadVideoUserRole = HAPUtils.arrayContainsAnotherArray(downloadVideoUserRoles, currentUserRoles);
4106	}
4107
4108
4109	if(settings.viewSongWithoutAdsUserRoles)viewSongWithoutAdsUserRoles = settings.viewSongWithoutAdsUserRoles.replace(/\s+/g, '').split(',')
4110
4111	if(currentUserRoles && viewSongWithoutAdsUserRoles){
4112		showAds = !HAPUtils.arrayContainsAnotherArray(viewSongWithoutAdsUserRoles, currentUserRoles);
4113	}
4114	//console.log('showAds',showAds)
4115
4116
4117
4118	//hide embed functionality if already embeded
4119	if(location.href.indexOf('apmap/includes/embed.php?') > -1 && settings.hideEmbedFunctionWhenEmbeded){
4120		if(embedToggle)embedToggle.remove()
4121		if(embedHolder)embedHolder.remove()
4122		settings.isEmbed = true;
4123	}
4124
4125
4126
4127
4128
4129	//ads
4130	if(settings.adContent && !settings.isPopup){
4131		//todo - if ad content in popup doesnt work, in destroyinstance2 remove it from copied html
4132
4133		adContent = document.createElement('div');
4134		adContent.classList.add('hap-ad-content-wrap')
4135		adContent.innerHTML = settings.adContent;
4136
4137		if(settings.adContentPositionCustom){
4138			var cp = wrapper.querySelector(settings.adContentPositionCustom)
4139			if(cp)cp.append(adContent);
4140		}
4141		else{
4142
4143			if(settings.adContentPosition == 'top'){
4144				wrapper.prepend(adContent)
4145			}
4146			else if(settings.adContentPosition == 'bottom'){
4147				wrapper.append(adContent);
4148			}
4149
4150		}
4151    }
4152
4153
4154
4155
4156
4157
4158	if(settings.isEmbed && settings.useSingleAudioEmbed){
4159		wrapper.querySelector('hap-playlist-content').remove();
4160		settings.usePlaylistScroll = false;
4161	}
4162
4163
4164
4165	
4166	if(settings.useInlineSeekbar){
4167		inlineSeekbar = HAPUtils.htmlToElement('<div class="hap-inline-seekbar-bg">'+
4168            '<div class="hap-inline-seekbar-progress"></div>'+
4169        '</div>')
4170	}
4171
4172
4173
4174
4175	//soundcloud
4176	if(settings.sck || settings.soundCloudAppId){
4177		var k = settings.sck || settings.soundCloudAppId
4178		settings.scak = k.split(",").map(function(item) {
4179		    return item.trim();
4180		});
4181	}
4182
4183
4184	//fixed
4185	if(settings.useStickyPlayer){
4186
4187		window['hap_fixed_active_player'] = settings.instanceName
4188
4189		if(!window.hap_fixedPlayer && !settings.hap_fixedPlayer){
4190			_HAPStickyPlayer = new HAPStickyPlayer({parent: self, settings: settings, wrapper: wrapper});
4191			window.hap_fixedPlayer = _HAPStickyPlayer
4192			settings.hap_fixedPlayer = _HAPStickyPlayer
4193		}else if(window.hap_fixedPlayer){
4194			_HAPStickyPlayer = window.hap_fixedPlayer
4195		}else if(settings.hap_fixedPlayer){//for popup
4196			_HAPStickyPlayer = settings.hap_fixedPlayer
4197		}
4198
4199		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.SET_PROGRESS_START', function(data){	
4200			if(data.inst == settings.instanceName){//only for the player currently active
4201				seekBarDown = true;
4202				seekingFixed = true;
4203			}
4204		})
4205
4206		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.SET_PROGRESS', function(data){	
4207			if(data.inst == settings.instanceName){//only for the player currently active
4208				setProgress(data.point, data.update, true)
4209				if(data.update){
4210					seekBarDown = false;
4211					seekingFixed = false;
4212				}
4213			}
4214		})
4215
4216		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.VOLUME_CHANGE', function(data){	
4217			if(data.inst == settings.instanceName){
4218				settings.volume = data.volume;
4219				self.setVolume(data.volume)
4220
4221				//if(_volumeSlider)_volumeSlider.setVisual(data.volume)
4222			}
4223		})	
4224
4225		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.TOGGLE_MUTE', function(data){	
4226			if(data.inst == settings.instanceName){
4227				self.toggleMute();
4228			}
4229		})	
4230
4231		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.TOGGLE_RANDOM', function(data){	
4232			if(data.inst == settings.instanceName){
4233				self.toggleRandom();
4234			}
4235		})	
4236
4237		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.TOGGLE_LOOP', function(data){	
4238			if(data.inst == settings.instanceName){
4239
4240				loopStateCounter++;
4241				if(loopStateCounter > loopStateArr.length - 1)loopStateCounter = 0;
4242				settings.loopState = loopStateArr[loopStateCounter];
4243
4244				self.setLoop(settings.loopState);
4245			}
4246		})	
4247
4248		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.PREVIOUS_MEDIA', function(data){	
4249			if(data.inst == settings.instanceName){
4250				self.previousMedia()
4251			}
4252		})	
4253
4254		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.SKIP_BACKWARD', function(data){	
4255			if(data.inst == settings.instanceName){
4256				self.seekBackward(settings.seekTime);
4257			}
4258		})	
4259
4260		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.SKIP_FORWARD', function(data){	
4261			if(data.inst == settings.instanceName){
4262				self.seekForward(settings.seekTime);
4263			}
4264		})	
4265
4266		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.SET_PLAYBACK_RATE', function(data){	
4267			if(data.inst == settings.instanceName){
4268				if(_HAPPlaybackRateSlider)_HAPPlaybackRateSlider.setValue(data.value);
4269				else self.setPlaybackRate(data.value);
4270			}
4271		})	
4272
4273		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.NEXT_MEDIA', function(data){	
4274			if(data.inst == settings.instanceName){
4275				self.nextMedia()
4276			}
4277		})	
4278
4279		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.TOGGLE_PLAYBACK', function(data){	
4280			if(data.inst == settings.instanceName){
4281				self.togglePlayback()
4282			}
4283		})	
4284
4285		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.LYRICS_TOGGLE', function(data){	
4286			if(data.inst == settings.instanceName){
4287				self.toggleLyrics()
4288			}
4289		})	
4290
4291		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.SHARE_TOGGLE', function(data){	
4292			if(data.inst == settings.instanceName){
4293				if(_HAPShareManager)_HAPShareManager.share(data.type.toLowerCase(), currMediaData, self.getCurrentMediaUrl());
4294			}
4295		})	
4296
4297		_HAPStickyPlayer.addEventListener('HAPStickyPlayer.TOGGLE_DOWNLOAD', function(data){	
4298			if(data.inst == settings.instanceName){
4299				handleDownloadFromSticky()
4300			}
4301		})	
4302
4303	}
4304
4305	
4306
4307
4308	
4309
4310
4311
4312
4313	if(playlistItemContent.indexOf('description') == -1)settings.searchDescriptionInPlaylist = false;
4314
4315
4316	if(typeof window.ap_mediaArr === 'undefined')window.ap_mediaArr = [];
4317	window.ap_mediaArr.push({inst: self, id: settings.instanceName});//save instance so we can pause playback in one instance if other is started
4318
4319	if(isMobile){
4320		autoPlay = false;
4321	}
4322	if(settings.autoPlayAfterFirst){
4323		autoPlay = false;
4324		initialAutoPlay = true;
4325	}
4326
4327
4328
4329
4330	
4331
4332
4333	//volume
4334	if(settings.volume < 0)settings.volume = 0;
4335	else if(settings.volume > 1)settings.volume = 1;
4336	if(settings.volume != 0)lastVolume = settings.volume;
4337
4338
4339
4340
4341
4342	//loop 
4343	if(loopStateCounter == -1)settings.loopState = 'off';
4344	loopStateCounter = loopStateArr.indexOf(settings.loopState);
4345
4346	if(loopToggle)loopToggle.querySelector('.hap-btn-loop-'+settings.loopState).style.display = 'block';
4347	//shuffle
4348	if(_HAPStickyPlayer)_HAPStickyPlayer.setLoop(settings.loopState)
4349
4350	if(randomToggle){
4351		if(settings.randomPlay)randomToggle.querySelector('.hap-btn-random-on').style.display = 'block';
4352		else randomToggle.querySelector('.hap-btn-random-off').style.display = 'block';
4353	}
4354	if(_HAPStickyPlayer)_HAPStickyPlayer.setRandom(settings.randomPlay)
4355	
4356
4357
4358
4359	if(isIOS)settings.useCrossFade = false;
4360	else if(settings.loopState == 'single')settings.useCrossFade = false;
4361	if(settings.stopOnSongEnd)settings.useCrossFade = false;
4362
4363	if(settings.useCrossFade){
4364		audio.c_active = true;
4365
4366		audio2 = document.createElement("audio")
4367		audio2.setAttribute('preload','auto')
4368		audioUp2Js2 = audio2
4369	}
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379    //load playlist from select
4380    /*if(settings.playlistSelector){
4381    	document.querySelector(settings.playlistSelector).addEventListener('change',function(){
4382	        self.loadPlaylist(document.querySelector(this).value);
4383    	});
4384    }*/
4385
4386    //breakpoints
4387    if(settings.breakPointArr){
4388    	if(typeof settings.breakPointArr === 'string'){
4389    		settings.breakPointArr = settings.breakPointArr.split(",");
4390			HAPUtils.sortNumericArray(settings.breakPointArr);//sort from low to high
4391    	}
4392	}
4393
4394
4395    //hidden playlist from dom 
4396    if(!playlistContent){
4397    	//playlistContent = document.querySelector('<div style="display:none;"></div>');
4398    	settings.usePlaylist = false;
4399    	settings.usePlaylistScroll = false;
4400    	settings.useNumbersInPlaylist = false;
4401    }
4402
4403
4404
4405    //set icon visibility on start
4406	if(sortAlpha)sortAlpha.querySelector('.hap-btn-sort-alpha-down').style.display = 'block';
4407
4408
4409
4410	this.toggleBigPlay = function(on){
4411		if(on){
4412			playbackToggle.querySelector('.hap-btn-play').style.display = 'none';
4413			playbackToggle.querySelector('.hap-btn-pause').style.display = 'block';
4414		}else{
4415			playbackToggle.querySelector('.hap-btn-play').style.display = 'block';
4416			playbackToggle.querySelector('.hap-btn-pause').style.display = 'none';
4417		}
4418	}
4419
4420	
4421
4422
4423	if(playbackToggle)self.toggleBigPlay()
4424    
4425	
4426	if(settings.sortableTracks && !settings.sortableTracksSet)setSortableTracks();
4427
4428
4429
4430	
4431
4432
4433
4434	if(settings.scrollToPlayer && !settings.isPopup){
4435		delete settings.scrollToPlayer;
4436		var rect = wrapper.getBoundingClientRect()
4437		var top = rect.top;
4438		document.querySelector('html,body').scrollTo({
4439		   top: top,
4440		   behavior: 'smooth'
4441		});
4442
4443	}
4444
4445
4446	//############################################//
4447	/* load more */
4448	//############################################//
4449
4450    var loadMoreBtn = wrapper.querySelector('.hap-load-more-btn')
4451
4452    if(loadMoreBtn){    
4453    	loadMoreBtn.addEventListener('click',function(){
4454
4455	    	if(!componentInited) return false;
4456	    	if(loadMoreProcess) return false;
4457	    	if(loadMoreOnTotalScroll)self.loadMore();
4458	    	else if(addMoreOnTotalScroll){
4459				addMore();
4460	    	}
4461	    	if(loadMoreBtn)loadMoreBtn.style.opacity = 0;
4462	    });
4463	}
4464
4465
4466	//############################################//
4467	/* dialog */
4468	//############################################//
4469
4470	var elements = []
4471
4472	if(lyricsHolder){
4473		elements.push({
4474			element:'lyrics',
4475			itemHandle:lyricsHolder.querySelector('.hap-dialog-header-drag'), 
4476			itemResizeHandle:lyricsHolder.querySelector('.hap-dialog-resizable'), 
4477			itemResizeMinW: parseInt(lyricsHolder.style.minWidth,10),
4478			itemResizeMinH: parseInt(lyricsHolder.style.minHeight,10),
4479			itemDialog:lyricsHolder
4480		})
4481	}
4482
4483	if(videoHolder){
4484		elements.push({
4485			element:'video',
4486			itemHandle:videoHolder.querySelector('.hap-dialog-header-drag'), 
4487			itemResizeHandle:videoHolder.querySelector('.hap-dialog-resizable'), 
4488			itemResizeMinW: parseInt(videoHolder.style.minWidth,10),
4489			itemResizeMinH: parseInt(videoHolder.style.minHeight,10),
4490			itemDialog:videoHolder
4491		})
4492	}
4493
4494	if(elements.length){
4495
4496		document.addEventListener("HAPDialog.LYRICS_AUTOSCROLL_CHANGE", function(e) {
4497			settings.lyricsAutoScroll = e.detail;
4498			if(_lyrics)_lyrics.setAutoScroll(settings.lyricsAutoScroll)
4499		})
4500
4501		_dialog = new HAPDialog(wrapper, elements, settings)
4502
4503	}
4504
4505	//############################################//
4506	/* lyrics */
4507	//############################################//
4508
4509	this.toggleLyrics = function(){
4510
4511		if(lyricsOpened){
4512
4513			lyricsHolder.addEventListener('transitionend', function cb(e){
4514				e.currentTarget.removeEventListener(e.type, cb);
4515				
4516				lyricsHolder.style.display = 'none';
4517			
4518			})
4519	        lyricsHolder.classList.remove('hap-lyrics-holder-shown')
4520
4521	        lyricsOpened = false;
4522
4523		}else{
4524
4525			lyricsHolder.style.display = 'block'
4526
4527			setTimeout(function(){
4528				lyricsHolder.classList.add('hap-lyrics-holder-shown')
4529			},50)	
4530
4531			lyricsOpened = true;
4532
4533		}
4534
4535	}
4536
4537
4538
4539	//############################################//
4540	/* video */
4541	//############################################//
4542
4543	this.toggleVideo = function(){
4544
4545		if(videoOpened){
4546
4547			if(videoSync){
4548				videoSync.pause()
4549			}
4550
4551			videoHolder.style.display = 'none';
4552			videoOpened = false;
4553
4554		}else{
4555
4556			videoHolder.style.display = 'block';
4557			videoOpened = true;
4558
4559			if(videoSync){
4560				if(mediaPlaying){
4561					var promise = videoSync.play();
4562					if (promise !== undefined) {
4563						promise.then(function(){
4564							var t = self.getCurrentTime()
4565							if(t)videoSync.currentTime = t
4566					    }).catch(function(error){
4567					    });
4568					}
4569				}
4570			}
4571
4572		}
4573
4574	}
4575
4576
4577
4578
4579
4580	//############################################//
4581	/* filter tracks */
4582	//############################################//
4583
4584	var searchSelector = settings.searchSelector ? document.querySelector(settings.searchSelector) : wrapper.querySelector('.hap-search-filter');
4585	var isSearchActive//note: not made with accordion
4586
4587	if(searchSelector){
4588
4589		searchSelector.addEventListener('keyup',function(e){
4590			if(playlistLength == 0)return false;
4591
4592			var value = e.target.value.toLowerCase(), i, j = 0, title, pi, searchHolder, search_len;
4593
4594			if(accordionMode){
4595				if(settings.allowOnlyOneOpenedAccordion){
4596
4597					//same as without accordion
4598
4599					if(value){
4600						
4601						for(i = 0; i< playlistLength; i++){
4602
4603							pi = lastOpenedAccordion.querySelectorAll('.hap-playlist-item')[i];
4604
4605							title = '';
4606
4607							if(pi.querySelector('.hap-playlist-title'))title += pi.querySelector('.hap-playlist-title').innerHTML.toLowerCase();
4608							if(pi.querySelector('.hap-playlist-artist'))title += pi.querySelector('.hap-playlist-artist').innerHTML.toLowerCase();
4609
4610							if(settings.searchDescriptionInPlaylist && pi.querySelector('.hap-playlist-description')){
4611								title += pi.querySelector('.hap-playlist-description').innerHTML.toLowerCase();
4612							}
4613
4614							if(title.indexOf(value) >- 1){
4615								pi.classList.remove('hap-filter-hidden', 'hap-hidden')
4616							}else{
4617								pi.classList.add('hap-filter-hidden', 'hap-hidden');
4618								j++;
4619							}
4620
4621						}
4622
4623						isSearchActive = true;
4624
4625					}else{
4626
4627						lastOpenedAccordion.querySelectorAll('.hap-playlist-item').forEach(el => {
4628							el.classList.remove('hap-filter-hidden', 'hap-hidden')
4629						})
4630
4631						isSearchActive = false;
4632
4633					}
4634				
4635					if(playlistFilterMsg){
4636						if(j == playlistLength){
4637							if(playlistFilterMsg)playlistFilterMsg.style.display = 'block';
4638						}
4639						else{
4640							if(playlistFilterMsg)playlistFilterMsg.style.display = 'none';
4641						} 
4642					}
4643
4644				}else{
4645
4646					//search all opened accordions
4647
4648					searchHolder = playlistHolder.querySelector('.hap-accordion-item-opened');
4649
4650					if(value){
4651
4652						searchHolder.querySelectorAll('.hap-accordion-item-content').forEach(function(el){
4653							//so they collapse while searching
4654							if(value == '')el.classList.remove('hap-accordion-item-content-search')
4655							else el.classList.add('hap-accordion-item-content-search')
4656						})
4657
4658						search_len = searchHolder.querySelectorAll('.hap-playlist-item').length;
4659
4660						for(i = 0; i< search_len; i++){
4661
4662							pi = searchHolder.querySelectorAll('.hap-playlist-item')[i];
4663
4664							title = '';
4665
4666							if(pi.querySelector('.hap-playlist-title'))title += pi.querySelector('.hap-playlist-title').innerHTML.toLowerCase();
4667							if(pi.querySelector('.hap-playlist-artist'))title += pi.querySelector('.hap-playlist-artist').innerHTML.toLowerCase();
4668
4669							if(settings.searchDescriptionInPlaylist && pi.querySelector('.hap-playlist-description')){
4670								title += pi.querySelector('.hap-playlist-description').innerHTML.toLowerCase();
4671							}
4672
4673							if(title.indexOf(value) >- 1){
4674								pi.classList.remove('hap-filter-hidden', 'hap-hidden')
4675							}else{
4676								pi.classList.add('hap-filter-hidden', 'hap-hidden');
4677								j++;
4678							}
4679
4680						}
4681
4682						isSearchActive = true;
4683
4684					}else{
4685
4686						searchHolder.querySelectorAll('.hap-playlist-item').forEach(el => {
4687							el.classList.remove('hap-filter-hidden', 'hap-hidden')
4688						})
4689
4690						isSearchActive = false;
4691
4692					}	
4693
4694				}
4695
4696			}else{
4697
4698				if(value){
4699
4700					if(settings.usePlaylistScroll){//if we scroll to bottom, then search, results would the shown on the top, so we have to reset scroll
4701						if(settings.playlistScrollType == 'perfect-scrollbar'){
4702							if(settings.playlistScrollOrientation == 'horizontal'){
4703								playlistInner.scrollLeft = 0;
4704							}else{
4705								playlistInner.scrollTop = 0;
4706							}
4707						}
4708					}
4709
4710
4711					for(i = 0; i< playlistLength; i++){
4712
4713						pi = playlistContent.querySelectorAll('.hap-playlist-item')[i];
4714
4715						title = '';
4716
4717						if(pi.querySelector('.hap-playlist-title'))title += pi.querySelector('.hap-playlist-title').innerHTML.toLowerCase();
4718						if(pi.querySelector('.hap-playlist-artist'))title += pi.querySelector('.hap-playlist-artist').innerHTML.toLowerCase();
4719
4720						if(settings.searchDescriptionInPlaylist && pi.querySelector('.hap-playlist-description')){
4721							title += pi.querySelector('.hap-playlist-description').innerHTML.toLowerCase();
4722						}
4723
4724						if(title.indexOf(value) >- 1){
4725							pi.classList.remove('hap-filter-hidden', 'hap-hidden')
4726						}else{
4727							pi.classList.add('hap-filter-hidden', 'hap-hidden');
4728							j++;
4729						}
4730						
4731					}
4732				
4733					isSearchActive = true;
4734
4735				}else{
4736
4737					playlistContent.querySelectorAll('.hap-playlist-item').forEach(el => {
4738						el.classList.remove('hap-filter-hidden', 'hap-hidden')
4739					})
4740
4741					isSearchActive = false;
4742
4743				}	
4744
4745				if(j == playlistLength){
4746					if(playlistFilterMsg)playlistFilterMsg.style.display = 'block';
4747					if(loadMoreBtn)loadMoreBtn.classList.add('hap-force-hidden')
4748				}else{
4749					if(playlistFilterMsg)playlistFilterMsg.style.display = 'none';
4750					if(loadMoreBtn)loadMoreBtn.classList.remove('hap-force-hidden')
4751				} 
4752
4753			}
4754
4755		});
4756
4757	}
4758
4759
4760	//############################################//
4761	/* share */
4762	//############################################//
4763
4764	if(settings.useShare){
4765		
4766		if(typeof HAPShareManager === 'undefined'){
4767			var script = document.createElement('script');
4768			script.type = 'text/javascript';
4769			script.src = HAPUtils.qualifyURL(settings.sourcePath+settings.sharemanager_js);
4770			script.onload = script.onreadystatechange = function() {
4771			    if(!this.readyState || this.readyState == 'complete'){
4772			    	_HAPShareManager = new HAPShareManager(settings);
4773			    }
4774			};
4775			script.onerror = function(){
4776				alert("Error loading " + this.src);
4777			}
4778			var tag = document.getElementsByTagName('script')[0];
4779			tag.parentNode.insertBefore(script, tag);
4780		}else{
4781			_HAPShareManager = new HAPShareManager(settings);
4782		}
4783
4784	}
4785
4786
4787
4788	//############################################//
4789	/* playlist selector */
4790	//############################################//
4791
4792	var playlistSelectorArr = [],
4793	playlistSelectorContainer,
4794	playlistSelectorInner,
4795	playlistSelectorActiveItem,
4796	playlistSelectorSearch,
4797	playlistSelectorLen = 0,
4798	playlistSelectorFilterMsg,
4799	playlistSelectorOpened
4800
4801	if(!wrapper.querySelector('.hap-playlist-selector-container'))settings.usePlaylistSelector = false;
4802
4803	if(settings.usePlaylistSelector){
4804
4805		playlistSelectorContainer = wrapper.querySelector('.hap-playlist-selector-container')
4806		playlistSelectorInner = playlistSelectorContainer.querySelector('.hap-playlist-selector-inner')
4807
4808		playlistSelectorFilterMsg = playlistSelectorContainer.querySelector('.hap-playlist-selector-filter-msg')
4809
4810		playlistSelectorContainer.querySelectorAll('.hap-playlist-selector-item').forEach(function(el){
4811
4812			var title = el.getAttribute('data-title') || '',
4813			desc = el.getAttribute('data-description') || '',
4814			thumb = el.getAttribute('data-thumb') || settings.playlistSelectorPlaceholderThumb
4815
4816			var s = '<div class="hap-playlist-selector-thumb">'+
4817                    '<img src="'+thumb+'"/>'+
4818                    '<div class="hap-playlist-selector-thumb-style"></div>'+
4819                '</div>'+
4820                '<div class="hap-playlist-selector-info">'+
4821                    '<div class="hap-playlist-selector-title">'+title+'</div>'+
4822                    '<div class="hap-playlist-selector-desc">'+desc+'</div>'+
4823                '</div>'
4824
4825             el.innerHTML = s;   
4826
4827             playlistSelectorArr.push(el)
4828
4829             //disable active playlist if exist
4830             if(el.getAttribute('data-playlist-id') == settings.activePlaylist){
4831             	el.classList.add('hap-playlist-selector-item-active')
4832             	playlistSelectorActiveItem = el;
4833             }
4834
4835             playlistSelectorLen++; 
4836		})
4837
4838		//click
4839		playlistSelectorInner.addEventListener('click', function(e){
4840
4841			if(playlistTransitionOn)return false;
4842
4843			if(e.target.closest('.hap-playlist-selector-item')){
4844
4845				var parent = e.target.closest('.hap-playlist-selector-item')
4846
4847				if(parent.classList.contains('hap-playlist-selector-item-active'))return false;
4848
4849				if(playlistSelectorActiveItem)playlistSelectorActiveItem.classList.remove('hap-playlist-selector-item-active')
4850
4851				playlistSelectorActiveItem = parent;
4852				playlistSelectorActiveItem.classList.add('hap-playlist-selector-item-active')
4853
4854				var ap = playlistSelectorActiveItem.getAttribute('data-playlist-id')
4855
4856				setPlaylist(ap);
4857
4858				//close playlist selector
4859
4860				setTimeout(function(){
4861					closePlaylistSelector();
4862				},200)
4863
4864			}
4865
4866		})
4867
4868		
4869
4870
4871		//search 
4872		playlistSelectorSearch = playlistSelectorContainer.querySelector('.hap-playlist-selector-search')
4873		playlistSelectorSearch.addEventListener('keyup',function(e){
4874		
4875			var value = e.target.value.toLowerCase(), i, j = 0, title, pi, search_len;
4876
4877			if(value){
4878
4879
4880				for(i = 0; i < playlistSelectorLen; i++){
4881
4882					pi = playlistSelectorContainer.querySelectorAll('.hap-playlist-selector-item')[i];
4883
4884					title = '';
4885
4886					if(pi.querySelector('.hap-playlist-selector-title'))title += pi.querySelector('.hap-playlist-selector-title').innerHTML.toLowerCase();
4887					if(pi.querySelector('.hap-playlist-selector-desc'))title += pi.querySelector('.hap-playlist-selector-desc').innerHTML.toLowerCase();
4888
4889					if(title.indexOf(value) >- 1){
4890						pi.classList.remove('hap-filter-hidden')
4891					}else{
4892						pi.classList.add('hap-filter-hidden');
4893						j++;
4894					}
4895					
4896				}
4897
4898			}else{
4899
4900				playlistSelectorContainer.querySelectorAll('.hap-playlist-selector-item').forEach(function(el){
4901					el.classList.remove('hap-filter-hidden')
4902				})
4903
4904			}	
4905
4906			if(j == playlistSelectorLen){
4907				if(playlistSelectorFilterMsg)playlistSelectorFilterMsg.style.display = 'block';
4908			}else{
4909				if(playlistSelectorFilterMsg)playlistSelectorFilterMsg.style.display = 'none';
4910			} 
4911
4912		})
4913
4914	}
4915
4916	function closePlaylistSelector(){
4917
4918		playlistSelectorContainer.addEventListener('transitionend', function cb(e){
4919			e.currentTarget.removeEventListener(e.type, cb);
4920			
4921			playlistSelectorContainer.style.display = 'none'
4922
4923			//hide thumbs
4924			var z=0;
4925			for(z=0;z<playlistSelectorLen;z++){
4926				playlistSelectorArr[z].classList.remove('hap-playlist-selector-item-shown','hap-filter-hidden');
4927			}
4928
4929			playlistSelectorOpened = false;
4930		
4931		})
4932        playlistSelectorContainer.style.opacity = 0;
4933
4934        //clear filter
4935        playlistSelectorFilterMsg.style.display = 'none';
4936        playlistSelectorSearch.value = '';
4937
4938        _body.classList.remove('hap-body-overflow')
4939		
4940	}
4941	
4942
4943	
4944
4945	//############################################//
4946	/* playlist manager */
4947	//############################################//
4948
4949	var _HAPPlaylistManager = new HAPPlaylistManager({loop:settings.loopState, random:settings.randomPlay});
4950
4951	_HAPPlaylistManager.addEventListener('HAPPlaylistManager.QUE_NEXT_READY', function(e){
4952		var data = playlistDataArr[e.counter].data
4953
4954		//check if we can crossfade
4955		if(data.type.indexOf('youtube') > -1 || data.type.indexOf('hls') > -1 || data.adPre || data.adEnd || data.start || data.end || data.vast){
4956			console.log('crossfade only works with media type audio, podcast, soudncloud, cannot work when audio has pre / end advert set, cannot work when audio start / end positions are used, cannot work with variations!')
4957			nextMediaData = null;
4958		}
4959		else{
4960			nextMediaData = data;
4961		}
4962
4963	})
4964
4965	_HAPPlaylistManager.addEventListener('HAPPlaylistManager.COUNTER_READY', function(e){
4966		if(mediaType){
4967			cleanMedia();
4968		}
4969
4970		mediaCounter = e.counter;
4971		currMediaData = playlistDataArr[mediaCounter].data;
4972
4973		self.fireEvent("soundRequest", [{instance:self, instanceName:settings.instanceName, media: currMediaData}]);
4974
4975		if(!currMediaData.type){
4976			currMediaData = getTrackData(currMediaData);
4977			playlistDataArr[mediaCounter].data = currMediaData;
4978		}
4979
4980		mediaType = currMediaData.type;
4981
4982		if(mediaType == 'youtube_single')mediaType = 'youtube';
4983
4984
4985
4986		allowedViewVideoUserRole = true;
4987
4988		if(settings.isUserLoggedIn){
4989			if(typeof currMediaData.lockTime != undefined){
4990				if(currentUserRoles && currMediaData.lockVideoUserRoles){
4991					allowedViewVideoUserRole = HAPUtils.arrayContainsAnotherArray(currMediaData.lockVideoUserRoles, currentUserRoles);
4992				}
4993			}
4994		}else{
4995			allowedViewVideoUserRole = false;
4996		}
4997
4998
4999		if(isEnqueue){
5000			isEnqueue = false;
5001			return;
5002		}
5003
5004		//check if we can crossfade (maybe last song in playlist and loop is off)
5005		nextMediaData = null;
5006		nextDataCheck = false;
5007		if(settings.useCrossFade){
5008			setTimeout(function(){
5009				isCancelCrossFade = false;//wait till ramp ends
5010			},1000)
5011		}
5012
5013
5014
5015		setTitleAndThumb();
5016		if(useMediaSession)updateMetadata();
5017
5018
5019
5020		if(seekbarWave){
5021
5022			if(waveformDataAws){//saved waveform from s3
5023				currMediaData.peaks = waveformDataAws;
5024				waveformDataAws = null;
5025				waveformLoading = false;
5026			}
5027
5028			if(currMediaData.peaks){
5029
5030				drawWaveSeekbar()
5031				if(waveformLoader)waveformLoader.classList.add('hap-waveform-loader-hidden')
5032				
5033				setTimeout(function(){//delay for crossfade
5034					seekbarWave.classList.add('hap-seekbar-wave-visible')
5035				},waveformCrossfadeTime)
5036	
5037			}else{
5038
5039				if(currMediaData.type != 's3_audio'){
5040					waveformLoading = true;
5041					checkPeakExist(currMediaData.peakName || currMediaData.title);
5042				}
5043
5044				//clearSeekbar()
5045
5046			}
5047			
5048		}else if(settings.useStickyPlayer){
5049			if(currMediaData.type != 's3_audio'){
5050				waveformLoading = true;
5051				checkPeakExist(currMediaData.peakName || currMediaData.title);
5052			}
5053		}
5054	
5055
5056
5057
5058		//check last position
5059		if(continousPlaybackTrackAllSongs){
5060			if(settings.lastPositionArr){
5061				var i, len = settings.lastPositionArr.length, item;
5062				for(i = 0;i<len; i++){
5063					item = settings.lastPositionArr[i];
5064					if((item.title && currMediaData.title == item.title) && currMediaData.artist == item.artist){
5065						currMediaData.start = item.start;
5066						break;
5067					}
5068				}
5069			}
5070		}
5071
5072		if(settings.usePlaylist){
5073
5074			disableActivePlaylistItem(mediaCounter);
5075
5076			hasVariation = false;
5077			if(activePlaylistItem.querySelector('.hap-audio-variation')){
5078				hasVariation = true;
5079			}
5080
5081		}
5082
5083
5084
5085		if(settings.useInlineSeekbar){
5086			activePlaylistItem.querySelector('.hap-playlist-thumb').append(inlineSeekbar)
5087		}
5088
5089
5090
5091
5092		//check password
5093		if(!settings.useCrossFade){
5094			if(currMediaData.pwd){
5095				pwdHolderToggle()
5096				return false;
5097			}
5098		}
5099
5100
5101		doPostPwdActions()//we have to execute this after we confim pwd as well
5102
5103
5104	})
5105
5106	_HAPPlaylistManager.addEventListener('PLAYLIST_END', function(){
5107		self.fireEvent("playlistEnd", [{instance:self, instanceName:settings.instanceName}]);
5108	});
5109
5110	function doPostPwdActions(){
5111
5112		if(currMediaData.lyrics && lyricsHolder){
5113
5114			if(!_lyrics){
5115
5116				lyricsReady = false;
5117
5118				if(settings.lyricsWrap && settings.lyricsContainer){
5119					var wrapContainer = document.querySelector(settings.lyricsWrap)
5120					var scrollContainer = document.querySelector(settings.lyricsContainer)
5121				}else{
5122					var wrapContainer = lyricsHolder.querySelector('.hap-lyrics-wrap')
5123					var scrollContainer = lyricsHolder.querySelector('.hap-lyrics-container')
5124				}
5125
5126				var lyrics_settings = {
5127					wrapContainer: wrapContainer,
5128					scrollContainer: scrollContainer,
5129					settings: settings,
5130					itemClass: "hap-lyrics-item",
5131					activeClass: "hap-lyrics-item-active",
5132				}
5133
5134				_lyrics = new HAPLyrics(lyrics_settings)
5135
5136				document.addEventListener("HAPLyrics.LYRICS_CLICKED", function(e) {
5137					if(_HAPAdManager && _HAPAdManager.isAdOn() && settings.pauseAudioDuringAds) return false;
5138					if(isIma && isImaOn) return false;
5139
5140					self.seek(e.detail)
5141					self.playMedia()
5142				});
5143
5144				document.addEventListener("HAPLyrics.LYRICS_READY", function(e) {
5145					//console.log('LYRICS_READY')
5146					if(e.detail)currMediaData.lyricsContent = e.detail;//save
5147
5148					lyricsReady = true;
5149
5150					if(settings.lyricsAutoOpen){
5151						if(!lyricsOpened)self.toggleLyrics()
5152					}
5153
5154				});
5155
5156			}
5157
5158			//check if lyrics exist
5159
5160			if(currMediaData.lyricsContent){
5161				_lyrics.setData(currMediaData.lyricsContent)
5162			}else{
5163				_lyrics.load(currMediaData.lyrics)
5164			}
5165
5166			if(lyricsToggle)lyricsToggle.style.display = 'block'
5167
5168		}else{
5169			if(lyricsToggle)lyricsToggle.style.display = 'none'
5170		}
5171
5172
5173		if(currMediaData.video && videoWrap){
5174
5175			var vfs = ' nofullscreen';
5176			if(settings.useVideoFullscreen)vfs = '';
5177
5178			var pip = ' disablepictureinpicture';
5179			if(settings.useVideoPictureInPicture)pip = '';
5180
5181			var dl = ' nodownload';
5182			if(settings.useVideoDownload)dl = '';
5183
5184			var controls = '';
5185			if(settings.useVideoControls)controls = ' controls';
5186
5187			var code = '<video class="hap-video" preload="'+settings.preload+'" muted playsinline'+pip+' controlsList="noremoteplayback'+dl+vfs+controls+'">';
5188
5189			code += '<source src="'+currMediaData.video+'" />';
5190			code += '</video>';
5191
5192			if(!settings.useVideoControls){
5193				code += '<div class="hap-video-blocker"></div>';
5194			}
5195
5196			videoWrap.innerHTML = code
5197
5198			videoSync = videoWrap.querySelector('.hap-video')
5199
5200			videoSync.addEventListener("canplay", function(){
5201				if(settings.playbackRate){
5202					try{
5203						videoSync.playbackRate = Number(settings.playbackRate);
5204					}catch(er){}
5205				}
5206			})
5207
5208			if(settings.useVideoControls){
5209				//sync audio
5210				videoSync.addEventListener("play", function(){
5211					syncComingFromVideo = true;
5212					self.playMedia()
5213				})
5214				videoSync.addEventListener("pause", function(){
5215					syncComingFromVideo = true;
5216					self.pauseMedia()
5217				})
5218				videoSync.addEventListener("seeked", function(){
5219					syncComingFromVideo = true;
5220					var ct = videoSync.currentTime;
5221					self.seek(ct)
5222				})
5223			}
5224
5225			if(settings.videoAutoOpen){
5226				if(!videoOpened)self.toggleVideo()
5227			}
5228
5229			videoToggle.style.display = 'block'
5230
5231		}else{
5232			if(videoToggle)videoToggle.style.display = 'none'
5233		}
5234
5235
5236
5237		//############################################//
5238		/* ads */
5239		//############################################//
5240
5241		if(currMediaData.adPre || currMediaData.adMid || currMediaData.adEnd){
5242			if(!_HAPAdManager){
5243
5244				if(typeof HAPAdManager === 'undefined'){
5245					console.log('link to admanager.js file missing in head tag!');
5246				}
5247
5248		      	_HAPAdManager = new HAPAdManager(settings, self, currMediaData, progressBg, progressLevel, mediaTimeAd);
5249
5250		      	_HAPAdManager.addEventListener('HAPAdManager.IOS_ADMID_FIX_START', function(data){
5251
5252		      	})
5253		      	_HAPAdManager.addEventListener('HAPAdManager.AD_PLAY_ACTION', function(data){
5254		      		if(playbackToggle)self.toggleBigPlay(true)
5255				})
5256				_HAPAdManager.addEventListener('HAPAdManager.AD_PAUSE_ACTION', function(data){
5257					if(playbackToggle)self.toggleBigPlay()
5258				})
5259				_HAPAdManager.addEventListener('HAPAdManager.ADPRE_PLAY', function(data){
5260
5261					self.showAdInterface()
5262
5263				})
5264				_HAPAdManager.addEventListener('HAPAdManager.ADPRE_ENDED', function(data){
5265
5266		      		self.hideAdInterface()
5267
5268					findAudioSrc();
5269
5270				})
5271				_HAPAdManager.addEventListener('HAPAdManager.ADMID_PLAY', function(data){
5272		      			
5273					if(settings.pauseAudioDuringAds)self.pauseMedia();
5274
5275					self.showAdInterface()
5276
5277				})
5278				_HAPAdManager.addEventListener('HAPAdManager.ADMID_ENDED', function(data){
5279		      			
5280					self.hideAdInterface()
5281
5282					if(settings.pauseAudioDuringAds)self.playMedia();
5283
5284				})
5285				_HAPAdManager.addEventListener('HAPAdManager.ADEND_PLAY', function(data){
5286
5287					self.showAdInterface()
5288
5289				})
5290				_HAPAdManager.addEventListener('HAPAdManager.ADEND_ENDED', function(data){
5291		      			
5292					self.hideAdInterface()
5293					
5294					if(!settings.stopOnSongEnd){
5295						audioEndedAction(true);
5296					}
5297
5298				});
5299
5300		    }else{
5301		    	_HAPAdManager.setAdData(currMediaData);
5302		    }
5303		}
5304
5305		if(currMediaData.vast){
5306
5307			isIma = true;
5308
5309			if(!imaLoaded){
5310				loadIma()
5311			}else{
5312				if(autoPlay){
5313					initVast()
5314				} 
5315			}
5316			
5317		}
5318
5319
5320
5321		if(currMediaData.adPre){
5322
5323			_HAPAdManager.initAd('pre');
5324
5325			autoPlay = true;
5326			settings.autoPlay = true;
5327
5328		//############################################//
5329		/* ads */
5330		//############################################//
5331
5332		}else{
5333
5334			if(crossFadeOn){
5335
5336				if(mediaType == 's3_audio'){
5337					mediaType = 'audio';//this was set in get aws url
5338				}
5339
5340			}else{
5341
5342				//check lock time
5343				if(currMediaData.lockTime == '0' && !allowedViewVideoUserRole){
5344					toggleRedirectLoginScreen('watch');
5345				}else{
5346					findAudioSrc();
5347				}
5348			}
5349
5350		}
5351
5352	}
5353
5354	function findAudioSrc(){
5355
5356		if(mediaType == 'youtube'){
5357			initYoutube();
5358
5359			//if we use youtube we have to start video manually
5360			if(settings.hidePlayerUntilMusicStart)unhidePlayer();
5361
5362		}else if(mediaType == 's3_audio'){
5363
5364			getAwsUrl(currMediaData)
5365
5366		}else if(currMediaData.origtype && currMediaData.origtype == 'soundcloud'){
5367
5368			if(currMediaData.sc_url){
5369
5370				mediaPath = currMediaData.sc_url;
5371				delete currMediaData.sc_url
5372
5373				initAudio();
5374			}else{
5375
5376				_scLoader.get_stream_url(currMediaData.sc_data.id)
5377			}
5378		
5379		}else{
5380			mediaPath = getAudioUrl(currMediaData)
5381
5382			initAudio();
5383			
5384		}
5385
5386	}
5387
5388	//############################################//
5389	/* ima */
5390	//############################################//
5391
5392	if(settings.useImaSkipButton){
5393
5394		if(!adSkipBtn){
5395			adSkipBtn = HAPUtils.htmlToElement('<div class="hap-ad-skip"></div>')
5396			HAPUtils.insertAfter(adSkipBtn, mediaTimeAd)
5397		}
5398
5399		adSkipBtn.addEventListener('click', function(){
5400			if(_IMALoader)_IMALoader.skip()
5401		})
5402	}
5403
5404	function loadIma(){
5405
5406		var imaReady,
5407		imaLoaderReady
5408
5409		var script = document.createElement('script');
5410		script.type = 'text/javascript';
5411		script.src = settings.ima_js;
5412		script.onload = script.onreadystatechange = function() {
5413		    if(!this.readyState || this.readyState == 'complete'){
5414		      	imaReady = true;
5415		    }
5416		};
5417		script.onerror = function(){
5418			console.log("Error loading " + this.src);
5419		}
5420		var tag = document.getElementsByTagName('script')[0];
5421		tag.parentNode.insertBefore(script, tag);
5422
5423
5424		if(typeof HAPIMALoader === 'undefined'){
5425			var script = document.createElement('script');
5426			script.type = 'text/javascript';
5427			script.src = HAPUtils.qualifyURL(settings.sourcePath+settings.imaLoader_js);
5428			script.onload = script.onreadystatechange = function() {
5429			    if(!this.readyState || this.readyState == 'complete'){
5430			    	imaLoaderReady = true;
5431			    }
5432			};
5433			script.onerror = function(){
5434				console.log("Error loading " + this.src);
5435			}
5436			var tag = document.getElementsByTagName('script')[0];
5437			tag.parentNode.insertBefore(script, tag);
5438
5439		}else{
5440			imaLoaderReady = true;
5441		}
5442
5443		var interval = setInterval(function(){
5444			if(imaReady && imaLoaderReady){
5445				clearInterval(interval);
5446				imaLoaded = true;
5447				initIma()
5448			}
5449		},100);
5450
5451	}
5452
5453	function initIma(){
5454
5455		var imaHolder = HAPUtils.htmlToElement('<div class="hap-ima-holder"></div>')
5456		wrapper.append(imaHolder)
5457
5458		_IMALoader = new HAPIMALoader(settings, self, audio, imaHolder);
5459
5460		if(comingFromPwd){
5461			comingFromPwd = false;
5462			if(!imaStarted)initVast()
5463		}
5464
5465	}
5466
5467	function initVast(){
5468
5469		imaStarted = true;
5470
5471		var url = currMediaData.vast
5472
5473		if(url.substr(-11) == 'correlator='){
5474			var randNum = Math.floor(Math.random() * (9999999 - 1 + 1)).toString();
5475			url += randNum;
5476		}
5477
5478		_IMALoader.load(url, settings.volume)
5479
5480	}
5481
5482	this.cancelIma = function(){
5483		//on ima error
5484		
5485		isIma = false;
5486		imaStarted = false;
5487		isImaOn = false;
5488		self.hideAdInterface()
5489
5490	}
5491
5492	this.setImaEnded = function(isEnd){//ads done
5493
5494		isIma = false;
5495		imaStarted = false;
5496		isImaOn = false;
5497
5498        if(isEnd){
5499        	if(!settings.stopOnSongEnd){
5500				audioEndedAction();
5501			}
5502        }else{
5503            self.hideAdInterface();
5504            self.playMedia();
5505        }
5506		
5507	}
5508
5509
5510	//############################################//
5511	/* aws */
5512	//############################################//
5513
5514	function getAwsUrl(data){
5515		console.log('getAwsUrl')
5516
5517		if(data.key.indexOf(bsf_match) != -1)data.key = HAPUtils.b64DecodeUnicode(data.key.substr(6));
5518
5519		if(settings.useCloudfront)var url = settings.sourcePath + 'includes/aws/aws2.php';
5520		else var url = settings.sourcePath + 'includes/aws/aws.php';
5521
5522		var params = 'object_key='+data.key+'&bucket='+data.bucket+'&action=get_url'+'&region='+settings.s3Region+'&expire='+settings.s3UrlExpireTime+'&cf_expire='+settings.cf_expire+'&cred='+settings.s3+'&upload_dir='+settings.upload_dir;
5523
5524		if(xhrRequest){
5525			xhrRequest.abort();
5526			xhrRequest = null;
5527		}
5528
5529		xhrRequest = new XMLHttpRequest();
5530		xhrRequest.onreadystatechange = function() {
5531			if (xhrRequest.readyState == 4) {
5532
5533				var result = JSON.parse(xhrRequest.responseText)
5534
5535				waveformDataAws2 = result;
5536
5537				if(crossFadeOn){
5538					s3UrlReady = result;  
5539
5540					if(settings.useWaveSeekbar){
5541
5542						if(!nextMediaData.peaks){//waveform for next media
5543
5544							waveformLoading = true;
5545
5546							getPeakData(result)
5547							
5548						}
5549					}
5550
5551				}else{
5552
5553					mediaType = 'audio';
5554					mediaPath = currMediaData.path = result
5555
5556					if(seekbarWave){
5557
5558						if(currMediaData.peaks){//waveform for current media
5559
5560							drawWaveSeekbar()
5561							if(waveformLoader)waveformLoader.classList.add('hap-waveform-loader-hidden')
5562							
5563							setTimeout(function(){//delay for crossfade
5564								seekbarWave.classList.add('hap-seekbar-wave-visible')
5565							},waveformCrossfadeTime)
5566
5567						}else{
5568
5569							checkPeakExist(currMediaData.peakName || currMediaData.title);
5570
5571						}
5572
5573					}
5574				
5575					initAudio()
5576				}
5577
5578		    }
5579		}
5580		xhrRequest.onerror = function(e) { 
5581		    console.log('Error getAwsUrl: ' + e);
5582			self.fireEvent('getAws_error:', [{instance:self, instanceName:settings.instanceName, media:currMediaData, error:errorThrown}]);
5583			if(settings.autoAdvanceToNextSongOnError)self.nextMedia()
5584		};
5585		xhrRequest.open('POST', url);
5586		xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
5587		xhrRequest.send(params);
5588
5589	}
5590
5591	function resumeAwsUrl(data){
5592
5593		if(data.key.indexOf(bsf_match) != -1)data.key = HAPUtils.b64DecodeUnicode(data.key.substr(6));
5594				
5595		if(settings.useCloudfront)var url = settings.sourcePath + 'includes/aws/aws2.php';
5596		else var url = settings.sourcePath + 'includes/aws/aws.php';
5597
5598		var params = 'object_key='+data.key+'&bucket='+data.bucket+'&action=get_url'+'&region='+settings.s3Region+'&expire='+settings.s3UrlExpireTime+'&cf_expire='+settings.cf_expire+'&cred='+settings.s3+'&upload_dir='+settings.upload_dir;
5599
5600		if(xhrRequest){
5601			xhrRequest.abort();
5602			xhrRequest = null;
5603		}
5604
5605		xhrRequest = new XMLHttpRequest();
5606		xhrRequest.onreadystatechange = function() {
5607			if (xhrRequest.readyState == 4) {
5608
5609				activeAudio.src = xhrRequest.responseText;
5610
5611				var promise = activeAudio.play();
5612				if (promise !== undefined) {
5613					promise.then(function(){
5614				    }).catch(function(error){
5615				    });
5616				}
5617		    }
5618		}
5619		xhrRequest.onerror = function(e) { 
5620		    console.log('Error resumeAwsUrl: ' + e);
5621			self.fireEvent('getAws_error:', [{instance:self, instanceName:settings.instanceName, media:currMediaData, error:errorThrown}]);
5622			if(settings.autoAdvanceToNextSongOnError)self.nextMedia()
5623		};
5624		xhrRequest.open('POST', url);
5625		xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
5626		xhrRequest.send(params);
5627
5628	}
5629
5630	function getAwsThumb(data){
5631				
5632		var url = settings.sourcePath + 'includes/aws/aws.php'
5633		
5634		var params = 'object_key='+data.thumb_key+'&bucket='+data.bucket+'&action=get_url_thumb'+'&region='+settings.s3Region+'&expire='+settings.s3UrlExpireTime+'&cred='+settings.s3;
5635
5636		var xhr = new XMLHttpRequest();
5637		xhr.onreadystatechange = function() {
5638			if (xhr.readyState == 4) {
5639
5640				currMediaData.thumb = this.responseText;
5641
5642				setThumb(xhr.responseText)
5643		    }
5644		}
5645		xhr.onerror = function(e) { 
5646		    console.log('Error getAwsThumb: ' + e);
5647			checkProcessCounter();
5648		};
5649		xhr.open('POST', url);
5650		xhr.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
5651		xhr.send(params);
5652
5653	}
5654
5655	// bucket 
5656
5657	function processS3Bucket(data){
5658
5659		if(window.location.protocol == 'file:'){
5660			console.log('Reading folders requires server connection.');
5661			return false;
5662		}
5663
5664		var url = settings.sourcePath + 'includes/aws/aws.php'
5665
5666		var params = 'bucket='+data.bucket+'&action=list_bucket'+'&region='+settings.s3Region+'&cred='+settings.s3;
5667
5668		if(xhrRequest){
5669			xhrRequest.abort();
5670			xhrRequest = null;
5671		}
5672
5673		xhrRequest = new XMLHttpRequest();
5674		xhrRequest.onreadystatechange = function() {
5675			if (xhrRequest.readyState == 4) {
5676
5677				var media = JSON.parse(xhrRequest.responseText)
5678				
5679				var i, len = media.length, entry, obj;
5680
5681				if(data.sort){
5682					if(data.sort == 'filename-asc')HAPUtils.keysrt4(media, 'filename');
5683					else if(data.sort == 'filename-desc')HAPUtils.keysrt4(media, 'filename', true);
5684				}
5685
5686				for(i=0; i < len; i++){
5687
5688					entry = media[i];
5689
5690					if(/mp3|wav|aac|ogg|flac/.test(entry)){
5691
5692						obj = HAPUtils.extendObj(true, {}, data);
5693						obj.type = 's3_audio';
5694						obj.key = entry;
5695						obj.title = entry.substr(0, entry.lastIndexOf('.'));
5696
5697						obj.thumb_key = obj.title + '.' + settings.s3ThumbExtension;
5698
5699						playlistProcessData.push(obj);
5700					}
5701					
5702				}
5703
5704				checkProcessCounter();
5705		    }
5706		}
5707		xhrRequest.onerror = function(e) { 
5708		    console.log('Error processS3Bucket: ' + e);
5709			checkProcessCounter();
5710		};
5711		xhrRequest.open('POST', url);
5712		xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
5713		xhrRequest.send(params);
5714
5715	}
5716
5717	/*****************/
5718
5719	var audioCtx; 
5720    var peakLength = 100;
5721    var peakCounter = 0
5722
5723	if(settings.useWaveSeekbar || settings.useStickyPlayer){
5724		audioCtx = new AudioContext(); 
5725	}
5726
5727    function getPeakData(audioData) {
5728
5729        return fetch(audioData)
5730        .then((response) => {
5731            if (!response.ok) {
5732                if(waveformLoader)waveformLoader.classList.add('hap-waveform-loader-hidden')
5733                if(settings.demoPeakArr)fakePeak()
5734            }
5735            return response.arrayBuffer();
5736        })
5737        .then((buffer) => audioCtx.decodeAudioData(buffer))
5738        .then((decodedData) => {
5739    
5740            var data = extractPeaks(decodedData),
5741            val = data.toString();
5742
5743            if(peakWriteRequired){
5744	            writePeaks(val, currMediaData.peakName || currMediaData.title);
5745			    peakWriteRequired = false;
5746			}
5747
5748	
5749
5750        	if(crossFadeOn && s3UrlReady){
5751        		//s3
5752
5753        		waveformDataAws = val;
5754
5755			}else{
5756
5757				currMediaData.peaks = val;
5758
5759        		waveformLoading = false;
5760
5761        		if(seekbarWave){
5762
5763	        		drawWaveSeekbar()
5764					if(waveformLoader)waveformLoader.classList.add('hap-waveform-loader-hidden')
5765					
5766					setTimeout(function(){//delay for crossfade
5767						seekbarWave.classList.add('hap-seekbar-wave-visible')
5768					},waveformCrossfadeTime)
5769
5770				}else if(settings.useStickyPlayer){
5771
5772	        		if(settings.useStickyPlayer && window['hap_fixed_active_player'] == settings.instanceName){
5773						_HAPStickyPlayer.setPeakData(currMediaData.peaks)
5774					}
5775
5776	        	}
5777
5778			} 
5779
5780        }).catch((error) => {
5781		    console.log(error)
5782		    if(waveformLoader)waveformLoader.classList.add('hap-waveform-loader-hidden')
5783            if(settings.demoPeakArr)fakePeak()
5784		});
5785        
5786    };
5787
5788    function extractPeaks( buffer ) {
5789        const channels = buffer.numberOfChannels,
5790                sampleSize = buffer.length / peakLength,
5791                sampleStep = ~~( sampleSize / 10) || 1
5792
5793        const peak_data = []
5794        var c
5795
5796        for (c = 0; c < channels; c++) {
5797            const stepPeaks = []
5798            const chan = buffer.getChannelData(c)
5799
5800            for (var i = 0; i < peakLength; i++) {
5801                  var start = ~~(i * sampleSize)
5802                  var end = ~~(start + sampleSize)
5803                  var min = chan[0]
5804                  var max = chan[0]
5805
5806                  for (var j = start; j < end; j += sampleStep) {
5807                      var value = chan[j]
5808                      if (value > max)
5809                          max = value
5810                      if (value < min)
5811                          min = value
5812                  }
5813                  stepPeaks[2 * i] = max
5814                  stepPeaks[2 * i + 1] = min
5815
5816                  if (c == 0 || max > peak_data[2 * i])
5817                      peak_data[2 * i] = Math.abs( max.toFixed(2) )
5818
5819                  if (c == 0 || min < peak_data[2 * i + 1])
5820                      peak_data[2 * i + 1] = Math.abs( min.toFixed(2) )
5821            }
5822        }
5823        return peak_data
5824    }
5825
5826	function fakePeak(){
5827 		//load demo peak if current fails
5828
5829 		var r = settings.demoPeakArr[Math.floor(Math.random()*settings.demoPeakArr.length)];
5830 		currMediaData.peaks = r
5831
5832 		waveformLoading = false;
5833
5834		if(seekbarWave)drawWaveSeekbar()
5835		if(waveformLoader)waveformLoader.classList.add('hap-waveform-loader-hidden')
5836
5837		if(seekbarWave){
5838			setTimeout(function(){//delay for crossfade
5839				seekbarWave.classList.add('hap-seekbar-wave-visible')
5840			},waveformCrossfadeTime)
5841		}
5842
5843    	if(settings.useStickyPlayer && window['hap_fixed_active_player'] == settings.instanceName){
5844			_HAPStickyPlayer.setPeakData(currMediaData.peaks)
5845		}
5846		
5847	}
5848
5849	function checkPeakExist(id){
5850
5851		if(window.location.protocol == 'file:'){
5852			console.log('Using peaks requires server connection!');
5853			return
5854		}
5855
5856	    if(xhrRequest_loadPeaks){
5857			xhrRequest_loadPeaks.abort();
5858			xhrRequest_loadPeaks = null;
5859		}
5860
5861		var url = settings.sourcePath + 'peaks/_peaks.php'
5862
5863		var params = 'action=hap_read_peaks'
5864	    +'&id='+encodeURIComponent(id)
5865
5866	    if(currMediaData.peakdir)params += '&path='+currMediaData.peakdir
5867
5868		xhrRequest_loadPeaks = new XMLHttpRequest();
5869		xhrRequest_loadPeaks.onreadystatechange = function() {
5870			if (this.readyState == 4) {
5871
5872				if(this.response.length > 2){
5873		        	//console.log('peak exist')
5874		        	//no need for json parse because we needs peak in string format
5875		        	var peaks = this.response.substring(1, this.response.length-1);//array to string
5876
5877		        	currMediaData.peaks = peaks;
5878
5879		        	waveformLoading = false;
5880
5881		        	if(seekbarWave){
5882
5883		        		drawWaveSeekbar()
5884
5885						if(waveformLoader)waveformLoader.classList.add('hap-waveform-loader-hidden')
5886
5887						setTimeout(function(){//delay for crossfade
5888							seekbarWave.classList.add('hap-seekbar-wave-visible')
5889						},waveformCrossfadeTime)
5890
5891		        	}else if(settings.useStickyPlayer){
5892
5893		        		if(window['hap_fixed_active_player'] == settings.instanceName){
5894							_HAPStickyPlayer.setPeakData(currMediaData.peaks)
5895						}
5896
5897		        	}
5898
5899		        }else{
5900		        	//console.log('peak NOT exist', currMediaData.remote)
5901
5902	        		peakWriteRequired = true;
5903
5904					if(currMediaData.remote){
5905
5906						if(currMediaData.origtype && currMediaData.origtype == 'soundcloud'){
5907
5908							if(!currMediaData.sc_url){
5909
5910								var interval = setInterval(function(){
5911									if(mediaPath){
5912										if(interval) clearInterval(interval);
5913										remoteDownload();
5914									}
5915								}, 100);
5916
5917							}else{
5918								remoteDownload();
5919							}
5920
5921						}else{
5922							remoteDownload();
5923						}
5924
5925					}else{
5926
5927						if(currMediaData.type == 's3_audio'){
5928							if(waveformDataAws2)getPeakData(waveformDataAws2)
5929						}else{
5930							getPeakData(mediaPath)
5931						}
5932
5933					}
5934	        	
5935		        }
5936
5937
5938		    }
5939		}
5940		xhrRequest_loadPeaks.onerror = function(e) { 
5941		 	console.log(jqXHR, textStatus, errorThrown);
5942		};
5943		xhrRequest_loadPeaks.open('POST', url);
5944		xhrRequest_loadPeaks.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
5945		xhrRequest_loadPeaks.send(params);
5946
5947	}
5948
5949	function writePeaks(peaks, title){
5950
5951	    var params = 'action=hap_write_peaks'
5952	    +'&peaks='+peaks
5953	    +'&id='+encodeURIComponent(title)
5954
5955	    if(currMediaData.peakdir)params += '&path='+currMediaData.peakdir
5956
5957	    if(xhrRequest_writePeaks){
5958			xhrRequest_writePeaks.abort();
5959			xhrRequest_writePeaks = null;
5960		}
5961
5962		var url = settings.sourcePath + 'peaks/_peaks.php';
5963
5964		xhrRequest_writePeaks = new XMLHttpRequest();
5965		xhrRequest_writePeaks.onreadystatechange = function() {
5966
5967		}
5968		xhrRequest_writePeaks.onerror = function(e) { 
5969		 	console.log(jqXHR, textStatus, errorThrown);
5970		};
5971		xhrRequest_writePeaks.open('POST', url);
5972		xhrRequest_writePeaks.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
5973		xhrRequest_writePeaks.send(params);
5974
5975	}
5976
5977	function remoteDownload(){
5978		console.log('remoteDownload')
5979
5980	    if(xhrRequest_remoteDownload){
5981			xhrRequest_remoteDownload.abort();
5982			xhrRequest_remoteDownload = null;
5983		}
5984
5985		var url = settings.sourcePath + 'peaks/_remote_dl.php';
5986
5987	    var da = {id: mediaPath, title: currMediaData.title }//sc need, id can contain multiple &
5988		if(currMediaData.peakdir)da.path = currMediaData.peakdir;
5989
5990		xhrRequest_remoteDownload = new XMLHttpRequest();
5991		xhrRequest_remoteDownload.onreadystatechange = function() {
5992			if (this.readyState == 4) {
5993
5994				if(this.response != ''){
5995					var response = JSON.parse(this.response);
5996					getPeakData(response)
5997				}
5998
5999		    }
6000		}
6001		xhrRequest_remoteDownload.onerror = function(e) { 
6002		 	console.log(jqXHR, textStatus, errorThrown);
6003
6004		 	if(waveformLoader)waveformLoader.classList.add('hap-waveform-loader-hidden')
6005		 	if(settings.demoPeakArr)fakePeak()
6006
6007		};
6008		xhrRequest_remoteDownload.open('POST', url);
6009		xhrRequest_remoteDownload.setRequestHeader("Content-Type", "application/json");
6010		xhrRequest_remoteDownload.send(JSON.stringify(da));
6011
6012	}
6013
6014
6015	//############################################//
6016	/* youtube player */
6017	//############################################//
6018
6019	function initYoutube() {
6020
6021		mediaPath = currMediaData.mp3 || currMediaData.path;
6022		if(mediaPath.indexOf(bsf_match) != -1)mediaPath = HAPUtils.b64DecodeUnicode(mediaPath.substr(6));
6023
6024		if(!youtubeInited){
6025
6026			if(!playerThumb){
6027				alert('Using Youtube requires player to have thumbnail image in player where Youtube player will be placed. Please use demo which has thumbnail in player! hap-player-thumb element');
6028				return;
6029			}
6030
6031			youtubeHolder = HAPUtils.htmlToElement('<div class="hap-youtube-holder"></div>');
6032			playerThumb.append(youtubeHolder)
6033
6034			var yt_frameId = 'ytplayer' + Math.floor(Math.random()*0xFFFFFF),
6035				prefix = ('https:' == window.location.protocol ? 'https:' : 'http:'),
6036				yt_embed = '//www.youtube.com/embed/',
6037				enablejsapi = '?enablejsapi=1',
6038				controls = '&controls=0',
6039				wmode = '&wmode=transparent',
6040				showinfo = '&showinfo=0',
6041				modestbranding = '&modestbranding=1',
6042				rel = '&rel=0',
6043				autoplay = '&autoplay=1',
6044				playsinline = '&playsinline=1',
6045				iv_load_policy = '&iv_load_policy=3',
6046				cc_load_policy = '&cc_load_policy=0',
6047				domain = window.location.href.split("/"),
6048				orig = domain[0] + "//" + domain[2],
6049				origin = '&origin=' + orig;
6050
6051			var iframeSrc = prefix + yt_embed + mediaPath + enablejsapi + controls + rel + showinfo + playsinline + modestbranding + wmode + iv_load_policy + cc_load_policy;
6052
6053			if(/^http/.test(orig))iframeSrc += origin;
6054
6055			if(settings.resumeTime){
6056				iframeSrc += '&start='+settings.resumeTime;
6057				delete settings.resumeTime;
6058			}else if(currMediaData.start)iframeSrc += '&start='+currMediaData.start;
6059			if(currMediaData.end)iframeSrc += '&end='+currMediaData.end;
6060
6061			youtubeIframe = HAPUtils.htmlToElement('<iframe id="'+yt_frameId+'" src="'+iframeSrc+'" frameborder="0" width="100%" height="100%" webkitAllowFullScreen mozallowfullscreen allowFullScreen allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"></iframe>');
6062
6063			youtubeIframe.classList.add('hap-media');
6064
6065			youtubeHolder.style.display = 'block';
6066			youtubeHolder.prepend(youtubeIframe); 
6067
6068			if(!window.YT){
6069				var tag = document.createElement('script');
6070				tag.src = youtube_js;
6071				var firstScriptTag = document.getElementsByTagName('script')[0];
6072				firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
6073			}
6074
6075			var interval = setInterval(function(){
6076				if(window.YT && window.YT.Player){
6077					if(interval) clearInterval(interval);
6078
6079					youtubePlayer = new YT.Player(yt_frameId, {
6080						events: {
6081							'onReady': onYtPlayerReady,
6082							'onStateChange': onYtPlayerStateChange,
6083							'onError': onYtPlayerError
6084						}
6085					});
6086				}
6087			}, 100);
6088		 
6089			youtubeInited = true;
6090
6091		}else{
6092
6093			if(youtubeReady){
6094
6095				var st = 0;
6096				if(settings.resumeTime)st = resumeTime;
6097				else if(currMediaData.start)st = currMediaData.start;
6098
6099				if(autoPlay){
6100					youtubePlayer.loadVideoById({'videoId':mediaPath, 'startSeconds':st, 'endSeconds':currMediaData.end, 'suggestedQuality':currMediaData.quality});
6101				}else{
6102					youtubePlayer.cueVideoById({'videoId':mediaPath, 'startSeconds':st, 'endSeconds':currMediaData.end, 'suggestedQuality':currMediaData.quality});
6103				}
6104				
6105			}
6106		}
6107
6108		youtubeHolder.style.display = 'block';
6109	}
6110
6111	window.onYouTubeIframeAPIReady = function() {}
6112
6113	function onYtPlayerReady(event) {
6114		youtubeReady = true;
6115		
6116		if(settings.forceYoutubeChromeless)youtubeIframe.classList.add('hap-yt-clean');
6117
6118		if(youtubePlayed && autoPlay){
6119			youtubePlayer.playVideo();
6120		}
6121	}
6122
6123	function onYtPlayerStateChange(event) {
6124		if(!youtubeHolder.style.display == 'none')return;
6125	
6126		if(event.data == -1){//unstarted
6127		}
6128		else if(event.data == 0){//ended
6129
6130			if(useRange && rangeOn && !rangeSet && !_HAPRangeSlider.isDrag()){
6131				rangeSet = true;
6132				youtubePlayer.seekTo(range_a_time);
6133				youtubePlayer.playVideo();
6134				return;
6135			}
6136
6137			if(youtubeEndFix)return;
6138			youtubeEndFix = true;
6139			mediaEndHandler();
6140		}
6141		else if(event.data == 1){//playing
6142			
6143			if(!youtubeStarted){
6144				youtubeEndFix = false;
6145
6146				youtubePlayer.setPlaybackRate(Number(settings.playbackRate));
6147
6148				if(!youtubeBlocker){
6149					youtubeBlocker = HAPUtils.htmlToElement('<div class="hap-iframe-blocker"></div>')
6150					youtubeBlocker.style.display = 'block'
6151					youtubeHolder.append(youtubeBlocker);
6152				}
6153
6154				youtubeStarted = true;	
6155				autoPlay = true;
6156				settings.autoPlay = true;
6157				youtubePlayed = true;
6158
6159				if(settings.hideYoutubeAfterStart)youtubeHolder.style.opacity = 0;
6160
6161				if(useRange){
6162					rangeMinTime.innerHTML = '00:00';
6163					rangeMaxTime.innerHTML = HAPUtils.formatTime(youtubePlayer.getDuration());
6164				}
6165
6166			}
6167
6168			if(useRange && rangeOn){
6169				setTimeout(function(){
6170					clearTimeout(this);
6171					rangeSet = false;
6172				},1000);
6173			}
6174
6175			if(dataIntervalID) clearInterval(dataIntervalID);
6176			dataIntervalID = setInterval(trackProgress, dataInterval);
6177			
6178			playHandler();
6179
6180		}
6181		else if(event.data == 2){//paused
6182			pauseHandler();
6183		}
6184		else if(event.data == 3){//buffering
6185			
6186		}
6187		else if(event.data == 5){//cued
6188		
6189		}
6190	}
6191	
6192	function onYtPlayerError(e) {
6193		var error = 'Youtube erorr';
6194		switch(e.data){
6195			case 2:
6196			error = "Error code = "+e.data+": The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks."
6197			break;
6198			case 100:
6199			error = "Error code = "+e.data+": Video not found, removed, or marked as private"
6200			break;
6201			case 101:
6202			error = "Error code = "+e.data+": Embedding disabled for this video"
6203			break;
6204			case 150:
6205			error = "Error code = "+e.data+": Video not found, removed, or marked as private [same as error 100]"
6206			break;
6207		}
6208
6209		self.fireEvent('soundError', [{instance:self, instanceName:settings.instanceName, media:currMediaData, error:e}]);
6210
6211		if(settings.autoAdvanceToNextSongOnError)self.nextMedia()
6212		
6213	}
6214
6215	//############################################//
6216	/* google analytics */
6217	//############################################//
6218
6219	if(useGa){
6220
6221        var script = document.createElement('script');
6222		script.type = 'text/javascript';
6223		script.async = true;
6224		script.src = settings.gtag_js + settings.gaTrackingId;
6225		script.onload = script.onreadystatechange = function() {
6226		    
6227		    window.dataLayer = window.dataLayer || [];
6228			function gtag(){dataLayer.push(arguments);}
6229			gtag('js', new Date());
6230			gtag('config', settings.gaTrackingId);
6231
6232		};
6233		script.onerror = function(er){
6234			console.log(er)
6235		}
6236		var tag = document.getElementsByTagName('script')[0];
6237		tag.parentNode.insertBefore(script, tag);
6238
6239    }
6240
6241    //download
6242    onHandler(wrapper, 'click', '.hap-download', (e) => downloadHandler(e));
6243
6244    function downloadHandler(e){
6245    	e.preventDefault()
6246
6247    	if(!componentInited) return false;
6248
6249    	var target = e.target,
6250    	item = target.closest('.hap-playlist-item'),
6251    	id = Number(item.getAttribute('data-id'))
6252
6253    	var i, len = playlistDataArr.length, data
6254		for(i = 0;i < len; i++){
6255			if(id == playlistDataArr[i].id){
6256				data = playlistDataArr[i].data
6257				break;
6258			}
6259		}
6260
6261		var title = self.getTitle(data, true)
6262
6263		pwdDownloadData = {}
6264		pwdDownloadData.orig_data = data
6265		pwdDownloadData.type = 'hap-download'
6266		pwdDownloadData.title = title;
6267
6268		if(data && data.download){
6269	    	//check password
6270			if(data.pwd){
6271				pwdAction = 'download'
6272
6273				pwdHolderToggle()
6274			}else{
6275				doDownloadAction()
6276			}
6277		}
6278
6279		return false;
6280
6281    };
6282    function handleDownloadFromSticky(){
6283
6284    	var data = currMediaData
6285
6286    	var title = self.getTitle(data, true)
6287
6288    	pwdDownloadData = {}
6289		pwdDownloadData.orig_data = data
6290		pwdDownloadData.type = 'hap-download'
6291		pwdDownloadData.title = title;
6292
6293		if(data && data.download){
6294	    	//check password
6295			if(data.pwd){
6296				pwdAction = 'download'
6297
6298				pwdHolderToggle()
6299			}else{
6300				doDownloadAction()
6301			}
6302		}
6303
6304		return false;
6305
6306    }
6307
6308    function doDownloadAction(){
6309
6310    	if(window.location.protocol == 'file:'){
6311			console.log('Download to work requires server connection!');
6312		}
6313
6314    	if((settings.restrictDownloadForLoggedInUser && !settings.isUserLoggedIn) || !allowedDownloadVideoUserRole){
6315			toggleRedirectLoginScreen('download');
6316			return;
6317		}
6318
6319    	downloadFile(pwdDownloadData.orig_data.download)
6320
6321    	if(pwdDownloadData.type == 'hap-download'){
6322
6323			if(useGa && typeof gtag !== "undefined"){
6324	        
6325	            gtag('event', 'download', {
6326	            	'send_to': settings.gaTrackingId,
6327	            	'event_category': "Modern audio player: " + settings.instanceName,
6328				    eventLabel: 'title: ' + pwdDownloadData.title,
6329				    'non_interaction': true
6330				});
6331	        }
6332
6333    	}
6334    	else if(pwdDownloadData.type == 'hap-download-count'){
6335
6336			getStats('hap_download_count', pwdDownloadData.id);
6337
6338    	}
6339
6340    	pwdAction = null;
6341    	pwdDownloadData = null;
6342
6343    }
6344
6345    function downloadFile(url) {
6346	    const a = document.createElement('a')
6347	    a.href = url
6348	    a.download = url.split('/').pop()
6349	    document.body.appendChild(a)
6350	    a.click()
6351	    document.body.removeChild(a)
6352	}
6353
6354	//############################################//
6355	/* continous playback */
6356	//############################################//
6357
6358	var eventName = isIOS ? "pagehide" : "beforeunload",
6359	addListener = window.attachEvent || window.addEventListener;
6360
6361	if(useContinousPlayback){
6362
6363		addListener(eventName, function (event) { 
6364		    if(window.event)window.event.cancelBubble = true;
6365			saveSongState()
6366		});
6367
6368		/*if(isIOS){
6369			document.addEventListener("unload", function(e){
6370			    saveSongState()
6371			});
6372		}*/
6373
6374	}else{
6375		localStorage.removeItem(settings.continousKey);
6376	}
6377
6378	function saveSongState(){
6379
6380		if(!componentInited) return false;
6381		if(!mediaType) return false;
6382
6383 		var d = {
6384		    activePlaylist: settings.activePlaylist,
6385		    volume: settings.volume,
6386            activeItem: _HAPPlaylistManager.getCounter(),
6387		    resumeTime: self.getCurrentTime(),
6388		    autoPlay : mediaPlaying
6389		};
6390		//remember last song time for each song
6391		if(continousPlaybackTrackAllSongs){
6392			var i, arr = [], item;
6393			for(i = 0;i<playlistLength; i++){
6394				item = playlistDataArr[i].data; 
6395				if(item.start)arr.push({title: item.title, artist: item.artist, start: item.start});
6396			}
6397			d.lastPositionArr = arr;
6398			d.lastPositionDataPath = lastPositionDataPath;
6399		}
6400		localStorage.setItem(settings.continousKey, JSON.stringify(d));
6401
6402	}
6403	 
6404	//############################################//
6405	/* media session */
6406	//############################################//
6407
6408	if(useMediaSession){
6409
6410	    navigator.mediaSession.setActionHandler('play', function() {
6411	        self.playMedia();
6412	    });
6413
6414	    navigator.mediaSession.setActionHandler('pause', function() {
6415	        self.pauseMedia();
6416	    });
6417
6418	    navigator.mediaSession.setActionHandler('previoustrack', function() {
6419	        self.previousMedia();
6420	    });
6421
6422	    navigator.mediaSession.setActionHandler('nexttrack', function() {
6423	      	self.nextMedia();
6424	    });
6425
6426	    navigator.mediaSession.setActionHandler('seekbackward', function() {
6427	        self.seekBackward();
6428	    });
6429
6430	    navigator.mediaSession.setActionHandler('seekforward', function() {
6431	        self.seekForward();
6432	    });
6433
6434	}
6435
6436	function updateMetadata(){
6437
6438		navigator.mediaSession.metadata = new MediaMetadata({
6439	        title: currMediaData.title,
6440	        artist: currMediaData.artist || '',
6441	        album: currMediaData.album || '',
6442	        artwork: [{src:currMediaData.thumb || ''}]
6443	    });
6444
6445	}
6446
6447	//############################################//
6448	/* tooltip */
6449	//############################################//
6450
6451	var tooltip = wrapper.querySelector('.hap-tooltip');
6452
6453	if(!isMobile){
6454
6455		if(tooltip && window.getComputedStyle(wrapper).position == 'static'){
6456			console.log("player css position is static, therefore tooltip might not work correctly. Please set wrapper css position to other than static if you use tooltip in player.");
6457		}
6458
6459		/*wrapper.querySelectorAll('[data-tooltip]').forEach(el => {
6460
6461			el.addEventListener("mouseenter", function(e){
6462
6463				if(!tooltip)return false;
6464
6465				var item = e.currentTarget
6466
6467				tooltip.style.display = 'block';
6468
6469				tooltip.innerHTML = item.getAttribute('data-tooltip');
6470
6471				var main = wrapper.getBoundingClientRect(),
6472				element = item.getBoundingClientRect();
6473
6474				var t = parseInt(element.top - main.top - tooltip.offsetHeight),
6475				l = parseInt(element.left - main.left - tooltip.offsetWidth / 2 + item.offsetWidth / 2);
6476
6477				//keep within window
6478
6479				var wv = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
6480
6481				if(l + main.left < 0)l = parseInt(element.left - main.left + 15);
6482				else if(l + main.left + tooltip.offsetWidth > wv){
6483					l = parseInt(element.left + element.width - main.left - tooltip.offsetWidth);
6484				}
6485
6486				if(t + main.top < 0)t = parseInt(element.top - main.top + tooltip.offsetHeight + 15);//position below
6487				//else if(t < 0) t = 0;
6488				
6489				tooltip.style.left = l+'px';
6490				tooltip.style.top = t+'px';
6491
6492			})
6493
6494			el.addEventListener("mouseleave", function(e){
6495				if(!tooltip)return false;
6496				tooltip.style.display = 'none';
6497			});	
6498
6499		})*/
6500
6501	}
6502
6503	function showTooltip(item){
6504
6505		var main = wrapper.getBoundingClientRect(),
6506			element = item.getBoundingClientRect();
6507
6508			tooltip.innerHTML = item.getAttribute('data-tooltip');
6509
6510			tooltip.style.display = 'block';
6511
6512			var t = parseInt(element.top - main.top - tooltip.offsetHeight),
6513			l = parseInt(element.left - main.left - tooltip.offsetWidth/2 + item.offsetWidth/2);
6514
6515			//keep within component
6516
6517			//if(l + tooltip.offsetWidth > wrapper.offsetWidth)l = wrapper.offsetWidth - tooltip.offsetWidth;
6518			//else if(l < 0) l = 0;
6519
6520			//if(t + main.top < 0)t = parseInt(element.top - main.top + tooltip.offsetHeight + 15);//position below
6521			//else if(t < 0) t = 0;
6522			
6523			tooltip.style.left = l+'px';
6524			tooltip.style.top = t+'px';
6525
6526	}
6527
6528
6529	//############################################//
6530	/* circle player */
6531	//############################################//
6532	
6533	var circleSeekbar = wrapper.querySelector('.hap-circle-player'),
6534	_HAPCirclePlayer;
6535
6536	if(circleSeekbar && HAPUtils.hasCanvas()){
6537		if(typeof HAPCirclePlayer === 'undefined'){
6538			console.log('Link to circleplayer.js file missing in head tag!');
6539		}else{
6540			_HAPCirclePlayer = new HAPCirclePlayer({settings:settings, parent:wrapper});
6541		}
6542		
6543	}
6544
6545
6546	//############################################//
6547	/* seekbar */
6548	//############################################//
6549
6550	if(circleSeekbar){
6551		var seekbar = wrapper.querySelector('.hap-progress-canvas');
6552	}else{
6553		var seekbar = wrapper.querySelector('.hap-seekbar');
6554	}
6555
6556	if(seekbar){
6557		seekbar.addEventListener(events.downEvent,function(e){
6558			if(!componentInited) return;
6559
6560			if(class_list.indexOf('hap-fixed') > -1 || class_list.indexOf('hap-fixed2') > -1)if(!settings.playerOpened)return false;
6561
6562			if(settings.disableSeekbarInRange && rangeOn)return false;
6563			if(settings.disableSeekbar) return false;
6564			if(settings.disableSongSkip) return false;
6565			if(_HAPAdManager && _HAPAdManager.isAdOn()) return false;
6566			if(isIma && isImaOn) return false;
6567			if(!mediaType) return false;
6568			if(crossFadeOn)cancelCrossFadeSeek();
6569
6570			onDragStartSeek(e);
6571
6572		});
6573
6574		if("ontouchstart" in window){
6575
6576			seekbar.addEventListener('touchstart',function(e){
6577				if(!componentInited) return;
6578
6579				if(e.preventDefault) e.preventDefault();
6580
6581				if(class_list.indexOf('hap-fixed') > -1 || class_list.indexOf('hap-fixed2') > -1)if(!settings.playerOpened)return false;
6582
6583				if(settings.disableSeekbarInRange && rangeOn)return false;
6584				if(settings.disableSeekbar) return false;
6585				if(settings.disableSongSkip) return false;
6586				if(_HAPAdManager && _HAPAdManager.isAdOn()) return false;
6587				if(isIma && isImaOn) return false;
6588				if(!mediaType) return false;
6589				if(crossFadeOn)cancelCrossFadeSeek();
6590
6591				onDragStartSeek(e);
6592
6593			});
6594
6595		}
6596	}
6597
6598	// Start dragging 
6599	function onDragStartSeek(e) {
6600
6601		if(!seekBarDown){	
6602
6603			seekBarDown = true;
6604			_doc.addEventListener(events.moveEvent, onDragMoveSeek)
6605			_doc.addEventListener(events.upEvent, onDragReleaseSeek);	
6606
6607			if("ontouchstart" in window){
6608				_doc.addEventListener('touchmove', onDragMoveSeek)
6609				_doc.addEventListener('touchend', onDragReleaseSeek);	
6610			}	
6611		}
6612	}
6613				
6614	function onDragMoveSeek(e) {	
6615
6616		var point;
6617		if(e.type == 'touchmove'){
6618			var touches = e.changedTouches;
6619
6620			if(touches.length > 1) {
6621				return false;
6622			}
6623			point = touches[0];	
6624			e.preventDefault();				
6625		} else {
6626			point = e;
6627			e.preventDefault();		
6628		}
6629		setProgress(point);
6630
6631		if(tooltip)mouseMoveHandlerSeekTooltip(point);
6632		
6633	}
6634	
6635	function onDragReleaseSeek(e) {
6636
6637		if(seekBarDown){	
6638			seekBarDown = false;			
6639			_doc.removeEventListener(events.moveEvent, onDragMoveSeek)
6640			_doc.removeEventListener(events.upEvent, onDragReleaseSeek);	
6641
6642			if("ontouchstart" in window){
6643				_doc.removeEventListener('touchmove', onDragMoveSeek)
6644				_doc.removeEventListener('touchend', onDragReleaseSeek);	
6645			}	
6646			
6647			var point;
6648			if(e.type == 'touchend'){
6649				var touches = e.changedTouches;
6650
6651				// If touches more then one, so stop sliding and allow browser do default action
6652				if(touches.length > 1) {
6653					return false;
6654				}
6655				point = touches[0];	
6656				e.preventDefault();				
6657			} else {
6658				point = e;
6659				e.preventDefault();		
6660			}
6661			setProgress(point, true);
6662
6663			if(tooltip)tooltip.style.display = 'none';
6664
6665		}
6666		
6667	}	
6668
6669	function setProgress(point, update, final) {
6670
6671		if(circleSeekbar){
6672			var v = _HAPCirclePlayer.setProgress(point);
6673		}else{
6674			if(final){
6675				var v = point;
6676			}else{
6677
6678				if(settings.useWaveSeekbar){
6679					var rect = seekbarWave.getBoundingClientRect();
6680					var seekPercent = point.pageX - rect.left;
6681				}else{
6682					var rect = progressBg.getBoundingClientRect();
6683					var seekPercent = point.pageX - rect.left;
6684				}
6685
6686				if(seekPercent < 0) seekPercent = 0;
6687				else if(seekPercent > seekBarSize) seekPercent = seekBarSize;
6688				var v = Math.max(0, Math.min(1, seekPercent / seekBarSize));
6689			}
6690		}
6691
6692		if(update){
6693
6694			var t,d;
6695
6696			if(mediaType == 'youtube'){
6697				
6698				if(youtubePlayer && youtubeReady)youtubePlayer.seekTo(v * youtubePlayer.getDuration());
6699
6700			}else{
6701
6702				if(activeAudio && !isNaN(activeAudio.duration)){
6703
6704					d = activeAudio.duration;
6705					t = v * d;
6706					if(t > d - 2)t = d - 2;//prevent seeking to end
6707				
6708					try{
6709						activeAudio.currentTime = t;
6710					}catch(er){console.log(er)}
6711				
6712				}
6713			}
6714
6715			if(progressLevel)if(!mediaStarted)progressLevel.style.width = v * seekBarSize + 'px';
6716
6717			if(seekingFixed){
6718				_HAPStickyPlayer.setProgressData(v, null)
6719			}
6720
6721		}else{
6722
6723			if(progressLevel)progressLevel.style.width = (v * seekBarSize) + 'px';//redraw seekbar while user seeking, we dont want to update seek position until seeking ends for performance
6724
6725			if(seekingFixed){//we are seeking fixed
6726
6727				//lastActiveTrackInstance.progressLevel.style.width = (v*100)+'%';
6728
6729			}else{//we are seeking song list
6730
6731				if(settings.useStickyPlayer && window['hap_fixed_active_player'] == settings.instanceName){
6732					if(mediaStarted){
6733						_HAPStickyPlayer.setProgressData(v, true)
6734					}
6735				}
6736
6737			}
6738		}
6739
6740	}
6741	
6742	if(settings.useWaveSeekbar){
6743
6744		var w = seekbar.offsetWidth, h = seekbar.offsetHeight
6745
6746		//bg
6747		var canvas = document.createElement('canvas');
6748		seekbarWaveCanvasBg = canvas;
6749		canvas.width = w;
6750		canvas.height = h;
6751		canvas.classList.add("hap-canvas-seekbar-bg");
6752		seekbarWaveCtx = canvas.getContext('2d');
6753		seekbarWaveCtx.fillStyle = settings.waveBgColor;
6754		seekbarWave.prepend(canvas)
6755
6756		//progress
6757	    var canvas2 = document.createElement('canvas');
6758	    seekbarWaveCanvasProgress = canvas2
6759	    canvas2.width = w;
6760		canvas2.height = h;
6761		canvas2.classList.add("hap-canvas-seekbar-progress");
6762		seekbarWaveCtx2 = canvas2.getContext('2d');
6763		seekbarWaveCtx2.fillStyle = settings.waveProgressColor;
6764		seekbarWaveProgress.prepend(canvas2)
6765
6766	}
6767
6768	function clearSeekbar(){
6769
6770		if(seekbarWaveCtx){
6771			seekbarWaveCtx.clearRect(0, 0, seekbarWaveCanvasBg.width, seekbarWaveCanvasBg.height);
6772			seekbarWaveCtx.beginPath();
6773		}
6774		
6775		if(seekbarWaveCtx2){
6776			seekbarWaveCtx2.clearRect(0, 0, seekbarWaveCanvasProgress.width, seekbarWaveCanvasProgress.height);
6777			seekbarWaveCtx2.beginPath();
6778		}
6779
6780		if(waveformLoader)waveformLoader.classList.remove('hap-waveform-loader-hidden')
6781
6782		if(settings.useStickyPlayer){		 
6783			_HAPStickyPlayer.clearSeekbar()
6784		}
6785
6786	}
6787
6788	function drawWaveSeekbar(){
6789
6790		var w = seekbar.offsetWidth, h = seekbar.offsetHeight
6791
6792		seekbarWaveCanvasBg.width = w
6793		seekbarWaveCanvasProgress.width = w
6794
6795		seekbarWaveCanvasBg.height = h
6796		seekbarWaveCanvasProgress.height = h
6797
6798		waveOptions.width = w;
6799		waveOptions.height = h;
6800
6801		seekbarWaveCtx.fillStyle = settings.waveBgColor;
6802		seekbarWaveCtx2.fillStyle = settings.waveProgressColor;
6803
6804		peakData = currMediaData.peaks;
6805
6806		if(waveOptions.barWidth > 0){
6807			HAPUtils.drawBars(seekbarWaveCtx, seekbarWaveCtx2, waveOptions, peakData.split(','), 0, w)
6808		}else{
6809			HAPUtils.drawWave(seekbarWaveCtx, seekbarWaveCtx2, waveOptions, peakData.split(','), 0, w)
6810		} 
6811	}
6812
6813
6814
6815
6816
6817
6818	//############################################//
6819	/* seekbar tooltip */
6820	//############################################//
6821
6822	if(seekbar && tooltip){
6823
6824		if(!isMobile){
6825
6826			seekbar.addEventListener('mouseover', function mouseOverHandlerSeek(){
6827				if(seekBarDown)return;
6828				if(_HAPAdManager && _HAPAdManager.isAdOn()) return false;
6829				if(isIma && isImaOn) return false;
6830
6831				if(class_list.indexOf('hap-fixed') > -1 || class_list.indexOf('hap-fixed2') > -1)if(!settings.playerOpened)return false;
6832
6833				seekbar.addEventListener(events.moveEvent, mouseMoveHandlerSeekTooltip)
6834				seekbar.addEventListener('mouseout', mouseOutHandlerSeek);
6835
6836				_doc.addEventListener('mouseout', mouseOutHandlerSeek);
6837			});
6838
6839		}
6840		
6841	}
6842
6843	function mouseOutHandlerSeek() {
6844		seekbar.removeEventListener(events.moveEvent, mouseMoveHandlerSeekTooltip)
6845		seekbar.removeEventListener('mouseout', mouseOutHandlerSeek);
6846
6847		_doc.removeEventListener('mouseout', mouseOutHandlerSeek);
6848		tooltip.style.display = 'none';
6849	}
6850
6851	function mouseMoveHandlerSeekTooltip(e){
6852
6853		tooltip.style.display = 'block';
6854
6855		var main = wrapper.getBoundingClientRect(),
6856			element = seekbar.getBoundingClientRect();
6857
6858		if(circleSeekbar){
6859
6860			var newPercent = _HAPCirclePlayer.trackTooltip(e);
6861
6862			var t = parseInt(element.top - main.top - tooltip.offsetHeight - 20),
6863				l = parseInt(element.left - main.left - tooltip.offsetWidth / 2 + seekbar.offsetWidth / 2);
6864
6865			//keep within component
6866			if(l + tooltip.offsetWidth > main.width)l = wrapper.offsetWidth - tooltip.offsetWidth;
6867			else if(l < 0) l = 0;
6868
6869			if(t + main.top < 0)t = parseInt(element.top - main.top + tooltip.offsetHeight + 20);//position below
6870			//if(t < 0) t = 0;
6871
6872			tooltip.style.left = l+'px'
6873			tooltip.style.top = t+'px'
6874			
6875		}else{
6876
6877			if(settings.useWaveSeekbar){
6878				var offsets = seekbarWave.getBoundingClientRect();
6879			}else{
6880				var offsets = progressBg.getBoundingClientRect();
6881			}
6882
6883			var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
6884
6885			var s = e.pageX - offsets.left - scrollLeft;
6886			
6887			if(!HAPUtils.isNumber(s))return false;
6888			if(s < 0) s = 0;
6889			else if(s > seekBarSize) s = seekBarSize;
6890			var newPercent = Math.max(0, Math.min(1, s / seekBarSize));
6891
6892			var l = parseInt(e.pageX - scrollLeft - main.left - (tooltip.offsetWidth / 2),10),
6893				t = parseInt(element.top - main.top - tooltip.offsetHeight,10) - 10;
6894
6895			//restrain
6896			if(l < element.left - main.left)l = element.left - main.left;
6897			else if(l > element.left - main.left + seekbar.clientWidth - tooltip.offsetWidth) l = element.left - main.left + seekbar.clientWidth - tooltip.offsetWidth;
6898			
6899			if(t + main.top < 0)t = parseInt(element.top - main.top + tooltip.offsetHeight + 15);//position below
6900			//if(t < 0) t = 0;
6901
6902			tooltip.style.left = l+'px'
6903			tooltip.style.top = t+'px'
6904
6905		}
6906
6907		if(!HAPUtils.isNumber(newPercent))return false;
6908
6909		if(mediaType == 'youtube'){
6910			
6911			if(youtubePlayer && youtubeReady)var d = youtubePlayer.getDuration(), t = newPercent * d;
6912
6913		}else{
6914
6915			if(activeAudio){
6916				var d = activeAudio.duration, t = newPercent * d;
6917			}
6918		}
6919
6920		if(!HAPUtils.isNumber(t) || !HAPUtils.isNumber(d)){
6921			tooltip.style.display = 'none';
6922			return;
6923		} 
6924
6925		tooltip.innerHTML = HAPUtils.formatTime(t)+' / '+HAPUtils.formatTime(d);
6926		
6927	}
6928	
6929	//############################################//
6930	/* volume */
6931	//############################################//
6932
6933	if(wrapper.querySelector('.hap-volume-seekbar') || wrapper.querySelector('.hap-volume-toggle')){
6934
6935		_volumeSlider = new HAPVolumeSlider({volume:settings.volume, container:wrapper, tooltip: tooltip, parent: self})
6936
6937		_volumeSlider.addEventListener('HAPVolumeSlider.VOLUME_CHANGE', function(data){
6938			settings.volume = data.volume;
6939			self.setVolume(settings.volume)
6940		})
6941
6942		_volumeSlider.addEventListener('HAPVolumeSlider.TOGGLE_MUTE', function(e){
6943			self.toggleMute()
6944		})
6945	}
6946
6947	
6948
6949
6950	
6951
6952	//############################################//
6953	/* range (a-b loop) */
6954	//############################################//
6955
6956	var range_handle_a = wrapper.querySelector('.hap-range-handle-a'),
6957	range_handle_b = wrapper.querySelector('.hap-range-handle-b'),
6958	range_handle_a_width,
6959	range_handle_b_width,
6960	range_a_time,
6961	range_b_time,
6962	range_a_pos,
6963	range_b_pos,
6964	rangeOn,
6965	useRange,
6966	rangeSet,
6967	rangeBg = wrapper.querySelector('.hap-range-bg'),
6968	rangeSize,
6969	rangeLevel = wrapper.querySelector('.hap-range-level'),
6970	rangeMinTime = wrapper.querySelector('.hap-range-min-time'),
6971	rangeMaxTime = wrapper.querySelector('.hap-range-max-time')
6972
6973	if(range_handle_a && range_handle_b){
6974		range_handle_a_width = parseInt(range_handle_a.getAttribute('data-width'),10)
6975		range_handle_b_width = parseInt(range_handle_b.getAttribute('data-width'),10)
6976	}
6977
6978	self.toggleRange = function(){//open / close range
6979		if(!mediaType)return;
6980
6981		if(rangeOn){
6982
6983			rangeHolder.addEventListener('transitionend', function cb(e){
6984				e.currentTarget.removeEventListener(e.type, cb);
6985				rangeHolder.classList.remove('hap-holder-animate')
6986
6987				self.resetRange()
6988			})
6989
6990			rangeHolder.classList.remove('hap-holder-shown')
6991
6992			rangeSet = true;
6993
6994		}else{
6995
6996			rangeHolder.classList.add('hap-holder-animate')
6997			
6998			setTimeout(function(){
6999				rangeHolder.classList.add('hap-holder-shown')
7000			},50)
7001
7002			rangeSize = rangeBg.offsetWidth;
7003			
7004			range_a_pos = range_handle_a.offsetLeft;
7005			range_b_pos = range_handle_b.offsetLeft;
7006
7007			range_a_time = getRangeTime(range_a_pos);
7008			range_b_time = getRangeTime(range_b_pos);
7009
7010			rangeSet = false;
7011
7012		}
7013		rangeOn = !rangeOn;
7014
7015	}
7016
7017	this.getRangeOn = function(){
7018		return rangeOn;
7019	}
7020
7021	function getRangeTime(x){//get media time by seek percent
7022		
7023		var v = Math.max(0, Math.min(1, x / rangeSize)), c, d;
7024
7025		if(mediaType == 'youtube'){
7026			d = youtubePlayer.getDuration();
7027		}else{
7028			d = activeAudio.duration;
7029		}
7030		c = v * d;
7031		return c;
7032	}
7033
7034	//range api
7035
7036	self.resetRange = function(){//reset range to default position
7037		rangeOn = false;
7038		rangeSet = true;
7039		range_handle_a.style.left = 0;
7040		range_handle_b.style.left = rangeSize + 'px';
7041		rangeMinTime.innerHTML = '00:00';
7042		rangeMaxTime.innerHTML = '00:00';
7043
7044		range_a_pos = range_handle_a.offsetLeft;
7045		range_b_pos = range_handle_b.offsetLeft;
7046
7047		rangeLevel.style.left = 0 + 'px'
7048		rangeLevel.style.right = 0 + 'px'
7049	}
7050
7051	self.setRange = function(a,b){//set range
7052		
7053		var d = self.getDuration();
7054
7055		if(typeof d === 'undefined')return false;
7056
7057		if(a < 0)a = 0;
7058		if(b > d)b = d;
7059		
7060		range_a_time = a;
7061		range_b_time = b;
7062
7063		rangeSet = false;
7064
7065		range_handle_a.style.left = (range_a_time / d) * rangeSize + 'px';
7066		range_handle_b.style.left = (range_b_time / d) * rangeSize + 'px';
7067
7068		rangeMinTime.innerHTML = HAPUtils.formatTime(a);
7069		rangeMaxTime.innerHTML = HAPUtils.formatTime(b);
7070
7071		var l = parseInt(range_handle_a.style.left,10),
7072		r = parseInt(range_handle_b.style.left,10)
7073
7074		rangeLevel.style.left = l + 'px';
7075		rangeLevel.style.right = rangeSize - r + 'px';
7076
7077		if(!rangeOn)self.toggleRange();
7078
7079	}
7080
7081	self.resizeRange = function(){
7082		
7083		var d = self.getDuration();
7084
7085		if(typeof d === 'undefined')return false;
7086		if(typeof range_a_time === 'undefined')return false;
7087		if(typeof range_b_time === 'undefined')return false;
7088
7089		rangeSize = rangeBg.offsetWidth;
7090
7091		range_handle_a.style.left = (range_a_time / d) * rangeSize + 'px';
7092		range_handle_b.style.left = (range_b_time / d) * rangeSize + 'px';
7093
7094		rangeMinTime.innerHTML = HAPUtils.formatTime(range_a_time);
7095		rangeMaxTime.innerHTML = HAPUtils.formatTime(range_b_time);
7096
7097		var l = parseInt(range_handle_a.style.left,10),
7098		r = parseInt(range_handle_b.style.left,10)
7099
7100		rangeLevel.style.left = l + 'px'
7101		rangeLevel.style.right = rangeSize - r + 'px'
7102
7103	}
7104
7105	self.getRange = function(){//get range times
7106		if(typeof range_a_time !== 'undefined' && typeof range_b_time !== 'undefined')return [range_a_time, range_b_time];
7107		else return null;
7108	}
7109
7110	if(range_handle_a){
7111
7112		useRange = true;
7113
7114		_HAPRangeSlider = new HAPRangeSlider({
7115			settings:settings,
7116			range_handle_a:range_handle_a, 
7117			range_handle_b:range_handle_b, 
7118		});
7119
7120		_HAPRangeSlider.addEventListener('HAPRangeSlider.RANGE_CHANGE', function(data){
7121
7122			var rect = rangeBg.getBoundingClientRect()
7123
7124			var pos = data.point.pageX - rect.left, m, elem = data.elem;
7125			if(pos < 0)pos = 0;
7126			else if(pos > rangeSize)pos = rangeSize;
7127
7128			if(elem.classList.contains('hap-range-handle-a')){
7129
7130				if(pos > parseInt(range_handle_b.style.left,10) - 3)pos = parseInt(range_handle_b.style.left,10) - 3;
7131				range_a_pos = pos;
7132				range_a_time = getRangeTime(range_a_pos);
7133
7134				rangeMinTime.innerHTML = HAPUtils.formatTime(range_a_time);
7135
7136			}else{
7137
7138				if(pos < parseInt(range_handle_a.style.left,10) + 4)pos = parseInt(range_handle_a.style.left,10) + 4;
7139				range_b_pos = pos;
7140				range_b_time = getRangeTime(range_b_pos);
7141
7142				rangeMaxTime.innerHTML = HAPUtils.formatTime(range_b_time);
7143
7144			}
7145
7146			rangeLevel.style.left = range_a_pos + 'px'
7147			rangeLevel.style.right = rangeSize - range_b_pos + 'px'
7148
7149			elem.style.left = pos + 'px';
7150
7151		});
7152
7153	}
7154
7155
7156	//############################################//
7157	/* playback rate */
7158	//############################################//
7159
7160	var playbackRateSeekbar = wrapper.querySelector('.hap-playback-rate-seekbar')
7161	
7162	if(wrapper.querySelector('.hap-playback-rate-min'))wrapper.querySelector('.hap-playback-rate-min').innerHTML = settings.playbackRateMin
7163	if(wrapper.querySelector('.hap-playback-rate-max'))wrapper.querySelector('.hap-playback-rate-max').innerHTML = settings.playbackRateMax
7164
7165	if(playbackRateSeekbar){
7166
7167		_HAPPlaybackRateSlider = new HAPPlaybackRateSlider({
7168			settings:settings,
7169			wrapper: wrapper, 
7170			e_holder: playbackRateHolder,
7171			seekbarHolder: wrapper.querySelector('.hap-playback-rate-wrap'),
7172			seekbar:playbackRateSeekbar, 
7173			sliderBg:wrapper.querySelector('.hap-playback-rate-bg'), 
7174			sliderLevel:wrapper.querySelector('.hap-playback-rate-level'), 
7175			isVertical:playbackRateSeekbar.classList.contains('hap-vertical'), 
7176			tooltip: tooltip,
7177		});
7178
7179		_HAPPlaybackRateSlider.addEventListener('HAPPlaybackRateSlider.RANGE_CHANGE', function(data){
7180			self.setPlaybackRate(data.value);
7181		});
7182
7183	}
7184
7185
7186	//############################################//
7187	/* context menu */
7188	//############################################//
7189
7190	if(settings.disableRightClickOverPlayer){
7191
7192		wrapper.addEventListener("contextmenu", function(){
7193			return false;
7194		});
7195
7196	}
7197
7198	//############################################//
7199	/* controls */
7200	//############################################//
7201
7202	if(settings.useKeyboardNavigationForPlayback && settings.keyboardControls.length){
7203
7204		if(settings.useGlobalKeyboardControls){
7205
7206			_doc.addEventListener('keydown',function(e) {
7207				return handleKeyboard(e)
7208			});	
7209
7210		}else{
7211
7212			wrapper.addEventListener('mouseenter', function(){
7213				_doc.addEventListener('keydown', handleKeyboard);
7214			})
7215			wrapper.addEventListener('mouseleave', function(){
7216				_doc.removeEventListener('keydown', handleKeyboard);
7217			})
7218		}
7219
7220	}
7221
7222	function handleKeyboard(e){
7223		
7224		if(!componentInited)return false;
7225		//if(playlistSelectorOpened)return false;
7226
7227		var _keycode = e.keyCode, target = e.target;
7228
7229		if(target.classList.contains('hap-search-filter')) return true;
7230		if(target.classList.contains('hap-input-field')) return true;
7231
7232		if(settings.modifierKey){
7233
7234			var i, len = settings.keyboardControls.length, obj
7235			for(i = 0; i < len; i++){
7236				obj = settings.keyboardControls[i]
7237
7238				if(e[settings.modifierKey] && _keycode == obj.keycode){
7239
7240					if (typeof self[obj.action] === "function") { 
7241					    self[obj.action]();
7242					}
7243					else{
7244						return true;
7245					}
7246
7247					break;
7248
7249				}
7250			}
7251
7252		}else{
7253
7254			var i, len = settings.keyboardControls.length, obj
7255			for(i = 0; i < len; i++){
7256				obj = settings.keyboardControls[i]
7257
7258				if(_keycode == obj.keycode){
7259
7260					if (typeof self[obj.action] === "function") { 
7261					    self[obj.action]();
7262					}
7263					else{
7264						return true;
7265					}
7266
7267					break;
7268				}
7269			}
7270
7271		}
7272
7273		return false;
7274
7275	}
7276
7277	function handleKeyboard2(e){
7278
7279		if(!componentInited) return false;
7280
7281		var _keycode = e.keyCode, target = e.target;
7282
7283		if(target.classList.contains('hap-search-filter')) return true;
7284		if(target.classList.contains('hap-input-field')) return true;
7285
7286		if(settings.modifierKey){
7287
7288			var i, len = settings.keyboardControls.length, obj
7289			for(i = 0; i < len; i++){
7290				obj = settings.keyboardControls[i]
7291
7292				if(e[settings.modifierKey] && _keycode == obj.keycode){
7293
7294					if(obj.action == 'seekBackward') {//left arrow
7295					  	self.seekBackward(settings.seekTime);
7296					} 
7297					else if(obj.action == 'seekForward') {//right arrow
7298						self.seekForward(settings.seekTime);
7299					}
7300					else if(obj.action == 'togglePlayback') {//space
7301
7302						self.togglePlayback();
7303
7304					}else if (obj.action == 'volumeUp'){//up arrow
7305
7306						settings.volume += .1;
7307						if(settings.volume > 1) settings.volume = 1;
7308						self.setVolume(settings.volume)
7309
7310					}else if (obj.action == 'volumeDown'){//down arrow
7311
7312						settings.volume -= .1;
7313						if(settings.volume < 0) settings.volume = 0;
7314						self.setVolume(settings.volume)
7315
7316					}
7317					else if(obj.action == 'toggleMute') {//m
7318						self.toggleMute();
7319					}
7320					else if(obj.action == 'nextMedia') {//page up
7321						self.nextMedia();
7322					}
7323					else if(obj.action == 'previousMedia') {//page down
7324						self.previousMedia();
7325					}
7326					else if(obj.action == 'rewind') {//r
7327						self.seek(0);
7328					}
7329
7330					break;
7331				}
7332			}
7333
7334		}else{
7335
7336			var i, len = settings.keyboardControls.length, obj
7337			for(i = 0; i < len; i++){
7338				obj = settings.keyboardControls[i]
7339				if(_keycode == obj.keycode){
7340
7341					if(obj.action == 'seekBackward') {//left arrow
7342					  	self.seekBackward(settings.seekTime);
7343					} 
7344					else if(obj.action == 'seekForward') {//right arrow
7345						self.seekForward(settings.seekTime);
7346					}
7347					else if(obj.action == 'togglePlayback') {//space
7348
7349						self.togglePlayback();
7350
7351					}else if (obj.action == 'volumeUp'){//up arrow
7352
7353						settings.volume += .1;
7354						if(settings.volume > 1) settings.volume = 1;
7355						self.setVolume(settings.volume)
7356
7357					}else if (obj.action == 'volumeDown'){//down arrow
7358
7359						settings.volume -= .1;
7360						if(settings.volume < 0) settings.volume = 0;
7361						self.setVolume(settings.volume)
7362
7363					}
7364					else if(obj.action == 'toggleMute') {//m
7365						self.toggleMute();
7366					}
7367					else if(obj.action == 'nextMedia') {//page up
7368						self.nextMedia();
7369					}
7370					else if(obj.action == 'previousMedia') {//page down
7371						self.previousMedia();
7372					}
7373					else if(obj.action == 'rewind') {//r
7374						self.seek(0);
7375					}
7376					else{
7377						return true;
7378					}
7379
7380					break;
7381				}
7382			}
7383	
7384		}
7385
7386		e.preventDefault()
7387
7388	}
7389	
7390	var buttons = [
7391		wrapper.querySelector('.hap-next-toggle'),
7392		wrapper.querySelector('.hap-prev-toggle'),
7393		wrapper.querySelector('.hap-skip-forward'),
7394		wrapper.querySelector('.hap-skip-backward'),
7395		wrapper.querySelector('.hap-playlist-toggle'),
7396		wrapper.querySelector('.hap-playlist-close'),
7397		shareToggle,
7398		embedToggle,
7399		wrapper.querySelector('.hap-embed-close'),
7400		wrapper.querySelector('.hap-share-close'),
7401		wrapper.querySelector('.hap-share-copy'),
7402		wrapper.querySelector('.hap-playback-rate-close'),
7403		wrapper.querySelector('.hap-range-close'),
7404		wrapper.querySelector('.hap-lyrics-close'),
7405		wrapper.querySelector('.hap-video-close'),
7406		wrapper.querySelector('.hap-pwd-close'),
7407		wrapper.querySelector('.hap-pwd-confirm'),
7408		wrapper.querySelector('.hap-playlist-selector-toggle'),
7409		wrapper.querySelector('.hap-playlist-selector-close'),
7410		playbackRateToggle,
7411		playbackRateToggleNum,
7412		rangeToggle,
7413		popupToggle,
7414		sortAlpha,
7415		playbackToggle,
7416		loopToggle,
7417		randomToggle,
7418		lyricsToggle,
7419		videoToggle
7420	];
7421
7422	var shareList = wrapper.querySelectorAll('.hap-share-item'),
7423	shares = [...shareList]; 
7424	
7425	var buttonArr = buttons.concat(shares);
7426	
7427	var redirectLogin = wrapper.querySelectorAll('.hap-redirect-login-cancel'),
7428	red_log = [...redirectLogin]; 
7429
7430	if(red_log.length){
7431		var t = 0;
7432		for(t; t < red_log.length; t++){
7433			buttonArr.push(red_log[t])
7434		}
7435	}
7436
7437	var btn,len = buttonArr.length,i;
7438	for(i=0;i<len;i++){
7439		btn = buttonArr[i]
7440		if(btn){
7441			btn.style.cursor = 'pointer';
7442			btn.addEventListener('click', clickControls);
7443			if(!isMobile){
7444				btn.addEventListener('mouseenter', overControls)
7445				btn.addEventListener('mouseleave', outControls);
7446			}
7447		}
7448		
7449	}
7450
7451	function overControls(e){
7452		if(!tooltip)return;
7453
7454		if(e.target.getAttribute('data-tooltip'))showTooltip(e.target)
7455		else if(e.target.classList.contains('hap-random-toggle')){
7456			showTooltip(randomToggle.querySelector('.hap-btn[style*="display: block"]'));
7457		}
7458		else if(e.target.classList.contains('hap-loop-toggle')){
7459			showTooltip(loopToggle.querySelector('.hap-btn[style*="display: block"]'));
7460		}	
7461		else if(e.target.classList.contains('hap-sort-alpha')){
7462			showTooltip(sortAlpha.querySelector('.hap-btn[style*="display: block"]'));
7463		}	
7464
7465	}
7466	function outControls(e){
7467		if(!tooltip)return;
7468		tooltip.style.display = 'none';
7469	}
7470
7471	//############################################//
7472	/* variation */
7473	//############################################//
7474
7475	onHandler(wrapper, 'click', '.hap-audio-variation', (e) => handleVariationClick(e))
7476
7477	function handleVariationClick(e){
7478
7479		cleanMedia()
7480
7481		activeVariation = e.target.closest('.hap-audio-variation')
7482
7483		activeVariation.classList.add('hap-active-variation')
7484
7485		var url = activeVariation.getAttribute('data-path')
7486
7487		currMediaData = {type:'audio', path: url};
7488		mediaType = 'audio';
7489		mediaPath = url;
7490
7491		initAudio();
7492
7493	}
7494
7495	
7496
7497	if(playlistContent){
7498		playlistContent.addEventListener('click', function(e){
7499
7500			settings.playlistItemClickSelectorArr.forEach(function(entry) {
7501			    if(e.target.classList.contains(entry)){
7502			        clickPlaylistItem(e.target.closest('.hap-playlist-item'));
7503			    }
7504			});
7505		})
7506	}
7507	
7508	function clickPlaylistItem(target){
7509
7510		if(!componentInited)return false;
7511		if(settings.disableSongSkip) return false;
7512		if(_HAPAdManager && _HAPAdManager.isAdOn()) return false;
7513		if(isIma && isImaOn) return false;
7514
7515		if(crossFadeOn)cancelCrossFadeAdvance();
7516
7517		if(isIOS && currMediaData && (currMediaData.adMid || currMediaData.adEnd) && !iosAdMidFixStart && _HAPAdManager){
7518			iosAdMidFixStart = true;
7519			_HAPAdManager.forceAdMidAudio();//ads 
7520		}
7521
7522		if(target.classList.contains('hap-playlist-item-disabled')){
7523
7524			if(hasVariation){
7525
7526				//if variations play and we click playlist item
7527
7528			}else{
7529
7530				if(settings.togglePlaybackOnPlaylistItem){
7531					if(mediaStarted){
7532						self.togglePlayback()
7533						return false;
7534					}
7535				}
7536				else{//dont reload whole audio process
7537
7538					//check password
7539					if(currMediaData.pwd){
7540						pwdHolderToggle()
7541						return false;
7542					}
7543
7544					self.seek(0)
7545					if(!mediaPlaying)self.playMedia()
7546					return false;
7547				}
7548			}
7549		}
7550
7551		playlistClick = true;
7552		autoPlay = true;
7553		settings.autoPlay = true;
7554
7555		//if(typeof mediaCounter != undefined)beforeSongAdvanceAction();
7556
7557		//############################################//
7558		/* accordion */
7559		//############################################//
7560
7561		if(accordionMode){
7562			//check if we are still in same accordion folder. If not, switch data to new playlist
7563			var aid = target.closest('.hap-accordion-item').getAttribute('data-id');
7564			if(aid != accordioPlaylistId){
7565
7566				accordioPlaylistId = aid;
7567
7568				playlistDataArr = playlistDataAccordion[accordioPlaylistId].data;
7569
7570				playlistLength = playlistDataArr.length;
7571				var i;
7572				for(i = 0;i < playlistLength; i++){
7573					playlistDataArr[i].id = i;
7574				}
7575			
7576				_HAPPlaylistManager.setPlaylistItems(playlistLength);//we treat each accordion as new playlist
7577
7578			}
7579
7580		}
7581
7582		//############################################//
7583		/* accordion */
7584		//############################################//
7585
7586		var id = target.getAttribute('data-id');
7587		_HAPPlaylistManager.processPlaylistRequest(id);
7588
7589
7590
7591		//preset mods
7592		if(class_list.indexOf('hap-art-narrow-light') > -1){
7593			self.togglePlaylist();	
7594		}
7595
7596		self.fireEvent('clickPlaylistItem', [{instance:self, instanceName:settings.instanceName}]);
7597	}
7598
7599	function pwdHolderToggle(hide){
7600
7601		if(!pwdHolder){
7602			console.log('this skin does not contain password screen!')
7603			return false;
7604		}
7605
7606		if(hide){
7607
7608			pwdHolder.addEventListener('transitionend', function cb(e){
7609				e.currentTarget.removeEventListener(e.type, cb);
7610				pwdHolder.classList.remove('hap-holder-animate')
7611			})
7612
7613			pwdHolder.classList.remove('hap-holder-shown')
7614
7615			pwdField.value = '';
7616
7617		}else{
7618
7619			pwdHolder.classList.add('hap-holder-animate')
7620			
7621			setTimeout(function(){
7622				pwdHolder.classList.add('hap-holder-shown')
7623			},50)
7624
7625		}
7626
7627	}
7628
7629	function overPlaylistItem(e){
7630
7631		e.preventDefault();
7632		if(!componentInited) return false;
7633
7634		e.currentTarget.closest('.hap-playlist-item').classList.add('hap-playlist-item-selected');
7635
7636		self.fireEvent('overPlaylistItem', [{instance:self, instanceName:settings.instanceName}]);
7637	}
7638	
7639	function outPlaylistItem(e){
7640		e.preventDefault();
7641		if(!componentInited) return false;
7642
7643		var item = e.currentTarget.closest('.hap-playlist-item');
7644
7645		if(!item.classList.contains('hap-playlist-item-disabled'))item.classList.remove('hap-playlist-item-selected');
7646
7647		self.fireEvent('outPlaylistItem', [{instance:self, instanceName:settings.instanceName}]);
7648	}
7649
7650	function clickControls(e){
7651		if(!componentInited) return false;
7652		
7653		var currentTarget = e.currentTarget;
7654
7655		if(currentTarget.classList.contains('hap-playlist-toggle') || currentTarget.classList.contains('hap-playlist-close')){	
7656
7657			self.togglePlaylist();
7658
7659		}else if(currentTarget.classList.contains('hap-skip-backward')){	
7660
7661			self.seekBackward(settings.seekTime);
7662
7663		}else if(currentTarget.classList.contains('hap-skip-forward')){	
7664
7665			self.seekForward(settings.seekTime);	
7666
7667		}else if(currentTarget.classList.contains('hap-prev-toggle')){	
7668
7669			if(isIOS && currMediaData && (currMediaData.adMid || currMediaData.adEnd) && !iosAdMidFixStart && _HAPAdManager){
7670				iosAdMidFixStart = true;
7671				_HAPAdManager.forceAdMidAudio();//ads 
7672			}
7673
7674			if(_HAPAdManager && _HAPAdManager.isAdOn()) return false;
7675			if(isIma && isImaOn) return false;
7676
7677			self.previousMedia();
7678
7679		}else if(currentTarget.classList.contains('hap-playback-toggle')){	
7680
7681			self.togglePlayback();
7682
7683		}else if(currentTarget.classList.contains('hap-next-toggle')){
7684
7685			if(isIOS && currMediaData && (currMediaData.adMid || currMediaData.adEnd) && !iosAdMidFixStart && _HAPAdManager){
7686				iosAdMidFixStart = true;
7687				_HAPAdManager.forceAdMidAudio();//ads 
7688			}
7689
7690			if(_HAPAdManager && _HAPAdManager.isAdOn()) return false;
7691			if(isIma && isImaOn) return false;
7692
7693			self.nextMedia();
7694
7695		}else if(currentTarget.classList.contains('hap-lyrics-toggle') || currentTarget.classList.contains('hap-lyrics-close')){	
7696
7697			self.toggleLyrics();
7698
7699		}else if(currentTarget.classList.contains('hap-video-toggle') || currentTarget.classList.contains('hap-video-close')){	
7700
7701			self.toggleVideo();
7702
7703		}else if(currentTarget.classList.contains('hap-loop-toggle')){	
7704
7705			if(loopToggle){
7706				loopToggle.querySelectorAll('.hap-btn').forEach(function(el){
7707					el.style.display = 'none';
7708				})
7709			}
7710			
7711			loopStateCounter++;
7712			if(loopStateCounter > loopStateArr.length - 1)loopStateCounter = 0;
7713			settings.loopState = loopStateArr[loopStateCounter];
7714
7715			if(loopToggle)loopToggle.querySelector('.hap-btn-loop-'+settings.loopState).style.display = 'block';
7716
7717			_HAPPlaylistManager.setLooping(settings.loopState);
7718
7719			if(tooltip)showTooltip(loopToggle.querySelector('.hap-btn[style*="display: block"]'));
7720
7721		}else if(currentTarget.classList.contains('hap-random-toggle')){	
7722
7723			self.toggleRandom()
7724
7725			if(tooltip)showTooltip(randomToggle.querySelector('.hap-btn[style*="display: block"]'));
7726
7727		}else if(currentTarget.classList.contains('hap-sort-alpha')){	
7728
7729			if(HAPUtils.isEmpty(sortOrder))sortOrder = 'title-asc';
7730			else if(sortOrder == 'title-asc')sortOrder = 'title-desc';
7731			else if(sortOrder == 'title-desc')sortOrder = 'title-asc';
7732			self.sort(sortOrder);
7733
7734			if(tooltip)showTooltip(sortAlpha.querySelector('.hap-btn[style*="display: block"]'));
7735
7736		}else if(currentTarget.classList.contains('hap-popup-toggle')){	
7737
7738			if(settings.isPopup) return false;
7739			hapOpenPopup(settings, self);
7740
7741		}else if(currentTarget.classList.contains('hap-playback-rate-toggle') || currentTarget.classList.contains('hap-playback-rate-close')){	
7742
7743			if(currentTarget.classList.contains('hap-playback-rate-toggle')){
7744				checkOpenedWindows('playback_rate')
7745			}
7746
7747			if(playbackRateHolder.classList.contains('hap-holder-shown')){
7748				
7749				playbackRateHolder.addEventListener('transitionend', function cb(e){
7750					e.currentTarget.removeEventListener(e.type, cb);
7751					playbackRateHolder.classList.remove('hap-holder-animate')
7752				})
7753
7754				playbackRateHolder.classList.remove('hap-holder-shown')
7755
7756			}else{
7757				
7758				playbackRateHolder.classList.add('hap-holder-animate')
7759				
7760				setTimeout(function(){
7761					playbackRateHolder.classList.add('hap-holder-shown')
7762				},50)
7763
7764			}
7765
7766		}else if(currentTarget.classList.contains('hap-playback-rate-toggle-num')){	
7767
7768			var elem = e.currentTarget.querySelector('.hap-playback-rate-toggle-inner')
7769
7770			var current = elem.innerHTML.toUpperCase()
7771			current = Number(current.substr(0, current.indexOf('X')))
7772
7773			var value = settings.playbackRateArr.indexOf(current)
7774
7775			//cycle rates
7776			value++;
7777			if(value > settings.playbackRateArr.length - 1){
7778				value = settings.playbackRateArr[0];
7779			}else{
7780				value = settings.playbackRateArr[value];
7781			}
7782
7783			elem.innerHTML = value + 'X';//update
7784
7785			self.setPlaybackRate(value)
7786
7787		}else if(currentTarget.classList.contains('hap-range-toggle') || currentTarget.classList.contains('hap-range-close')){	
7788
7789			if(currentTarget.classList.contains('hap-range-toggle')){
7790				checkOpenedWindows('range')
7791			}
7792
7793			self.toggleRange();
7794
7795		}else if(currentTarget.classList.contains('hap-share-toggle') || currentTarget.classList.contains('hap-share-close')){	
7796
7797			if(currentTarget.classList.contains('hap-share-toggle')){
7798				checkOpenedWindows('share')
7799			}
7800
7801			if(shareHolder.classList.contains('hap-holder-shown')){
7802
7803				shareHolder.addEventListener('transitionend', function cb(e){
7804					e.currentTarget.removeEventListener(e.type, cb);
7805					shareHolder.classList.remove('hap-holder-animate')
7806
7807					if(shareHolder.querySelector('.hap-embed-field-wrap')){
7808						shareHolder.querySelector('.hap-embed-field-wrap').classList.remove('hap-embed-field-wrap-selected')
7809					}
7810
7811				})
7812
7813				shareHolder.classList.remove('hap-holder-shown')
7814			}else{
7815
7816				//link
7817				if(shareHolder.querySelector('.hap-share-link')){
7818					var url = self.getCurrentMediaUrl();
7819					shareHolder.querySelector('.hap-share-link').innerHTML = url;
7820				}
7821
7822				shareHolder.classList.add('hap-holder-animate')
7823				
7824				setTimeout(function(){
7825					shareHolder.classList.add('hap-holder-shown')
7826				},50)
7827
7828			}
7829
7830		}else if(currentTarget.classList.contains('hap-share-copy')){	
7831
7832			var btn = e.target,
7833			field = btn.closest('.hap-embed-box').querySelector('.hap-embed-field-wrap')
7834			field.classList.add('hap-embed-field-wrap-selected')
7835
7836			var data = field.textContent.trim()
7837
7838			var dummy = document.createElement("input");
7839			dummy.setAttribute("id", "hap-copy-url");
7840			document.body.appendChild(dummy);
7841			document.getElementById("hap-copy-url").value = data;
7842			dummy.select();
7843			try{
7844		        document.execCommand("copy");
7845			}catch(er){}
7846			document.body.removeChild(dummy);
7847
7848		}else if(currentTarget.classList.contains('hap-embed-toggle') || currentTarget.classList.contains('hap-embed-close')){	
7849
7850			if(currentTarget.classList.contains('hap-embed-toggle')){
7851				checkOpenedWindows('embed')
7852			}
7853
7854			if(embedHolder.classList.contains('hap-holder-shown')){
7855				
7856				embedHolder.addEventListener('transitionend', function cb(e){
7857					e.currentTarget.removeEventListener(e.type, cb);
7858					embedHolder.classList.remove('hap-holder-animate')
7859				})
7860
7861				embedHolder.classList.remove('hap-holder-shown')
7862
7863			}else{
7864
7865				setEmbedData();
7866				
7867				embedHolder.classList.add('hap-holder-animate')
7868				
7869				setTimeout(function(){
7870					embedHolder.classList.add('hap-holder-shown')
7871				},50)
7872
7873			}
7874
7875		}else if(currentTarget.classList.contains('hap-pwd-close')){	
7876
7877			pwdHolderToggle(true)
7878
7879		}else if(currentTarget.classList.contains('hap-pwd-confirm')){	
7880
7881			var pwd = pwdField.value;
7882
7883			if(HAPUtils.isEmpty(pwd)){
7884				alert(pwdErrorMsg);
7885			}else{
7886
7887				if(typeof md5 === 'undefined'){
7888
7889					var script = document.createElement('script');
7890					script.type = 'text/javascript';
7891					if(!HAPUtils.relativePath(settings.md5_js))var src = HAPUtils.qualifyURL(settings.sourcePath+settings.md5_js);
7892					else var src = settings.md5_js;
7893					script.src = src;
7894					script.onload = script.onreadystatechange = function() {
7895					    if(!this.readyState || this.readyState == 'complete'){
7896
7897					    	if(pwdAction == 'download'){
7898
7899					    		if(md5(pwd) != pwdDownloadData.orig_data.pwd){
7900									alert(pwdErrorMsg);
7901								}else{
7902									delete pwdDownloadData.orig_data.pwd;
7903
7904									pwdHolderToggle(true)
7905
7906									doDownloadAction()
7907									
7908								}
7909
7910					    	}else{
7911
7912						    	if(md5(pwd) != currMediaData.pwd){
7913									alert(pwdErrorMsg);
7914								}else{
7915									delete currMediaData.pwd;
7916
7917									pwdHolderToggle(true)
7918
7919									comingFromPwd = true;
7920									doPostPwdActions()
7921									
7922								}
7923
7924							}
7925
7926					    }
7927					};
7928					script.onerror = function(){
7929						console.log("Error loading " + this.src);
7930					}
7931					var tag = document.getElementsByTagName('script')[0];
7932					tag.parentNode.insertBefore(script, tag);
7933
7934				}else{
7935
7936					if(pwdAction == 'download'){
7937
7938			    		if(md5(pwd) != pwdDownloadData.orig_data.pwd){
7939							alert(pwdErrorMsg);
7940						}else{
7941							delete pwdDownloadData.orig_data.pwd;
7942
7943							pwdHolderToggle(true)
7944
7945							doDownloadAction()
7946							
7947						}
7948
7949			    	}else{
7950
7951				    	if(md5(pwd) != currMediaData.pwd){
7952							alert(pwdErrorMsg);
7953						}else{
7954							delete currMediaData.pwd;
7955
7956							pwdHolderToggle(true)
7957
7958							comingFromPwd = true;
7959							doPostPwdActions()
7960							
7961						}
7962
7963					}
7964
7965				}
7966			}
7967
7968		}else if(currentTarget.classList.contains('hap-share-item')){	
7969
7970			if(!mediaType)return false;
7971
7972			if(_HAPShareManager)_HAPShareManager.share(currentTarget.getAttribute('data-type').toLowerCase(), currMediaData, self.getCurrentMediaUrl());
7973			
7974		}
7975		else if(currentTarget.classList.contains('hap-redirect-login-cancel')){	
7976			toggleRedirectLoginScreen();
7977		}
7978		else if(currentTarget.classList.contains('hap-playlist-selector-toggle')){	
7979			
7980			if(!playlistSelectorOpened){
7981
7982				_body.classList.add('hap-body-overflow')
7983
7984				playlistSelectorContainer.style.display = 'block'
7985				setTimeout(function(){
7986					playlistSelectorContainer.style.opacity = 1;
7987				},50)
7988
7989				var p=0, z;
7990				for(z=0;z<playlistSelectorLen;z++){
7991					setTimeout(function(){
7992					   clearTimeout(this);
7993					   playlistSelectorArr[p].classList.add('hap-playlist-selector-item-shown');
7994					   p++;
7995					},50 + ( z * 150 ));
7996				}
7997
7998				playlistSelectorOpened = true;
7999
8000			}
8001
8002		}
8003		else if(currentTarget.classList.contains('hap-playlist-selector-close')){	
8004			
8005			if(playlistSelectorOpened){
8006				closePlaylistSelector();
8007			}
8008
8009		}
8010	}
8011
8012	function checkOpenedWindows(open_type){
8013
8014		if(open_type != 'range')if(rangeOn)self.toggleRange()
8015
8016		if(open_type != 'playback_rate')if(playbackRateHolder)playbackRateHolder.classList.remove('hap-holder-animate', 'hap-holder-shown')
8017
8018		if(open_type != 'share')if(shareHolder){
8019			shareHolder.classList.remove('hap-holder-animate', 'hap-holder-shown')
8020			if(shareHolder.querySelector('.hap-embed-field-wrap'))shareHolder.querySelector('.hap-embed-field-wrap').classList.remove('hap-embed-field-wrap-selected')
8021		}
8022
8023		if(open_type != 'pwd'){
8024			if(pwdHolder){
8025				pwdHolder.classList.remove('hap-holder-animate', 'hap-holder-shown')
8026				pwdField.value = ''
8027			}
8028		}
8029
8030		if(open_type != 'embed')if(embedHolder)embedHolder.classList.remove('hap-holder-animate', 'hap-holder-shown')
8031
8032	}
8033
8034	function enableActivePlaylistItem(){
8035
8036		if(activePlaylistItem){
8037			self.fireEvent('playlistItemEnabled', [{instance:self, instanceName:settings.instanceName, item:activePlaylistItem}]);
8038
8039			if(settings.togglePlaybackOnPlaylistItem){
8040				activePlaylistItem.querySelector('.hap-playlist-thumb-style')?.classList.remove('hap-playlist-thumb-style-pause')
8041			}
8042
8043			//variation
8044			if(activePlaylistItem.querySelector('.hap-audio-variations')){
8045				activePlaylistItem.querySelector('.hap-audio-variations').style.height = 0;
8046			}
8047
8048			activePlaylistItem.classList.remove('hap-playlist-item-selected', 'hap-playlist-item-disabled');
8049			activePlaylistItem = null;
8050		}
8051	}
8052
8053	function disableActivePlaylistItem(id){
8054
8055		if(activePlaylistItem){//enable active playlist item
8056			enableActivePlaylistItem();
8057
8058			if(settings.useStickyPlayer && settings.clonePlaylistInFixedPlayer && window['hap_fixed_active_player'] == settings.instanceName){
8059				_HAPStickyPlayer.enableActivePlaylistItem();
8060			}
8061		}
8062
8063		activePlaylistItem = playlistContent.querySelectorAll('.hap-playlist-item')[id];
8064
8065		if(activePlaylistItem){
8066
8067			if(playbackToggleIndicator){
8068				playbackToggleIndicator.style.display = 'block'
8069				playbackToggleIndicator.querySelector('.hap-btn-play').style.display = 'block'
8070				playbackToggleIndicator.querySelector('.hap-btn-pause').style.display = 'none'
8071				activePlaylistItem.querySelector('.hap-playlist-title-wrap').prepend(playbackToggleIndicator)
8072			}
8073
8074			activePlaylistItem.classList.add('hap-playlist-item-selected', 'hap-playlist-item-disabled');
8075
8076			if(settings.useStickyPlayer && settings.clonePlaylistInFixedPlayer && window['hap_fixed_active_player'] == settings.instanceName){
8077				_HAPStickyPlayer.disableActivePlaylistItem(id);
8078			}
8079
8080			//variation
8081			if(activePlaylistItem.querySelector('.hap-audio-variations')){
8082
8083				var vp = activePlaylistItem.querySelector('.hap-audio-variations');
8084
8085				vp.style.display = 'block';
8086		          
8087	            slidetoggle(vp);
8088
8089				//activePlaylistItem.querySelector('.hap-audio-variations').classList.add('hap-audio-variation-opened')
8090				//activePlaylistItem.querySelector('.hap-audio-variations').slideToggle(settings.variationToggleSpeed);
8091			}
8092
8093			if(settings.usePlaylistScroll && playlistLength > 0){//scroll to active item
8094
8095				if(!playlistClick){//not on playlist item click
8096
8097					setTimeout(function(){
8098
8099						var rect = activePlaylistItem.getBoundingClientRect();
8100
8101						if(settings.playlistScrollOrientation == 'horizontal'){
8102
8103							var leftPos = activePlaylistItem.offsetLeft;
8104
8105							playlistInner.scrollTo({ left: leftPos, top: 0, behavior: 'smooth' });
8106
8107						}else{
8108							
8109							var topPos = activePlaylistItem.offsetTop;
8110			
8111							//https://jsfiddle.net/mrjLbf2n/1/
8112						
8113							//playlistInner.scrollTop = topPos
8114
8115							playlistInner.scrollTo({ left: 0, top: topPos, behavior: 'smooth' });
8116
8117							//activePlaylistItem.scrollIntoView({behavior: 'smooth', block: 'nearest' })
8118
8119						}
8120					},1000);
8121					
8122				}
8123				playlistClick = false;
8124				
8125			}
8126
8127			self.fireEvent('playlistItemDisabled', [{instance:self, instanceName:settings.instanceName, item:activePlaylistItem}]);
8128		}
8129	}
8130
8131	function slidetoggle(el) {
8132	
8133	    const ch = el.clientHeight,
8134	      sh = el.scrollHeight,
8135	      isCollapsed = !ch,
8136	      noHeightSet = !el.style.height;
8137
8138	    el.style.height = (isCollapsed || noHeightSet ? sh : 0) + "px";
8139	    if (noHeightSet) return slidetoggle.call(this, el);
8140	  
8141	}
8142
8143
8144	//############################################//
8145	/* login */
8146	//############################################//
8147	
8148	function toggleRedirectLoginScreen(type){
8149		if(!redirectLoginHolderDownload && !redirectLoginHolderWatch){
8150			console.log('this skin does not contain login screen!')
8151			return false;
8152		}
8153
8154		//note: we use different screen for watch / download login but we cant have both opened at the same time since media is paused during this screen
8155
8156		if(redirectLoginOpened){
8157
8158			redirectLoginHolder.addEventListener('transitionend', function cb(e){
8159				e.currentTarget.removeEventListener(e.type, cb);
8160				redirectLoginHolder.style.display = 'none';
8161			})
8162
8163			redirectLoginHolder.classList.remove('hap-redirect-login-holder-visible')
8164
8165			redirectLoginOpened = false;
8166
8167			/*if(mediaForcePause){//if it was playing
8168				self.playMedia();
8169				mediaForcePause = false;
8170			}*/
8171
8172		}else{
8173
8174			self.fireEvent("beforeLoginScreen", [{instance:self, instanceName: settings.instanceName, media:currMediaData}]);
8175
8176			if(mediaPlaying){
8177				self.pauseMedia();
8178				mediaForcePause = true;
8179			}
8180
8181			redirectLoginHolder = (type == 'download') ? redirectLoginHolderDownload : redirectLoginHolderWatch;
8182
8183			redirectLoginHolder.style.display = 'block';
8184			setTimeout(function(){
8185				redirectLoginHolder.classList.add('hap-redirect-login-holder-visible')
8186			},50)
8187
8188			redirectLoginOpened = true;
8189
8190		}
8191
8192	}
8193
8194	//############################################//
8195	/* embed */
8196	//############################################//
8197
8198	function setEmbedData(){
8199
8200		embedHolder.querySelectorAll('.hap-embed-field-wrap').forEach(function(el){
8201			el.classList.remove('hap-embed-field-wrap-selected')
8202		})
8203
8204		if(settings.wpEmbedUrl){
8205			//wp
8206
8207			var e_url = settings.wpEmbedUrl + 'includes/embed.php?',
8208			pid_exist
8209
8210			if(settings.playerId != null && settings.playerId != -1){
8211				e_url += 'player_id='+settings.playerId 
8212				pid_exist = true;
8213			}else{
8214				//use default preset
8215			}
8216
8217			
8218
8219			if(pid_exist)e_url += '&'
8220
8221			if(playlistId != null){
8222
8223				e_url += 'playlist_id='+playlistId+'&active_item='+mediaCounter
8224
8225				if(settings.useSingleAudioEmbed){
8226					e_url += '&single_audio=1&media_id='+currMediaData.mediaId;
8227				}
8228
8229			}else{
8230				//copy html
8231				var h = playlistList.querySelector('.hap-playlist-anon').innerHTML.replace(/\n/g,''),
8232				v = encodeURIComponent(h)
8233
8234				e_url += 'plhtml='+v
8235
8236			}
8237			
8238		}else{
8239
8240			//manual embed
8241			var e_url = '';
8242			if(settings.embedSrc)e_url = settings.embedSrc
8243
8244		}
8245
8246		var es = '<iframe width="100%" height="100%" src="'+e_url+'" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'
8247
8248		//embed
8249		wrapper.querySelector('.hap-audio-embed').textContent = es;
8250
8251		//link
8252		var url = self.getCurrentMediaUrl();
8253		embedHolder.querySelector('.hap-audio-link').innerHTML = url;
8254
8255	}
8256
8257	/*function getEmbedCode(){
8258
8259		var skip_keys = ['playlistId','mediaPath','safeTitle','safeArtist']
8260
8261		var s = ''
8262
8263		Object.keys(currMediaData).forEach(function(key,index) {
8264		    // key: the name of the object key
8265		    // index: the ordinal position of the key within the object 
8266
8267		    if(typeof currMediaData[key] === 'string'){
8268
8269		    	if(skip_keys.indexOf(key) > 0) return
8270
8271		    	key = key.split(/(?=[A-Z])/).join('_').toLowerCase();//camelCase to _
8272
8273		    	s += '&' + key + '=' + currMediaData[key]
8274		    }
8275		});
8276
8277		return s;
8278
8279	}*/
8280
8281	if(embedHolder)onHandler(embedHolder, 'click', '.hap-embed-copy', (e) => embedHandler(e));
8282
8283	function embedHandler(e){
8284
8285		embedHolder.querySelectorAll('.hap-embed-field-wrap').forEach(function(el){
8286			el.classList.remove('hap-embed-field-wrap-selected')
8287		})
8288
8289		var field = e.target.closest('.hap-embed-box').querySelector('.hap-embed-field-wrap')
8290		field.classList.add('hap-embed-field-wrap-selected')
8291		var data = field.textContent
8292
8293		var dummy = document.createElement("input");
8294		dummy.setAttribute("id", "hap-copy-url");
8295		document.body.appendChild(dummy);
8296		document.getElementById("hap-copy-url").value = data.trim();
8297		dummy.select();
8298		try{
8299	        document.execCommand("copy");
8300		}catch(er){}
8301		document.body.removeChild(dummy);
8302
8303	}
8304
8305	//############################################//
8306	/* playlist process */
8307	//############################################//
8308	
8309	function setPlaylist(id){
8310
8311		playlistTransitionOn = true;
8312		if(preloader)preloader.style.display = 'block';
8313		
8314		if(lastPlaylist)destroyPlaylist();
8315
8316		if(playlistList){
8317			var playlist = playlistList.querySelector(id);
8318		}else{
8319			var playlist = document.querySelector(id);
8320		}
8321		if(playlist.length == 0){
8322			console.log('Failed playlist selection! Playlist - ' + id + ' does not exist. Check activePlaylist option in settings!');
8323			settings.activePlaylist = null;
8324			playlistTransitionOn = false;
8325			preloader.style.display = 'none';
8326			return false;
8327		}
8328
8329
8330
8331		//for load more and stats
8332		if(playlist.getAttribute('data-playlist-id') != undefined){
8333			playlistId = playlist.getAttribute('data-playlist-id');
8334		}else{
8335			playlistId = null;
8336		}
8337
8338		settings.activePlaylist = id;
8339
8340		self.fireEvent('playlistStartLoad', [{instance:self, instanceName:settings.instanceName}]);
8341
8342		//global playlist options
8343		if(playlist.querySelector('.hap-playlist-options')){
8344
8345			playlistData = playlist.querySelector('.hap-playlist-options')//.cloneNode(true)
8346			//wrapper.prepend(playlistData);
8347		
8348			//playlist tile, desc
8349			if(playlistTitle){
8350				if(playlistData.getAttribute('data-playlist-title') != undefined){
8351					playlistTitle.innerHTML = playlistData.getAttribute('data-playlist-title')
8352					playlistTitle.style.display = 'block'
8353				}else{
8354					playlistTitle.innerHTML = ''
8355					playlistTitle.style.display = 'none'
8356				}
8357			}
8358
8359			if(playlistDescription){
8360				if(playlistData.querySelector('.hap-global-playlist-description')){
8361					playlistDescription.innerHTML = playlistData.querySelector('.hap-global-playlist-description').innerHTML
8362					playlistDescription.style.display = 'block'
8363				}else{
8364					playlistDescription.innerHTML = ''
8365					playlistDescription.style.display = 'none'
8366				}
8367			}
8368
8369
8370			addMoreLimit = parseInt(playlistData.getAttribute('data-add-more-limit'),10);
8371			addMoreNumResults = parseInt(playlistData.getAttribute('data-add-more-num-results'),10);
8372			addMoreOffset = parseInt(playlistData.getAttribute('data-add-more-offset'),10);
8373			addMoreSortOrder = playlistData.getAttribute("data-add-more-sort-order");
8374			addMoreSortDirection = playlistData.getAttribute("data-add-more-sort-direction");
8375
8376
8377			if(playlistData.getAttribute('data-thumb-global') != undefined){
8378				globalThumb = playlistData.getAttribute('data-thumb-global');
8379			}
8380			if(playlistData.getAttribute('data-download-global') != undefined){
8381				globalDownload = playlistData.getAttribute('data-download-global');
8382			}
8383			if(playlistData.getAttribute('data-start') != undefined){
8384				globalStartTime = Number(playlistData.getAttribute('data-start'));
8385
8386				globalStartTimeMediaId = null;
8387				if(playlistData.getAttribute('data-start-media-id') != undefined)globalStartTimeMediaId = playlistData.getAttribute('data-start-media-id');
8388			}
8389			if(playlistData.getAttribute('data-end') != undefined && !HAPUtils.isEmpty(playlistData.getAttribute('data-end'))){
8390				globalEndTime = Number(playlistData.getAttribute('data-end'));
8391			}
8392
8393			if(playlistData.getAttribute('data-pwd') != undefined){
8394				globalPwd = playlistData.getAttribute('data-pwd');
8395			}
8396
8397			if(playlistData.getAttribute('data-add-more-on-total-scroll') != undefined){
8398				addMoreOnTotalScroll = true;
8399			}
8400			if(playlistData.getAttribute('data-media-prefix-url') != undefined){
8401				mediaPrefixUrl = playlistData.getAttribute('data-media-prefix-url');
8402			}
8403			if(playlistData.getAttribute('data-active-media-id') != undefined){
8404				activeMediaId = playlistData.getAttribute('data-active-media-id');
8405			}
8406
8407			if(playlistData.getAttribute('data-lock-time') != undefined){
8408				globalLockTime = Number(playlistData.getAttribute('data-lock-time'));
8409			}
8410			if(playlistData.getAttribute('data-lock-video-user-roles') != undefined && !HAPUtils.isEmpty(playlistData.getAttribute('data-lock-video-user-roles'))){
8411				globalLockVideoUserRoles = playlistData.getAttribute('data-lock-video-user-roles').split(',');
8412			}
8413
8414			//ads
8415			if(showAds){
8416
8417				if(playlistData.getAttribute('data-vast') != undefined){//ima
8418					globalVast = playlistData.getAttribute('data-vast')
8419				}
8420				else{
8421
8422					if(playlistData.getAttribute('data-ad-pre') != undefined){
8423						globalAdPre = playlistData.getAttribute('data-ad-pre').split(",").map(function(item){
8424						    return item.trim();
8425						});
8426						if(playlistData.getAttribute('data-shuffle-ads') != undefined && globalAdPre.length>1)HAPUtils.shuffleArray(globalAdPre);
8427					}
8428					if(playlistData.getAttribute('data-ad-mid') != undefined){
8429						globalAdMid = playlistData.getAttribute('data-ad-mid').split(",").map(function(item){
8430						    return item.trim();
8431						});
8432						if(playlistData.getAttribute('data-shuffle-ads') != undefined && globalAdMid.length>1)HAPUtils.shuffleArray(globalAdMid);
8433						if(playlistData.getAttribute('data-ad-mid-interval') != undefined){
8434							globalAdMidInterval = playlistData.getAttribute('data-ad-mid-interval');
8435						}
8436					}
8437					if(playlistData.getAttribute('data-ad-end') != undefined){
8438						globalAdEnd = playlistData.getAttribute('data-ad-end').split(",").map(function(item){
8439						    return item.trim();
8440						});
8441						if(playlistData.getAttribute('data-shuffle-ads') != undefined && globalAdEnd.length>1)HAPUtils.shuffleArray(globalAdEnd);
8442					}
8443				}
8444
8445			}
8446
8447		}
8448
8449		var item, type;
8450
8451		var div_list = playlist.querySelectorAll('.hap-playlist-item').forEach(div => {
8452
8453			item = div;
8454			type = item.getAttribute('data-type');
8455
8456			if(RegExp(/^audio|hls$/).test(type) && item.classList.contains('hap-playlist-item-ready')) {//copy directly to playlist
8457				playlistProcessDataUrl.push(item.cloneNode(true));
8458			}else{
8459				playlistProcessDataUrl.push(getTrackData(item));
8460			}
8461
8462			if(item.getAttribute('data-path') != undefined)lastPositionDataPath = item.getAttribute('data-path')
8463			
8464		});
8465
8466		playlistLength = playlistProcessDataUrl.length;
8467		checkProcessCounter();
8468
8469	}
8470
8471	//############################################//
8472	/* xml */
8473	//############################################//
8474	
8475	function processXml(data){
8476		//first load xml file, then process its content 
8477
8478		if(xhrRequest){
8479			xhrRequest.abort();
8480			xhrRequest = null;
8481		}
8482
8483		xhrRequest = new XMLHttpRequest();
8484		xhrRequest.onreadystatechange = function() {
8485			if (xhrRequest.readyState == 4) {
8486
8487				var dom = HAPUtils.parseXML(xhrRequest.responseText);
8488
8489				var arr = dom.getElementsByClassName("hap-playlist-item"), obj
8490
8491				var i, len = arr.length
8492				for(i = 0;i < len; i++){
8493
8494					obj = getTrackData(arr[i]);
8495
8496					if(data.origtype)obj.origtype = data.origtype;
8497					if(data.mediaId != undefined)obj.mediaId = data.mediaId;//for stats
8498
8499					playlistProcessDataUrl.push(obj);
8500
8501				}
8502
8503				playlistLength = playlistProcessDataUrl.length;
8504				checkProcessCounter();
8505				
8506		    }
8507		}
8508		xhrRequest.onerror = function(e) { 
8509		    console.log('Error processXML: ' + e);
8510			checkProcessCounter();
8511		};
8512		xhrRequest.open('GET', data.path);
8513		xhrRequest.setRequestHeader("Content-Type", "text/xml");
8514		xhrRequest.send();
8515
8516	}
8517
8518	//############################################//
8519	/* m3u */
8520	//############################################//
8521
8522	function processM3u(data) {
8523
8524		if(window.location.protocol == 'file:'){
8525			console.log('Reading m3u files requires server connection.');
8526			return false;
8527		}
8528
8529		//note, no mixed playlists!
8530		//only audio file type
8531
8532		if(xhrRequest){
8533			xhrRequest.abort();
8534			xhrRequest = null;
8535		}
8536
8537		xhrRequest = new XMLHttpRequest();
8538		xhrRequest.onreadystatechange = function() {
8539			if (xhrRequest.readyState == 4) {
8540
8541				var result = xhrRequest.responseText
8542
8543				var obj, channel;
8544
8545		        result
8546		        .replace('#EXTM3U', '')
8547		        .split('#EXTINF:')
8548		        .slice(1)
8549		        .map(function(str, index) {
8550
8551		        	obj = HAPUtils.extendObj(true, {}, data);
8552		            
8553		            obj.type = 'audio';
8554
8555		            channel = str.split('\n').slice(0,-1);
8556
8557		            //audio url
8558		            obj.mp3 = channel[1].trim();
8559
8560		            //title, artist 
8561		            var t = channel[0].split(',')
8562		            if(t.length > 1){
8563		            	var d = t[0];
8564		            	if(HAPUtils.isNumber(d) && d != -1)obj.duration = d;
8565		            	var title = t[1];
8566		            }else{
8567		            	var title = t[0];
8568		            }
8569
8570		            if(title.indexOf('-')>-1){
8571		                var s = title.split('-')
8572		                obj.title = s[1].trim();
8573
8574		                if(s[0].match(/^\d+(\s*)+\./)) {//contains numbers in front of artist
8575			                var a = s[0].substr(s[0].indexOf('.')+1);
8576			                obj.artist = a[1].trim();
8577		                }else{
8578		                	obj.artist = s[0].trim();
8579		                }
8580		            }else{
8581		                obj.title = title.trim();
8582		            }
8583
8584		            playlistProcessData.push(obj);
8585
8586		        });
8587
8588		        checkProcessCounter();
8589				
8590		    }
8591		}
8592		xhrRequest.onerror = function(e) { 
8593		    console.log('Error processM3u: ' + e);
8594			checkProcessCounter();
8595		};
8596		xhrRequest.open('GET', data.path);
8597		xhrRequest.setRequestHeader("Content-Type", "text/xml");
8598		xhrRequest.send();
8599		
8600    }
8601
8602	//############################################//
8603	/* json */
8604	//############################################//
8605	
8606	function processJson(data){
8607		//first load json file, then process its content 
8608		
8609		if(xhrRequest){
8610			xhrRequest.abort();
8611			xhrRequest = null;
8612		}
8613
8614		xhrRequest = new XMLHttpRequest();
8615		xhrRequest.onreadystatechange = function() {
8616			if (xhrRequest.readyState == 4) {
8617
8618				var track = JSON.parse(xhrRequest.responseText)
8619
8620				playlistProcessCounter = -1;
8621				playlistProcessData = [];
8622				playlistProcessDataUrl = [];
8623
8624				lastPlaylist = playlistContent;
8625
8626				if(Array.isArray(track))playlistProcessDataUrl = track;
8627				else playlistProcessDataUrl.push(track);
8628
8629				if(data.origtype && data.mediaId != undefined){
8630					var i, len = playlistProcessDataUrl.length;
8631					for(i = 0;i<len;i++){
8632						playlistProcessDataUrl[i].origtype = data.origtype;
8633						if(!playlistProcessDataUrl[i].mediaId !== 'undefined')playlistProcessDataUrl[i].mediaId = data.mediaId;//for stats
8634					}
8635				}
8636
8637				playlistLength = playlistProcessDataUrl.length;
8638				
8639				checkProcessCounter();
8640				
8641		    }
8642		}
8643		xhrRequest.onerror = function(e) { 
8644		    console.log('Error processJSON: ' + e);
8645			checkProcessCounter();
8646		};
8647		xhrRequest.open('GET', data.path);
8648		xhrRequest.setRequestHeader("Content-Type", "application/json");
8649		xhrRequest.send();
8650
8651	}
8652
8653	//############################################//
8654	/*  */
8655	//############################################//
8656
8657	function checkProcessCounter() {
8658		playlistProcessCounter++;
8659
8660		if(playlistProcessCounter > playlistLength - 1){
8661			buildPlaylist();
8662		}else{
8663			var data = playlistProcessDataUrl[playlistProcessCounter], type = data.type;
8664
8665			if(type){
8666
8667				if(type == 'soundcloud') {
8668
8669					loadMoreType = 'soundcloud';
8670
8671					if(data.limit){
8672						resultLimit = data.limit;
8673						if(data.loadMore)loadMoreOnTotalScroll = true;//from addtrack
8674					}else{
8675						resultLimit = allResults;
8676						loadMoreOnTotalScroll = false;
8677					} 
8678
8679					nextPageToken = [];
8680
8681					if(!_scLoader){
8682						
8683						_scLoader = new HAPSCLoader({settings: settings})
8684
8685						_scLoader.addEventListener('HAPSCLoader.END_LOAD', function(data){
8686
8687							var i, len = data.data.length, obj;
8688							for(i=0;i<len;i++){
8689								obj = data.data[i];
8690								playlistProcessData.push(obj);
8691							}
8692
8693							nextPageToken = data.nextPageToken;
8694
8695							if(loadMoreProcess){
8696								buildPlaylist();
8697							}else{
8698								checkProcessCounter();
8699							}
8700							
8701						});
8702
8703						_scLoader.addEventListener('HAPSCLoader.GET_STREAM_URL', function( data){
8704
8705							if(crossFadeOn){
8706								scUrlReady = data.location;
8707							}else{
8708								mediaPath = currMediaData.sc_url = data.location;
8709								initAudio()
8710							}
8711							
8712						});
8713
8714					}
8715
8716					_scLoader.request(data, resultLimit);
8717			
8718				}else if(type == 'podcast') {
8719
8720					loadMoreType = 'podcast';
8721
8722					processPodcast(data);
8723
8724				}else if(type == 'itunes_podcast_music') {
8725
8726					processiTunes(data);
8727
8728				}else if(type == 'folder') {
8729
8730					loadMoreType = 'folder';
8731
8732					processFolder(data);
8733
8734				}else if(type == 'folder_accordion') {
8735
8736					accordionMode = true;
8737
8738					processFolderAccordion(data);	
8739
8740				}else if(type == 'json_accordion') {
8741
8742					accordionMode = true;
8743
8744					processJsonAccordion(data);		
8745
8746				}else if(type == 'gdrive_folder'){
8747
8748					processGdriveFolder(data);
8749
8750				}else if(type == 'onedrive_folder') {
8751
8752					processOnedriveFolder(data);	
8753
8754				}else if(type == 'hls') {
8755
8756					playlistProcessData.push(data);
8757					checkProcessCounter();
8758
8759				}else if(type == 'audio'){
8760
8761					playlistProcessData.push(data);
8762					checkProcessCounter();
8763
8764				}else if(type == 's3_audio'){
8765
8766					playlistProcessData.push(data);
8767					checkProcessCounter();
8768
8769				}else if(type == 's3_bucket_audio'){
8770
8771					processS3Bucket(data);
8772
8773				}else if(type == 'youtube_single' || type == 'youtube_playlist') {
8774
8775					if(data.noApi){
8776
8777						playlistProcessData.push(data);
8778						checkProcessCounter();
8779
8780					}else{
8781
8782						loadMoreType = 'youtube';
8783
8784						if(data.loadMore)loadMoreOnTotalScroll = true;//from addtrack
8785
8786						if(!_HAPYoutubeLoader)createLoader('youtube');
8787						_HAPYoutubeLoader.setData(data);
8788
8789					}
8790
8791				}else if(type == 'xml'){
8792
8793					processXml(data);
8794
8795				}else if(type == 'json'){
8796
8797					processJson(data);
8798
8799				}else if(type == 'm3u'){
8800
8801					processM3u(data);
8802
8803				}
8804
8805			}else{//copy directly to playlist
8806
8807				playlistProcessData.push(data);
8808
8809				checkProcessCounter();
8810			}		
8811		}
8812	}
8813
8814	//############################################//
8815	/* get track data */
8816	//############################################//
8817
8818	function getTrackData(track){
8819
8820		var obj = {};
8821
8822		obj.type = obj.origtype = track.getAttribute('data-type');
8823		obj.origclasses = track.getAttribute('class');//copy classes
8824
8825		if(nonProcessMedia.indexOf(obj.type) != -1){
8826			obj.pi_item = track;
8827		}
8828
8829		//if(!track.is(':empty'))obj.content = track.innerHTML = );//copy any content to generated playlist item
8830
8831		if(track.querySelector('.hap-custom-content')){
8832			obj.content = track.querySelector('.hap-custom-content').innerHTML;
8833		}
8834
8835		if(track.getAttribute('data-noapi') != undefined){
8836			obj.noApi = true;
8837		}
8838
8839		if(globalPwd){
8840			obj.pwd = globalPwd;
8841		}else if(track.getAttribute('data-pwd') != undefined){//password
8842			obj.pwd = track.getAttribute('data-pwd');
8843		}
8844
8845		if(track.getAttribute('data-video') != undefined){
8846			obj.video = track.getAttribute('data-video');
8847			if(obj.video.indexOf(bsf_match) != -1)obj.video = HAPUtils.b64DecodeUnicode(obj.video.substr(6));
8848		}
8849
8850		if(track.getAttribute('data-media-id') != undefined){//stats
8851			obj.mediaId = parseInt(track.getAttribute('data-media-id'),10);
8852		}
8853
8854		if(track.getAttribute('data-audio-preview') != undefined){
8855			obj.audioPreview = track.getAttribute('data-audio-preview');
8856			if(obj.audioPreview.indexOf(bsf_match) != -1)obj.audioPreview = HAPUtils.b64DecodeUnicode(obj.audioPreview.substr(6));
8857		}
8858
8859		//peaks
8860
8861		if(track.getAttribute('data-peaks') != undefined)obj.peaks = track.getAttribute('data-peaks');
8862
8863		if(track.getAttribute('data-peak-dir') != undefined){
8864			obj.peakdir = track.getAttribute('data-peak-dir');
8865		}
8866		if(track.getAttribute('data-peak-name') != undefined){
8867			obj.peakName = track.getAttribute('data-peak-name');
8868		}
8869		if(track.getAttribute('data-remote') != undefined){
8870			obj.remote = true;
8871		}
8872
8873		if(track.getAttribute('data-bucket') != undefined){
8874			obj.bucket = track.getAttribute('data-bucket');
8875			if(obj.bucket.indexOf(bsf_match) != -1)obj.bucket = HAPUtils.b64DecodeUnicode(obj.bucket.substr(6));
8876		}
8877		if(track.getAttribute('data-key') != undefined){
8878			obj.key = track.getAttribute('data-key');
8879			if(obj.key.indexOf(bsf_match) != -1)obj.key = HAPUtils.b64DecodeUnicode(obj.key.substr(6));
8880		}
8881
8882		if(track.getAttribute('data-content-url') != undefined){
8883			obj.contentUrl = track.getAttribute('data-content-url');
8884		}
8885
8886		if(track.getAttribute('data-path') != undefined){
8887
8888			var path = track.getAttribute('data-path');
8889			if(path.indexOf(bsf_match) != -1)path = HAPUtils.b64DecodeUnicode(path.substr(6));
8890
8891			obj.path = path;
8892
8893		}
8894		else if(track.getAttribute('data-mp3') != undefined)obj.path = track.getAttribute('data-mp3');
8895		else if(track.getAttribute('data-wav') != undefined)obj.path = track.getAttribute('data-wav');
8896		else if(track.getAttribute('data-aac') != undefined)obj.path = track.getAttribute('data-aac');
8897		else if(track.getAttribute('data-flac') != undefined)obj.path = track.getAttribute('data-flac');
8898
8899		
8900
8901		if(track.getAttribute('data-lyrics') != undefined){
8902			obj.lyrics = track.getAttribute('data-lyrics');
8903			if(obj.lyrics.indexOf(bsf_match) != -1)obj.lyrics = HAPUtils.b64DecodeUnicode(obj.lyrics.substr(6));
8904		}
8905
8906		if(track.getAttribute('data-limit') != undefined){
8907			obj.limit = Math.abs(parseInt(track.getAttribute('data-limit'),10));
8908		}
8909
8910		if(globalThumb){
8911			obj.thumb = globalThumb;
8912		}else if(track.getAttribute('data-thumb') != undefined){
8913			obj.thumb = track.getAttribute('data-thumb');
8914		}
8915		if(track.getAttribute('data-thumb-small') != undefined){
8916			obj.thumbSmall = track.getAttribute('data-thumb-small');
8917		}
8918		if(track.getAttribute('data-thumb-default') != undefined){
8919			obj.thumbDefault = track.getAttribute('data-thumb-default');
8920		}
8921		if(track.getAttribute('data-thumb-alt') != undefined){
8922			obj.thumbAlt = track.getAttribute('data-thumb-alt');
8923		}
8924		if(track.getAttribute('data-title') != undefined){
8925			obj.title = track.getAttribute('data-title');
8926		}
8927		if(track.getAttribute('data-description') != undefined){
8928			obj.description = track.getAttribute('data-description');
8929		}
8930		else if(track.querySelector('.hap-playlist-description')){
8931			var elem = track.querySelector('.hap-playlist-description')
8932			obj.descriptionHtml = elem.innerHTML
8933		}
8934		if(track.getAttribute('data-artist') != undefined){
8935			obj.artist = track.getAttribute('data-artist');
8936		}
8937		if(track.getAttribute('data-album') != undefined){
8938			obj.album = track.getAttribute('data-album');
8939		}
8940
8941		if(globalDownload){
8942			obj.download = globalDownload;
8943		}else if(track.getAttribute('data-download') != undefined){
8944			obj.download = track.getAttribute('data-download');
8945		}
8946
8947
8948		if(track.getAttribute('data-duration') != undefined){
8949			obj.duration = track.getAttribute('data-duration');
8950		}
8951		if(track.getAttribute('data-date') != undefined){
8952			obj.date = track.getAttribute('data-date');
8953		}
8954		if(track.getAttribute('data-id3') != undefined){
8955			obj.id3 = true;
8956		}
8957		if(globalStartTime){
8958			if(globalStartTimeMediaId){
8959				if(obj.mediaId != undefined && obj.mediaId == globalStartTimeMediaId){
8960					obj.start = globalStartTime;	
8961					globalStartTimeMediaId = null;
8962					globalStartTime = null;
8963				}
8964			}else{
8965				obj.start = globalStartTime;
8966			}
8967		}else if(track.getAttribute('data-start') != undefined){
8968			obj.start = Math.abs(track.getAttribute('data-start'));
8969		}
8970		if(globalEndTime){
8971			obj.end = globalEndTime;
8972		}else if(track.getAttribute('data-end') != undefined){
8973			obj.end = Math.abs(track.getAttribute('data-end'));
8974		}
8975		if(track.getAttribute('data-link') != undefined){
8976			obj.link = track.getAttribute('data-link');
8977			obj.target = '_blank';
8978			if(track.getAttribute('data-target') != undefined){
8979				obj.target = track.getAttribute('data-target');
8980			}
8981			if(track.getAttribute('data-rel') != undefined){
8982				obj.rel = track.getAttribute('data-rel');
8983			}
8984		}
8985
8986		if(globalLockTime != undefined){
8987			obj.lockTime = globalLockTime;
8988		}else if(track.getAttribute('data-lock-time') != undefined){
8989			obj.lockTime = Number(track.getAttribute('data-lock-time'));
8990		}
8991		if(globalLockVideoUserRoles){
8992			obj.lockVideoUserRoles = globalLockVideoUserRoles;
8993		}
8994
8995		if(track.getAttribute('data-playlist-icons') != undefined){
8996			obj.playlistIcons = JSON.parse(track.dataset.playlistIcons);
8997		}	
8998
8999		if(track.getAttribute('data-sort') != undefined){
9000			obj.sort = track.getAttribute('data-sort');
9001		}	
9002		if(track.getAttribute('data-active-accordion') != undefined){
9003			obj.activeAccordion = track.getAttribute('data-active-accordion');
9004		}
9005
9006		if(track.getAttribute('data-load-more') != undefined){
9007			loadMoreOnTotalScroll = true;
9008		}
9009
9010		//ads (individual overwrites global)
9011		if(showAds){
9012
9013			if(track.getAttribute('data-vast') != undefined){//ima
9014				obj.vast = track.getAttribute('data-vast')
9015			}else if(globalVast){
9016				obj.vast = globalVast
9017			}
9018
9019			if(!obj.vast){//there can be either vast or normal ads, not both
9020
9021				if(track.getAttribute('data-ad-pre') != undefined){
9022					obj.adPre = track.getAttribute('data-ad-pre').split(",").map(function(item){
9023					    return item.trim();
9024					});
9025					if(track.getAttribute('data-shuffle-ads') != undefined && obj.adPre.length>1)HAPUtils.shuffleArray(obj.adPre);
9026				}else if(globalAdPre){
9027					obj.adPre = globalAdPre;
9028				}
9029
9030				if(track.getAttribute('data-ad-mid') != undefined){
9031					obj.adMid = track.getAttribute('data-ad-mid').split(",").map(function(item){
9032					    return item.trim();
9033					});
9034					if(track.getAttribute('data-shuffle-ads') != undefined && obj.adMid.length>1)HAPUtils.shuffleArray(obj.adMid);
9035					if(track.getAttribute('data-ad-mid-interval') != undefined){
9036						obj.adMidInterval = track.getAttribute('data-ad-mid-interval');
9037					}
9038				}else if(globalAdMid){
9039					obj.adMid = globalAdMid;
9040					if(globalAdMidInterval)obj.adMidInterval = globalAdMidInterval;
9041				}
9042
9043				if(track.getAttribute('data-ad-end') != undefined){
9044					obj.adEnd = track.getAttribute('data-ad-end').split(",").map(function(item){
9045					    return item.trim();
9046					});
9047					if(track.getAttribute('data-shuffle-ads') != undefined && obj.adEnd.length>1)HAPUtils.shuffleArray(obj.adEnd);
9048				}else if(globalAdEnd){
9049					obj.adEnd = globalAdEnd;
9050				}
9051
9052			}
9053
9054		}
9055
9056
9057
9058
9059
9060
9061		return obj;
9062	}
9063
9064	//############################################//
9065	/* youtube */
9066	//############################################//
9067
9068	function createLoader(type){
9069
9070		if(type == 'youtube'){
9071
9072			_HAPYoutubeLoader = new HAPYoutubeLoader(settings);
9073			_HAPYoutubeLoader.addEventListener('HAPYoutubeLoader.END_LOAD', function(data){
9074
9075				var i, len = data.data.length, obj;
9076				for(i=0;i<len;i++){
9077					obj = data.data[i];
9078					playlistProcessData.push(obj);
9079				}
9080
9081				nextPageToken = data.nextPageToken;
9082
9083				if(loadMoreProcess){
9084					buildPlaylist();
9085				}else{
9086					checkProcessCounter();
9087				}
9088				
9089			});
9090
9091		}
9092	}
9093
9094	//############################################//
9095	/* folder */
9096	//############################################//
9097
9098	function processFolder(data){
9099
9100		if(window.location.protocol == 'file:'){
9101			console.log('Reading folders requires server connection.');
9102			return false;
9103		}
9104
9105		if(data.limit){
9106			resultLimit = data.limit;
9107			if(data.loadMore)loadMoreOnTotalScroll = true;//from addtrack
9108		}else{
9109			resultLimit = allResults;
9110		} 
9111
9112		var path = data.path.replace(/\/\//g, "/"), url = settings.sourcePath + 'includes/folder_parser.php', limit = resultLimit, load_more_limit = resultLimit;
9113
9114		nextPageToken = [];
9115
9116		if(data.id3){
9117			id3Start = id3Counter = playlistProcessData.length-1;
9118			useId3 = true;
9119		}else{
9120			useId3 = false;
9121		}
9122
9123		if(xhrRequest){
9124			xhrRequest.abort();
9125			xhrRequest = null;
9126		}
9127
9128		var params = 'dir='+path
9129		if(data.contentUrl)params += '&content_url='+data.contentUrl
9130
9131		xhrRequest = new XMLHttpRequest();
9132		xhrRequest.onreadystatechange = function() {
9133			if (xhrRequest.readyState == 4) {
9134
9135				try{
9136					var media = JSON.parse(xhrRequest.responseText)
9137				}catch(er){
9138					alert(xhrRequest.responseText);
9139					return;
9140				}
9141
9142				var i, len = media.length, entry, obj, full_path;
9143
9144				if(data.sort){
9145					if(data.sort == 'filename-asc')HAPUtils.keysrt(media, 'filename');
9146					else if(data.sort == 'filename-desc')HAPUtils.keysrt(media, 'filename', true);
9147					else if(data.sort == 'date-asc')HAPUtils.keysrt(media, 'filemtime');
9148					else if(data.sort == 'date-desc')HAPUtils.keysrt(media, 'filemtime', true);
9149				}
9150
9151				for(i=0; i < len; i++){
9152
9153					entry = media[i];
9154
9155					obj = HAPUtils.extendObj(true, {}, data);
9156					obj.type = 'audio';
9157					full_path = entry.fullpath;
9158					obj.path = full_path;
9159
9160					if(settings.createDownloadIconsInPlaylist)if(!obj.download)obj.download = full_path;
9161					if(settings.createLinkIconsInPlaylist)if(!obj.link)obj.link = full_path;
9162
9163					if(!obj.title)obj.title = entry.filename;
9164
9165					if(i < load_more_limit){
9166						playlistProcessData.push(obj);
9167						id3Counter++; 
9168					}else{
9169						nextPageToken.push(obj);
9170					}
9171					
9172				}
9173
9174				if(nextPageToken.length == 0){
9175					loadMoreOnTotalScroll = false;
9176					nextPageToken = null;
9177				}
9178
9179				if(data.id3){
9180					if(typeof jsmediatags === 'undefined'){
9181	        			console.log('Link to jsmediatags.js missing in head tag!')
9182	        		}
9183					getId3();
9184				}else{
9185					checkProcessCounter();
9186				} 
9187				
9188		    }
9189		}
9190		xhrRequest.onerror = function(e) { 
9191		    console.log('Error processFolder: ' + e);
9192			checkProcessCounter();
9193		};
9194		xhrRequest.open('POST', url);
9195		xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
9196		xhrRequest.send(params);
9197
9198	}
9199
9200	//############################################//
9201	/* accordion */
9202	//############################################//
9203
9204	function processFolderAccordion(data){
9205
9206		if(window.location.protocol == 'file:'){
9207			console.log('Reading folders requires server connection.');
9208			return false;
9209		}
9210
9211		var path = data.path.replace(/\/\//g, "/"), url = settings.sourcePath + 'includes/folder_accordion.php' 
9212
9213		if(xhrRequest){
9214			xhrRequest.abort();
9215			xhrRequest = null;
9216		}
9217
9218		var params = 'dir='+path
9219		if(data.contentUrl)params += '&content_url='+data.contentUrl
9220
9221		xhrRequest = new XMLHttpRequest();
9222		xhrRequest.onreadystatechange = function() {
9223			if (xhrRequest.readyState == 4) {
9224
9225				try{
9226					var media = JSON.parse(xhrRequest.responseText)
9227				}catch(er){
9228					alert(xhrRequest.responseText);
9229					return;
9230				}
9231
9232				var i, len = media.length, len2, j, parent, entry, children, obj, full_path;
9233
9234				for(i=0; i < len; i++){
9235
9236					children = media[i].children;
9237
9238					if(data.sort){
9239						if(data.sort == 'filename-asc')HAPUtils.keysrt(children, 'filename');
9240						else if(data.sort == 'filename-desc')HAPUtils.keysrt(children, 'filename', true);
9241						else if(data.sort == 'date-asc')HAPUtils.keysrt(children, 'filemtime');
9242						else if(data.sort == 'date-desc')HAPUtils.keysrt(children, 'filemtime', true);
9243					}
9244
9245					playlistDataAccordion.push({media:children})
9246
9247					len2 = children.length;
9248
9249					for(j=0; j < len2; j++){
9250
9251						entry = children[j];
9252
9253						obj = {type: 'audio'};
9254						if(data.mediaId != undefined)obj.mediaId = data.mediaId;
9255						
9256						full_path = entry.fullpath;
9257
9258						obj[entry.extension] = full_path;
9259
9260						if(settings.createDownloadIconsInPlaylist)if(!obj.download)obj.download = full_path;
9261						if(settings.createLinkIconsInPlaylist)if(!obj.link)obj.link = full_path;
9262
9263						if(!obj.title)obj.title = entry.filename;
9264
9265						children[j] = obj;
9266
9267					}
9268						
9269				}
9270
9271				buildAccordion(media, data);//create accordion parents
9272
9273				//set accordion item active on start
9274				if(!accordionActiveId)accordionActiveId = 0;
9275				accordioPlaylistId = accordionActiveId;
9276
9277				var ah = playlistContent.querySelector('.hap-accordion-item[data-id="'+accordionActiveId+'"]')
9278				ah.classList.add('hap-accordion-item-inited', 'hap-accordion-item-opened')
9279
9280				accordionHolder = ah.querySelector('.hap-accordion-item-content');
9281
9282				playlistContent = accordionHolder;
9283
9284				playlistProcessData = media[accordionActiveId].children;
9285
9286				if(data.id3){
9287					accordionUseId3 = true;
9288					if(typeof jsmediatags === 'undefined'){
9289	        			console.log('Link to jsmediatags.js missing in head tag!')
9290	        		}
9291	        		id3Start = -1;
9292	        		id3Counter = playlistProcessData.length-1;
9293					getId3_all();
9294				}else{
9295					buildPlaylist();
9296				}
9297					
9298		    }
9299		}
9300		xhrRequest.onerror = function(e) { 
9301		    console.log('Error processFolderAccordion: ' + e);
9302			checkProcessCounter();
9303		};
9304		xhrRequest.open('POST', url);
9305		xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
9306		xhrRequest.send(params);
9307
9308	}
9309
9310	function processJsonAccordion(data){
9311
9312		if(window.location.protocol == 'file:'){
9313			console.log('Reading json requires server connection.');
9314			return false;
9315		}
9316
9317		if(xhrRequest){
9318			xhrRequest.abort();
9319			xhrRequest = null;
9320		}
9321
9322		var path = data.path.replace(/\/\//g, "/")
9323
9324		var params = 'dir='+path
9325		if(data.contentUrl)params += '&content_url='+data.contentUrl
9326
9327		xhrRequest = new XMLHttpRequest();
9328		xhrRequest.onreadystatechange = function() {
9329			if (xhrRequest.readyState == 4) {
9330
9331				var media = JSON.parse(xhrRequest.responseText)
9332				
9333				var i, len = media.length, len2, j, parent, entry, children, obj, full_path;
9334
9335				for(i=0; i < len; i++){
9336
9337					children = media[i].children;
9338
9339					//sort each folder individually
9340					if(data.sort){
9341						if(data.sort == 'filename-asc')HAPUtils.keysrt(children, 'filename');
9342						else if(data.sort == 'filename-desc')HAPUtils.keysrt(children, 'filename', true);
9343						else if(data.sort == 'date-asc')HAPUtils.keysrt(children, 'filemtime');
9344						else if(data.sort == 'date-desc')HAPUtils.keysrt(children, 'filemtime', true);
9345					}
9346
9347					playlistDataAccordion.push({media:children})
9348
9349					len2 = children.length;
9350
9351					for(j=0; j < len2; j++){
9352
9353						entry = children[j];
9354
9355						obj = {type: 'audio'};
9356						if(data.mediaId != undefined)obj.mediaId = data.mediaId;
9357
9358						if(mediaPrefixUrl)full_path = mediaPrefixUrl + '/' + media[i].parent + '/' + entry;
9359						else full_path = entry.full_path;
9360						obj.mp3 = full_path;
9361
9362						if(settings.createDownloadIconsInPlaylist)if(!obj.download)obj.download = full_path;
9363						if(settings.createLinkIconsInPlaylist)if(!obj.link)obj.link = full_path;
9364
9365						children[j] = obj;
9366
9367					}
9368						
9369				}
9370
9371				buildAccordion(media, data);//create accordion parents
9372
9373				//set accordion item active on start
9374				if(!accordionActiveId)accordionActiveId = 0;
9375				accordioPlaylistId = accordionActiveId;
9376
9377				var ah = playlistContent.querySelector('.hap-accordion-item[data-id="'+accordionActiveId+'"]')
9378				ah.classList.add('hap-accordion-item-inited', 'hap-accordion-item-opened')
9379
9380				accordionHolder = ah.querySelector('.hap-accordion-item-content');
9381
9382				playlistContent = accordionHolder;
9383
9384				playlistProcessData = media[accordionActiveId].children;
9385
9386				if(data.id3){
9387					accordionUseId3 = true;
9388					if(typeof jsmediatags === 'undefined'){
9389	        			console.log('Link to jsmediatags.js missing in head tag!')
9390	        		}
9391	        		id3Start = -1;
9392	        		id3Counter = playlistProcessData.length-1;
9393					getId3_all();
9394				}else{
9395					buildPlaylist();
9396				}
9397					
9398			    }
9399			}
9400
9401		xhrRequest.onerror = function(e) { 
9402		    console.log('Error processJSONAcc: ' + e);
9403			checkProcessCounter();
9404		};
9405		xhrRequest.open('POST', data.path);
9406		xhrRequest.setRequestHeader("Content-Type", "application/json");
9407		xhrRequest.send(params);
9408
9409	}
9410
9411	//############################################//
9412	/* google drive folder */
9413	//############################################//
9414
9415	function processGdriveFolder(data){
9416
9417		if(window.location.protocol == 'file:'){
9418			console.log('Reading files from folders locally is not possible! This requires server connection.');
9419			return false;
9420		}
9421
9422		var key = settings.gDriveAppId || settings.gdk
9423
9424		if(HAPUtils.isEmpty(key)){
9425			console.log('Google Drive api key has not been set in settings!');
9426			return false;
9427		}
9428
9429		var url = "https://www.googleapis.com/drive/v3/files?q='" + data.path + "'+in+parents&pageSize=1000&key=" + key;
9430
9431		if(data.id3 && settings.cors){
9432			id3Start = id3Counter = playlistProcessData.length-1;
9433			useId3 = true;
9434			useID3Cors = true;
9435		}else{
9436			useId3 = false;
9437			useID3Cors = false;
9438		}
9439
9440		if(xhrRequest){
9441			xhrRequest.abort();
9442			xhrRequest = null;
9443		}
9444
9445		xhrRequest = new XMLHttpRequest();
9446		xhrRequest.onreadystatechange = function() {
9447			if (xhrRequest.readyState == 4) {
9448
9449				var media = JSON.parse(xhrRequest.responseText)
9450				
9451				var i, len = media.files.length, entry, obj, imgArr = [], mediaArr = [];
9452
9453				for(i=0; i < len; i++){
9454
9455					entry = media.files[i];
9456
9457					//split audio and images (assume there is equal number of audio and image files named the same in folder!)
9458					if(/audio/.test(entry.mimeType)){	
9459						mediaArr.push(entry);
9460					}else if(/image/.test(entry.mimeType)){	
9461						imgArr.push(entry);
9462					}
9463				}
9464
9465				if(data.sort){
9466					if(data.sort == 'filename-asc'){
9467						HAPUtils.keysrt(mediaArr, 'name');
9468						if(imgArr.length)HAPUtils.keysrt(imgArr, 'name');
9469					}
9470					else if(data.sort == 'filename-desc'){
9471						HAPUtils.keysrt(mediaArr, 'name', true);	
9472						if(imgArr.length)HAPUtils.keysrt(imgArr, 'name', true);
9473					}
9474				}
9475
9476				len = mediaArr.length;
9477
9478				for(i=0; i < len; i++){
9479
9480					entry = mediaArr[i];
9481
9482					obj = HAPUtils.extendObj(true, {}, data);
9483					obj.type = 'audio';
9484
9485					obj.path = 'https://drive.google.com/uc?export=view&id='+entry.id
9486
9487					if(settings.createDownloadIconsInPlaylist){
9488						if(!obj.download)obj.download = obj.path
9489					}
9490					
9491					//if(!obj.share)obj.share = 'https://drive.google.com/open?id=' + entry.id;
9492					if(settings.createLinkIconsInPlaylist)if(!obj.link)obj.link = 'https://drive.google.com/open?id=' + entry.id;
9493
9494					if(!obj.thumb && imgArr[i])obj.thumb = 'https://drive.google.com/uc?export=view&id=' + imgArr[i].id;
9495
9496					if(!obj.title)obj.title = entry.name.substr(0, entry.name.lastIndexOf('.'));
9497
9498					playlistProcessData.push(obj); 
9499					id3Counter++; 
9500
9501				}
9502
9503				if(data.id3){
9504					if(typeof jsmediatags === 'undefined'){
9505	        			console.log('Link to jsmediatags.js missing in head tag!')
9506	        			checkProcessCounter();
9507	        		}else{
9508	        			getId3();
9509	        		}
9510				}else{
9511					checkProcessCounter();
9512				} 
9513					
9514		    }
9515		}
9516		xhrRequest.onerror = function(e) { 
9517		    console.log('Error processGDriveFolder: ' + e);
9518			checkProcessCounter();
9519		};
9520		xhrRequest.open('GET', url);
9521		xhrRequest.setRequestHeader("Content-Type", "application/json");
9522		xhrRequest.send();
9523
9524	}
9525
9526	function resumeFolderLoad(){
9527		if(!nextPageToken)return false;
9528
9529		playlistTransitionOn = true;
9530		if(preloader)preloader.style.display = 'block';
9531		playlistProcessData = nextPageToken.splice(0,resultLimit);
9532		if(nextPageToken.length == 0)nextPageToken = null;
9533
9534		//check id3
9535		if(useId3){
9536			id3Start = -1;
9537			id3Counter = playlistProcessData.length-1;
9538			getId3();
9539		}else{
9540			buildPlaylist();
9541		}
9542		
9543	}
9544
9545	//############################################//
9546	/* id3 tags */
9547	//############################################//
9548
9549	function getId3(){
9550		/*https://github.com/aadsm/jsmediatags*/
9551
9552		var item = playlistProcessData[id3Counter],
9553		url = item.path || item.mp3 || item.wav || item.aac;
9554
9555		if(useID3Cors)url = settings.cors + url
9556
9557		jsmediatags.read(url, {
9558		    onSuccess: function(tag) {
9559		        var tags = tag.tags, image = tags.picture;
9560
9561		        if(tags.artist) item.artist = tags.artist;
9562				if(tags.title) item.title = tags.title;
9563				if(tags.album) item.album = tags.album;
9564				if(settings.getId3Image && image){
9565					var base64String = "", i, len = image.data.length;
9566					for(i = 0; i < len; i++)base64String += String.fromCharCode(image.data[i]);
9567					item.thumb = "data:" + image.format + ";base64," + window.btoa(base64String);
9568					item.thumbIsBase64 = true;
9569				}
9570				if(tags.TLEN){//https://community.mp3tag.de/t/calculate-mp3-length/10145
9571					item.duration = tags.TLEN.data / 1000;
9572				}
9573
9574				id3Counter--;
9575				if(id3Counter > id3Start){
9576					getId3();  
9577				}else{
9578					if(loadMoreProcess){
9579						buildPlaylist();//from resume folder load
9580					}else{
9581						checkProcessCounter();
9582					}
9583					
9584				}
9585
9586		    },
9587		    onError: function(error) {
9588		        console.log("ID3 error: ", error.type, error.info);
9589		        id3Counter--;
9590				if(id3Counter > id3Start){
9591					getId3();  
9592				}else{
9593					checkProcessCounter();
9594				}  
9595		    }
9596		});
9597	}
9598
9599	//############################################//
9600	/* onedrive folder */
9601	//############################################//
9602
9603	function processOnedriveFolder(data){
9604
9605		if(window.location.protocol == 'file:'){
9606			console.log('Reading files from folders locally is not possible! This requires server connection.');
9607			return false;
9608		}
9609
9610		var p = data.path
9611		if(p.indexOf('?') > -1)p = p.substr(0,p.indexOf('?'))
9612		var id = btoa(p);
9613		var url = 'https://api.onedrive.com/v1.0/shares/u!'+id+'/root?expand=children'
9614
9615		if(data.id3 && settings.cors){
9616			id3Start = id3Counter = playlistProcessData.length-1;
9617			useId3 = true;
9618			useID3Cors = true;
9619		}else{
9620			useId3 = false;
9621			useID3Cors = false;
9622		}
9623
9624		if(xhrRequest){
9625			xhrRequest.abort();
9626			xhrRequest = null;
9627		}
9628
9629		xhrRequest = new XMLHttpRequest();
9630		xhrRequest.onreadystatechange = function() {
9631			if (xhrRequest.readyState == 4) {
9632
9633				var d = JSON.parse(xhrRequest.responseText)
9634
9635	            var i, len = d.children.length, entry, obj, imgArr = [], mediaArr = [];
9636
9637				for(i=0; i < len; i++){
9638
9639					entry = d.children[i];
9640
9641					if(entry.file){
9642
9643						if(/audio/.test(entry.file.mimeType)){	
9644							mediaArr.push(entry);
9645						}else if(/image/.test(entry.file.mimeType)){	
9646							imgArr.push(entry);
9647						}
9648
9649						if(data.sort == 'date-asc' || data.sort == 'date-desc'){
9650							var date = new Date(entry.fileSystemInfo.lastModifiedDateTime);
9651							entry.fileSystemInfo.lastModifiedDateTime = date.getTime(); 
9652						}
9653						else if(data.sort == 'created-date-asc' || data.sort == 'created-date-desc'){
9654							var date = new Date(entry.fileSystemInfo.createdDateTime);
9655							entry.fileSystemInfo.createdDateTime = date.getTime(); 
9656						}
9657
9658					}
9659				}
9660
9661				if(data.sort){
9662					if(data.sort == 'filename-asc'){
9663						HAPUtils.keysrt(mediaArr, 'name');
9664						if(imgArr.length)HAPUtils.keysrt(imgArr, 'name');
9665					}
9666					else if(data.sort == 'filename-desc'){
9667						HAPUtils.keysrt(mediaArr, 'name', true);
9668						if(imgArr.length)HAPUtils.keysrt(imgArr, 'name', true);		
9669					}
9670					else if(data.sort == 'date-asc'){
9671						HAPUtils.keysrt2(mediaArr, 'fileSystemInfo', 'lastModifiedDateTime');	
9672						if(imgArr.length)HAPUtils.keysrt2(imgArr, 'fileSystemInfo', 'lastModifiedDateTime');	
9673					}
9674					else if(data.sort == 'date-desc'){
9675						HAPUtils.keysrt2(mediaArr, 'fileSystemInfo', 'lastModifiedDateTime', true);	
9676						if(imgArr.length)HAPUtils.keysrt2(imgArr, 'fileSystemInfo', 'lastModifiedDateTime', true);	
9677					}
9678					else if(data.sort == 'created-date-asc'){
9679						HAPUtils.keysrt2(mediaArr, 'fileSystemInfo', 'createdDateTime');	
9680						if(imgArr.length)HAPUtils.keysrt2(imgArr, 'fileSystemInfo', 'createdDateTime');	
9681					}
9682					else if(data.sort == 'created-date-desc'){
9683						HAPUtils.keysrt2(mediaArr, 'fileSystemInfo', 'createdDateTime', true);	
9684						if(imgArr.length)HAPUtils.keysrt2(imgArr, 'fileSystemInfo', 'createdDateTime', true);
9685					}
9686				}
9687
9688				len = mediaArr.length;
9689
9690	            for(i=0; i < len; i++){
9691
9692					entry = mediaArr[i];
9693
9694					obj = HAPUtils.extendObj(true, {}, data);
9695					obj.type = 'audio';
9696
9697					//meta
9698					if(entry.audio){
9699						if(entry.audio.title)obj.title = entry.audio.title;
9700						if(entry.audio.artist)obj.artist = entry.audio.artist;
9701						if(entry.audio.duration)obj.duration = entry.audio.duration / 1000;
9702						if(entry.audio.genre)obj.genre = entry.audio.genre;
9703						if(entry.audio.album)obj.album = entry.audio.album;
9704						if(entry.audio.bitrate)obj.bitrate = entry.audio.bitrate;
9705					}
9706
9707					if(!obj.title)obj.title = entry.name.substr(0, entry.name.lastIndexOf('.'));
9708
9709					obj.path = entry['@content.downloadUrl']
9710
9711					if(settings.createDownloadIconsInPlaylist){
9712						if(!obj.download)obj.download = obj.path
9713					}
9714
9715					if(settings.createLinkIconsInPlaylist)if(!obj.link)obj.link = entry.webUrl;
9716
9717					if(!obj.thumb && imgArr[i])obj.thumb = imgArr[i]['@content.downloadUrl']
9718
9719					playlistProcessData.push(obj); 
9720					id3Counter++; 
9721
9722				}
9723
9724				if(data.id3){
9725					if(typeof jsmediatags === 'undefined'){
9726	        			console.log('Link to jsmediatags.js missing in head tag!')
9727	        			checkProcessCounter();
9728	        		}else{
9729	        			getId3();
9730	        		}
9731				}else{
9732					checkProcessCounter();
9733				} 
9734
9735			}
9736		}
9737		xhrRequest.onerror = function(e) { 
9738			console.log('Error processOnedriveFolder: ' + e);
9739			checkProcessCounter();
9740		};
9741		xhrRequest.open('GET', url);
9742		xhrRequest.setRequestHeader("Content-Type", "application/json");
9743		xhrRequest.send();
9744
9745	}
9746
9747	//############################################//
9748	/* accordion */
9749	//############################################//
9750
9751	function getId3_all(){
9752
9753		var item = playlistProcessData[id3Counter],
9754		url = item.path || item.mp3 || item.wav || item.aac;
9755
9756		jsmediatags.read(url, {
9757		    onSuccess: function(tag) {
9758		        var tags = tag.tags, image = tags.picture;
9759
9760		        if(tags.artist) item.artist = tags.artist;
9761				if(tags.title) item.title = tags.title;
9762				if(tags.album) item.album = tags.album;
9763				if(settings.getId3Image && image){
9764					var base64String = "", i, len = image.data.length;
9765					for(i = 0; i < len; i++)base64String += String.fromCharCode(image.data[i]);
9766					item.thumb = "data:" + image.format + ";base64," + window.btoa(base64String);
9767					item.thumbIsBase64 = true;
9768				}
9769				if(tags.TLEN){
9770					item.duration = tags.TLEN.data / 1000;
9771				}
9772
9773				id3Counter--;
9774				if(id3Counter > id3Start){
9775					getId3_all();  
9776				}else{
9777					buildPlaylist();
9778				}
9779
9780		    },
9781		    onError: function(error) {
9782		        console.log("ID3 error: ", error.type, error.info);
9783		        id3Counter--;
9784				if(id3Counter > id3Start){
9785					getId3_all();  
9786				}else{
9787					buildPlaylist();
9788				}  
9789		    }
9790		});
9791	}
9792
9793
9794	//############################################//
9795	/* podcast */
9796	//############################################//
9797
9798	function processPodcast(data){
9799
9800		if(window.location.protocol == 'file:'){
9801			console.log('Using Podcast requires server connection!');
9802			return;
9803		}
9804
9805		if(data.limit){
9806			resultLimit = data.limit;
9807			if(data.loadMore)loadMoreOnTotalScroll = true;//from addtrack
9808		}else{
9809			resultLimit = allResults;
9810		} 
9811
9812		var url = proxy + '?url='+ encodeURIComponent(data.path), limit = resultLimit, load_more_limit = resultLimit;
9813
9814		nextPageToken = [];
9815
9816		if(xhrRequest){
9817			xhrRequest.abort();
9818			xhrRequest = null;
9819		}
9820
9821		xhrRequest = new XMLHttpRequest();
9822		xhrRequest.onreadystatechange = function() {
9823			if (xhrRequest.readyState == 4) {
9824				var media = xhrRequest.responseText
9825
9826				var rss = media.substr(media.indexOf('<?xml'));
9827
9828				rss = rss.substring(0,rss.indexOf('/rss>')+5);
9829
9830				rss = rss.replace(/(\\t|\\n|\\r)/gm, " ");
9831
9832				rss = rss.replace(/\\/g, '');
9833
9834				var dom = HAPUtils.parseXML(rss), item, obj, default_artwork;
9835
9836				if(dom.getElementsByTagName('image') && dom.getElementsByTagName('image').length && dom.getElementsByTagName('image')[0].getAttribute('href')){
9837					default_artwork = dom.getElementsByTagName('image')[0].getAttribute('href');
9838
9839				}else if(dom.getElementsByTagName('itunes\:image') && dom.getElementsByTagName('itunes\:image').length && dom.getElementsByTagName('itunes\:image')[0].getAttribute('href')){
9840					default_artwork = dom.getElementsByTagName('itunes\:image')[0].getAttribute('href');
9841				}
9842
9843				if(default_artwork && default_artwork.lastIndexOf('&size=Large'))default_artwork = default_artwork.replace("=Large", "=Small");
9844
9845				var arr = dom.getElementsByTagName("item"), zlen = 0;
9846
9847				var i, len = arr.length
9848				for(i = 0;i < len; i++){
9849					item = arr[i]
9850
9851					//if(zlen >= limit) return false;
9852
9853					obj = HAPUtils.extendObj(true, {}, data);
9854
9855					obj.type = 'audio';
9856
9857					obj.mp3 = item.getElementsByTagName('enclosure')[0].getAttribute('url');
9858
9859					if(!obj.title){
9860						var tt;
9861						if(item.getElementsByTagName('title').length)tt = item.getElementsByTagName('title')[0].innerHTML;
9862						else if(item.getElementsByTagName('itunes\:title').length) tt = item.getElementsByTagName('itunes\:title')[0].innerHTML;
9863
9864						if(tt){
9865							if(tt.indexOf('[CDATA[') > -1){
9866								tt = tt.replace("<![CDATA[", "").replace("]]>", "");
9867							}
9868							obj.title = tt;
9869						}
9870
9871					}
9872
9873					if(!obj.artist){
9874						var tt;
9875						if(item.getElementsByTagName('author').length)tt = item.getElementsByTagName('author')[0].innerHTML;
9876						else if(item.getElementsByTagName('itunes\:author').length) tt = item.getElementsByTagName('itunes\:author')[0].innerHTML;
9877
9878						if(tt){
9879							if(tt.indexOf('[CDATA[') > -1){
9880								tt = tt.replace("<![CDATA[", "").replace("]]>", "");
9881							}
9882							obj.artist = tt;
9883						}
9884
9885					}  
9886
9887					if(item.getElementsByTagName('pubDate'))obj.date = item.getElementsByTagName('pubDate')[0].innerHTML;
9888						
9889					if(!obj.description && item.getElementsByTagName('description')){
9890						var desc = item.getElementsByTagName('description')[0].innerHTML;
9891						var div = document.createElement("div");
9892						div.innerHTML = desc;
9893						obj.description = div.textContent || div.innerText || "";
9894					}
9895					if(!obj.duration){
9896						var duration
9897						if(item.getElementsByTagName('itunes\:duration').length)duration = item.getElementsByTagName('itunes\:duration')[0].innerHTML;
9898						else if(item.getElementsByTagName('duration').length)duration = item.getElementsByTagName('duration')[0].innerHTML;
9899
9900						if(duration){
9901							obj.duration = HAPUtils.hmsToSecondsOnly(duration)
9902						}
9903					}
9904					if(!obj.thumb){
9905						if(item.getElementsByTagName('image').length && item.getElementsByTagName('image')[0].getAttribute('href')){
9906							obj.thumb = item.getElementsByTagName('image')[0].getAttribute('href');
9907						}
9908						else if(item.getElementsByTagName('itunes\:image').length && item.getElementsByTagName('itunes\:image')[0].getAttribute('href')){
9909							obj.thumb = item.getElementsByTagName('itunes\:image')[0].getAttribute('href');
9910						}
9911						else if(default_artwork){
9912							obj.thumb = default_artwork;
9913						}
9914					}
9915
9916					if(obj.thumb && obj.thumb.lastIndexOf('&size=Large'))obj.thumb = obj.thumb.replace("=Large", "=Small");
9917
9918					if(settings.createDownloadIconsInPlaylist)if(!obj.download)obj.download = obj.mp3;
9919
9920					if(!obj.link)if(settings.createLinkIconsInPlaylist){
9921						if(item.getElementsByTagName('link').length){
9922							var link = item.getElementsByTagName('link')[0];
9923							obj.link = link?.firstChild?.wholeText?.trim();
9924						}
9925					}
9926
9927					if(zlen < load_more_limit)playlistProcessData.push(obj); 
9928					else nextPageToken.push(obj); 
9929
9930					zlen++;
9931
9932				}
9933
9934				checkProcessCounter();
9935		    }
9936		}
9937		xhrRequest.onerror = function(e) { 
9938		    console.log('Error processPodcast: ' + e);
9939			checkProcessCounter();
9940		};
9941		xhrRequest.open('GET', url);
9942		xhrRequest.setRequestHeader("Content-Type", "text/xml");
9943		xhrRequest.send();
9944
9945	}
9946
9947	//############################################//
9948	/* itunes */
9949	//############################################//
9950
9951	function processiTunes(data){
9952
9953		if(window.location.protocol == 'file:'){
9954			console.log('Using Podcast requires server connection!');
9955			return;
9956		}
9957
9958		var match = data.path.match(/id(\d+)/); 
9959		if (match) var podID = match[1];
9960		//else var podID = url.match(/\d+/); 
9961
9962		if(!podID){
9963		  	console.log('No Podcast ID found!');
9964		  	checkProcessCounter();
9965		  	return;
9966		}
9967
9968		if(xhrRequest){
9969			xhrRequest.abort();
9970			xhrRequest = null;
9971		}
9972
9973		var url = 'https://itunes.apple.com/lookup'
9974		var params = 'id='+parseInt(podID)+'&entity=podcast';
9975
9976		xhrRequest = new XMLHttpRequest();
9977		xhrRequest.onreadystatechange = function() {
9978			if (xhrRequest.readyState == 4) {
9979
9980				var result = JSON.parse(xhrRequest.responseText)
9981				
9982				//console.log(result)
9983
9984				data.path = result.results[0]['feedUrl'];
9985		    	processPodcast(data);
9986				
9987		    }
9988		}
9989		xhrRequest.onerror = function(e) { 
9990		    console.log('Error processItunes: ' + e);
9991			checkProcessCounter();
9992		};
9993		xhrRequest.open('POST', url);
9994		xhrRequest.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
9995		xhrRequest.send(params);
9996	
9997	}
9998
9999	///////////////
10000
10001	function resumePodcast(){
10002		if(!nextPageToken)return false;
10003
10004		playlistTransitionOn = true;
10005		if(preloader)preloader.style.display = 'block';
10006		playlistProcessData = [];
10007		playlistProcessData = nextPageToken.splice(0,resultLimit);
10008		if(nextPageToken.length == 0)nextPageToken = null;
10009		buildPlaylist();
10010	}
10011
10012	function resumeSoundCloud(){
10013		if(!nextPageToken)return false;
10014
10015		playlistTransitionOn = true;
10016		if(preloader)preloader.style.display = 'block';
10017		playlistProcessData = [];
10018		playlistProcessData = nextPageToken.splice(0,resultLimit);
10019		if(nextPageToken.length == 0)nextPageToken = null;
10020		buildPlaylist();
10021
10022		/*playlistTransitionOn = true;
10023		preloader.style.display = 'block';
10024		playlistProcessCounter = 0;
10025		playlistProcessDataUrl = [];
10026		playlistLength = 1;
10027
10028		if(!window.SC){
10029			initSoundCloud();
10030			var interval = setInterval(function(){
10031				if(soundCloudInited){
10032					if(interval) clearInterval(interval);
10033					getSoundCloudPage(false, nextPageToken);
10034				}
10035			}, 100);
10036		}else{
10037			getSoundCloudPage(false, nextPageToken);
10038		}*/
10039	}
10040
10041	
10042
10043	//############################################//
10044	/* build playlist */
10045	//############################################//
10046
10047	function buildPlaylist() {
10048
10049		var counter, i, j = !addTrackProcess ? playlistDataArr.length : insertPosition, len = playlistProcessData.length, type, div, str, item_content, item_content_wrap, data, thumbsrc, thumbImg, playlistThumb, title, artist, img, alt, desc, descHtml, date, duration, counter_add = 0, currentInsert, playlist_data = [], icon_content;
10050
10051		lastPlaylistDataArr = [];
10052
10053		for (i=0; i < len; i++){
10054
10055			counter = i+j;			
10056			if(addTrackProcess)counter_add++;
10057
10058			if(playlistDataReady){
10059				data = playlistProcessData[i].data;
10060			}else{
10061				data = playlistProcessData[i];
10062			}
10063			type = data.type;
10064
10065			if(settings.usePlaylist){	
10066
10067				if(data.type){	
10068
10069					icon_content = null;
10070					img = null;
10071					desc = null;
10072					descHtml = null;
10073					duration = null;
10074					date = null;
10075
10076					//thumb
10077					if(playlistItemContent.indexOf('thumb') != -1){
10078						var thumb = data.thumbSmall || data.thumb || data.thumbDefault;
10079
10080						if(thumb){
10081
10082							if(data.origtype == 'soundcloud'){
10083								if(settings.soundCloudThumbQualityInPlaylist)thumb = thumb.replace('large.jpg', settings.soundCloudThumbQualityInPlaylist);
10084							}
10085
10086							if(mediaPrefixUrl && !data.thumbIsBase64 && !HAPUtils.relativePath(thumb))thumb = mediaPrefixUrl + thumb;
10087
10088							if(data.thumbAlt)alt = data.thumbAlt;
10089							else if(data.title)alt = data.title.replace(/"/g, "'");
10090							else alt = 'image';
10091
10092							img = '<img class="hap-thumbimg" src="'+thumb+'" alt="'+alt+'"/>';
10093
10094						}
10095					}
10096
10097					//duration
10098					if(playlistItemContent.indexOf('duration') != -1 && data.duration){
10099						if(isNaN(data.duration)){
10100							if(data.duration.lastIndexOf(':')==-1)duration = HAPUtils.formatTime(data.duration);
10101							else duration = data.duration;
10102						}else{
10103							duration = HAPUtils.formatTime(data.duration);
10104						}
10105					}
10106
10107					//date
10108					if(playlistItemContent.indexOf('date') != -1 && data.date){
10109						date = new Date(data.date).toDateString().slice(4,10);
10110					}
10111
10112					if(nonProcessMedia.indexOf(data.origtype) != -1){
10113						div = data.pi_item.cloneNode(true)
10114					}else{
10115						if(!data.origclasses)data.origclasses = 'hap-playlist-item';//copy classes if exist
10116						div = HAPUtils.htmlToElement('<div class="'+data.origclasses+'"></div>');
10117						delete data.origclasses;
10118					}
10119
10120					item_content_wrap = HAPUtils.htmlToElement('<div class="hap-playlist-item-wrap"></div>')
10121					div.prepend(item_content_wrap)
10122
10123					item_content = HAPUtils.htmlToElement('<div class="hap-playlist-item-content"></div>')
10124					item_content_wrap.prepend(item_content)
10125
10126					//thumb
10127					if(playlistItemContent.indexOf('thumb') != -1 && img){
10128						var pt = '<div class="hap-playlist-thumb">'+img;
10129
10130						if(settings.togglePlaybackOnPlaylistItem)pt += '<div class="hap-playlist-thumb-style"></div>';
10131
10132						pt += '</div>'
10133
10134						pt = HAPUtils.htmlToElement(pt)
10135
10136						item_content.append(pt)
10137
10138						if(!isMobile){
10139
10140							pt.addEventListener('mouseenter', function (e) {
10141								overPlaylistItem(e)
10142							}, false);
10143
10144							pt.addEventListener('mouseleave', function (e) {
10145								outPlaylistItem(e)
10146							}, false);
10147						}
10148					
10149					}
10150
10151					//title, description
10152					if(playlistItemContent.indexOf('title') != -1 || (playlistItemContent.indexOf('description') != -1 && data.description)){
10153
10154						var info = HAPUtils.htmlToElement('<div class="hap-playlist-info">')
10155						item_content.append(info)
10156						
10157						if(playlistItemContent.indexOf('title') != -1 && data.title){
10158
10159							var par = '<div class="hap-playlist-title-wrap">';
10160
10161							if(settings.useNumbersInPlaylist)par += '<div class="hap-playlist-title-num"></div>';
10162
10163							if(data.artist && data.title){//both title and artist exist
10164
10165								if(playlistTitleOrder[0] == 'title'){
10166
10167									par += '<div class="hap-playlist-title">'+data.title+'</div>'+settings.artistTitleSeparator+'<div class="hap-playlist-artist">'+data.artist+'</div>';
10168								}else{
10169									par += '<div class="hap-playlist-artist">'+data.artist+'</div>'+settings.artistTitleSeparator+'<div class="hap-playlist-title">'+data.title+'</div>';
10170								}
10171							}
10172							else if(data.title){//only title
10173								par += '<div class="hap-playlist-title">'+data.title+'</div>';
10174							}
10175							else if(data.artist){//only artist
10176								par += '<div class="hap-playlist-artist">'+data.artist+'</div>';
10177							}
10178
10179							par += '</div>';//hap-playlist-title-wrap
10180
10181							par = HAPUtils.htmlToElement(par)
10182
10183							info.append(par)
10184
10185							if(!isMobile){
10186
10187								par.addEventListener('mouseenter', function (e) {
10188									overPlaylistItem(e)
10189								}, false);
10190
10191								par.addEventListener('mouseleave', function (e) {
10192									outPlaylistItem(e)
10193								}, false);
10194							}
10195
10196						}
10197
10198						//description
10199						if(playlistItemContent.indexOf('description') != -1 && data.description){
10200
10201							desc = data.description;
10202
10203							if(settings.limitDescriptionText && settings.limitDescriptionText != 0 && desc.length > parseInt(settings.limitDescriptionText,10)){
10204
10205								if(settings.createReadMoreInDescription){
10206
10207									var d1 = desc.substr(0,parseInt(settings.limitDescriptionText,10)),
10208									d2 = desc.substr(parseInt(settings.limitDescriptionText,10))
10209
10210									desc = d1 + '<span class="hap-playlist-description-read-more-text hap-playlist-description-read-more-text-hidden">'+d2+'</span><span class="hap-playlist-description-read-more-dots">...</span>&nbsp;<span class="hap-playlist-description-read-more-btn" title="'+settings.limitDescriptionReadMoreText+'">'+settings.limitDescriptionReadMoreText+'</span>';
10211
10212								}else{
10213
10214									desc = desc.substr(0,parseInt(settings.limitDescriptionText,10)) + '...'
10215
10216								}
10217
10218							}
10219							//if(nonProcessMedia.indexOf(data.origtype) == -1){
10220							info.append(HAPUtils.htmlToElement('<div class="hap-playlist-description">'+desc+'</div>'))
10221						
10222						}
10223					}
10224
10225					//duration, date
10226					if(playlistItemContent.indexOf('duration') != -1 && data.duration || playlistItemContent.indexOf('date') != -1 && data.date){
10227
10228						var info2 = HAPUtils.htmlToElement('<div class="hap-playlist-info2"></div>')
10229						item_content.append(info2)
10230						
10231						if(playlistItemContent.indexOf('duration') != -1 && duration){
10232							var dur1 = HAPUtils.htmlToElement('<div class="hap-playlist-duration">'+duration+'</div>')
10233							info2.append(dur1)
10234						}
10235						
10236						if(playlistItemContent.indexOf('date') != -1 && date){
10237							var date1 = HAPUtils.htmlToElement('<div class="hap-playlist-date">'+date+'</div>')
10238							info2.append(date1)
10239						}
10240					}
10241
10242					div.setAttribute('data-type', type);
10243
10244					//we need media-id, title, and artist for stats and api!
10245					if(data.mediaId != undefined){
10246						div.setAttribute('data-media-id', data.mediaId);
10247					}
10248					if(data.title){
10249						var st = data.title.replace(/['"\|\&]/g,''),
10250						tt = self.getTitle(data, true);
10251
10252						div.setAttribute('data-title', data.title)
10253						div.setAttribute('data-safe-title', st)//note: we have added this so we can have stats on grouped media with same media id!
10254
10255						data.safeTitle = st;
10256					}
10257					if(data.artist){
10258						var sa = data.artist.replace(/['"\|\&]/g,'');
10259						div.setAttribute('data-artist', data.artist);
10260						div.setAttribute('data-safe-artist', sa)
10261
10262						data.safeArtist = sa;
10263					}
10264					if(data.description){
10265						//div.append(HAPUtils.htmlToElement('<div class="hap-playlist-description-full">'+data.description+'</div>'))
10266					}
10267
10268					/*if(data.peaks != undefined){
10269						div.setAttribute('data-peaks', data.peaks);
10270					}*/
10271
10272
10273					if(settings.useStatistics && statisticsContent.length){
10274
10275						var icon_content = HAPUtils.htmlToElement('<div class="hap-playlist-icons"></div>')
10276						item_content.after(icon_content)	
10277						//icon_content = document.querySelector('<div class="hap-playlist-icons"></div>').insertAfter(item_content);
10278						
10279						//statistics icons
10280						var stats = HAPUtils.htmlToElement('<div class="hap-stats"></div>');
10281						icon_content.prepend(stats);
10282
10283						if(statisticsContent.indexOf('plays') != -1){
10284							var p = HAPUtils.htmlToElement('<div class="hap-stat-icon hap-play-count" title="'+settings.tooltipStatPlays+'">'+settings.statPlayIcon+'<span>0</span></div>');
10285							stats.append(p);
10286						}
10287
10288						if(statisticsContent.indexOf('likes') != -1){
10289						    var p = HAPUtils.htmlToElement('<div class="hap-stat-icon hap-like-count" title="'+settings.tooltipStatLikes+'">'+settings.statLikeIcon+'<span>0</span></div>');
10290						    stats.append(p);
10291						}
10292
10293						if(statisticsContent.indexOf('downloads') != -1){
10294						    if(hasDownloadSupport && data.download != undefined){
10295
10296						    	var p = HAPUtils.htmlToElement('<a href="#" class="hap-stat-icon hap-download-count" aria-label="'+settings.tooltipStatDownloads+'" title="'+settings.tooltipStatDownloads+'">'+settings.statDownloadIcon+'<span>0</span></a>');
10297
10298							}else{
10299								//if download not set on item still show download icon count?
10300								var p = HAPUtils.htmlToElement('<div class="hap-stat-icon hap-download-count hap-no-download" title="'+settings.tooltipStatDownloads+'">'+settings.statDownloadIcon+'<span>0</span></div>');
10301							}
10302							stats.append(p);
10303
10304						}else{
10305
10306							//download icon
10307							/*if(hasDownloadSupport && data.download){
10308
10309								icon_content = document.querySelector('<div class="hap-playlist-icons"></div>').appendTo(div);
10310
10311								icon_content.prepend(getDownloadIcon(data));
10312							}*/
10313
10314						}
10315
10316					}else{
10317
10318						//download icon
10319						if(hasDownloadSupport && data.download){
10320
10321							var icon_content = HAPUtils.htmlToElement('<div class="hap-playlist-icons"></div>')
10322							item_content.after(icon_content)	
10323
10324							icon_content.insertAdjacentHTML('afterbegin', getDownloadIcon(data));
10325						}
10326
10327					}
10328
10329
10330					if(data.playlistIcons){
10331
10332						if(!icon_content){
10333							var icon_content = HAPUtils.htmlToElement('<div class="hap-playlist-icons"></div>')
10334							item_content.after(icon_content)
10335						}
10336
10337						var j, pi_len = data.playlistIcons.length, pi_obj, cl
10338						for(j = 0; j < pi_len; j++){
10339							pi_obj = data.playlistIcons[j]
10340
10341							if(pi_obj.icon){
10342
10343								if(pi_obj.class)cl = ' '+pi_obj.class
10344								else cl = ''
10345
10346								var ci = '<a class="hap-playlist-icon'+cl+'"'
10347								if(pi_obj.url)ci += ' href="'+pi_obj.url+'"'
10348								if(pi_obj.target)ci += ' target="'+pi_obj.target+'"'
10349								if(pi_obj.title)ci += ' title="'+pi_obj.title+'"'
10350								if(pi_obj.rel)ci += ' rel="'+pi_obj.rel+'"'	
10351								ci += '>'+pi_obj.icon+'</a>';	
10352
10353							}
10354
10355							icon_content.insertAdjacentHTML("beforeend",ci);
10356						}
10357					}
10358
10359	
10360
10361
10362					//link icon
10363					if(data.link){
10364						
10365						if(!icon_content){
10366							var icon_content = HAPUtils.htmlToElement('<div class="hap-playlist-icons"></div>')
10367							item_content.after(icon_content)
10368						}
10369
10370						icon_content.insertAdjacentHTML("beforeend",getLinkIcon(data));
10371					}
10372
10373					
10374					//variations, create icons if added dynamically
10375					if(div.querySelector(".hap-audio-variations")){
10376
10377						var variations = div.querySelector(".hap-audio-variations");
10378						if(variations.querySelectorAll('.hap-audio-variation')){
10379							
10380							variations.querySelectorAll('.hap-audio-variation').forEach(div => {
10381
10382								var item = div
10383
10384								if(item.getAttribute('data-download') || item.getAttribute('data-link')){
10385									var vstr = '<div class="hap-playlist-icons">'
10386
10387									if(item.getAttribute('data-download')){
10388										vstr += getDownloadIcon({download: item.getAttribute('data-download')})
10389									}
10390									if(item.getAttribute('data-link')){
10391										vstr += getLinkIcon({link: item.getAttribute('data-link'), target: item.getAttribute('data-target'), rel: item.getAttribute('data-rel')})
10392									}
10393
10394									vstr += '</div>'
10395
10396									item.insertAdjacentHTML("beforeend",vstr)
10397
10398								}
10399
10400							});
10401							
10402						}
10403					}
10404
10405
10406
10407
10408
10409
10410
10411					if(!addTrackProcess){
10412						playlistContent.append(div)
10413					}else{
10414						if(!currentInsert){
10415							if(endInsert){
10416								playlistContent.append(div);
10417							}else{
10418								var de = playlistContent.querySelectorAll('.hap-playlist-item')[insertPosition]
10419								de.before(div);
10420							}
10421						}else{
10422							currentInsert.after(div);
10423						}
10424						currentInsert = div;
10425					}
10426
10427				}
10428				else{//copy directly to playlist 
10429
10430					div = data;
10431
10432					if(!addTrackProcess){
10433						playlistContent.append(div);
10434					}else{
10435						if(!currentInsert){
10436							if(endInsert){
10437								playlistContent.append(div);
10438							}else{
10439								var de = playlistContent.querySelectorAll('.hap-playlist-item')[insertPosition]
10440								de.before(div);
10441							}
10442						}else{
10443							currentInsert.after(div);
10444						}
10445						currentInsert = div;
10446					}
10447
10448				}
10449
10450
10451			}
10452			else{//no playlist
10453
10454				if(settings.useStatistics){
10455
10456					if(data.mediaId != undefined){
10457						if(data.title){
10458							var st = data.title.replace(/['"\|\&]/g,'');
10459							data.safeTitle = st;
10460						}
10461						if(data.artist){
10462							var sa = data.artist.replace(/['"\|\&]/g,'');
10463							data.safeArtist = sa;
10464						}
10465					}
10466				}
10467
10468			}
10469
10470			if(playlistId != undefined)data.playlistId = playlistId;
10471
10472			if(accordionMode)playlist_data.push({id: i, type: type, data: data});
10473			else playlistDataArr.splice(counter, 0, {id: counter, type: type, data: data});
10474
10475			lastPlaylistDataArr.push({id: counter, type: type, data: data});
10476				
10477		}
10478
10479		if(accordionMode){
10480
10481			settings.usePlaylistScroll = false;
10482
10483			if(!settings.allowOnlyOneOpenedAccordion)playlistFilterMsg.classList.add('hap-force-hidden')//we cant use nothing found msg with multi opened accordions
10484
10485			if(playlistInner)playlistInner.classList.add('hap-accordion')
10486
10487			//############################################//
10488			/* accordion */
10489			//############################################//
10490
10491			playlistDataAccordion[accordionActiveId].data = playlist_data;//save all accordion data (when we click other acordion item, we change data)
10492
10493			//only first time accordion is created, next time we switch playlist only when song is clicked in other accordion
10494
10495			if(!accordionInited){
10496				playlistDataArr = playlist_data;//current accordion playlist data
10497
10498				updatePlaylist(true);	
10499				if(settings.useStatistics)updateStats();
10500			
10501				_HAPPlaylistManager.setPlaylistItems(playlistLength);//we treat each accordion as new playlist
10502
10503			}else{
10504				updatePlaylist(false);	
10505				if(settings.useStatistics)updateStats();
10506			}
10507
10508			endInit();
10509
10510			//open accordion item
10511			var content = accordionHolder;
10512			setTimeout(function(){
10513				content.style.height = content.scrollHeight + "px";
10514			},200)
10515			
10516
10517       	    
10518
10519			accordionLoading = false;//set accordion item loaded
10520
10521			accordionInited = true;
10522
10523			//############################################//
10524			/* accordion */
10525			//############################################//
10526
10527		}else{
10528
10529			if(playlistInner)playlistInner.classList.remove('hap-accordion')
10530		
10531			lastPlaylist = playlistContent;
10532
10533			//console.log('playlistDataArr = ', playlistDataArr);
10534
10535			updatePlaylist(true);	
10536			if(settings.useStatistics)updateStats();
10537
10538			
10539
10540
10541			if(!addTrackProcess){
10542				_HAPPlaylistManager.setPlaylistItems(playlistLength);
10543			}else{
10544				var current_counter = _HAPPlaylistManager.getCounter();
10545				_HAPPlaylistManager.setPlaylistItems(playlistLength, false);
10546				
10547				if(insertPosition <= current_counter){
10548					if(!endInsert)_HAPPlaylistManager.reSetCounter(current_counter+counter_add);
10549				}
10550
10551				if(addTrackPlayit)_HAPPlaylistManager.setCounter(insertPosition, false);
10552
10553				isEnqueue = false;
10554			}
10555
10556			endInit();
10557
10558		}
10559			
10560	}
10561
10562	function getDownloadIcon(data){
10563
10564		var d = '<a class="hap-playlist-icon hap-download" href="'+data.download+'" download title="'+settings.downloadIconTitle+'" aria-label="'+settings.downloadIconTitle+'">'+settings.downloadIcon+'</a>';
10565
10566		return d;
10567	}
10568	function getLinkIcon(data){
10569
10570		var target = data.target || '_blank',
10571		rel = data.rel || 'nofollow';
10572
10573		var d = '<a class="hap-playlist-icon hap-link" href="'+data.link+'" target="'+target+'" title="'+settings.linkIconTitle+'" aria-label="'+settings.linkIconTitle+'" rel="'+rel+'">'+settings.linkIcon+'</a>';
10574
10575		return d;
10576	}
10577
10578	//############################################//
10579	/* accordion */
10580	//############################################//
10581
10582	function checkAccordion(holder){//check accordion item when its clicked first time 
10583
10584		if(preloader)preloader.style.display = 'block';
10585
10586		accordionActiveId = holder.getAttribute('data-id');
10587		accordionHolder = holder.classList.add('hap-accordion-item-inited');
10588		accordionHolder = holder.querySelector('.hap-accordion-item-content');
10589
10590		playlistContent = accordionHolder;//current accordion playlist
10591		playlistProcessData = playlistDataAccordion[accordionActiveId].media;
10592
10593		if(accordionUseId3){//load id3 tags
10594    		id3Start = -1;
10595    		id3Counter = playlistProcessData.length-1;
10596			getId3_all();
10597		}else{//we dont use id3 tags
10598			buildPlaylist();
10599		}
10600	}
10601
10602	function buildAccordion(arr, data){//make accordion parents
10603
10604		var i, len = arr.length, str = '', title;
10605
10606		for(i=0; i < len; i++){
10607			title = arr[i].parent.replace(/"/g, "'");
10608
10609			if(data.activeAccordion !== 'undefined'){//active folder on start
10610				if(data.activeAccordion == title)accordionActiveId = i;
10611			}
10612
10613			str += '<div class="hap-accordion-item" data-id="'+i+'" title="'+title+'"><span class="hap-accordion-item-title">'+title+'</span>'+
10614			'<div class="hap-accordion-item-content">';
10615			if(arr[i].description)str += '<span class="hap-accordion-item-description">'+arr[i].description+'</span>';
10616			str += '</div>'+
10617			'</div>';
10618		}
10619
10620		playlistContent.innerHTML = str;		
10621
10622	}
10623
10624	//############################################//
10625	/* accordion */
10626	//############################################//
10627
10628	function updatePlaylist(update_playlist_data){
10629		if(update_playlist_data)playlistLength = playlistDataArr.length;
10630
10631		if(settings.usePlaylist){
10632
10633			var i = 0, item, num, stat_arr = [], title_node;
10634
10635			var div_list = playlistContent.querySelectorAll('.hap-playlist-item').forEach(div => {
10636				
10637				item = div
10638				item.setAttribute('data-id', i);
10639
10640				title_node = item.querySelector('.hap-playlist-title');
10641
10642				if(title_node){
10643
10644					if(settings.useNumbersInPlaylist){//append counter
10645						num = HAPUtils.formatNumber(i) + settings.numberTitleSeparator;
10646						if(item.querySelector('.hap-playlist-title-num')){//if already exist
10647							item.querySelector('.hap-playlist-title-num').innerHTML = num;
10648						}else{
10649
10650							var tn = HAPUtils.htmlToElement('<div class="hap-playlist-title-num">
10650'+num+'</div>')
10651							title_node.before(tn)
10652
10653							//title_node.before(document.querySelector('<div class="hap-playlist-title-num">'+num+'</div>'));
10654						}
10655					}
10656
10657				}
10658
10659				if(update_playlist_data)playlistDataArr[i].id = i;
10660
10661				i++;
10662			});
10663
10664		}
10665
10666	}
10667
10668	function updateStats(){
10669
10670		var i, item, stat_arr = [], mid; 
10671
10672		for(i = 0;i < playlistLength; i++){
10673			item = playlistDataArr[i].data;
10674			item.id = i;
10675
10676			if(!item.hapStatsSet){
10677				mid = item.mediaId != undefined ? item.mediaId : null;
10678				stat_arr.push({media_id: mid, title: item.safeTitle || item.title || '', artist: item.safeArtist || item.artist || ''});
10679				item.hapStatsSet = true;
10680			}
10681		}
10682
10683		if(stat_arr.length)getStats('hap_all_count', null, stat_arr);
10684
10685	}
10686
10687	function checkPlaylistScroll(elem){
10688
10689		if(settings.playlistScrollType == 'perfect-scrollbar'){
10690
10691			if(typeof PerfectScrollbar !== 'function'){
10692
10693				var script = document.createElement('script');
10694				script.type = 'text/javascript';
10695				script.src = HAPUtils.qualifyURL(settings.sourcePath+settings.perfectScrollbar_js);
10696				script.onload = script.onreadystatechange = function() {
10697				    if(!this.readyState || this.readyState == 'complete'){
10698				      	checkPlaylistScroll(elem);
10699				    }
10700				};
10701				script.onerror = function(){
10702					alert("Error loading " + this.src);
10703				}
10704				var tag = document.getElementsByTagName('script')[0];
10705				tag.parentNode.insertBefore(script, tag);
10706
10707			}else{
10708
10709				playlistScroll = new PerfectScrollbar(elem, {
10710				  	wheelSpeed: 2,
10711				 	wheelPropagation: true,
10712				  	minScrollbarLength: 30
10713				});
10714
10715				var event = settings.playlistScrollOrientation == 'horizontal' ? 'ps-x-reach-end' : 'ps-y-reach-end'
10716
10717				elem.addEventListener(event, () => {
10718
10719					if(loadMoreOnTotalScroll){
10720			    		if(nextPageToken && loadMoreType){
10721				    		if(!playlistTransitionOn){
10722
10723				    			playlistTransitionOn = true;
10724				    			if(preloader)preloader.style.display = 'block';
10725				    			loadMoreProcess = true;
10726								playlistProcessData = [];
10727								if(loadMoreBtn)loadMoreBtn.style.opacity = 0;
10728
10729								if(loadMoreType == 'soundcloud'){
10730									resumeSoundCloud();
10731								}else if(loadMoreType == 'podcast'){
10732									resumePodcast();	
10733								}else if(loadMoreType == 'folder'){
10734									resumeFolderLoad();	
10735								}else if(loadMoreType == 'youtube'){
10736									if(!_HAPYoutubeLoader)createLoader('youtube');
10737									_HAPYoutubeLoader.resumeLoad(nextPageToken);
10738								}
10739							}
10740						}
10741					}else if(addMoreOnTotalScroll){
10742						if(!loadMoreProcess){
10743							addMore();
10744						}
10745					}
10746
10747				});
10748
10749
10750			}
10751
10752		}else if(settings.playlistScrollType == 'browser'){
10753
10754			var cl = settings.playlistScrollOrientation == 'horizontal' ? 'hap-playlist-overflow-x' : 'hap-playlist-overflow-y'
10755
10756			playlistInner.classList.add(cl)
10757
10758		}
10759
10760	}
10761
10762	function endInit(){
10763
10764		if(preloader)preloader.style.display = 'none';
10765		playlistTransitionOn = false;
10766		if(settings.autoResumeAfterAdd != undefined)return;
10767
10768		if(!componentInited){
10769			componentInited = true;
10770
10771			if(settings.placeHolderThumb){
10772				setThumb(settings.placeHolderThumb)
10773			}
10774
10775
10776
10777			//playlist global data
10778
10779			if(playlistData){
10780
10781				if(playlistData.getAttribute('data-use-pagination') != undefined){//has to be here because savePagination needs build playlist items
10782					paginationTotalPages = Math.ceil(addMoreNumResults / addMoreLimit);
10783
10784					if (addMoreNumResults > addMoreLimit){
10785						paginationCurrentPage = 0;
10786						createPagination(paginationCurrentPage);
10787
10788						var i,obj;
10789						for(i = 0;i<paginationTotalPages;i++){
10790							obj = i == 0 ? {page:0} : {page:null}//first page is already available
10791							paginationArr.push(obj);
10792						}
10793						savePagination(paginationCurrentPage);
10794
10795					}
10796				}
10797			}
10798
10799
10800			//read more playlist desc
10801			if(settings.createReadMoreInDescription){
10802				onHandler(playlistContent, 'click', '.hap-playlist-description-read-more-btn', (e) => handleReadMore(e))
10803			}
10804
10805
10806			if(settings.useStickyPlayer && settings.clonePlaylistInFixedPlayer){
10807				var cp = playlistContent.cloneNode(true)
10808				_HAPStickyPlayer.clonePlaylist(cp)
10809				checkPlaylistScroll(_HAPStickyPlayer.getPlaylist());
10810			}	
10811
10812			if(settings.usePlaylistScroll && !playlistScrollInit){//was calling total scroll event (and calling add more) so we put it here
10813				if(playlistInner)checkPlaylistScroll(playlistInner);
10814				playlistScrollInit = true;
10815			}
10816
10817
10818			
10819
10820
10821			setTimeout(function(){
10822				if(!settings.hidePlayerUntilMusicStart){
10823
10824					checkPresetMods();
10825
10826					if(settings.addResizeEvent)doneResizing();
10827
10828					if(playerOuter){
10829						setTimeout(function(){
10830							playerOuter.style.opacity = 1;
10831						},50);
10832					}
10833					
10834				}
10835
10836				self.fireEvent('setupDone', [{instance:self, instanceName:settings.instanceName}]);
10837
10838			},100);
10839		
10840
10841
10842
10843
10844			if(accordionMode){
10845
10846				lastOpenedAccordion = wrapper.querySelectorAll('.hap-accordion-item')[accordionActiveId];
10847
10848	            wrapper.querySelectorAll('.hap-accordion-item-title').forEach(function(el){
10849
10850	            	el.addEventListener('click', function(e){
10851
10852		                var parent = e.currentTarget.closest('.hap-accordion-item');
10853
10854		                if(parent.classList.contains('hap-accordion-item-opened')){
10855		                    parent.classList.remove('hap-accordion-item-opened');
10856		                    var content = parent.querySelector('.hap-accordion-item-content')
10857		                    content.style.height = "0";
10858		                }else{
10859		                    //close other opened if new is clicked
10860		                    if(settings.allowOnlyOneOpenedAccordion){
10861		                        if(lastOpenedAccordion && lastOpenedAccordion != parent){
10862
10863		                            lastOpenedAccordion.classList.remove('hap-accordion-item-opened');
10864		                            var content = lastOpenedAccordion.querySelector('.hap-accordion-item-content')
10865		                            content.style.height = "0";
10866
10867		                            //show hidden tracks from search
10868		                            var div_list = document.querySelectorAll('.hap-playlist-item').forEach(div => {
10869										div.style.display = 'block';
10870									})
10871
10872		                        }
10873		                    }
10874
10875		                    //reset search
10876		                    if(searchSelector)searchSelector.value = '';
10877							if(playlistFilterMsg)playlistFilterMsg.style.display = 'none';
10878
10879
10880		                    if(parent.classList.contains('hap-accordion-item-inited')){
10881		                        var content = parent.querySelector('.hap-accordion-item-content')
10882		                        content.style.height = content.scrollHeight + "px";
10883		                    }else{
10884		                        if(accordionLoading)return false;
10885		                        accordionLoading = true;
10886		                        checkAccordion(parent); 
10887		                    }
10888
10889		                    lastOpenedAccordion = parent
10890		                    lastOpenedAccordion.classList.add('hap-accordion-item-opened');
10891		                    
10892		                }
10893
10894		                
10895	                }); 
10896
10897	            });
10898
10899			}
10900
10901		}
10902
10903		if(settings.usePlaylist && playlistLength > 0){
10904
10905			if(settings.usePlaylistScroll && settings.playlistScrollType == 'perfect-scrollbar'){
10906				if(playlistScroll)playlistScroll.update() 
10907			}
10908
10909
10910
10911			//fade thumbs
10912			var fadeArr = [];
10913
10914			var div_list = playlistContent.querySelectorAll('.hap-thumbimg:not(.hap-visible)').forEach(div => {
10915				fadeArr.push(div);
10916			});
10917
10918			var p=0, len = fadeArr.length, z;
10919			for(z=0;z<len;z++){
10920				setTimeout(function(){
10921				   clearTimeout(this);
10922				   fadeArr[p].classList.add('hap-visible');
10923				   p++;
10924				},50 + ( z * 50 ));
10925			}
10926
10927			if(!accordionMode && !HAPUtils.isEmpty(sortOrder))self.sort(sortOrder);//sort on start
10928
10929		}
10930
10931		if(!playlistInitLoad){//only after destroy playlist, not after add media
10932			playlistInitLoad = true;
10933
10934			if(!addTrackProcess && !loadMoreProcess && playlistLength > 0){
10935
10936				if(settings.mediaId != undefined && !HAPUtils.isEmpty(settings.mediaId)){
10937
10938			    	if(settings.mediaTitle){//used when we share song url from processed media
10939			    		var pi = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+settings.mediaId+'"][data-safe-title="'+settings.mediaTitle+'"]');//if we have title
10940
10941			    		if(!pi){//if fails
10942			    			var pi = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+settings.mediaId+'"]');
10943			    		}
10944
10945			    		delete settings.mediaTitle;
10946			    	}else{
10947			    		var pi = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+settings.mediaId+'"]');
10948			    	}
10949			    	
10950					var index = getElementIndex(playlistContent, pi, '.hap-playlist-item');
10951				
10952					if(index == undefined || index == -1){
10953						alert('No media with ID to load! LoadMedia failed.');
10954						return false;
10955					}
10956
10957					delete settings.mediaId;
10958					_HAPPlaylistManager.processPlaylistRequest(index);
10959
10960				}else{	
10961
10962					if(activeMediaId != null){
10963						var pi = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+activeMediaId+'"]');
10964						if(pi){
10965							var ai = pi.getAttribute('data-id');
10966							activeMediaId = null;
10967						}else{
10968							var ai = 0
10969						}
10970					}else{
10971						var ai = settings.activeItem;
10972					}
10973					if(ai > playlistLength-1)ai = playlistLength-1;
10974					if(ai>-1)_HAPPlaylistManager.setCounter(ai, false);
10975
10976				}
10977				
10978			}
10979			/*else if(playlistDataReady){//load song into connected player on start
10980				var ai = settings.activeItem;
10981				if(ai > playlistLength-1)ai = playlistLength-1;
10982				if(ai>-1)_HAPPlaylistManager.setCounter(ai, false);
10983			}*/
10984		}
10985
10986		addTrackProcess = false;
10987		loadMoreProcess = false;
10988
10989		if(loadMoreBtn){
10990			if(loadMoreOnTotalScroll){
10991				if(nextPageToken){
10992					loadMoreBtn.style.opacity = 1;
10993				}else{
10994	        		loadMoreBtn.remove();
10995					loadMoreBtn = null;
10996					loadMoreOnTotalScroll = false;
10997	        	} 
10998	        }else if(addMoreOnTotalScroll){
10999	        	if(addMoreNumResults > 0){
11000					if(addMoreNumResults > addMoreOffset){
11001						loadMoreBtn.style.opacity = 1;
11002					}else{
11003						loadMoreBtn.remove();
11004						loadMoreBtn = null;
11005						addMoreOnTotalScroll = false;
11006					}
11007				}else{
11008					loadMoreBtn.remove();
11009					loadMoreBtn = null;
11010				}
11011	        }else{
11012	        	loadMoreBtn.remove();
11013				loadMoreBtn = null;
11014	        }
11015        }
11016
11017        self.fireEvent('playlistEndLoad', [{instance:self, instanceName:settings.instanceName, loadMoreOnTotalScroll:loadMoreOnTotalScroll, addMoreOnTotalScroll:addMoreOnTotalScroll, playlistLength: playlistLength}]);
11018
11019
11020
11021
11022
11023
11024
11025
11026
11027
11028	}
11029
11030	function handleReadMore(e){
11031
11032		var btn = e.target,
11033		parent = btn.closest('.hap-playlist-description'),
11034		moreText = parent.querySelector('.hap-playlist-description-read-more-text'),
11035		dots = parent.querySelector('.hap-playlist-description-read-more-dots')
11036
11037		if(dots.style.display == 'none'){
11038			//all text shown, hide it
11039			btn.innerHTML = settings.limitDescriptionReadMoreText;
11040			btn.setAttribute('title', settings.limitDescriptionReadMoreText)
11041			dots.style.display = 'inline'
11042
11043			moreText.classList.add('hap-playlist-description-read-more-text-hidden')
11044
11045		}else{
11046			btn.innerHTML = settings.limitDescriptionReadLessText;
11047			btn.setAttribute('title', settings.limitDescriptionReadLessText)
11048			dots.style.display = 'none'
11049
11050			moreText.classList.remove('hap-playlist-description-read-more-text-hidden')
11051
11052		}
11053	}
11054
11055	//############################################//
11056	/* hls */
11057	//############################################//
11058
11059	var recoverMediaErrorDate,
11060    swapAudioCodecDate
11061
11062	function initHls(){
11063
11064		if(settings.hlsConfig){
11065			hls = new Hls(settings.hlsConfig);
11066		}else{
11067			hls = new Hls();
11068		}
11069
11070		hlsSupport = Hls.isSupported();
11071		hlsInited = true;
11072
11073	    hls.on(Hls.Events.MEDIA_ATTACHED, function () {
11074	        //console.log("video and hls.js are now bound together !");
11075			hls.loadSource(mediaPath);
11076	    });
11077
11078	    hls.on(Hls.Events.ERROR, function (event, data) {
11079	    	if(data.fatal){
11080	        	switch(data.type) {
11081		        case Hls.ErrorTypes.NETWORK_ERROR:
11082			        // try to recover network error
11083			        console.log("fatal network error encountered, try to recover");
11084			        hls.startLoad();
11085			        break;
11086		        case Hls.ErrorTypes.MEDIA_ERROR:
11087			        console.log("fatal media error encountered, try to recover");
11088			        //hls.recoverMediaError();
11089
11090			        var now = performance.now();
11091				    if (!recoverMediaErrorDate || now - recoverMediaErrorDate > 3000) {
11092				        recoverMediaErrorDate = performance.now();
11093				        hls.recoverMediaError();
11094				    } else if (!swapAudioCodecDate || (now - swapAudioCodecDate) > 3000) {
11095				        swapAudioCodecDate = performance.now();
11096				        hls.swapAudioCodec();
11097				        hls.recoverMediaError();
11098				    }
11099
11100			        break;
11101		        default:
11102			        // cannot recover
11103			        hls.destroy();
11104			        break;
11105		        }
11106	        }
11107	    });
11108
11109	}
11110
11111	//############################################//
11112	/* audio player */
11113	//############################################//
11114
11115	function getAudioUrl(data){
11116
11117		var url
11118
11119		if(mediaType == 'hls'){
11120
11121			url = data.path;
11122
11123		}
11124		else{
11125
11126			if(settings.useAudioPreview && data.audioPreview){
11127				url = data.audioPreview;
11128			}else{
11129
11130				if(mp3Support && data.mp3){
11131					url = data.mp3;
11132				}
11133				else if(aacSupport && data.aac){
11134					url = data.aac;
11135				}
11136				else if(wavSupport && data.wav){
11137					url = data.wav;
11138				}
11139				else if(flacSupport && data.flac){
11140					url = data.flac;
11141				}
11142				else if(data.path){
11143					url = data.path;
11144				}
11145			}
11146
11147		} 
11148
11149		if(!url){	
11150			console.log('No required audio format supplied! Please add mp3 audio format to play!');
11151			return;
11152		}
11153
11154		if(url.indexOf(bsf_match) != -1)url = HAPUtils.b64DecodeUnicode(url.substr(6));
11155
11156		if(mediaType == 'audio' && mediaPrefixUrl && !HAPUtils.relativePath(url))url = mediaPrefixUrl + url;
11157
11158		if(isSafari && settings.applySafariAudioRestartFix)url += "?rand=" + (Math.random() * 99999999)
11159
11160		return url;
11161
11162	}
11163
11164	function initAudio(){
11165
11166		if(mediaType == 'hls'){
11167
11168			if(!hlsInited){
11169
11170				if(typeof Hls === 'undefined'){
11171					
11172					var script = document.createElement('script');
11173					script.type = 'text/javascript';
11174					script.src = hls_js;
11175					script.onload = script.onreadystatechange = function() {
11176					    if(!this.readyState || this.readyState == 'complete'){
11177					      	initHls();
11178							initAudio();
11179					    }
11180					};
11181					script.onerror = function(){
11182						alert("Error loading " + this.src);
11183					}
11184					var tag = document.getElementsByTagName('script')[0];
11185					tag.parentNode.insertBefore(script, tag);
11186
11187				}else{
11188					initHls();
11189					initAudio();
11190				}
11191				return;
11192			}
11193
11194		}
11195
11196		audioReady = false;
11197
11198		if(!audioInited){
11199			audioInited = true;
11200
11201			audio.addEventListener("ended", function(){
11202				if(activeAudio != audio)return;
11203
11204				if(!crossFadeOn){
11205					handleAudioEnded()
11206				}
11207				
11208			})
11209			audio.addEventListener("loadedmetadata", function(){
11210				if(activeAudio != audio)return;
11211
11212				handleAudioLoadedmetadata()
11213
11214			})
11215			audio.addEventListener("canplay", function(){
11216				if(activeAudio != audio)return;
11217
11218				handleAudioCanplay()
11219			})
11220			audio.addEventListener("canplaythrough", function(){
11221				if(activeAudio != audio)return;
11222
11223			})	
11224			audio.addEventListener("play", function(){
11225				if(activeAudio != audio)return;
11226
11227				handleAudioPlay()
11228			})
11229			audio.addEventListener("pause", function(){
11230				if(activeAudio != audio)return;
11231
11232				handleAudioPause()
11233			})
11234			audio.addEventListener("seeked", function(){
11235				if(activeAudio != audio)return;
11236
11237				handleAudioSeeked()
11238			})	
11239			audio.addEventListener("error", function(e){
11240				if(activeAudio != audio)return;
11241
11242				handleAudioError(e)
11243				
11244			});
11245
11246
11247			if(settings.useCrossFade){
11248
11249				audio2.addEventListener("ended", function(){
11250					if(activeAudio != audioUp2Js2)return;
11251
11252					if(!crossFadeOn){
11253						handleAudioEnded()
11254					}
11255
11256				})
11257				audio2.addEventListener("loadedmetadata", function(){
11258					if(activeAudio != audioUp2Js2)return;
11259
11260					handleAudioLoadedmetadata()
11261
11262				})
11263				audio2.addEventListener("canplay", function(){
11264					if(activeAudio != audioUp2Js2)return;
11265
11266					handleAudioCanplay()
11267				})
11268				audio2.addEventListener("canplaythrough", function(){
11269					if(activeAudio != audioUp2Js2)return;
11270
11271				})	
11272				audio2.addEventListener("play", function(){
11273					if(activeAudio != audioUp2Js2)return;
11274
11275					handleAudioPlay()
11276				})
11277				audio2.addEventListener("pause", function(){
11278					if(activeAudio != audioUp2Js2)return;
11279
11280					handleAudioPause()
11281				})
11282				audio2.addEventListener("seeked", function(){
11283					if(activeAudio != audioUp2Js2)return;
11284
11285					handleAudioSeeked()
11286				})	
11287				audio2.addEventListener("error", function(e){
11288					if(activeAudio != audioUp2Js2)return;
11289
11290					handleAudioError(e)
11291					
11292				});
11293
11294			}
11295
11296		}
11297
11298
11299		if(mediaType == 'hls'){
11300
11301			if(window.location.protocol == 'file:'){
11302				console.log('Playing HLS requires server connection!');
11303				return false;
11304			}
11305
11306			if(hlsSupport){
11307				hls.attachMedia(activeAudio);
11308			}else if(activeAudio.canPlayType('application/vnd.apple.mpegurl')){//ios backup
11309
11310				if(useBlob){
11311
11312					if(bUrlIntervalID){
11313						clearInterval(bUrlIntervalID);
11314						bUrlIntervalID = null;
11315					}
11316					self.getBUrl(mediaPath);
11317					bUrlIntervalID = setInterval(function(){
11318						if(bUrl){
11319							clearInterval(bUrlIntervalID);
11320							bUrlIntervalID = null;
11321						
11322							activeAudio.src = bUrl;
11323
11324						}
11325					},100);
11326
11327				}else{
11328					activeAudio.src = mediaPath;
11329				}
11330
11331			}else if(currMediaData.mp3){
11332
11333				if(useBlob){
11334
11335					if(bUrlIntervalID){
11336						clearInterval(bUrlIntervalID);
11337						bUrlIntervalID = null;
11338					}
11339					self.getBUrl(currMediaData.mp3);
11340					bUrlIntervalID = setInterval(function(){
11341						if(bUrl){
11342							clearInterval(bUrlIntervalID);
11343							bUrlIntervalID = null;
11344						
11345							activeAudio.src = bUrl;
11346
11347						}
11348					},100);
11349
11350				}else{
11351					activeAudio.src = currMediaData.mp3;
11352				}
11353
11354			}else{
11355				console.log("This browser or device does not support HLS extension. Please use mp3 audio for playback!");
11356				return;
11357			}
11358
11359			if(autoPlay){
11360				var promise = activeAudio.play();
11361				if (promise !== undefined) {
11362					promise.then(function(){
11363				    }).catch(function(error){
11364				    });
11365				}
11366			}
11367
11368		}else{
11369
11370			if(useBlob && (currMediaData.origtype == 'audio')){
11371
11372				if(bUrlIntervalID){
11373					clearInterval(bUrlIntervalID);
11374					bUrlIntervalID = null;
11375				}
11376				self.getBUrl(mediaPath);
11377				bUrlIntervalID = setInterval(function(){
11378					if(bUrl){
11379						clearInterval(bUrlIntervalID);
11380						bUrlIntervalID = null;
11381
11382						activeAudio.src = bUrl;
11383
11384					}
11385				},100);
11386
11387			}else{
11388				activeAudio.src = mediaPath;
11389			}
11390
11391			if(autoPlay){
11392				var promise = activeAudio.play();
11393				if (promise !== undefined) {
11394					promise.then(function(){
11395				    }).catch(function(error){
11396				    });
11397				}
11398			}
11399
11400		}
11401
11402		activeAudio.volume = settings.volume;
11403
11404		autoPlay = true;
11405		settings.autoPlay = true;
11406
11407		//10.3.2020 - added this here because there is a lot of delay on ios
11408		
11409		if(dataIntervalID) clearInterval(dataIntervalID);
11410		dataIntervalID = setInterval(trackProgress, dataInterval);
11411
11412	}
11413
11414	//############################################//
11415	/* media events */
11416	//############################################//
11417
11418	function handleAudioEnded(){
11419		if(useRange && rangeOn && !rangeSet && !_HAPRangeSlider.isDrag()){
11420			rangeSet = true;
11421			activeAudio.currentTime = range_a_time;
11422
11423			var promise = activeAudio.play();
11424			if (promise !== undefined) {
11425				promise.then(function(){
11426			    }).catch(function(error){
11427			    });
11428			}
11429
11430			return;
11431		}
11432		mediaEndHandler();	
11433	}
11434
11435	function handleAudioLoadedmetadata(){
11436		setTimeout(function(){
11437			if(!audioReady){
11438				handleAudioCanplay()
11439			}
11440		},1000);
11441	}
11442
11443	function handleAudioCanplay(){
11444		if(!audioReady){
11445
11446			audioReady = true;
11447
11448			try{
11449				activeAudio.playbackRate = Number(settings.playbackRate);
11450			}catch(er){}
11451
11452			//set visible duration
11453			var d = activeAudio.duration;
11454
11455			if(HAPUtils.isNumber(d)){
11456				if(mediaTimeTotal)mediaTimeTotal.innerHTML = HAPUtils.formatTime(d);
11457
11458				if(useRange){
11459					rangeMinTime.innerHTML = '00:00';
11460					rangeMaxTime.innerHTML = HAPUtils.formatTime(d);
11461				}
11462
11463			}
11464
11465			if(settings.resumeTime){//from continous playback
11466				activeAudio.currentTime = settings.resumeTime;
11467				delete settings.resumeTime;
11468			}
11469			else if(currMediaData.start){
11470				activeAudio.currentTime = currMediaData.start;
11471			}
11472
11473		}
11474		else{
11475			if(settings.resumeTime){
11476				activeAudio.currentTime = settings.resumeTime;
11477				delete settings.resumeTime;
11478			}
11479		}
11480	}
11481
11482	function handleAudioPlay(){
11483		playHandler();
11484	}
11485
11486	function handleAudioPause(){
11487		pauseHandler();
11488	}
11489
11490	function handleAudioSeeked(){
11491		if(useRange && rangeOn){
11492			setTimeout(function(){
11493				clearTimeout(this);
11494				rangeSet = false;
11495			},500);
11496		}
11497		if(videoSync){
11498			if(!syncComingFromVideo)videoSync.currentTime = activeAudio.currentTime
11499			syncComingFromVideo = false;
11500		}
11501	}
11502
11503	function handleAudioError(e){
11504		console.log(activeAudio.error);
11505
11506		if(currMediaData.origtype && currMediaData.origtype == 'soundcloud'){
11507
11508			if(activeAudio.currentTime)settings.resumeTime = activeAudio.currentTime
11509
11510			_scLoader.resume()
11511
11512		}else if((currMediaData.origtype == 's3_bucket_audio' || currMediaData.origtype == 's3_audio') && activeAudio.error && activeAudio.error.message && activeAudio.error.message.indexOf('PIPELINE_ERROR_READ') > -1){
11513
11514			if(activeAudio.currentTime)settings.resumeTime = activeAudio.currentTime
11515
11516			resumeAwsUrl(currMediaData)
11517
11518		}else{
11519
11520			self.fireEvent('soundError', [{instance:self, instanceName:settings.instanceName, media:currMediaData, error:e}]);
11521			if(settings.autoAdvanceToNextSongOnError)self.nextMedia()
11522		}
11523
11524		/*switch (e.target.error.code) {
11525	     case e.target.error.MEDIA_ERR_ABORTED:
11526	       console.log('You aborted the video playback.');
11527	       break;
11528	     case e.target.error.MEDIA_ERR_NETWORK:
11529	       console.log('A network error caused the audio download to fail.');
11530	       break;
11531	     case e.target.error.MEDIA_ERR_DECODE:
11532	       console.log('The audio playback was aborted due to a corruption problem or because the video used features your browser did not support.');
11533	       break;
11534	     case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
11535	       console.log('The video audio not be loaded, either because the server or network failed or because the format is not supported.');
11536	       break;
11537	     default:
11538	       console.log('An unknown error occurred.');
11539	       break;
11540	   }*/
11541	}
11542
11543	function cancelCrossFadeSeek(){//cancel when seeking (seek, seek forward / backward)
11544
11545		isCancelCrossFade = true;
11546		crossFadeOn = false;
11547
11548	  	if(audio.c_active){
11549	  		if(audioUp2Js2){
11550				audioUp2Js2.pause();
11551				audioUp2Js2.src = '';
11552			}
11553	  	}
11554	  	else {
11555	  		if(audio){
11556				audio.pause();
11557				audio.src = '';
11558			}
11559
11560			audioUp2Js2.c_active = false;
11561			audio.c_active = true;
11562			activeAudio = audio;
11563	  	}
11564
11565	  	nextMediaData = null;//we have to kill crossfade otherwise it hard to manage all
11566
11567		setTimeout(function(){
11568			isCancelCrossFade = false;//wait till ramp ends
11569		},1000)
11570
11571	}
11572
11573	function cancelCrossFadeAdvance(){//cancel when advancing (next, previous, click playlist item)
11574
11575		isCancelCrossFade = true;
11576		crossFadeOn = false;
11577
11578		if(audio){
11579			audio.pause();
11580			audio.src = '';
11581		}
11582
11583		if(audioUp2Js2){
11584			audioUp2Js2.pause();
11585			audioUp2Js2.src = '';
11586		}
11587
11588		audio.c_active = true;
11589		audioUp2Js2.c_active = false;
11590
11591		activeAudio = audio;
11592
11593		nextMediaData = null;
11594
11595	}
11596
11597	function initCrossFade(next_url, t, d){
11598
11599		var active_a = audio.c_active ? audio : audioUp2Js2,
11600		next_active_a = !audio.c_active ? audio : audioUp2Js2;
11601
11602		var fade_time = d - t;//if user seeks into fade time
11603
11604		fadeOutAudio(active_a, 0, fade_time)
11605  		fadeInAudio(next_active_a, next_url, settings.volume, fade_time)
11606
11607	}
11608
11609	function fadeInAudio(audiox, url, targetVolume, fade_time) {
11610
11611        if(!targetVolume) {
11612            targetVolume = 1;
11613        }
11614
11615        var rampTime = fade_time * 1000
11616
11617        // How often adjust audio volume (ms)
11618        var tick = 50;
11619
11620        var volumeIncrease = targetVolume / (rampTime / tick);
11621
11622        function ramp() {
11623        	if(isCancelCrossFade)return
11624
11625            var vol = Math.min(targetVolume, audiox.volume + volumeIncrease);
11626
11627            audiox.volume = vol;
11628
11629             // Have we reached target volume level yet?
11630            if(audiox.volume < targetVolume) {
11631                // Keep up going until 11
11632                setTimeout(ramp, tick);
11633            }else{
11634
11635            	var interval = setInterval(function(){
11636					if(fadeOutAudioComplete){//after clean media
11637						clearInterval(interval);
11638
11639						comingFromCrossFade = true;
11640						mediaEndHandler();
11641
11642						if(audio.c_active){
11643							audio.c_active = false;
11644							audioUp2Js2.c_active = true;
11645
11646							activeAudio = audioUp2Js2
11647						}else{
11648							audio.c_active = true;
11649							audioUp2Js2.c_active = false;
11650
11651							activeAudio = audio
11652						}
11653
11654						crossFadeOn = false;
11655
11656					}
11657				}, 100);
11658
11659            }
11660        }
11661
11662        function startRampUp() {
11663            // For now, we capture only the first playing event
11664            // as we assume the user calls fadeIn()
11665            // every time when wants to resume playback
11666            audiox.removeEventListener("playing", startRampUp);
11667
11668            ramp();
11669            
11670        }
11671
11672        // Start with zero audio level
11673        audiox.volume = 0;
11674
11675        // Start volume ramp up when the audio actually stars to play (not when begins to buffer, etc.)
11676        audiox.addEventListener("playing", startRampUp);
11677
11678        audiox.src = url
11679
11680        if(isIOS)audiox.load()
11681
11682    	var promise = audiox.play();
11683		if (promise !== undefined) {
11684			promise.then(function(){
11685		    }).catch(function(error){
11686		    	console.log(error)
11687			});
11688		}
11689    };
11690
11691    function fadeOutAudio(audiox, targetVolume, fade_time) {
11692
11693    	fadeOutAudioComplete = false;
11694
11695        var originalVolume = audiox.volume;
11696
11697        if(!targetVolume) {
11698            targetVolume = 0;
11699        }
11700
11701        var rampTime = fade_time * 1000
11702
11703        // How often adjust audio volume (ms)
11704        var tick = 50;
11705
11706        var volumeStep = (audiox.volume - targetVolume) / (rampTime / tick);
11707
11708        if(!volumeStep) {
11709            // Volume already at 0
11710            return;
11711        }
11712
11713        function ramp() {
11714        	if(isCancelCrossFade)return
11715
11716            var vol = Math.max(0, audiox.volume - volumeStep);
11717
11718            audiox.volume = vol;
11719
11720            // Have we reached target volume level yet?
11721            if(audiox.volume > targetVolume) {
11722                // Keep up going until 11
11723                setTimeout(ramp, tick);
11724            } else {
11725                audiox.pause();
11726
11727				fadeOutAudioComplete = true;
11728
11729                // Reset audio volume so audio can be played again
11730                audiox.volume = originalVolume;
11731            }
11732        }
11733
11734        ramp();
11735    };
11736
11737	//############################################//
11738	/*  */
11739	//############################################//
11740
11741	function trackProgress(){
11742
11743		if(seekBarDown)return;//for performance
11744		if(_HAPAdManager && _HAPAdManager.isAdOn()){
11745			if(settings.pauseAudioDuringAds){
11746			 	return;
11747			}else{
11748				if(_HAPAdManager.isAdPreOn() || _HAPAdManager.isAdEndOn()) return;
11749			}
11750		}
11751		if(_HAPAdManager && _HAPAdManager.isAdOn()) return;
11752		if(isIma && isImaOn)return;
11753
11754
11755		var loadPercent;
11756
11757		if(mediaType == 'youtube'){
11758
11759			if(youtubePlayer){
11760				var t = youtubePlayer.getCurrentTime(), d = youtubePlayer.getDuration();	
11761				loadPercent = youtubePlayer.getVideoLoadedFraction();
11762			}
11763
11764		}else{
11765
11766			if(activeAudio){
11767
11768				var t = activeAudio.currentTime, d = activeAudio.duration;
11769
11770				if(HAPUtils.isNumber(t) && HAPUtils.isNumber(d)){
11771					
11772					if(typeof activeAudio.buffered !== 'undefined' && activeAudio.buffered.length != 0) {
11773						try{
11774							var bufferedEnd = activeAudio.buffered.end(activeAudio.buffered.length - 1);
11775						}catch(error){}
11776						if(!isNaN(bufferedEnd)){
11777							var loadPercent = bufferedEnd / d;
11778						}
11779					}
11780				}
11781
11782				if(settings.useCrossFade && !isCancelCrossFade && !seekBarDown && settings.volume != 0 && !rangeOn && !activeVariation){
11783
11784					if(!crossFadeOn && (t + Number(settings.crossFadeTime) > d)){
11785
11786						if(!nextDataCheck){
11787							nextMediaData = null;
11788							nextDataCheck = true;
11789
11790							//que here, not on song start, because if search active or next song gets removed before it comes time to play it
11791
11792							if(settings.usePlaylist && isSearchActive){
11793
11794								var current = playlistContent.querySelector('.hap-playlist-item-selected'),
11795								next = current.nextAll('.hap-playlist-item:not(.hap-filter-hidden)').filter(':first')
11796								if(next){
11797									var c = next.getAttribute('data-id')
11798									_HAPPlaylistManager.queNext('pick', c);
11799								}
11800								else{//no more in search, try to advance normally
11801									_HAPPlaylistManager.queNext('advance', 1);
11802								}
11803
11804							}else{
11805								_HAPPlaylistManager.queNext('advance', 1);
11806							}
11807							
11808						}
11809
11810						if(nextMediaData){
11811
11812							//start crossfade
11813							crossFadeOn = true;
11814
11815							if(nextMediaData.type == 's3_audio'){
11816
11817								s3UrlReady = false;
11818								getAwsUrl(nextMediaData)
11819
11820								var interval = setInterval(function(){
11821									if(s3UrlReady){
11822										if(interval) clearInterval(interval);
11823										initCrossFade(s3UrlReady, t, d);
11824									}
11825								}, 100);
11826
11827
11828							}else if(nextMediaData.origtype && nextMediaData.origtype == 'soundcloud'){
11829
11830								scUrlReady = false;
11831								_scLoader.get_stream_url(currMediaData.sc_data.id)
11832
11833								var interval = setInterval(function(){
11834									if(scUrlReady){
11835										if(interval) clearInterval(interval);
11836										initCrossFade(scUrlReady, t, d);
11837									}
11838								}, 100);
11839				
11840							}else{
11841								var next_url = getAudioUrl(nextMediaData)
11842							
11843								initCrossFade(next_url, t, d);
11844
11845					  		}
11846
11847					  	}
11848					}
11849				}
11850			}
11851
11852		}
11853
11854		if(HAPUtils.isNumber(t) && HAPUtils.isNumber(d)){
11855
11856			
11857
11858			if(!startEventDone){
11859				if(t > 0 && mediaPlaying){
11860					startEventDone = true;
11861					self.fireEvent("soundPlay", [{instance:self, instanceName:settings.instanceName, media:currMediaData}]);
11862				}
11863			}
11864
11865			if(settings.useWaveSeekbar){
11866
11867				if(peakData)seekbarWaveProgress.style.width = (t / d)*100 + '%';
11868
11869			}else{
11870
11871				if(circleSeekbar){
11872					_HAPCirclePlayer.drawSeekbar(loadPercent,t,d);
11873				}else{
11874					if(progressLevel)progressLevel.style.width = ((t / d) * seekBarSize) + 'px';
11875					if(loadLevel)if(loadPercent)loadLevel.style.width = (loadPercent * seekBarSize) + 'px';
11876				}
11877
11878			}
11879
11880			if(settings.useInlineSeekbar){
11881				var is = inlineSeekbar.querySelector('.hap-inline-seekbar-progress')
11882				is.style.width = (t / d) * parseInt(inlineSeekbar.offsetWidth,10) + 'px'
11883			}
11884
11885			var current_time = HAPUtils.formatTime(t),
11886			total_time = HAPUtils.formatTime(d)
11887
11888			if(mediaTimeCurrent)mediaTimeCurrent.innerHTML = current_time;
11889			if(mediaTimeTotal)mediaTimeTotal.innerHTML = total_time;
11890
11891			if(currMediaData.end || (currMediaData.lockTime && !allowedViewVideoUserRole)){
11892				var et = currMediaData.end || currMediaData.lockTime;
11893
11894				if(t >= et){
11895					if(currMediaData.lockTime){
11896						if(dataIntervalID) clearInterval(dataIntervalID);
11897						dataIntervalID = null;
11898						toggleRedirectLoginScreen('watch');
11899					}else{
11900						mediaEndHandler();
11901					}
11902					return;
11903				}
11904			}
11905
11906
11907			//fixed
11908			if(settings.useStickyPlayer && window['hap_fixed_active_player'] == settings.instanceName){
11909				if(mediaStarted && mediaPlaying)_HAPStickyPlayer.setProgressData(t / d, null, current_time, total_time, d)
11910			}
11911			
11912
11913			if(useRange && rangeOn && !rangeSet && !_HAPRangeSlider.isDrag()){
11914				if(parseInt(t,10) < parseInt(range_a_time,10) || t > range_b_time){
11915					rangeSet = true;
11916					self.seek(range_a_time);
11917					return;
11918				}
11919			}
11920
11921			//lyrics
11922			if(currMediaData.lyrics && _lyrics && lyricsReady && lyricsOpened && mediaPlaying){
11923				_lyrics.synchronize(t)
11924	        }
11925
11926		}
11927	}
11928
11929	function playHandler(){
11930
11931		if(!mediaStarted){
11932			mediaStarted = true;	
11933
11934			if(!circleSeekbar){
11935				if(settings.useWaveSeekbar){
11936					if(seekbarWave)seekBarSize = seekbarWave.offsetWidth;
11937				}else{
11938					if(progressBg)seekBarSize = progressBg.offsetWidth;
11939				}
11940			}
11941
11942			self.fireEvent("soundStart", [{instance:self, instanceName:settings.instanceName, media:currMediaData}]);
11943
11944			//############################################//
11945			/* ads */
11946			//############################################//
11947			if(currMediaData.adMid){
11948				_HAPAdManager.adMidStartHandler();
11949			}
11950			//############################################//
11951			/* ads */
11952			//############################################//
11953
11954
11955			if(settings.useGlobalDownloadButton && currMediaData.download){
11956
11957				if(!globalDownloadBtn){
11958					globalDownloadBtn = HAPUtils.htmlToElement('<a class="hap-contr-btn hap-global-download-btn" href="'+currMediaData.download+'" download data-tooltip="'+settings.downloadIconTitle+'" aria-label="'+settings.downloadIconTitle+'">'+settings.downloadIcon+'</a>')
11959
11960					if(!isMobile){
11961						globalDownloadBtn.addEventListener('mouseenter', overControls);
11962						globalDownloadBtn.addEventListener('mouseleave', outControls);
11963					}
11964				}else{
11965					globalDownloadBtn.setAttribute('href', currMediaData.download)
11966				}
11967
11968				globalDownloadBtn.style.display = 'block';
11969				setTimeout(function(){
11970					globalDownloadBtn.classList.add('hap-btn-show')
11971				},20)
11972
11973				if(settings.globalDownloadBtnSelector){
11974					if(wrapper.querySelector(settings.globalDownloadBtnSelector)){
11975						wrapper.querySelector(settings.globalDownloadBtnSelector).prepend(globalDownloadBtn)
11976					}
11977				}
11978				else if(class_list.indexOf('hap-brona') > -1){
11979					wrapper.querySelector('.hap-controls-right').prepend(globalDownloadBtn)
11980				}
11981				else if(class_list.indexOf('hap-epic') > -1){
11982					wrapper.querySelector('.hap-player-controls-right').prepend(globalDownloadBtn)
11983				}
11984				else if(class_list.indexOf('hap-art-wide') > -1){
11985					wrapper.querySelector('.hap-player-controls-right').prepend(globalDownloadBtn)
11986				}
11987				else if(class_list.indexOf('hap-art-narrow') > -1){
11988					wrapper.querySelector('.hap-top-bar-controls-right').prepend(globalDownloadBtn)
11989				}
11990				else if(class_list.indexOf('hap-metalic') > -1){
11991					wrapper.querySelector('.hap-bottom-bar-controls').prepend(globalDownloadBtn)
11992				}
11993				else if(class_list.indexOf('hap-modern') > -1){
11994					wrapper.querySelector('.hap-bottom-bar-controls').prepend(globalDownloadBtn)
11995				}
11996				else if(class_list.indexOf('hap-wave') > -1){
11997					wrapper.querySelector('.hap-player-controls-right').prepend(globalDownloadBtn)
11998				}
11999				else if(class_list.indexOf('hap-fixed') > -1){
12000					wrapper.querySelector('.hap-playlist-bar-controls-right').prepend(globalDownloadBtn)
12001				}
12002				else if(class_list.indexOf('hap-fixed2') > -1){
12003					wrapper.querySelector('.hap-playlist-bar-controls-right').prepend(globalDownloadBtn)
12004				}
12005
12006			}
12007
12008		}
12009
12010		if(settings.useStickyPlayer){//needs to be here, becuase if we have 2 players in page and we just press play between them, sticky player has to change active song data
12011
12012			window['hap_fixed_active_player'] = settings.instanceName
12013
12014			if(waveformLoading){
12015				//wait for waveform to load so we can give to sticky player
12016
12017				if(waveformLoadingInterval)clearInterval(waveformLoadingInterval)
12018
12019				waveformLoadingInterval = setInterval(function(){
12020					if(!waveformLoading && currMediaData.peaks){
12021
12022						clearInterval(waveformLoadingInterval);
12023						waveformLoadingInterval = null;
12024
12025						_HAPStickyPlayer.setData({
12026							inst: settings.instanceName,
12027							media: currMediaData,
12028							volume: settings.volume,
12029							playlistData: playlistDataArr
12030						})
12031					}
12032				}, 100);
12033
12034			}else{
12035
12036				_HAPStickyPlayer.setData({
12037					inst: settings.instanceName,
12038					media: currMediaData,
12039					volume: settings.volume,
12040					playlistData: playlistDataArr
12041				})
12042
12043			}
12044
12045		}
12046
12047
12048		//############################################//
12049		/* ads */
12050		//############################################//
12051		if(currMediaData.adMid){
12052			_HAPAdManager.adMidPlayHandler();
12053		}
12054		//############################################//
12055		/* ads */
12056		//############################################//
12057
12058
12059		if(settings.useStickyPlayer && window['hap_fixed_active_player'] == settings.instanceName){
12060			_HAPStickyPlayer.setPauseBtn()
12061		}
12062
12063		if(settings.togglePlaybackOnPlaylistItem && activePlaylistItem)activePlaylistItem.querySelector('.hap-playlist-thumb-style')?.classList.add('hap-playlist-thumb-style-pause')
12064
12065		if(settings.togglePlaybackOnMultipleInstances){
12066			if(ap_mediaArr.length > 1){
12067				var i, len = ap_mediaArr.length;
12068				for(i=0;i<len;i++){
12069					if(self != ap_mediaArr[i].inst){
12070						ap_mediaArr[i].inst.pauseMedia();
12071					}
12072				}
12073			}
12074		}
12075
12076		if(videoSync){
12077			if(!syncComingFromVideo){
12078				var promise = videoSync.play();
12079				if (promise !== undefined) {
12080					promise.then(function(){
12081				    }).catch(function(error){
12082				    });
12083				}
12084			}
12085			syncComingFromVideo = false;
12086		}
12087
12088		if(startEventDone){//first time this is done in trackprogress after time is > 0
12089			self.fireEvent("soundPlay", [{instance:self, instanceName:settings.instanceName, media:currMediaData}]);
12090		}
12091
12092		if(playbackToggle)self.toggleBigPlay(true)
12093
12094		//preset mods
12095		if(class_list.indexOf('hap-fixed')>-1 || class_list.indexOf('hap-fixed2')>-1)setExternalIconData(true);
12096
12097		if(playbackToggleIndicator){
12098			playbackToggleIndicator.querySelector('.hap-btn-play').style.display = 'none'
12099			playbackToggleIndicator.querySelector('.hap-btn-pause').style.display = 'block'
12100		}
12101		
12102		mediaPlaying = true;
12103
12104		if(settings.autoPlayAfterFirst){
12105			autoPlay = true;
12106			settings.autoPlay = true;
12107		}
12108
12109		if(settings.hidePlayerUntilMusicStart)unhidePlayer();
12110
12111		if(useGa && typeof gtag !== "undefined"){
12112        
12113            gtag('event', 'play', {
12114            	'send_to': settings.gaTrackingId,
12115            	'event_category': "Modern audio player: " + settings.instanceName,
12116			     eventLabel: 'title: ' + self.getTitle(currMediaData, true) + ' | time: ' + Math.round(self.getCurrentTime()),
12117			    'non_interaction': true
12118			});
12119        }
12120
12121	}
12122
12123	function pauseHandler(){
12124
12125		if(crossFadeOn)return;
12126
12127		if(playbackToggle)self.toggleBigPlay()
12128
12129		//preset mods
12130		if(class_list.indexOf('hap-fixed')>-1 || class_list.indexOf('hap-fixed2')>-1)setExternalIconData();
12131
12132		if(playbackToggleIndicator){
12133			playbackToggleIndicator.querySelector('.hap-btn-play').style.display = 'block'
12134			playbackToggleIndicator.querySelector('.hap-btn-pause').style.display = 'none'
12135		}
12136
12137		if(videoSync){
12138			if(!syncComingFromVideo)videoSync.pause()
12139			syncComingFromVideo = false;
12140		}
12141
12142		if(settings.useStickyPlayer && window['hap_fixed_active_player'] == settings.instanceName){
12143			_HAPStickyPlayer.setPlayBtn()
12144		}
12145
12146		if(settings.togglePlaybackOnPlaylistItem && activePlaylistItem)activePlaylistItem.querySelector('.hap-playlist-thumb-style')?.classList.remove('hap-playlist-thumb-style-pause')
12147			
12148		mediaPlaying = false;
12149		self.fireEvent("soundPause", [{instance:self, instanceName:settings.instanceName, media:currMediaData}]);
12150
12151		if(useGa && typeof gtag !== "undefined"){
12152        
12153           gtag('event', 'pause', {
12154            	'send_to': settings.gaTrackingId,
12155            	'event_category': "Modern audio player: " + settings.instanceName,
12156			     eventLabel: 'title: ' + self.getTitle(currMediaData, true) + ' | time: ' + Math.round(self.getCurrentTime()),
12157			    'non_interaction': true
12158			});
12159        }
12160
12161	}
12162
12163	function mediaEndHandler(){
12164
12165		//variations
12166		if(hasVariation && settings.playVariationInSequence){
12167
12168			if(activeVariation){
12169
12170				var next = getNextSibling(activeVariation);
12171
12172				if(next){
12173					next.click()
12174					return;
12175				}
12176
12177			}else{
12178				//play first variation
12179				var ti = activePlaylistItem.querySelectorAll('.hap-audio-variation')[0]
12180				tu.querySelector('.hap-audio-variation').click()
12181				return;	
12182			}
12183		}
12184
12185
12186
12187		if(_HAPAdManager)_HAPAdManager.clearAdMidTimeout();
12188
12189		self.fireEvent('soundEnd', [{instance:self, instanceName:settings.instanceName, media:currMediaData}]);
12190
12191		if(settings.useStatistics)getStats('hap_finish_count', mediaCounter);
12192
12193		if(useGa && typeof gtag !== "undefined"){
12194        
12195            gtag('event', 'end', {
12196            	'send_to': settings.gaTrackingId,
12197            	'event_category': "Modern audio player: " + settings.instanceName,
12198			    eventLabel: 'title: ' + self.getTitle(currMediaData, true),
12199			    'non_interaction': true
12200			});
12201        }
12202
12203
12204		if(currMediaData.adEnd){
12205
12206			//############################################//
12207			/* ads */
12208			//############################################//
12209
12210			_HAPAdManager.initAd('end');
12211
12212			//############################################//
12213			/* ads */
12214			//############################################//
12215
12216		}else if(isIma){
12217
12218			_IMALoader.contentComplete()
12219
12220		}else{
12221			if(!settings.stopOnSongEnd){
12222				audioEndedAction();
12223			}
12224		}
12225
12226	}
12227
12228	function audioEndedAction(reset_src){
12229
12230		var st = currMediaData.start || 0;
12231
12232		if(settings.loopState == 'single'){
12233
12234			if(!reset_src)beforeSongAdvanceAction();//only is main media plays, not ad
12235
12236			if(mediaType == 'youtube'){
12237				if(youtubePlayer){
12238					youtubePlayer.seekTo(st);
12239					youtubePlayer.playVideo();
12240				}
12241			}else{
12242				if(reset_src){//if coming from end ad
12243
12244					if(useBlob && (currMediaData.origtype == 'audio')){
12245
12246						if(bUrlIntervalID){
12247							clearInterval(bUrlIntervalID);
12248							bUrlIntervalID = null;
12249						}
12250						self.getBUrl(mediaPath);
12251						bUrlIntervalID = setInterval(function(){
12252							if(bUrl){
12253								clearInterval(bUrlIntervalID);
12254								bUrlIntervalID = null;
12255							
12256								activeAudio.src = bUrl;
12257
12258							}
12259						},100);
12260
12261					}else{
12262						activeAudio.src = mediaPath;
12263					}
12264
12265				}
12266
12267				activeAudio.currentTime = st;
12268
12269				var promise = activeAudio.play();
12270				if (promise !== undefined) {
12271					promise.then(function(){
12272				    }).catch(function(error){
12273				    });
12274				}
12275			}
12276
12277		}else if(settings.loopState == 'off' || settings.loopState == 'playlist'){
12278			self.nextMedia();
12279		}
12280
12281	}
12282
12283	function cleanMedia(){
12284
12285		waveformDataAws2 = null;
12286
12287		if(xhrRequest_remoteDownload){
12288            xhrRequest_remoteDownload.abort();
12289            xhrRequest_remoteDownload = null;
12290        }
12291
12292        if(xhrRequest_writePeaks){
12293            xhrRequest_writePeaks.abort();
12294            xhrRequest_writePeaks = null;
12295        }
12296
12297        if(xhrRequest_loadPeaks){
12298            xhrRequest_loadPeaks.abort();
12299            xhrRequest_loadPeaks = null;
12300        }
12301
12302		if(!crossFadeOn){
12303			if(dataIntervalID) clearInterval(dataIntervalID);
12304
12305			if(playbackToggle)self.toggleBigPlay()
12306		}
12307
12308		//############################################//
12309		/* ads */
12310		//############################################//
12311
12312		if(progressLevel)progressLevel.classList.remove('hap-ad-progress-level');
12313		if(mediaTimeAd)mediaTimeAd.style.display = 'none';
12314		if(adSkipBtn)adSkipBtn.style.display = 'none'
12315
12316		if(_HAPAdManager){
12317			_HAPAdManager.cleanAds();
12318		}
12319
12320		//ima
12321		isIma = false;
12322		isImaOn = false;
12323		imaStarted = false;
12324
12325		//############################################//
12326		/* ads */
12327		//############################################//
12328
12329		if(mediaType == 'hls'){
12330			if(hlsSupport)hls.detachMedia();
12331		}
12332		else if(mediaType == 'youtube'){
12333			if(youtubePlayer){
12334				youtubeEndFix = true;
12335				youtubeHolder.style.display = 'none';
12336				if(youtubeReady)youtubePlayer.stopVideo();
12337				youtubeStarted = false;
12338			}
12339		}
12340
12341		if(!settings.useCrossFade){
12342
12343			if(audio){
12344				audio.pause();
12345				audio.src = '';
12346				audio.removeAttribute('src')
12347			}
12348
12349			//if(audio)audio.removeEventListener('ended canplay canplaythrough loadedmetadata pause play error seeked');
12350		}else{
12351
12352			if(audio.c_active){
12353				if(audio){
12354					audio.pause();
12355					audio.src = '';
12356					audio.removeAttribute('src')
12357				}
12358				//if(audio)audio.removeEventListener('ended canplay canplaythrough loadedmetadata pause play error seeked');
12359			}else{
12360				if(audioUp2Js2){
12361					audioUp2Js2.pause();
12362					audioUp2Js2.src = '';
12363					audioUp2Js2.removeAttribute('src')
12364				}
12365				//if(audio2)audio2.removeEventListener('ended canplay canplaythrough loadedmetadata pause play error seeked');	
12366			}
12367
12368		}
12369
12370		if(redirectLoginHolder){
12371			redirectLoginHolder.style.display = 'none';
12372			redirectLoginHolder.classList.remove('hap-redirect-login-holder-visible')
12373		}
12374		redirectLoginOpened = false;
12375
12376
12377		if(globalDownloadBtn){
12378			globalDownloadBtn.style.display = 'none';
12379			globalDownloadBtn.classList.remove('hap-btn-show')
12380		}
12381
12382
12383	    if(mediaTimeCurrent)mediaTimeCurrent.innerHTML = '00:00';
12384		if(mediaTimeTotal)mediaTimeTotal.innerHTML = '00:00';
12385	    if(playerTitle)playerTitle.innerHTML = '';
12386		if(playerArtist)playerArtist.innerHTML = '';
12387		if(progressLevel)progressLevel.style.width = 0;
12388	   	if(loadLevel)loadLevel.style.width = 0;
12389
12390	   	peakData = null;	
12391		if(seekbarWaveProgress)seekbarWaveProgress.style.width = 0;
12392
12393		mediaType = null;
12394		mediaPlaying = false;
12395		mediaStarted = false;
12396		addTrackPlayit = false;
12397		//isEnqueue = false;
12398		if(!initialAutoPlay) autoPlay = false;//note: will not work with ad pre, ad pre will auto trigger autoplay true
12399
12400		if(useRange)self.resetRange();
12401
12402		startEventDone = false;
12403
12404		if(inlineSeekbar){
12405			inlineSeekbar.querySelector('.hap-inline-seekbar-progress').style.width = 0;
12406		}
12407
12408		//lyrics
12409		if(lyricsHolder){
12410			//lyricsHolder.style.display = 'none';//dont close container on going to next media
12411			lyricsHolder.querySelector('.hap-lyrics-container').innerHTML = ''
12412		}
12413
12414		if(_lyrics)_lyrics.deactivate()
12415		lyricsReady = false;
12416
12417		if(videoSync){
12418			videoSync.pause();
12419			videoSync.src = '';
12420			videoSync = null;
12421			videoWrap.innerHTML = ''
12422		}
12423
12424		if(waveformLoadingInterval){
12425			clearInterval(waveformLoadingInterval);
12426			waveformLoadingInterval = null;
12427		}
12428		waveformLoading = false;
12429
12430		if(activeVariation){
12431			activeVariation.classList.remove('hap-active-variation')
12432			activeVariation = null;
12433		}
12434
12435		//pwd
12436		if(pwdField)pwdField.value = '';
12437		if(pwdHolder)pwdHolder.classList.remove('hap-holder-animate', 'hap-holder-shown')
12438
12439
12440		//waveform
12441		if(waveformLoader)waveformLoader.classList.remove('hap-waveform-loader-hidden')
12442		if(seekbarWave)seekbarWave.classList.remove('hap-seekbar-wave-visible')	
12443
12444	}
12445
12446	function destroyMedia(){
12447		if(!componentInited || !mediaType) return;
12448		if(activePlaylistItem)enableActivePlaylistItem();
12449		cleanMedia();
12450		_HAPPlaylistManager.reSetCounter();
12451	}
12452
12453	function setSortableTracks(){
12454
12455		if(typeof Sortable === "function"){
12456
12457			Sortable.create(playlistContent, {
12458			 	animation: 150,
12459				draggable: ".hap-playlist-item",
12460				ghostClass: 'hap-sortable-ghost',
12461				onEnd: function (evt) {
12462
12463					var current_counter = _HAPPlaylistManager.getCounter();
12464
12465					if(current_counter != -1){
12466
12467						var new_counter = getElementIndex(playlistContent, activePlaylistItem, '.hap-playlist-item')
12468
12469						//reposition data
12470						var i, arr = [], items = playlistContent.querySelectorAll('.hap-playlist-item').forEach(function(el){
12471							arr.push(parseInt(el.getAttribute('data-id'),10));
12472						});
12473
12474						playlistDataArr = HAPUtils.sortArray(playlistDataArr, arr);
12475
12476						_HAPPlaylistManager.reSetCounter(new_counter);
12477
12478					}
12479
12480					updatePlaylist();
12481
12482				}
12483
12484			});
12485
12486			settings.sortableTracksSet = true;
12487
12488		}else{
12489
12490			var script = document.createElement('script');
12491			script.type = 'text/javascript';
12492			script.src = HAPUtils.qualifyURL(settings.sourcePath+settings.sortable_js);
12493			script.onload = script.onreadystatechange = function() {
12494			    if(!this.readyState || this.readyState == 'complete'){
12495			    	setSortableTracks()
12496			    }
12497			};
12498			script.onerror = function(){
12499				alert("Error loading " + this.src);
12500			}
12501			var tag = document.getElementsByTagName('script')[0];
12502			tag.parentNode.insertBefore(script, tag);
12503
12504		}
12505
12506	}
12507
12508	function getElementIndex(parent, element, cl) {
12509	    return Array.from(parent.querySelectorAll(cl)).indexOf(element);
12510	}
12511	
12512	function destroyPlaylist(){
12513
12514		if(xhrRequest){
12515			xhrRequest.abort();
12516			xhrRequest = null;
12517		}
12518
12519		if(xhrRequest_pag){
12520			xhrRequest_pag.abort();
12521			xhrRequest_pag = null;
12522		}
12523
12524		if(burl_request){
12525			burl_request.abort();
12526			burl_request = null;
12527		}
12528
12529
12530		if(mediaType){
12531			cleanMedia();
12532			if(activePlaylistItem)enableActivePlaylistItem();
12533		}
12534
12535		if(settings.sortableTracks && settings.sortableTracksSet){
12536			Sortable.destroy();//because of popup
12537			settings.sortableTracksSet = false;
12538		}
12539
12540		if(playlistData){
12541			playlistData.remove()
12542			playlistData = null;
12543		}
12544
12545		playlistContent.innerHTML = "";
12546
12547		lastPlaylist = null;
12548		addTrackProcess = false;
12549		playlistInitLoad = false;
12550		playlistLength = 0;
12551		playlistProcessCounter = -1;
12552		playlistDataArr = [];
12553		playlistProcessData = [];
12554		playlistProcessDataUrl = [];
12555		_HAPPlaylistManager.reSetCounter();
12556
12557		playlistData = null;
12558		globalThumb = null;
12559		globalDownload = null;
12560		globalStartTime = null;
12561		globalStartTimeMediaId = null;
12562		globalEndTime = null;
12563		globalAdPre = null;
12564		globalAdMid = null;
12565		globalAdMidInterval = null;
12566		globalAdEnd = null;
12567		globalVast = null;
12568		globalPwd = null;
12569		globalLockVideoUserRoles = null;
12570		globalLockTime = null;
12571
12572		addMoreOnTotalScroll = false;
12573		mediaPrefixUrl = null;
12574
12575
12576		loadMoreProcess = false;
12577		nextPageToken = null;
12578		loadMoreType = null;
12579		loadMoreOnTotalScroll = false;
12580
12581		addMoreLimit = null;
12582		addMoreNumResults = null;
12583		addMoreOffset = null;
12584		addMoreSortOrder = null;
12585		addMoreSortDirection = null;
12586
12587
12588		//reset sort because tracks are loaded with no sort. TODO: Do we keep last sort on new playlist load?
12589		sortOrder = "";
12590		if(sortAlpha){
12591			sortAlpha.querySelector('.hap-btn-sort-alpha-up').style.display = 'none';
12592			sortAlpha.querySelector('.hap-btn-sort-alpha-down').style.display = 'block';
12593		}
12594
12595		//search
12596		if(searchSelector)searchSelector.value = '';
12597		if(playlistFilterMsg)playlistFilterMsg.style.display = 'none';
12598
12599		//pagination
12600		lastActivePaginationBtn = null;
12601		paginationWrap = null;
12602		paginationArr = [];
12603
12604		if(playerThumb){
12605			playerThumb.style.backgroundImage = 'none'
12606			playerThumb.style.opacity = 0;
12607		}
12608
12609		self.fireEvent('destroyPlaylist', [{instance:self, instanceName:settings.instanceName}]);
12610
12611	}
12612
12613	function beforeSongAdvanceAction(){
12614		//before we move to next song
12615		if(useContinousPlayback && continousPlaybackTrackAllSongs)playlistDataArr[mediaCounter].data.start = self.getCurrentTime();
12616
12617		if(settings.useStatistics){
12618			
12619			getStats('hap_play_count', mediaCounter);
12620
12621			var t = self.getCurrentTime()
12622			if(t > 3 && t < 60){
12623				getStats('hap_skipped_first_minute', mediaCounter);
12624			}
12625
12626		}
12627
12628	}
12629
12630	if(settings.useStatistics){
12631
12632		addListener(eventName, function (event) { 
12633		    if(window.event)window.event.cancelBubble = true;
12634			savetimePlayed()
12635		});
12636
12637	}
12638
12639	function savetimePlayed(e){
12640        if(mediaCounter){
12641
12642    		getStats('hap_play_count', mediaCounter);
12643
12644        	var t = self.getCurrentTime()
12645			if(t > 3 && t < 60){
12646				getStats('hap_skipped_first_minute', mediaCounter);
12647			}
12648
12649        }
12650    };
12651
12652	//############################################//
12653	/* resize */
12654	//############################################//
12655	
12656	if(settings.addResizeEvent){
12657		_window.addEventListener('resize',function() {
12658			if(!componentInited) return false;
12659			if(windowResizeTimeoutID) clearTimeout(windowResizeTimeoutID);
12660			windowResizeTimeoutID = setTimeout(doneResizing, windowResizeTimeout);
12661		});
12662	}
12663		
12664	function doneResizing(){
12665		if(!componentInited) return false;
12666		if(!settings.addResizeEvent) return false;
12667	    
12668		if(tooltip)tooltip.style.display = 'none';
12669
12670		//player resize
12671		var w = parseInt(wrapper.clientWidth,10);
12672
12673
12674
12675		//playlist items
12676		if(playlistContent){
12677			if(w < settings.playlistItemMultilineWidth){
12678				playlistContent.classList.add('hap-playlist-item-multiline');
12679			}else{
12680				playlistContent.classList.remove('hap-playlist-item-multiline');
12681			}
12682		}
12683		
12684
12685		//player
12686		if(settings.breakPointArr){
12687			var i, len = settings.breakPointArr.length, ev;
12688			for(i=0;i<len;i++){
12689				ev = settings.breakPointArr[i];
12690				if(w < ev){
12691					wrapper.classList.add('hap-breakpoint-'+ev.toString());
12692				}else{
12693					wrapper.classList.remove('hap-breakpoint-'+ev.toString());
12694				}
12695			}
12696		}
12697
12698
12699		
12700		if(settings.useWaveSeekbar && peakData){
12701
12702			var w = parseInt(seekbar.clientWidth,10);
12703
12704			seekbarWaveCanvasBg.width = w
12705			seekbarWaveCanvasProgress.width = w
12706
12707			waveOptions.width = w;
12708
12709			//clearSeekbar()
12710
12711			seekbarWaveCtx.fillStyle = settings.waveBgColor;
12712			seekbarWaveCtx2.fillStyle = settings.waveProgressColor;
12713
12714			if(waveOptions.barWidth > 0){
12715				HAPUtils.drawBars(seekbarWaveCtx, seekbarWaveCtx2, waveOptions, peakData.split(','), 0, w)
12716			}else{
12717				HAPUtils.drawWave(seekbarWaveCtx, seekbarWaveCtx2, waveOptions, peakData.split(','), 0, w)
12718			} 
12719
12720		}
12721
12722		if(!circleSeekbar){
12723			if(settings.useWaveSeekbar){
12724				if(seekbarWave)seekBarSize = seekbarWave.offsetWidth
12725			}else{
12726				if(progressBg)seekBarSize = progressBg.offsetWidth
12727			}
12728		}
12729		
12730		if(useRange && rangeOn)self.resizeRange()
12731
12732		
12733
12734
12735		//if(useStickyPlayer && _HAPStickyPlayer)_HAPStickyPlayer.setprog
12736
12737
12738
12739	}
12740
12741	//############################################//
12742	/* presets */
12743	//############################################//
12744
12745	//preset mods
12746    function checkPresetMods(){
12747		
12748		if(class_list.indexOf('hap-fixed') > -1){
12749
12750			if(externalPlayerToggleBtn){
12751
12752				externalPlayerToggleBtn.addEventListener('click', function(){
12753
12754	                if(settings.playerOpened){
12755
12756		                wrapper.addEventListener('transitionend', function cb(e){
12757							e.currentTarget.removeEventListener(e.type, cb);
12758							
12759	                    	if(externalPlaybackBtn)externalPlaybackBtn.style.display = 'block';
12760
12761	                    	wrapper.classList.remove('hap-is-playlist-opened')
12762						
12763						})
12764		                wrapper.style.bottom = -wrapper.offsetHeight+'px';
12765
12766		                externalPlayerToggleBtn.querySelector('.hap-btn-player-toggle').classList.add('hap-btn-player-toggle-rotate')
12767
12768	                    settings.playlistOpened = false;
12769	                }else{
12770
12771	                	wrapper.style.bottom = -playlistHolder.offsetHeight+'px';
12772
12773	                    externalPlayerToggleBtn.querySelector('.hap-btn-player-toggle').classList.remove('hap-btn-player-toggle-rotate')
12774
12775
12776	                    if(externalPlaybackBtn)externalPlaybackBtn.style.display = 'none';
12777	                }   
12778	                settings.playerOpened = !settings.playerOpened;
12779	            });
12780
12781			}
12782
12783            if(externalPlaybackBtn){
12784
12785            	externalPlaybackBtn.addEventListener('click',function(e){
12786	                self.togglePlayback();
12787	            }); 
12788
12789	            externalPlaybackBtn.querySelector('.hap-btn-play').style.display = 'block';
12790	            externalPlaybackBtn.querySelector('.hap-btn-pause').style.display = 'none';
12791
12792            }
12793
12794            if(!settings.isPopup){//do not repeat in popup
12795
12796            	if(!settings.playerOpened){
12797		            settings.playlistOpened = false;
12798		            wrapper.style.bottom = -wrapper.offsetHeight+'px';
12799
12800		           if(externalPlayerToggleBtn)externalPlayerToggleBtn.querySelector('.hap-btn-player-toggle').classList.add('hap-btn-player-toggle-rotate')
12801
12802
12803		            if(externalPlaybackBtn)externalPlaybackBtn.style.display = 'block';
12804		        }else{
12805		        	if(settings.playlistOpened){
12806		            	wrapper.style.bottom = 0;
12807		            }else{
12808		                wrapper.style.bottom = -playlistHolder.offsetHeight+'px';
12809		            }
12810
12811		            if(externalPlayerToggleBtn)externalPlayerToggleBtn.querySelector('.hap-btn-player-toggle').classList.remove('hap-btn-player-toggle-rotate')
12812
12813		        }
12814
12815            }
12816
12817		}
12818		else if(class_list.indexOf('hap-fixed2') > -1){
12819
12820			if(externalPlayerToggleBtn){
12821
12822				externalPlayerToggleBtn.addEventListener('click', function(){
12823
12824	                if(!settings.playerOpened){
12825
12826		                playerHolder.addEventListener('transitionend', function cb(e){
12827							e.currentTarget.removeEventListener(e.type, cb);
12828						})
12829		                playerHolder.style.bottom = 0+'px';
12830	                    
12831	                    externalPlayerToggleBtn.querySelector('.hap-btn-player-toggle').classList.remove('hap-btn-player-toggle-rotate')
12832
12833	                    settings.playerOpened = false;
12834
12835	                }else{
12836
12837	                	playerHolder.style.bottom = -playerHolder.offsetHeight+'px';
12838
12839	                    externalPlayerToggleBtn.querySelector('.hap-btn-player-toggle').classList.add('hap-btn-player-toggle-rotate')
12840
12841	                    if(externalPlaybackBtn)externalPlaybackBtn.style.display = 'block';
12842	                }   
12843	                settings.playerOpened = !settings.playerOpened;
12844	            });
12845
12846			}
12847
12848			if(externalPlaybackBtn){
12849
12850				externalPlaybackBtn.addEventListener('click',function(e){
12851	                self.togglePlayback();
12852	            }); 
12853
12854	            externalPlaybackBtn.querySelector('.hap-btn-play').style.display = 'block';
12855	            externalPlaybackBtn.querySelector('.hap-btn-pause').style.display = 'none'; 
12856
12857			}
12858
12859            if(!settings.isPopup){//do not repeat in popup
12860
12861            	if(!settings.playerOpened)settings.playlistOpened = false;
12862
12863            	if(settings.playerOpened){
12864            		playerHolder.style.bottom = 0+'px';
12865
12866            		if(externalPlayerToggleBtn)externalPlayerToggleBtn.querySelector('.hap-btn-player-toggle').classList.remove('hap-btn-player-toggle-rotate')
12867
12868		        }else{
12869		        	playerHolder.style.bottom = -playerHolder.offsetHeight+'px';
12870
12871		        	if(externalPlayerToggleBtn)externalPlayerToggleBtn.querySelector('.hap-btn-player-toggle').classList.add('hap-btn-player-toggle-rotate')
12872
12873		        }
12874
12875		        if(settings.playlistOpened){
12876		        	playlistHolder.style.bottom = playerHolder.offsetHeight+'px';
12877		        }else{
12878		        	playlistHolder.style.bottom = -playlistHolder.offsetHeight+'px'; 
12879		        	wrapper.querySelector('.hap-ex-controls')?.classList.add('hap-ex-controls-shown')
12880		        }
12881            }
12882
12883		}
12884		else if(class_list.indexOf('hap-art-wide') > -1 || class_list.indexOf('hap-brona') > -1){
12885
12886		    if(!settings.playlistOpened){
12887				
12888				var ph = parseInt(playlistHolder.clientHeight,10)
12889	            playlistHolder.style.height = ph+'px'
12890
12891	            playlistHolder.classList.add('hap-playlist-closed-v')
12892
12893		    }
12894		}
12895		else if(class_list.indexOf('hap-art-narrow') > -1){
12896
12897		    if(settings.playlistOpened){
12898	            playlistHolder.classList.add('hap-playlist-holder-opened')
12899	            playerHolder.classList.add('hap-player-holder-closed')
12900		    }
12901		}
12902
12903		if(settings.playlistOpened){
12904        	wrapper.classList.add('hap-is-playlist-opened')
12905        }else{
12906        	wrapper.classList.remove('hap-is-playlist-opened')
12907        }
12908	
12909	}
12910
12911	function setExternalIconData(playing){
12912        externalPlaybackBtn.querySelectorAll('.hap-btn').forEach(function(el){
12913			el.style.display = 'none';
12914		})
12915        if(playing) externalPlaybackBtn.querySelector('.hap-btn-pause').style.display = 'block';
12916        else externalPlaybackBtn.querySelector('.hap-btn-play').style.display = 'block';
12917    }
12918
12919
12920	//############################################//
12921	/* misc */
12922	//############################################//
12923	
12924	this.getTitle = function(data, clean){//get title from data
12925		var title;
12926
12927		if(data.artist && !HAPUtils.isEmpty(data.artist) && data.title && !HAPUtils.isEmpty(data.title)){
12928			if(clean)title = data.artist + ' - ' + data.title;
12929			else title = data.artist + settings.artistTitleSeparator + data.title;
12930		}
12931		else if(data.title && !HAPUtils.isEmpty(data.title)){
12932			title = data.title;
12933		}
12934		else if(data.artist && !HAPUtils.isEmpty(data.artist)){
12935			title = data.artist;
12936		}
12937
12938		return title;
12939	}
12940	
12941	function setTitleAndThumb(){
12942
12943		//title, artist
12944		if(currMediaData.title || currMediaData.artist){
12945			var full_title = self.getTitle(currMediaData)
12946
12947			audio.setAttribute('title', self.getTitle(currMediaData, true))
12948
12949		}else{
12950			audio.setAttribute('title', '')
12951		}
12952		
12953		if(playerTitle && currMediaData.title)playerTitle.innerHTML = currMediaData.title;
12954		if(playerArtist && currMediaData.artist)playerArtist.innerHTML = currMediaData.artist;
12955		if(wrapper.querySelector('.hap-player-desc') && currMediaData.description)wrapper.querySelector('.hap-player-desc').innerHTML = currMediaData.description;
12956
12957		if(playerThumbInited && globalThumb)return;
12958
12959		//thumb
12960		if(playerThumb){
12961
12962			if(!currMediaData.thumb && (currMediaData.origtype == 's3_bucket_audio' || currMediaData.origtype == 's3_audio') && settings.getThumbFromBucket){
12963
12964				if(!currMediaData.thumb_key){
12965					var key = currMediaData.key.substr(0, currMediaData.key.lastIndexOf('.'));
12966					currMediaData.thumb_key = key + '.' + settings.s3ThumbExtension;
12967				}
12968
12969				getAwsThumb(currMediaData)
12970				return;
12971			}
12972
12973			var next_thumb = currMediaData.thumb || currMediaData.thumbDefault;
12974			if(next_thumb)setThumb(next_thumb)
12975			else if(mediaType == 'youtube'){
12976				playerThumb.style.opacity = 1;
12977			}
12978		}
12979		
12980	}
12981
12982	function setThumb(next_thumb){
12983
12984		if(mediaPrefixUrl && !currMediaData.thumbIsBase64 && !HAPUtils.relativePath(next_thumb))next_thumb = mediaPrefixUrl + next_thumb;
12985
12986		if(currMediaData && currMediaData.origtype == 'soundcloud'){
12987			if(settings.soundCloudThumbQuality)next_thumb = next_thumb.replace('large.jpg', settings.soundCloudThumbQuality);
12988		}
12989
12990		//if(next_thumb.indexOf("'") > -1)next_thumb = next_thumb.replace(/'/g, "%27");//if has single quote?
12991
12992		if(currMediaData && currMediaData.origtype != 's3_bucket_audio' && currMediaData.origtype != 's3_audio' && next_thumb.indexOf(' ') > -1)next_thumb = encodeURI(next_thumb);//spaces in url
12993
12994		if(playerThumb.style.backgroundImage.indexOf(next_thumb) > -1)return;//dont reload same thumb
12995
12996		if(currMediaData && currMediaData.title)playerThumb.setAttribute('aria-label', currMediaData.title);
12997		else playerThumb.setAttribute('aria-label','');
12998
12999		if(playerThumbInited){
13000			playerThumb.style.opacity = 0;
13001			setTimeout(function(){
13002				playerThumb.style.backgroundImage = 'url("'+next_thumb+'")'
13003				playerThumb.style.opacity = 1;
13004			},300);
13005		}else{
13006			playerThumb.style.backgroundImage = 'url("'+next_thumb+'")'
13007			playerThumb.style.opacity = 1;
13008		}
13009		playerThumbInited = true;
13010
13011
13012		//preset mods
13013		if(class_list.indexOf('hap-metalic') > -1 || class_list.indexOf('hap-poster') > -1){
13014            var pi = wrapper.querySelector('.hap-player-image')
13015            if(pi)pi.style.backgroundImage = 'url('+next_thumb+')';
13016        }
13017       
13018
13019	}
13020
13021
13022	//############################################//
13023	/* statistics */
13024	//############################################//
13025
13026	function handleLikeClick(e){
13027
13028		if(!componentInited) return false;
13029		if(!mediaType) return false;
13030
13031		var p = e.target.closest('.hap-playlist-item');
13032        getStats('hap_like_count', p.getAttribute('data-id'));
13033
13034	}
13035
13036	if(settings.useStatistics){
13037
13038		if(settings.ipInfo){
13039			countryData = settings.ipInfo
13040		}
13041
13042		/*fetch('https://extreme-ip-lookup.com/json/')
13043		.then( res => res.json())
13044		.then(response => {
13045			countryData = response;
13046		    console.log("Country: ", response);
13047		})
13048		.catch((data, status) => {
13049		    console.log('country request failed');
13050		})*/
13051
13052
13053
13054	    onHandler(wrapper, 'click', '.hap-like-count', (e) => handleLikeClick(e));
13055
13056	    onHandler(wrapper, 'click', '.hap-download-count:not(.hap-no-download)', (e) => handleDownloadClick(e));
13057
13058	    function handleDownloadClick(e){
13059	    	e.preventDefault()
13060
13061	    	if(!componentInited) return false;
13062
13063
13064			var target = e.target,
13065	    	id = Number(target.closest('.hap-playlist-item').getAttribute('data-id'))
13066
13067	    	var i, len = playlistDataArr.length, data
13068			for(i = 0;i < len; i++){
13069				if(id == playlistDataArr[i].id){
13070					data = playlistDataArr[i].data
13071					break;
13072				}
13073			}
13074
13075			var title = getTitleForStats(data);
13076
13077			pwdDownloadData = {}
13078			pwdDownloadData.orig_data = data
13079			pwdDownloadData.type = 'hap-download-count'
13080			pwdDownloadData.id = id
13081			pwdDownloadData.title = title
13082
13083			if(data && data.download){
13084		    	//check password
13085				if(data.pwd){
13086					pwdAction = 'download'
13087					
13088					pwdHolderToggle()
13089				}else{
13090					doDownloadAction()
13091				}
13092			}
13093
13094			return false;
13095
13096	    }
13097
13098	}
13099
13100	function getTitleForStats(data){
13101
13102		var title, artist
13103
13104		title = data.safeTitle || data.title || ''
13105		artist = data.safeArtist || data.artist || ''
13106
13107		return {title: title, artist: artist}
13108
13109	}
13110
13111	function getStats(action, id, arr){
13112
13113		if(window.location.protocol == 'file:'){
13114			return;
13115		}
13116
13117		var plid = settings.playerId != -1 ? settings.playerId : null;
13118
13119		if(action == 'hap_all_count'){
13120
13121		    var params = 'action='+action+'&data='+JSON.stringify(arr);
13122
13123		}
13124		else if(action == 'hap_play_count'){
13125
13126			if(!playlistDataArr[id])return;
13127
13128			var data = playlistDataArr[id].data;
13129
13130			//if(data.mediaId == undefined)return;
13131			if(playlistId == undefined){//if we get stats from connected player
13132				if(data.playlistId != undefined)playlistId = data.playlistId;
13133				//else return;
13134			}
13135
13136			var songData = getTitleForStats(data)
13137
13138			var album = data.album || '',
13139			thumb = data.thumb || data.thumbDefault || '',
13140			c_data = countryData ? JSON.stringify(countryData) : ''
13141
13142		    var params = 'action='+action
13143		    +'&percentToCountAsPlay='+settings.percentToCountAsPlay
13144		    +'&playlist_id='+playlistId
13145		    +'&player_id='+settings.playerId
13146		    +'&media_id='+data.mediaId
13147		    +'&audio_url='+mediaPath
13148		    +'&title='+songData.title
13149		    +'&artist='+songData.artist
13150		    +'&album='+ album
13151		    +'&thumb='+ thumb
13152		    +'&currentTime='+activeAudio.currentTime
13153		    +'&duration='+activeAudio.duration
13154		    +'&countryData='+c_data
13155
13156		}
13157		else if(action == 'hap_download_count' || action == 'hap_like_count' || action == 'hap_finish_count'){
13158
13159			var data = playlistDataArr[id].data;
13160
13161			//if(data.mediaId == undefined)return;
13162			if(playlistId == undefined){//if we get stats from connected player
13163				if(data.playlistId != undefined)playlistId = data.playlistId;
13164				//else return;
13165			}
13166
13167			var songData = getTitleForStats(data)
13168
13169			var album = data.album || '',
13170			thumb = data.thumb || data.thumbDefault || ''
13171
13172		    var params = 'action='+action
13173		    +'&playlist_id='+playlistId
13174		    +'&player_id='+settings.playerId
13175		    +'&media_id='+data.mediaId
13176		    +'&audio_url='+mediaPath
13177		    +'&title='+songData.title
13178		    +'&artist='+songData.artist
13179		    +'&album='+ album
13180		    +'&thumb='+ thumb
13181
13182		}
13183		else if(action == 'hap_skipped_first_minute'){
13184
13185			var data = playlistDataArr[id].data;
13186
13187			var songData = getTitleForStats(data)
13188
13189			var album = data.album || '',
13190			thumb = data.thumb || data.thumbDefault || ''
13191
13192		    var params = 'action='+action
13193		    +'&playlist_id='+playlistId
13194		    +'&player_id='+settings.playerId
13195		    +'&media_id='+data.mediaId
13196		    +'&audio_url='+mediaPath
13197		    +'&title='+songData.title
13198		    +'&artist='+songData.artist
13199		    +'&album='+ album
13200		    +'&thumb='+ thumb
13201
13202		}
13203		else if(action == 'hap_time_played'){
13204
13205			if(!playlistDataArr[id])return;
13206
13207			var data = playlistDataArr[id].data;
13208
13209			if(playlistId == undefined){
13210				if(data.playlistId != undefined)playlistId = data.playlistId;
13211			}
13212
13213			var songData = getTitleForStats(data)
13214
13215			var album = data.album || '',
13216			thumb = data.thumb || data.thumbDefault || ''
13217
13218		    var params = 'action='+action
13219		    +'&playlist_id='+playlistId
13220		    +'&player_id='+settings.playerId
13221		    +'&media_id='+data.mediaId
13222		    +'&audio_url='+mediaPath
13223		    +'&title='+songData.title
13224		    +'&artist='+songData.artist
13225		    +'&album='+ album
13226		    +'&thumb='+ thumb
13227
13228		}
13229
13230
13231		var	url = settings.ajax_url;
13232
13233
13234
13235		var xhrRequest_stat = new XMLHttpRequest();
13236		xhrRequest_stat.onreadystatechange = function() {
13237			if (xhrRequest_stat.readyState == 4) {
13238
13239				var response = JSON.parse(this.responseText);
13240
13241				if(response){
13242
13243					if(settings.usePlaylist && response && response != '-1'){
13244
13245			    		if(action == 'hap_like_count' && response.c_like){
13246
13247			    			var p = playlistContent.querySelector('.hap-playlist-item[data-id="'+id+'"]');
13248
13249			    			p.querySelector('.hap-like-count span').innerHTML = HAPUtils.nFormatter(parseInt(response.c_like,10), 1);
13250			    		}
13251			    		else if(action == 'hap_download_count' && response.c_download){
13252
13253			    			var p = playlistContent.querySelector('.hap-playlist-item[data-id="'+id+'"]');
13254
13255			    			p.querySelector('.hap-download-count span').innerHTML = HAPUtils.nFormatter(parseInt(response.c_download,10), 1);
13256			    		}
13257			    		else if(action == 'hap_play_count' && response.c_play){
13258
13259			    			var p = playlistContent.querySelector('.hap-playlist-item[data-id="'+id+'"]');
13260
13261			    			p.querySelector('.hap-play-count span').innerHTML = HAPUtils.nFormatter(parseInt(response.c_play,10), 1);
13262			    		}
13263			    		else if(action == 'hap_all_count'){
13264
13265			    			var i, len = response.length, obj, item;
13266			    			for(i=0;i<len;i++){
13267			    				obj = response[i];
13268
13269			    				if(obj.title && obj.artist && obj.media_id){
13270
13271			    					item = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+obj.media_id+'"][data-safe-title="'+obj.title+'"][data-safe-artist="'+obj.artist+'"]');
13272
13273			    				}else if(obj.title && obj.artist){
13274
13275			    					item = playlistContent.querySelector('.hap-playlist-item[data-safe-title="'+obj.title+'"][data-safe-artist="'+obj.artist+'"]');
13276
13277			    				}else if(obj.title){
13278
13279			    					item = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+obj.media_id+'"][data-safe-title="'+obj.title+'"]');
13280
13281			    				}else if(obj.artist){
13282
13283			    					item = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+obj.media_id+'"][data-safe-artist="'+obj.artist+'"]');
13284			    				}	
13285								
13286			    				if(item.querySelector('.hap-like-count'))item.querySelector('.hap-like-count span').innerHTML = HAPUtils.nFormatter(parseInt(obj.c_like,10), 1);
13287
13288			    				if(item.querySelector('.hap-download-count'))item.querySelector('.hap-download-count span').innerHTML = HAPUtils.nFormatter(parseInt(obj.c_download,10), 1);
13289
13290			    				if(item.querySelector('.hap-play-count'))item.querySelector('.hap-play-count span').innerHTML = HAPUtils.nFormatter(parseInt(obj.c_play,10), 1);
13291			    			}
13292			    		}
13293
13294				    }
13295
13296				}
13297
13298		    }
13299		}
13300		xhrRequest_stat.onerror = function(e) { 
13301		    console.log('Error getStats: ' + e);
13302		};
13303		xhrRequest_stat.open('POST', url);
13304		xhrRequest_stat.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
13305		xhrRequest_stat.send(params);
13306
13307	}
13308
13309	//############################################//
13310	/* ads */
13311	//############################################//
13312
13313	this.showAdInterface = function(){
13314
13315		if(progressLevel)progressLevel.classList.add('hap-ad-progress-level');
13316		if(mediaTimeCurrent)mediaTimeCurrent.style.display = 'none';
13317		if(mediaTimeTotal)mediaTimeTotal.style.display = 'none';
13318		if(mediaTimeAd)mediaTimeAd.style.display = 'block';
13319
13320	}
13321
13322	this.hideAdInterface = function(){
13323
13324		if(progressLevel)progressLevel.classList.remove('hap-ad-progress-level');
13325	    if(mediaTimeCurrent)mediaTimeCurrent.style.display = 'block';
13326		if(mediaTimeTotal)mediaTimeTotal.style.display = 'block';
13327	    if(mediaTimeAd)mediaTimeAd.style.display = 'none';
13328
13329	}
13330
13331	this.hideImaAdInterface = function(){
13332
13333		if(progressLevel)progressLevel.classList.remove('hap-ad-progress-level');
13334	    if(mediaTimeCurrent)mediaTimeCurrent.style.display = 'block';
13335		if(mediaTimeTotal)mediaTimeTotal.style.display = 'block';
13336
13337		if(mediaTimeAd)mediaTimeAd.style.display = 'none';
13338    	if(adSkipBtn)adSkipBtn.style.display = 'none';
13339
13340		isImaOn = false;
13341
13342	}
13343
13344	this.showImaAdInterface = function(){
13345
13346		if(progressLevel)progressLevel.classList.add('hap-ad-progress-level');
13347		if(mediaTimeCurrent)mediaTimeCurrent.style.display = 'none';
13348		if(mediaTimeTotal)mediaTimeTotal.style.display = 'none';
13349		if(mediaTimeAd)mediaTimeAd.style.display = 'block';
13350		
13351		isImaOn = true;
13352
13353	}
13354
13355	this.displayAdTime = function(d, t, skip_allowed){
13356
13357		if(HAPUtils.isNumber(t) && HAPUtils.isNumber(d)){
13358
13359			if(progressLevel)progressLevel.style.width = (t / d) * seekBarSize + 'px';
13360
13361			var adt = parseInt(d-t,10);
13362			if(mediaTimeAd)mediaTimeAd.querySelector('span').innerHTML = HAPUtils.formatTime(adt);
13363		}
13364
13365		if(settings.useImaSkipButto && adSkipBtn){
13366			if(skip_allowed){
13367				adSkipBtn.style.display = 'block'
13368			}else{
13369				adSkipBtn.style.display = 'none'
13370			}
13371		}
13372
13373	}
13374
13375
13376	//############################################//
13377	/* api */
13378	//############################################//
13379	
13380	function log(s){
13381		var h = document.createElement("H1");
13382	    var t = document.createTextNode(s);
13383	    h.style.color = '#ffffff';
13384	    h.appendChild(t);
13385	    document.body.appendChild(h);
13386	}
13387
13388	this.playMedia = function() {
13389		if(!componentInited) return false;
13390		if(!mediaType) return false;
13391		if(mediaPlaying) return false;
13392
13393		if(mediaType == 'youtube'){
13394			if(youtubePlayer && youtubeReady)youtubePlayer.playVideo();
13395		}else{
13396			if(activeAudio){
13397				var promise = activeAudio.play();
13398				if (promise !== undefined) {
13399					promise.then(function(){
13400				    }).catch(function(error){
13401				    });
13402				}
13403			}
13404		}
13405	}
13406	this.pauseMedia = function() {	
13407		if(!componentInited) return false;
13408		if(!mediaType) return false;
13409		if(!mediaPlaying) return false;
13410
13411		if(mediaType == 'youtube'){
13412			if(youtubePlayer && youtubeReady)youtubePlayer.pauseVideo();
13413		}else{
13414			if(activeAudio)activeAudio.pause();
13415		}
13416	}	
13417	this.togglePlayback = function(){
13418
13419		if(!componentInited) return false;
13420		if(!mediaType) return false;
13421		if(_HAPAdManager)_HAPAdManager.clearAdMidTimeout();
13422		if(crossFadeOn)cancelCrossFadeSeek();
13423
13424		//check password
13425		if(currMediaData.pwd){
13426			pwdHolderToggle()
13427			return false;
13428		}
13429
13430		if(!mediaStarted){
13431			if(currMediaData.lockTime == '0' && !allowedViewVideoUserRole){
13432				toggleRedirectLoginScreen('watch');
13433				return
13434			}
13435		}
13436
13437
13438		if(dataIntervalID == null){//from login
13439			dataIntervalID = setInterval(trackProgress, dataInterval);
13440		}
13441
13442
13443		if(_HAPAdManager && _HAPAdManager.isAdOn()){	
13444
13445			if(isIOS && currMediaData && (currMediaData.adMid || currMediaData.adEnd) && !iosAdMidFixStart && _HAPAdManager){
13446				iosAdMidFixStart = true;
13447				_HAPAdManager.forceAdMidAudio();//ads 
13448			}
13449
13450			//############################################//
13451			/* ads */
13452			//############################################//
13453
13454			if(_HAPAdManager.isAdPreOn()){
13455				_HAPAdManager.toggleAdAudio();
13456			}
13457			else if(_HAPAdManager.isAdMidOn() && settings.pauseAudioDuringAds){
13458				_HAPAdManager.toggleAdAudio();
13459			}
13460
13461			//############################################//
13462			/* ads */
13463			//############################################//
13464
13465		}else if(isIma){
13466			
13467			if(!imaStarted){
13468				initVast()
13469			}else if(isImaOn){
13470				_IMALoader.togglePlayPause()
13471			}else{
13472				if(mediaPlaying)self.pauseMedia()
13473				else self.playMedia()
13474			}
13475		
13476		}else{
13477
13478			if(mediaType == 'youtube'){
13479				if(youtubePlayer && youtubeReady){
13480				    var player_state = youtubePlayer.getPlayerState();
13481				    
13482				    if(player_state == 1){//playing
13483						youtubePlayer.pauseVideo();
13484					}else if(player_state == 2){//paused
13485						youtubePlayer.playVideo();
13486					}else if(player_state == -1 || player_state == 5 || player_state == 0){//unstarted, cued, ended
13487						youtubePlayer.playVideo();
13488					}
13489				}
13490			}else{
13491
13492				if(activeAudio){
13493
13494				    if(activeAudio.paused) {
13495
13496				    	if(isSafari && settings.applySafariAudioRestartFix){
13497				    		var url = mediaPath.substr(0,mediaPath.indexOf('?') )
13498						    url += "?rand=" + (Math.random() * 99999999)
13499						    activeAudio.url = mediaPath;
13500						    activeAudio.load()
13501				    	}
13502
13503					    var promise = activeAudio.play();
13504						if (promise !== undefined) {
13505							promise.then(function(){
13506						    }).catch(function(error){
13507						    });
13508						}
13509				    }else{
13510					    activeAudio.pause();
13511				    }
13512
13513				    if(_HAPAdManager && !settings.pauseAudioDuringAds && _HAPAdManager.isAdMidOn()){
13514				    	//_HAPAdManager.toggleAdAudio();//potential useful if one wants to enable ad pause while main audio plays?
13515				    }
13516				}
13517			}
13518
13519		}
13520	}
13521	this.nextMedia = function() {
13522		if(!componentInited) return false;
13523
13524		if(_HAPAdManager && _HAPAdManager.isAdOn()) return false;
13525		if(isIma && isImaOn) return false;
13526
13527		if(settings.disableSongSkip) return false;
13528		if(playlistLength == 0) return;
13529		if(crossFadeOn && !comingFromCrossFade)cancelCrossFadeAdvance();
13530		comingFromCrossFade = false;
13531
13532		if(typeof mediaCounter != undefined)beforeSongAdvanceAction();
13533
13534		if(settings.usePlaylist && isSearchActive){
13535
13536			var current = playlistContent.querySelector('.hap-playlist-item-selected:not(.hap-filter-hidden)'),
13537			next
13538
13539			if(current){
13540				next = getNextSibling(current);
13541			}else{
13542				//if no active item from search
13543				next = playlistContent.querySelector('.hap-playlist-item:not(.hap-filter-hidden)');
13544			}
13545
13546			if(next){
13547				var c = next.getAttribute('data-id')
13548				_HAPPlaylistManager.setCounter(c, false);
13549			}
13550			else{//no more in search, try to advance normally
13551				//_HAPPlaylistManager.advanceHandler(1, true);
13552			}
13553
13554		}else{
13555			_HAPPlaylistManager.advanceHandler(1, true);
13556		}
13557	}
13558	function getNextSibling (elem, selector) {
13559		//https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/
13560
13561		// Get the next sibling element
13562		var sibling = elem.nextElementSibling;
13563
13564		// If there's no selector, return the first sibling
13565		//if (!selector) return sibling;
13566
13567		// If the sibling matches our selector, use it
13568		// If not, jump to the next sibling and continue the loop
13569		while (sibling) {
13570			if (sibling.classList.contains('hap-playlist-item') && !sibling.classList.contains('hap-filter-hidden')) {
13571				return sibling;
13572			}
13573			sibling = sibling.nextElementSibling
13574		}
13575
13576	};
13577	function getPreviousSibling (elem, selector) {
13578
13579		// Get the next sibling element
13580		var sibling = elem.previousElementSibling;
13581
13582		// If there's no selector, return the first sibling
13583		//if (!selector) return sibling;
13584
13585		// If the sibling matches our selector, use it
13586		// If not, jump to the next sibling and continue the loop
13587		while (sibling) {
13588			if (sibling.classList.contains('hap-playlist-item') && !sibling.classList.contains('hap-filter-hidden')) {
13589				return sibling;
13590			}
13591			sibling = sibling.nextElementSibling
13592		}
13593
13594	};
13595	this.previousMedia = function() {
13596		if(!componentInited) return false;
13597
13598		if(_HAPAdManager && _HAPAdManager.isAdOn()) return false;
13599		if(isIma && isImaOn) return false;
13600
13601		if(settings.disableSongSkip) return false;
13602		if(playlistLength == 0) return;
13603		if(crossFadeOn && !comingFromCrossFade)cancelCrossFadeAdvance();
13604		comingFromCrossFade = false;
13605
13606		if(typeof mediaCounter != undefined)beforeSongAdvanceAction();
13607
13608		if(settings.usePlaylist && isSearchActive){
13609
13610			var current = playlistContent.querySelector('.hap-playlist-item-selected:not(.hap-filter-hidden)'),
13611			next
13612
13613			if(current)next = getPreviousSibling(current);
13614			else{
13615				//if no active item from search
13616				next = playlistContent.querySelector('.hap-playlist-item:not(.hap-filter-hidden)');
13617			}
13618
13619			if(next){
13620				var c = next.getAttribute('data-id')
13621				_HAPPlaylistManager.setCounter(c, false);
13622			}
13623			else{//no more in search, try to advance normally
13624				//_HAPPlaylistManager.advanceHandler(-1, true);
13625			}
13626
13627		}else{
13628			_HAPPlaylistManager.advanceHandler(-1, true);
13629		}
13630
13631	}
13632	this.loadMedia = function(format, value, title, artist) {
13633		if(!componentInited) return false;
13634		if(playlistTransitionOn) return false;
13635		if(playlistLength == 0) return;
13636				console.log(format, value, title, artist)
13637		var index = -1;
13638
13639		if(format == 'title'){
13640
13641			var i, found;
13642			for(i = 0;i < playlistLength; i++){
13643				if(value == playlistDataArr[i].data.title){
13644					index = i;
13645					found = true;
13646					break;	
13647				}
13648			}
13649
13650			if(!found){
13651				alert('No media with title "'+value+'" to load! LoadMedia failed.');
13652				return false;
13653			}
13654
13655		}else if(format == 'title-artist'){
13656
13657			var pi = playlistContent.querySelector('.hap-playlist-item[data-title="'+title+'"][data-artist="'+artist+'"]');
13658
13659			if(!pi){
13660				alert('No media with title "'+title+'" and artist "'+artist+'" to load! Loa
13660dMedia failed.');
13661				return false;
13662			}	
13663
13664			index = pi.getAttribute('data-id');
13665			
13666		}else if(format == 'counter'){
13667			
13668			if(value<0){
13669				alert('Invalid track number. Track number  "' + value + '" doesnt exist. LoadMedia failed.');
13670				return false;
13671			}
13672			else if(value > playlistLength-1){
13673				alert('Invalid track number. Track number  "' + value + '" doesnt exist. LoadMedia failed.');
13674				return false;
13675			}
13676			else{
13677				index = value;
13678			}
13679			
13680		}else if(format == 'id'){//data-media-id
13681
13682			var pi = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+value+'"]');
13683			
13684			if(!pi){
13685				alert('No media with media ID '+value+' to load! LoadMedia failed.');
13686				return false;
13687			}
13688
13689			index = pi.getAttribute('data-id');
13690
13691		}else if(format == 'id-title'){//data-media-id and title, artist (we use this if playlist is grouped source and all items can have same media-id)
13692
13693			if(title && artist){
13694				var pi = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+value+'"][title="'+title+'"][data-artist="'+artist+'"]');
13695			}else if(title){
13696				var pi = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+value+'"][title="'+title+'"]');
13697			}else if(artist){
13698				var pi = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+value+'"][data-artist="'+artist+'"]');
13699			}	
13700			
13701			if(!pi){
13702				alert('No media with media ID '+value+' to load! LoadMedia failed.');
13703				return false;
13704			}
13705
13706			index = pi.getAttribute('data-id');
13707
13708		}else{
13709			console.log('loadMedia function requires format parameter!');
13710			return false;
13711		}
13712
13713		if(typeof mediaCounter != undefined)beforeSongAdvanceAction();
13714
13715		autoPlay = true;
13716		settings.autoPlay = true;
13717		
13718		_HAPPlaylistManager.processPlaylistRequest(index);
13719	}
13720
13721	//############################################//
13722	/* load more, add more */
13723	//############################################//
13724
13725	this.loadMore = function(){
13726
13727		if(!componentInited) return false;
13728		if(!loadMoreType) return false;
13729		if(!nextPageToken) return false;
13730		if(playlistTransitionOn)return false;
13731
13732		playlistTransitionOn = true;
13733		if(preloader)preloader.style.display = 'block';
13734		loadMoreProcess = true;
13735		playlistProcessData = [];
13736
13737		if(loadMoreType == 'soundcloud'){
13738			resumeSoundCloud();
13739		}else if(loadMoreType == 'podcast'){
13740			resumePodcast();	
13741		}else if(loadMoreType == 'folder'){
13742			resumeFolderLoad();
13743		}else if(loadMoreType == 'youtube'){
13744			if(!_HAPYoutubeLoader)createLoader('youtube');
13745			_HAPYoutubeLoader.resumeLoad(nextPageToken);
13746		}
13747	}
13748	this.setLoadMore = function(value){
13749		loadMoreOnTotalScroll = value;
13750	}
13751	this.endLoadMore = function(){
13752		playlistTransitionOn = false;
13753		preloader.style.display = 'none';
13754		loadMoreProcess = false;
13755	}
13756	function addMore(){//load more from database
13757
13758		if(preloader)preloader.style.display = 'block';
13759		loadMoreProcess = true;
13760		if(loadMoreBtn)loadMoreBtn.style.opacity = 0;
13761
13762	    var params = 'action=hap_add_more'
13763	    +'&playlist_id='+playlistId
13764	    +'&addMoreOffset='+addMoreOffset
13765	    +'&addMoreLimit='+addMoreLimit
13766	    +'&addMoreSortOrder='+addMoreSortOrder
13767	    +'&addMoreSortDirection='+addMoreSortDirection
13768	    +'&encryptMediaPaths='+settings.encryptMediaPaths
13769
13770	    if(xhrRequest_pag){
13771			xhrRequest_pag.abort();
13772			xhrRequest_pag = null;
13773		}
13774
13775		var url = settings.ajax_url
13776
13777		xhrRequest_pag = new XMLHttpRequest();
13778		xhrRequest_pag.onreadystatechange = function() {
13779			if (xhrRequest_pag.readyState == 4) {
13780
13781				var response = JSON.parse(xhrRequest_pag.responseText);
13782
13783				if(playlistData && playlistData.getAttribute('data-add-more-offset') != undefined){
13784					addMoreOffset = parseInt(playlistData.getAttribute('data-add-more-offset'),10) + addMoreLimit;
13785					playlistData.setAttribute('data-add-more-offset', addMoreOffset);
13786				}
13787		        self.addTrack(response);
13788			
13789		    }
13790		}
13791		xhrRequest_pag.onerror = function(e) { 
13792		 	console.log(jqXHR, textStatus, errorThrown);
13793			self.endLoadMore();
13794		};
13795		xhrRequest_pag.open('POST', url);
13796		xhrRequest_pag.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
13797		xhrRequest_pag.send(params);
13798
13799	}
13800	this.addMore = function(){
13801		if(!componentInited) return false;
13802		if(playlistTransitionOn)return false;
13803		if(addMoreOnTotalScroll){
13804			if(!loadMoreProcess){
13805				addMore();
13806			}
13807		}
13808	}
13809	this.setAddMore = function(value){
13810		addMoreOnTotalScroll = value;
13811	}
13812
13813	//############################################//
13814	/* pagination */
13815	//############################################//
13816
13817	function paginate(){
13818
13819		if(preloader)preloader.style.display = 'block';
13820		loadMoreProcess = true;
13821
13822	    var params = 'action=hap_paginate'
13823	    +'&playlist_id='+playlistId
13824	    +'&addMoreOffset='+addMoreOffset
13825	    +'&addMoreLimit='+addMoreLimit
13826	    +'&addMoreSortOrder='+addMoreSortOrder
13827	    +'&addMoreSortDirection='+addMoreSortDirection
13828	    +'&encryptMediaPaths='+settings.encryptMediaPaths
13829
13830	    if(xhrRequest_pag){
13831			xhrRequest_pag.abort();
13832			xhrRequest_pag = null;
13833		}
13834
13835		var url = settings.ajax_url
13836
13837		xhrRequest_pag = new XMLHttpRequest();
13838		xhrRequest_pag.onreadystatechange = function() {
13839			if (xhrRequest_pag.readyState == 4) {
13840
13841				var response = JSON.parse(xhrRequest_pag.responseText);
13842
13843				//hide visible playlist items (do this now so layout doesnt change until we wait for new playlist items)
13844				playlistContent.querySelectorAll('.hap-playlist-item:not(.hap-pagination-hidden)').forEach(function(el){
13845					el.classList.add('hap-pagination-hidden')
13846					el.querySelector('.hap-thumbimg').classList.remove('hap-visible')
13847				})
13848
13849		        self.addTrack(response);
13850				
13851				createPagination(paginationCurrentPage);
13852
13853				savePagination();
13854			
13855		    }
13856		}
13857		xhrRequest_pag.onerror = function(e) { 
13858		 	console.log(jqXHR, textStatus, errorThrown);
13859			self.endLoadMore();
13860		};
13861		xhrRequest_pag.open('POST', url);
13862		xhrRequest_pag.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
13863		xhrRequest_pag.send(params);
13864
13865	}
13866
13867	function savePagination(){
13868		paginationArr[paginationCurrentPage].page = paginationCurrentPage;
13869
13870		var arr = [];
13871		playlistContent.querySelectorAll('.hap-playlist-item:not(.hap-pagination-hidden)').forEach(function(el){
13872			arr.push(el)
13873		})
13874		paginationArr[paginationCurrentPage].media_id = arr;
13875
13876	}
13877
13878	function createPagination(page){
13879
13880		page += 1;
13881
13882 		var id, c, str = '<div class="hap-pagination-container">';
13883
13884	 	if (page > 1){
13885			str += '<div class="hap-pagination-page hap-pagination-prev" data-page-id="prev" title="'+settings.paginationPreviousBtnTitle+'">'+settings.paginationPreviousBtnText+'</div>';
13886		}
13887
13888		if (page > 3){
13889			str += '<div class="hap-pagination-page hap-pagination-start" data-page-id="0">1</div>'+
13890			'<div class="hap-pagination-dots">...</div>';
13891		}
13892
13893		if (page-2 > 0){
13894			id = page-2;
13895			c = id-1;
13896			str += '<div class="hap-pagination-page" data-page-id="'+c+'">'+id+'</div>';
13897		}
13898		if (page-1 > 0){
13899			id = page-1;
13900			c = id-1;
13901			str += '<div class="hap-pagination-page" data-page-id="'+c+'">'+id+'</div>';
13902		}
13903
13904		id = page;
13905		c = id-1;
13906		str += '<div class="hap-pagination-page hap-pagination-currentpage" data-page-id="'+c+'">'+id+'</div>'
13907
13908		if (page+1 < paginationTotalPages+1){
13909			id = page+1;
13910			c = id-1;
13911			str += '<div class="hap-pagination-page" data-page-id="'+c+'">'+id+'</div>';
13912		}
13913			
13914		if (page+2 < paginationTotalPages+1){
13915			id = page+2;
13916			c = id-1;
13917			str += '<div class="hap-pagination-page" data-page-id="'+c+'">'+id+'</div>';
13918		}
13919
13920		if (page < paginationTotalPages-2){
13921			id = paginationTotalPages;
13922			c = id-1;
13923			str += '<div class="hap-pagination-dots">...</div>'+
13924			'<div class="hap-pagination-page hap-pagination-end" data-page-id="'+c+'">'+id+'</div>';
13925		}
13926
13927		if (page < paginationTotalPages){
13928			str += '<div class="hap-pagination-page hap-pagination-next" data-page-id="next" title="'+settings.paginationNextBtnTitle+'">'+settings.paginationNextBtnText+'</div>';
13929		}
13930
13931		str += '</div>';
13932
13933		if(paginationWrap)paginationWrap.innerHTML = str;
13934		else{
13935			wrapper.insertAdjacentHTML("beforeend",'<div class="hap-pagination-wrap">'+str+'</div>');
13936		} 
13937
13938		if(!paginationInited){
13939			paginationInited = true;
13940
13941			paginationWrap = wrapper.querySelector('.hap-pagination-wrap')
13942
13943			onHandler(paginationWrap, 'click', '.hap-pagination-page:not(.hap-pagination-currentpage)', (e) => handlePaginationClick(e));
13944
13945			lastActivePaginationBtn = paginationWrap.querySelector('.hap-pagination-currentpage')
13946
13947		}
13948
13949	}
13950
13951	function handlePaginationClick(e){
13952
13953		if(!componentInited)return false;
13954		if(playlistTransitionOn)return false;
13955		if(loadMoreProcess)return false;
13956
13957		loadMoreProcess = true;
13958
13959		var btn = e.target;
13960
13961		if(lastActivePaginationBtn)lastActivePaginationBtn.classList.remove('hap-pagination-currentpage');
13962		lastActivePaginationBtn = btn.classList.add('hap-pagination-currentpage');
13963
13964		//get new page
13965		var page = btn.getAttribute('data-page-id')
13966		if(page == 'prev')paginationCurrentPage -= 1;
13967		else if(page == 'next')paginationCurrentPage += 1;
13968		else paginationCurrentPage = parseInt(page,10);
13969
13970		if(paginationArr[paginationCurrentPage].page == null){//sql
13971			
13972			addMoreOffset = paginationCurrentPage * addMoreLimit;
13973			paginate();
13974
13975		}else{//already have data
13976			//console.log('already have data')
13977
13978			//hide visible playlist items
13979			playlistContent.querySelectorAll('.hap-playlist-item:not(.hap-pagination-hidden)').forEach(function(el){
13980				el.classList.add('hap-pagination-hidden')
13981				el.querySelector('.hap-thumbimg').classList.remove('hap-visible');
13982			})
13983
13984			//show current page
13985			var ids = paginationArr[paginationCurrentPage].media_id;
13986
13987			ids.forEach(element => {
13988				element.classList.remove('hap-pagination-hidden')
13989				element.querySelector('.hap-thumbimg').classList.remove('hap-visible');
13990
13991				setTimeout(function(){
13992					element.querySelector('.hap-thumbimg').classList.add('hap-visible');
13993				},20);
13994
13995			});
13996
13997			createPagination(paginationCurrentPage);
13998			loadMoreProcess = false;
13999
14000		}
14001	}
14002
14003	//############################################//
14004	/*  */
14005	//############################################//
14006	
14007	this.processPlaylistRequest = function(index){
14008		if(typeof mediaCounter != undefined)beforeSongAdvanceAction();
14009		autoPlay = true;
14010		settings.autoPlay = true;
14011		_HAPPlaylistManager.processPlaylistRequest(index);
14012	}
14013
14014	this.loadPlaylist = function(value, active_item) {
14015		if(!componentInited || playlistTransitionOn) return false;
14016
14017		if(typeof(value) === 'string'){
14018			if(typeof active_item != undefined)settings.mediaId = active_item;
14019			if(settings.activePlaylist == value)return false;//playlist already loaded!
14020			setPlaylist(value);			
14021		}else{
14022			alert('Invalid value loadPlaylist!');
14023			return false;	
14024		}
14025	}
14026	this.addTrack = function(track, playit, position, ready) {
14027		if(!componentInited || playlistTransitionOn) return false;
14028
14029		if(typeof track === 'undefined'){
14030			alert('addTrack method requires track parameter. AddTrack failed.');
14031			return false;
14032		}
14033
14034		addTrackPlayit = false;
14035		if(typeof playit !== 'undefined'){
14036			addTrackPlayit = playit;
14037			if(playit == false)isEnqueue = true;
14038		}
14039
14040		if(ready){//pass ready playlistData to build playlist in connected player without processing
14041			playlistDataReady = true;
14042			playlistInitLoad = false;
14043		}else{
14044			playlistDataReady = false;
14045		}
14046
14047		if(playlistFilterMsg)playlistFilterMsg.style.display = 'none';
14048
14049		insertPosition = position;
14050		endInsert = false;
14051		addTrackProcess = true;
14052		
14053		if(lastPlaylist){
14054
14055			if(typeof insertPosition !== 'undefined'){
14056				if(insertPosition < 0){
14057					insertPosition = 0;
14058				}
14059				else if(insertPosition >= playlistLength || insertPosition == 'end'){
14060					insertPosition = playlistLength;
14061					endInsert = true;
14062				}
14063			}else{
14064				endInsert = true;
14065				insertPosition = playlistLength;	
14066			}
14067		}else{//first time create playlist from addTrack method
14068			insertPosition = 0;
14069			endInsert = true;
14070		}
14071		
14072		playlistTransitionOn = true;
14073		if(preloader)preloader.style.display = 'block';
14074		
14075		playlistProcessCounter = -1;
14076		playlistProcessData = [];
14077		playlistProcessDataUrl = [];
14078
14079		if(Array.isArray(track))playlistProcessDataUrl = track;
14080		else playlistProcessDataUrl.push(track);
14081
14082		playlistLength = playlistProcessDataUrl.length;
14083
14084		lastPlaylist = playlistContent;
14085		
14086		if(playlistDataReady){
14087			playlistProcessData = playlistProcessDataUrl;
14088			buildPlaylist();
14089		}else{
14090			checkProcessCounter();
14091		}
14092
14093	}
14094
14095	this.inputAudio = function(data){
14096		if(!componentInited) return false;
14097		if(playlistTransitionOn) return false;
14098
14099		if(typeof data === 'undefined'){
14100			alert('inputAudio method requires data parameter. inputAudio failed.');
14101			return false;
14102		}
14103
14104		//enable active item, if exist, but dont dispose current playlist
14105		if(activePlaylistItem)enableActivePlaylistItem();
14106		if(playlistLength > 0)_HAPPlaylistManager.reSetCounter();
14107		
14108		currMediaData = data;
14109		mediaType = data.type;
14110		mediaPath = getAudioUrl(data);
14111
14112		if(nonProcessMedia.indexOf(mediaType) == -1){
14113			alert('inputAudio method supports tracks that dont require processing: ' + nonProcessMedia);
14114			return false;
14115		}
14116
14117		setTitleAndThumb();
14118		if(useMediaSession)updateMetadata();
14119
14120		autoPlay = true;
14121		settings.autoPlay = true;
14122
14123		initAudio();
14124	}
14125	
14126	this.removeTrack = function(format, value) {
14127		if(!componentInited || playlistTransitionOn) return false;
14128		if(playlistLength == 0) return;
14129
14130		var track, found, title;
14131
14132		if(format == 'title'){
14133
14134			var i;
14135			for(i = 0;i < playlistLength; i++){
14136				if(value == playlistDataArr[i].data.title){
14137					track = playlistContent.querySelectorAll('.hap-playlist-item')[i];
14138					console.log(track,value)
14139					found = true;
14140					break;	
14141				}
14142			}
14143
14144			if(!found){
14145				alert('Track with title "' + value + '" doesnt exist. RemoveTrack failed.');
14146				return false;	
14147			}
14148			
14149		}else if(format == 'counter'){
14150			
14151			value = parseInt(value,10);
14152			
14153			if(value < 0 || value > playlistLength-1){
14154				alert('Track number  "' + value + '" doesnt exist. RemoveTrack failed.');
14155				return false;
14156			}
14157
14158			track = playlistContent.querySelectorAll('.hap-playlist-item')[value];
14159			
14160		}else if(format == 'id'){//data-media-id
14161
14162			track = playlistContent.querySelector('.hap-playlist-item[data-media-id="'+value+'"]');
14163
14164			if(!track){
14165				alert('Track with media id "' + value + '" doesnt exist. RemoveTrack failed.');
14166				return false;	
14167			}
14168
14169		}else{
14170			alert('removeTrack method failed.');
14171			return false;
14172		}
14173
14174
14175		//remove track
14176		track.remove();
14177		playlistDataArr.splice(parseInt(track.getAttribute('data-id'),10),1);
14178
14179		
14180		updatePlaylist(true);	
14181		
14182		if(playlistLength > 0){
14183			
14184			var current_counter = _HAPPlaylistManager.getCounter();
14185			if(value == current_counter){//remove number equal to current counter
14186				cleanMedia();	
14187				_HAPPlaylistManager.setPlaylistItems(playlistLength);//counter resets to -1
14188			}else{
14189				_HAPPlaylistManager.setPlaylistItems(playlistLength, false);
14190				if(value < current_counter){//remove number less than current counter
14191					_HAPPlaylistManager.reSetCounter(_HAPPlaylistManager.getCounter()-1);//if we removed media before current playing media, descrease counter!	
14192				}else{//remove number larger than current counter, current counter doesnt change
14193				}
14194			}
14195
14196		}else{//we removed last track in playlist
14197			destroyPlaylist();	
14198		}
14199
14200	}
14201
14202	this.sort = function(order) {
14203		if(!componentInited) return false;
14204		if(playlistLength < 3) return false;
14205	
14206		if(typeof order === 'undefined'){
14207			console.log('Sort method requires order parameter. Sort method failed.');
14208			return false;
14209		}
14210
14211		if(sortOrder == v) return false;
14212
14213		var item_list = playlistContent.querySelectorAll('.hap-playlist-item'),
14214		items = [...item_list],
14215		len = items.length, v = order.toLowerCase();
14216
14217		if(v == 'title-asc'){
14218
14219			HAPUtils.keysrt2(playlistDataArr, 'data', 'title');
14220
14221			var i, arr = [];
14222			for(i = 0;i < playlistLength; i++){
14223				arr.push(playlistDataArr[i].id);
14224			}
14225
14226			if(sortAlpha)sortAlpha.querySelector('.hap-btn-sort-alpha-up').style.display = 'none';
14227			if(sortAlpha)sortAlpha.querySelector('.hap-btn-sort-alpha-down').style.display = 'block';
14228		
14229		}
14230		else if(v == 'title-desc'){
14231
14232			HAPUtils.keysrt2(playlistDataArr, 'data', 'title', true);
14233
14234			var i, arr = [];
14235			for(i = 0;i < playlistLength; i++){
14236				arr.push(playlistDataArr[i].id);
14237			}
14238
14239			if(sortAlpha)sortAlpha.querySelector('.hap-btn-sort-alpha-up').style.display = 'block';
14240			if(sortAlpha)sortAlpha.querySelector('.hap-btn-sort-alpha-down').style.display = 'none';
14241
14242		}
14243		else if(v == 'random'){
14244
14245			var arr = HAPUtils.randomiseArray(playlistLength);
14246
14247			playlistDataArr = HAPUtils.sortArray(playlistDataArr, arr);
14248
14249		}
14250		else{
14251			console.log('Unknown sort order. Sort method failed.');
14252			return;
14253		}
14254
14255		sortOrder = v;
14256
14257		var current_counter = mediaCounter, orderedItems;
14258
14259		//reposition data
14260
14261		let modifiedArr = arr.map(function(v){
14262		    return items[v];
14263		});
14264
14265		modifiedArr.forEach(function(item) {
14266		    playlistContent.append(item)
14267		});
14268		
14269		//update counter
14270		if(current_counter!=-1){
14271
14272			//find new position of active item
14273
14274			var el = playlistContent.querySelector(".hap-playlist-item[data-id='" + current_counter + "']")
14275			var new_counter = [...playlistContent.childNodes].indexOf(el);
14276			
14277			_HAPPlaylistManager.reSetCounter(new_counter);
14278
14279		}
14280
14281		updatePlaylist(true);
14282
14283	}
14284
14285	this.destroyInstance = function(){
14286		destroyPlaylist();
14287		cleanMedia();
14288		if(settings.usePlaylistScroll){
14289
14290			if(settings.playlistScrollType == 'perfect-scrollbar'){
14291				if(playlistScroll){
14292					playlistScroll.destroy();
14293					playlistScroll = null; 
14294				}
14295			}
14296			
14297			playlistScrollInit = false;
14298		}
14299
14300		if(adContent){
14301			adContent.remove();
14302			adContent = null;
14303		}
14304
14305		
14306	}
14307	this.destroyInstance2 = function(){
14308		cleanMedia();
14309
14310		playlistContent.innerHTML = "";
14311
14312		if(settings.usePlaylistScroll){
14313
14314			if(settings.playlistScrollType == 'perfect-scrollbar'){
14315				if(playlistScroll){
14316					playlistScroll.destroy();
14317					playlistScroll = null; 
14318				}
14319			}
14320
14321			playlistScrollInit = false;
14322		}
14323		if(settings.sortableTracks && settings.sortableTracksSet){
14324			if(Sortable)Sortable.destroy();//because of popup
14325			settings.sortableTracksSet = false;
14326		}
14327
14328		/*if(adContent){
14329			adContent.remove();
14330			adContent = null;
14331		}*/
14332	}
14333	this.destroyMedia = function() {
14334		if(!componentInited) return false;
14335		destroyMedia();
14336	}
14337	this.destroyPlaylist = function() {
14338		if(!componentInited) return false;
14339		destroyPlaylist();
14340	}
14341	this.isCrossFade = function(){
14342		return crossFadeOn;
14343	}
14344	this.setPlaybackRate = function(v){
14345		if(!componentInited) return false;
14346		if(!mediaType) return false;
14347
14348		settings.playbackRate = v;
14349		if(_HAPPlaybackRateSlider)_HAPPlaybackRateSlider.setVisual(v);
14350
14351		if(mediaType == 'youtube'){
14352			youtubePlayer.setPlaybackRate(Number(v));
14353		}else{
14354			if(activeAudio){
14355				try{
14356					activeAudio.playbackRate = Number(v);
14357				}catch(er){}
14358			}
14359		}
14360		if(videoSync){
14361			try{
14362				videoSync.playbackRate = Number(v);
14363			}catch(er){}
14364		}
14365		
14366	}
14367	this.toggleRandom = function(v) {
14368		if(!componentInited) return false;
14369		if(typeof _HAPPlaylistManager === 'undefined') return false;
14370		if(typeof v !== 'undefined')settings.randomPlay=v;
14371		else settings.randomPlay=!settings.randomPlay;
14372		_HAPPlaylistManager.setRandom(settings.randomPlay);
14373
14374		if(randomToggle){
14375			randomToggle.querySelectorAll('.hap-btn').forEach(function(el){
14376				el.style.display = 'none';
14377			})
14378
14379			if(settings.randomPlay)randomToggle.querySelector('.hap-btn-random-on').style.display = 'block';
14380			else randomToggle.querySelector('.hap-btn-random-off').style.display = 'block';
14381		}
14382
14383		if(_HAPStickyPlayer)_HAPStickyPlayer.setRandom(settings.randomPlay)
14384
14385	}
14386	this.setLoop = function(v) {
14387		if(!componentInited) return false;
14388		if(typeof _HAPPlaylistManager === 'undefined') return false;
14389
14390		settings.loopState = v;
14391
14392		if(loopToggle){
14393			loopToggle.querySelectorAll('.hap-btn').forEach(function(el){
14394				el.style.display = 'none';
14395			})
14396
14397			loopStateCounter = loopStateArr.indexOf(settings.loopState);
14398			loopToggle.querySelector('.hap-btn-loop-'+settings.loopState).style.display = 'block';
14399
14400		}
14401
14402		_HAPPlaylistManager.setLooping(settings.loopState);
14403
14404		if(_HAPStickyPlayer)_HAPStickyPlayer.setLoop(settings.loopState)
14405	}
14406	this.getVolume = function() {
14407		return settings.volume;
14408	}
14409	this.setVolume = function(val) {
14410		if(!componentInited) return false;
14411
14412		if(val<0) val=0;
14413		else if(val>1) val=1;
14414
14415		settings.volume = val;
14416
14417		if(_HAPAdManager)_HAPAdManager.setVolume(settings.volume)
14418
14419		if(isIma && isImaOn)_IMALoader.setVolume(settings.volume)
14420
14421		if(mediaType == 'youtube'){
14422			if(youtubePlayer && youtubeReady){
14423				youtubePlayer.setVolume(settings.volume * 100);
14424			}
14425		}else{
14426			if(activeAudio){
14427				activeAudio.volume = settings.volume;
14428				if(settings.volume == 0)activeAudio.muted = true;
14429				else activeAudio.muted = false;
14430			}
14431		}
14432
14433		if(_volumeSlider)_volumeSlider.setVisual(settings.volume);
14434
14435		if(settings.useStickyPlayer && window['hap_fixed_active_player'] == settings.instanceName){
14436			_HAPStickyPlayer.setVisual(settings.volume)
14437		}
14438	}
14439	this.toggleMute = function(){
14440		if(!componentInited) return false;
14441
14442		if(settings.volume > 0){
14443			lastVolume = settings.volume;//remember last volume
14444			settings.volume = 0;
14445		}else{
14446			settings.volume = lastVolume;//restore last volume
14447		}
14448
14449		self.setVolume(settings.volume)
14450
14451	}
14452	this.setAutoPlay = function(value){
14453		autoPlay = value;
14454		settings.autoPlay = true;
14455	}
14456	this.seek = function(v){
14457		if(!componentInited) return false;
14458		if(!mediaType) return false;
14459
14460		if(settings.disableSeekbarInRange && rangeOn)return false;
14461		if(settings.disableSeekbar) return false;
14462		if(settings.disableSongSkip) return false;
14463
14464		if(mediaType == 'youtube'){
14465		    if(youtubePlayer && youtubeReady)youtubePlayer.seekTo(v);
14466		}else if(mediaType == 'audio'){
14467			if(activeAudio){
14468				try{
14469					activeAudio.currentTime = v;
14470				}catch(er){console.log(er)}
14471		    }
14472		}
14473	}
14474	this.seekBackward = function(val){
14475	    if(!componentInited) return false;
14476	    if(!mediaType) return false;
14477	    if(crossFadeOn)cancelCrossFadeSeek();
14478
14479	    var st = val || settings.seekTime
14480	    st = parseInt(st,10)
14481	    
14482	    if(mediaType == 'youtube'){
14483		    if(youtubePlayer && youtubeReady){
14484		    	var pos = Math.max(youtubePlayer.getCurrentTime() - st, 0);
14485		    	youtubePlayer.seekTo(pos);
14486		    }
14487		}else{
14488			if(activeAudio){
14489		    	try{
14490					activeAudio.currentTime = Math.max(activeAudio.currentTime - st, 0);
14491				}catch(er){console.log(er)}
14492		    }
14493		}
14494	}
14495	this.seekForward = function(val){
14496	    if(!componentInited) return false;
14497	    if(!mediaType) return false;
14498	    if(crossFadeOn)cancelCrossFadeSeek();
14499
14500	    if(settings.disableSeekbarInRange && rangeOn)return false;
14501		if(settings.disableSeekbar) return false;
14502		if(settings.disableSongSkip) return false;
14503
14504	    var st = val || settings.seekTime
14505	    st = parseInt(st,10)
14506	    
14507	    if(mediaType == 'youtube'){
14508	    	if(youtubePlayer && youtubeReady){
14509				var pos = Math.min(youtubePlayer.getCurrentTime() + st, youtubePlayer.getDuration());
14510			    youtubePlayer.seekTo(pos);	
14511			}
14512		}else{
14513			if(activeAudio){
14514		    	try{
14515					activeAudio.currentTime = Math.min(activeAudio.currentTime + st, activeAudio.duration);
14516				}catch(er){console.log(er)}
14517		    }
14518		}
14519	}
14520	this.getCurrentMediaUrl = function(){
14521		if(!componentInited) return false;
14522		if(!mediaType) return '';
14523
14524		var current_url = window.location.href, separator;//check if url contains other parameters 
14525
14526		//clear existing parameters from url
14527		if(current_url.indexOf('hap-') > -1){
14528			var params = current_url.substring(current_url.indexOf('?'));
14529            params = params.split('&');
14530            var i, len = params.length;
14531            for(i = len - 1; i > -1; i--){
14532                if(params[i].indexOf('hap-') > -1){
14533                    params.splice(i, 1);
14534                }
14535            }
14536
14537            var base_url = current_url.substring(0, current_url.indexOf('?')+1);//get with ?
14538            if(params.length){//append rest params which are not ours
14539                var i, len = params.length, p;
14540                for(i = 0; i < len; i++){
14541                    p = params[i];
14542                    if(p.charAt(0) == '?'){
14543                        base_url += p.substring(1);//remove ?
14544                    }else{
14545                        if(i > 0){
14546                            base_url += '&'+p;
14547                        }else{
14548                            base_url += p;//after ? 
14549                        }
14550                    }
14551                }
14552            }
14553
14554            current_url = base_url;
14555		}
14556
14557		if(current_url.indexOf('?') == -1){
14558			separator = '?'
14559		}else{
14560			if(current_url.charAt(current_url.length-1) == '?' || current_url.charAt(current_url.length-1) == '&')separator = '';	
14561			else separator = '&';	
14562		} 
14563
14564
14565		var instance = 'hap-query-instance='+encodeURIComponent(settings.instanceName);	//differentaite multiple instances
14566		var scroll = '&hap-scroll-to-player=1';	//scroll to player on start	
14567
14568		//share url to specific media
14569		if(currMediaData.mediaId != undefined)var activeItem = '&hap-media-id='+currMediaData.mediaId;
14570		else var activeItem = '&hap-active-item='+mediaCounter;
14571		var start = mediaStarted ? '&hap-resume-time='+Math.floor(self.getCurrentTime()) : '';
14572
14573		var autoplay = '&hap-auto-play=' + (settings.autoPlay ? '1' : '0');
14574
14575		var share_url = separator + instance + scroll + activeItem + start + autoplay;
14576
14577		if(currMediaData.mediaId != undefined && singleMediaSourcesArr.indexOf(currMediaData.origtype) == -1 && currMediaData.safeTitle){//add title if we have same media-id
14578			var title = '&hap-media-title=' + encodeURIComponent(currMediaData.safeTitle);
14579			share_url += title;
14580		}
14581
14582		return current_url + share_url;
14583		
14584	}
14585	this.getBUrl = function(url){
14586
14587		bUrl = null;
14588
14589		if(burl_request){
14590			burl_request.abort();
14591			burl_request = null;
14592		}
14593
14594		burl_request = new XMLHttpRequest();
14595		burl_request.onreadystatechange = function() {
14596			if (burl_request.readyState == 4) {
14597
14598			    var reader = new FileReader();
14599			    reader.readAsArrayBuffer(burl_request.response);
14600			    reader.onload = function(e){
14601
14602			        var contents = e.target.result;
14603			        var uint8Array  = new Uint8Array(contents);
14604					var arrayBuffer = uint8Array.buffer;
14605					if(isIOS || isSafari){
14606						var blob = new Blob([arrayBuffer], {type: "audio/mpeg" });
14607					}else{
14608						var blob = new Blob([arrayBuffer]);
14609					}
14610
14611			        bUrl = URL.createObjectURL(blob);
14612
14613		    	}
14614		    }
14615		};
14616		burl_request.responseType = 'blob';
14617		burl_request.open('GET', url, true);
14618		burl_request.send();
14619
14620	}
14621	this.getCurrentTime = function(){
14622		if(mediaType == 'youtube'){
14623			if(youtubePlayer && youtubeReady)return youtubePlayer.getCurrentTime();
14624			else return 0;
14625		}else{
14626			if(activeAudio)return activeAudio.currentTime;
14627			else return 0;
14628		}
14629	}
14630	this.getDuration = function(){
14631		if(!componentInited) return false;
14632		if(!mediaType) return false;
14633		
14634		if(mediaType == 'youtube'){
14635			if(youtubePlayer && youtubeReady)return youtubePlayer.getDuration();
14636		}else{
14637			if(activeAudio)return activeAudio.duration;
14638		}
14639	}
14640	this.setSortable = function(){
14641		if(!componentInited) return false;
14642		if(settings.sortableTracks && !settings.sortableTracksSet)setSortableTracks();
14643	}
14644
14645	this.initPlaylistScroll = function(){
14646		if(!componentInited) return false;
14647		if(settings.usePlaylistScroll){
14648			
14649			if(settings.playlistScrollType == 'perfect-scrollbar' && !playlistScrollInit){
14650				if(playlistInner)checkPlaylistScroll(playlistInner);
14651				playlistScrollInit = true;
14652			}
14653
14654		}
14655	}
14656	this.destroyPlaylistScroll = function(){
14657		if(!componentInited) return false;
14658		if(settings.usePlaylistScroll){
14659			
14660			if(settings.playlistScrollType == 'perfect-scrollbar'){
14661				if(playlistScroll){
14662					playlistScroll.destroy();
14663					playlistScroll = null; 
14664				}
14665			}
14666
14667			playlistScrollInit = false;
14668		}
14669	}
14670	this.getSetupDone = function(){
14671		return componentInited;
14672	}
14673	this.getMediaPlaying = function(){
14674		if(!componentInited) return false;
14675		return mediaPlaying;
14676	}
14677	this.getPlaylistLoading = function(){
14678		return playlistTransitionOn;
14679	}
14680	this.getCounter = function(){
14681		if(componentInited) return typeof _HAPPlaylistManager !== 'undefined' ? _HAPPlaylistManager.getCounter() : -1;
14682		else return null;
14683	}
14684	this.getPlaylistContent = function(){
14685		return playlistContent;	
14686	}
14687	this.getPlaylistData = function(){
14688		return playlistDataArr;	
14689	}
14690	this.getLastPlaylistData = function(){
14691		if(!componentInited) return false;
14692		return lastPlaylistDataArr;
14693	}
14694	this.getPlaylistLength = function(){
14695		if(!componentInited) return false;
14696		return HAPUtils.isNumber(playlistLength) ? playlistLength : 0;
14697	}
14698	this.getPlaylistList = function(){
14699		return playlistList;
14700	}
14701	this.getSettings = function(){
14702		return settings;
14703	}
14704	this.getCurrMediaData = function(){
14705		if(!componentInited) return false;
14706		return currMediaData;	
14707	}
14708	this.getWrapper = function(){
14709		return wrapper;
14710	}
14711	this.getPlaylistItems = function(){
14712		if(!componentInited) return false;
14713		var arr = [];
14714		playlistContent.querySelectorAll('.hap-playlist-item').forEach(function(el){
14715			arr.push(el);
14716		});
14717		return arr;
14718	}
14719	this.openPopup = function(){
14720		if(!componentInited) return false;
14721		if(settings.isPopup) return false;
14722		hapOpenPopup(settings, self);
14723	}
14724	this.togglePlaylist = function(){
14725
14726		//preset mods
14727
14728		if(class_list.indexOf('hap-art-narrow-light') > -1 || class_list.indexOf('hap-art-narrow-dark') > -1){
14729
14730            if(!settings.playlistOpened){
14731            	playerHolder.classList.add('hap-player-holder-closed')
14732            	playlistHolder.classList.add('hap-playlist-holder-opened')
14733            }else{
14734                playerHolder.classList.remove('hap-player-holder-closed')
14735            	playlistHolder.classList.remove('hap-playlist-holder-opened')
14736            }   
14737
14738        }else if(class_list.indexOf('hap-art-wide-light') > -1 || class_list.indexOf('hap-art-wide-dark') > -1 || class_list.indexOf('hap-brona-light') > -1 || class_list.indexOf('hap-brona-dark') > -1){
14739     
14740            if(!settings.playlistOpened){
14741
14742            	playlistHolder.addEventListener('transitionend', function cb(e){
14743					e.currentTarget.removeEventListener(e.type, cb);
14744					
14745					playlistHolder.style.height = 'auto'
14746				})
14747
14748				playlistHolder.classList.remove('hap-playlist-closed-v')
14749               
14750            }else{
14751            	
14752        		var ph = parseInt(playlistHolder.clientHeight,10)
14753            	playlistHolder.style.height = ph+'px'
14754
14755            	setTimeout(function(){
14756            		playlistHolder.classList.add('hap-playlist-closed-v')
14757            	},100)
14758
14759            }   
14760
14761        }else if(class_list.indexOf('hap-fixed') > -1){
14762
14763            if(settings.playlistOpened){
14764                wrapper.style.bottom = -playlistHolder.offsetHeight+'px'; 
14765            }else{
14766                wrapper.style.bottom = -1+'px'; 
14767            }  
14768
14769        }else if(class_list.indexOf('hap-fixed2') > -1){
14770
14771            if(settings.playlistOpened){
14772                playlistHolder.style.bottom = -playlistHolder.offsetHeight+'px'; 
14773
14774                wrapper.querySelector('.hap-ex-controls')?.classList.add('hap-ex-controls-shown')
14775
14776            }else{
14777                playlistHolder.style.bottom = playerHolder.offsetHeight+'px';
14778
14779        		wrapper.querySelector('.hap-ex-controls')?.classList.remove('hap-ex-controls-shown')
14780 
14781            }  
14782
14783        }
14784
14785        settings.playlistOpened = !settings.playlistOpened;
14786
14787        if(settings.playlistOpened){
14788        	wrapper.classList.add('hap-is-playlist-opened')
14789        }else{
14790        	wrapper.classList.remove('hap-is-playlist-opened')
14791        }
14792
14793    }
14794
14795    function unhidePlayer(){
14796
14797		if(settings.hidePlayerUntilMusicStart){
14798			settings.hidePlayerUntilMusicStart = false;
14799			if(playerOuter)playerOuter.classList.remove('hap-music-player-force-hidden');
14800			doneResizing();
14801			checkPresetMods();
14802
14803			if(playerOuter){
14804				setTimeout(function(){
14805					playerOuter.style.opacity = 1;
14806				},50);
14807			}
14808			
14809		}
14810	}
14811
14812
14813	
14814
14815
14816
14817
14818
14819
14820
14821	//############################################//
14822	/* remember playback position */
14823	//############################################//
14824
14825	if(hasLocalStorage){	
14826		if(settings.continousKey){
14827			if(!useContinousPlayback){
14828				localStorage.removeItem(settings.continousKey);
14829			}else{
14830				if(localStorage.getItem(settings.continousKey)){
14831
14832					var d = JSON.parse(localStorage.getItem(settings.continousKey));
14833				
14834					settings.activePlaylist = d.activePlaylist,
14835					settings.resumeTime = d.resumeTime;
14836					settings.volume = d.volume;
14837					settings.activeItem = d.activeItem;
14838					settings.autoPlay = d.autoPlay;
14839					autoPlay = d.autoPlay;
14840					settings.lastPositionArr = d.lastPositionArr;
14841
14842					localStorage.removeItem(settings.continousKey);
14843				}
14844			}
14845		}
14846	}
14847
14848
14849
14850	//############################################//
14851	/* load playlist */
14852	//############################################//
14853
14854	setTimeout(function(){
14855
14856		if(settings.isPopup && settings.copyCurrentPlaylistToPopup){//open in popup and copy playlist from parent
14857
14858			/*playlistDataArr = settings.playlistDataArr;//get playlist data from settings
14859			playlistLength = playlistDataArr.length;
14860			_HAPPlaylistManager.setPlaylistItems(playlistLength, false);
14861
14862			if(settings.useStatistics){
14863				if(!settings.usePlaylist){
14864					var i; 
14865					for(i = 0;i < playlistLength; i++){
14866						playlistDataArr[i].data.hapStatsSet = false;
14867					}
14868				}
14869				updateStats();
14870			}
14871
14872			lastPlaylist = playlistContent;
14873
14874			endInit();*/
14875
14876		}else{
14877
14878			if(queryPlaylist.length){//proces playlist from query params
14879
14880				playlistTransitionOn = true;
14881				preloader.style.display = 'block';
14882
14883				self.fireEvent('playlistStartLoad', [{instance:self, instanceName:settings.instanceName}]);
14884
14885				playlistProcessDataUrl = queryPlaylist;
14886				playlistLength = playlistProcessDataUrl.length;
14887				checkProcessCounter();
14888
14889			}else if(settings.activePlaylist && !HAPUtils.isEmpty(settings.activePlaylist)){
14890
14891				setPlaylist(settings.activePlaylist);//process playlist
14892
14893			}else{
14894				endInit();
14895			}
14896
14897		}
14898
14899	},50);
14900
14901
14902	this.initPlaylist = function(){//from popup closed
14903		
14904		if(queryPlaylist.length){//proces playlist from query params
14905
14906			playlistTransitionOn = true;
14907			preloader.style.display = 'block';
14908
14909			self.fireEvent('playlistStartLoad', [{instance:self, instanceName:settings.instanceName}]);
14910
14911			playlistProcessDataUrl = queryPlaylist;
14912			playlistLength = playlistProcessDataUrl.length;
14913			checkProcessCounter();
14914
14915		}else if(settings.activePlaylist && !HAPUtils.isEmpty(settings.activePlaylist)){
14916
14917			setPlaylist(settings.activePlaylist);//process playlist
14918
14919		}else{
14920			endInit();
14921		}
14922
14923	}
14924
14925	return this;
14926
14927	}
14928
14929	window.hap = hap;
14930	
14931})();
14932
14933
14934
14935
14936
14937
14938
14939
14940//############################################//
14941/* popup */
14942//############################################//
14943
14944var hap_popup_window, hap_player_instance, hap_player_auto_instance, hasLocalStorage = HAPUtils.hasLocalStorage();  
14945
14946function hapOpenPopup(settings, inst){
14947
14948	if(window.location.protocol == 'file:'){
14949		alert('Using popup requires server connection.');
14950		return false;
14951	}
14952
14953	if(hasLocalStorage && localStorage.getItem('hap_popup')){
14954		return;
14955	}
14956
14957	hap_player_instance = inst;
14958
14959	if(hap_player_instance){
14960		var wrapper = hap_player_instance.getWrapper();
14961	}else if(hap_player_auto_instance){
14962		var wrapper = hap_player_auto_instance.wrapper;
14963	}
14964
14965    var popupUrl = settings.popupUrl || settings.sourcePath + 'popup.html'+"?rand=" + (Math.random() * 99999999), 
14966    pw = settings.popupWidth ? settings.popupWidth : wrapper.offsetWidth, 
14967    ph = settings.popupHeight ? settings.popupHeight : wrapper.offsetHeight, 
14968    left = (window.screen.width - parseInt(pw,10)) / 2, 
14969    top = (window.screen.height - parseInt(ph,10)) / 2;
14970   
14971    if(!hap_popup_window || hap_popup_window.closed){
14972        hap_popup_window = window.open(popupUrl,'audio_player','menubar=no,toolbar=no,location=no,scrollbars=1,resizable,width='+pw+',height='+ph+',left='+left+',top='+top+'');
14973
14974        if(!hap_popup_window) {
14975            alert("Player can not be opened in a popup window because your browser is blocking Pop-Ups. You need to allow Pop-Ups in browser for this site to use the Player.");
14976            return false;
14977        }
14978       
14979    }
14980}
14981
14982function hapPopupClosed(id, active_item){
14983	if(document.getElementById(id)){
14984		var wrapper = document.getElementById(id)
14985        wrapper.style.display = 'block';
14986		setTimeout(function(){
14987            wrapper.querySelector('.hap-player-outer').style.opacity = 1;
14988        },100)
14989		if(hap_player_instance){
14990
14991			hap_player_instance.getSettings().activeItem = active_item;
14992
14993			hap_player_instance.initPlaylist()
14994
14995			hap_player_instance.initPlaylistScroll();
14996			hap_player_instance.setSortable()
14997		}else{
14998			//on auto open popup
14999			if(hap_player_auto_instance.settings)new hap(wrapper, hap_player_auto_instance.settings);
15000			else wrapper.style.display = 'none';
15001		}
15002	}
15003}
15004
15005function hapNotifyParent(){//called from popup window when popup window has opened!
15006
15007    if(hap_popup_window && hap_popup_window.initPopup != undefined){
15008
15009    	/*
15010    	NOTES:
15011    	new+copies whole playlist list and loads active playlist again in player
15012
15013		old-This copies current playlist from playlist content to popup window (if load more, add more was used etc..)
15014		actions:
15015		add more - works
15016		load more (needs token from before, currently not implemented)
15017
15018		- accordion in popup has to reload
15019    	*/
15020
15021    	if(hap_player_instance){
15022
15023	        var settings = hap_player_instance.getSettings();
15024
15025	        settings.volume = hap_player_instance.getVolume();
15026	        settings.activePlaylist = settings.activePlaylist;
15027	        settings.activeItem = hap_player_instance.getCounter();
15028	        settings.resumeTime = hap_player_instance.getCurrentTime();
15029
15030	        hap_player_instance.destroyInstance2();
15031
15032	        var wrapper = hap_player_instance.getWrapper();
15033
15034	    }else if(hap_player_auto_instance){
15035
15036	    	var settings = hap_player_auto_instance.settings;
15037	    	settings.copyCurrentPlaylistToPopup = false;
15038	    	var wrapper = hap_player_auto_instance.wrapper;
15039
15040	    }
15041
15042        var wrapper_id = wrapper.getAttribute('id'),
15043		css = document.getElementById('hap-inline-css') ? document.getElementById('hap-inline-css').outerHTML : null,
15044		playlist_list = document.getElementById('hap-playlist-list') ? document.getElementById('hap-playlist-list').outerHTML : null;
15045		wrapper2 = wrapper.outerHTML;
15046		wrapper.style.display = 'none';
15047		wrapper.querySelector('.hap-player-outer').style.opacity = 0;
15048
15049        try {
15050            window[settings.instanceName] = hap_popup_window.initPopup(wrapper2, wrapper_id, settings, css, playlist_list);
15051        }catch(e){
15052            alert('initPopup error: ' + e.message);
15053            return false;
15054        }
15055
15056    }
15057}
15058
15059

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.