PageSourceSearch

https://www.grrif.ch/wp-content/themes/grrif/js/kast.js?ver=20170320

js grrif.ch collected 2026-09-24 09:44:40 UTC 87,872 bytes, 2,177 lines download raw bytes

1/*!
2 * Kast - Extraordinary SHOUTcast HTML5 Radio Player for jQuery - Material Design
3 * Copyrights: 2015-2016 Manar Kamel (manarkamel.github.io)
4 */
5
6(function ($, window, document, undefined) {
7
8    // Plugin defaults
9    // ======================================================== //
10    var defaults = {
11        host: '', // Server IP or domain name  @string
12        port: 80, // Server port @integer
13        protocol: 'http', // Server protocol @string
14        version: 2, // SHOUTcast Version @integer
15        sid: 1, // Server stream ID (If you have multiple SHOUTcast V2 stations) @integer
16        statsPath: 'stats', // Path to stats @string
17        playedPath: 'played', // Path to played @string
18        directStreamURL: false, // Add custom direct audio stream URL
19        ui: 'colored', // Transparent or colored and kast-nowplaying @string
20        theme: 'light', // Dark, light or dynamic: based on the current album artwork @string
21        colors: {
22            primary: 'cyan', // Material Design primary color - Material name @string
23            accent: 'yellow' // Material Design accent color - Material name @string
24        }, // set fixed colors or set 'dynamic': based on the current album artwork @object
25        startTemplate: 'maximized', // Start minimized or maximized on load @string
26        position: 'right', // Player position (Bottom right or left) @string
27        container: 'body', // Player container element, class or ID. everything other than 'body' isn't fixed @string
28        autoPlay: false, // Autoplay radio on load @boolean
29        autoUpdate: true, // Autoupdate (Boolean or string: 'all') (current info / Played info / album artwork) @boolean @string
30        artwork: true, // Pull album artwork (or artist artwork as a fallback) from Spotify API or set a custom one @boolean @array
31        statusBar: true, // Show status bar at the top @boolean
32        minimizeMaximize: true, // Show minimize/maximize button in status bar @boolean
33        muteUnmute: true, // Show mute/unmute button in status bar
34        startMuted: false, // Start audio muted on load @boolean
35        serverInfo: false, // Show serverInfo in status bar or set custom info @string @array @false
36        played: true, // Show played tracks/artists @boolean
37        currentTrack: true, // Show current track name, or set a custom track name @boolean @string
38        currentArtist: true, // Show the current artist name, or set a custom artist name @boolean @string
39        playedTracks: true, // Show the played track names @boolean
40        playedArtists: true, // Show the played artist names @boolean
41        offlineCheck: true, // On offline, show offline status @boolean
42        language: {
43            offlineText: 'Temporarily Offline', // On offline: status bar text @string
44            playedText: 'Played', // Played title (player bottom) @string
45            unknownTrackText: 'Unknown Track', // Set unknown track text when error
46            unknownArtistText: 'Unknown Artist' // Set unknown artist text when error
47        }, // Set text (Multi language support) @object
48        mobileCare: true, // low (aka true), medium, high, very high, ultra @boolean @array
49        irrelevantWords: ['feat.', 'ft.', 'Feat.', 'Ft.'], // irrelvant words in SHOUTcast full song title @false or @array
50        skipInfo: false, // Skip info (metadata) @false or @array of objects {track,artist}
51        defaultArtwork: false, // Set default album artwork shows when artwork not found. @false @array
52        customCheckerInterval: false, // Set custom stats checker interval @false @integer
53        popup: false, // Enable popup player @boolean 
54        popupCSSLink: '', // set Kast CSS file for popup @string
55        popupWidth: 380, // Set popup width @integer
56        popupHeight: 570, // Set popup height @integer
57        overHTTPS: false, // Run SHOUTcast stream over an HTTPS proxy for HTTPS websites (experimental) (false) @boolean
58        direct: false, // Fetch SHOUTcast metadata direclty from the servers (Supported from >=2.2 DNAS via JSONP) (Note: non compatible with mixed content)
59        betaProxies: false, // Use Beta Proxies to fetch SHOUTcast metadata
60        continuous: true, // Enable continuous play (Play/Pause radio)
61		isMedia:false,
62        onReady: function () {}, // Kast event
63        onAudioLoad: function () {}, // Kast event
64        onMobile: function () {}, // Kast event
65        onPlay: function () {}, // Kast event
66        onPause: function () {}, // Kast event
67        onStop: function () {}, // Kast event
68        onMinimize: function () {}, // Kast event
69        onMaximize: function () {}, // Kast event
70        onMute: function () {}, // Kast event
71        onUnmute: function () {}, // Kast event
72        onUpdate: function () {}, // Kast event
73        onUpdateAll: function () {}, // Kast event
74        onOffline: function () {}, // Kast event
75        onOnline: function () {}, // Kast event
76        onCurrentArtwork: function () {}, // Kast event
77        onPlayedArtworks: function () {}, // Kast event
78        onCurrentInfo: function () {}, // Kast event
79        onPlayedInfo: function () {}, // Kast event
80        onDynamicColors: function () {}, // Kast event
81        onDynamicColorsContrast: function () {}, // Kast event
82        onDynamicTheme: function () {} // Kast event
83    };
84
85    // Plugin constructor
86    // ======================================================== //
87    function Plugin(element, options) {
88
89        this.opt = $.extend({}, defaults, options);
90
91        // -- Validation
92        var HTML5Audio = function () {
93            var a = new Audio();
94            return !!((a.canPlayType) && (a.canPlayType('audio/mpeg;').replace(/^no$/, '') || a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '') || a.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, '') || a.canPlayType('audio/aac;').replace(/^no$/, '')));
95        }
96        if (!this.opt.host || !HTML5Audio || !window.DOMParser || !document.implementation.createHTMLDocument) { // Essential features for Kast 
97            $.data(window, 'plugin_kast', null)
98
99            throw Error("Kast couldn't load")
100        }
101
102        // -- Extend extra objects
103        if (options.language) {
104            this.opt.language = $.extend({}, defaults.language, options.language);
105        }
106        if (typeof options.colors === 'object') {
107            this.opt.colors = $.extend({}, defaults.colors, options.colors);
108        }
109
110        // -- Options correction (idiot-proof)
111        if (this.opt.startTemplate === 'minimized' && (!this.opt.statusBar || !this.opt.minimizeMaximize)) {
112            this.opt.played = false;
113        }
114
115
116        var iot = this.opt;
117
118        // -- Protocol
119        var protocol = (document.location.protocol === 'https:') ? 'https' : 'http'; // Document not window for wider browser support
120
121        // -- Protocol correction
122        // ** Force protocol 'https' to be compatiable with Direct metadata,
123        // ** if SHOUTcast doesn't support HTTPS protocol and client website was secured,
124        // ** then direct metadata won't work (mixed content).
125        // ** anyway i'm forcing HTTPS protocol, if user didn't set it
126        if (iot.direct && protocol === 'https') {
127            iot.protocol = protocol
128        }
129
130        // -- Host
131        //this.host = iot.protocol + '://' + iot.host + ':' + iot.port + '/';
132		//this.host = iot.protocol + '://' + iot.host;
133		this.host = iot.host;
134		
135        this.hostCORS = [
136            protocol + '://crossorigin.me/' + this.host,
137            'https://cors-anywhere.herokuapp.com/' + this.host,
138            protocol + '://kastproxy-us.herokuapp.com/' + this.host,
139            protocol + '://kastproxy-eu.herokuapp.com/' + this.host
140        ];
141
142        //this.hostCORS = [
143        //    protocol + '://kastdemo-us.herokuapp.com/' + this.host,
144        //    protocol + '://kastdemo-eu.herokuapp.com/' + this.host
145        //];
146
147        if (iot.overHTTPS && protocol === 'https:') {
148            this.host = this.hostCORS[0];
149        }
150
151
152        // -- Audio
153        var sc2sid = iot.sid,
154            //audioSrc = iot.directStreamURL || ((sc2sid > 1) ? this.host + 'stream/' + sc2sid + '/;' : this.host + ';');
155			audioSrc = iot.directStreamURL || ((sc2sid > 1) ? this.host + 'stream/' + sc2sid : this.host);
156			
157        this.audio = document.createElement('audio');
158        //this.audio.src = audioSrc + '?_=' + Math.random();
159		this.audio.src = audioSrc;
160		this.audio.id  = 'AudioPlayer';
161		//if(this.opt.isMedia == true) {
162			this.audio.preload = 'none';	
163		//}
164        this.audio.load();
165
166        // -- Event: on audio load
167        iot.onAudioLoad(this.audio)
168
169        // -- Mobile Care
170        this.mobile = false;
171        this.mobileMedium = false;
172        //this.mobileHigh = false;
173        //this.mobileVeryHigh = false;
174        this.mobileUltra = false;
175        var mC = iot.mobileCare,
176            mCMW = mC[1] || '599px';
177
178
179        if (mC && window.matchMedia && window.matchMedia('(max-width: ' + mCMW + ')').matches) {
180            this.mobile = true;
181
182            iot.autoPlay = false;
183            iot.startTemplate = 'minimized';
184            iot.offlineCheck = false;
185
186            // -- Inside plugin
187            // Checker Interval 12s
188
189            if (mC[0] === 'medium' || mC[0] === 'high' || mC[0] === 'very high' || mC[0] === 'ultra') {
190                this.mobileMedium = true;
191                if (iot.autoUpdate === 'all') {
192                    iot.autoUpdate = true
193                }
194                if (iot.theme === 'dynamic') {
195                    iot.theme = 'light'
196                }
197                if (iot.colors === 'dynamic') {
198                    iot.colors = false;
199                }
200                // -- Inside plugin
201                // Medium quality artwork images
202                // Checker Interval 16s
203            }
204            if (mC[0] === 'high' || mC[0] === 'very high' || mC[0] === 'ultra') {
205                iot.artwork = iot.played = false;
206            }
207            if (mC[0] === 'very high' || mC[0] === 'ultra') {
208                iot.autoUpdate = iot.minimizeMaximize = false;
209            }
210            if (mC[0] === 'ultra') {
211                this.mobileUltra = true;
212                iot.statusBar = iot.serverInfo = iot.currentArtist = iot.currentTrack = false;
213            }
214
215            iot.onMobile() // Event
216        }
217
218
219        // -- Initialize
220        this.init();
221
222
223    }
224	
225	// Return Hour Seconde
226    // ======================================================== //
227	var getDurationHours = function(totalSeconds) {
228		totalSeconds = Math.round(totalSeconds);
229		hours 		 = Math.floor(totalSeconds / 3600);
230		totalSeconds %= 3600;
231		minutes 	 = Math.floor(totalSeconds / 60);
232		seconds 	 = totalSeconds % 60;
233		return (hours > 0) ? hours + ':' + minutes + ':' + seconds : minutes + ':' + seconds;
234	};
235		
236    // Plugin dependencies, helpers and feature detection
237    // ======================================================== //
238    /*
239     * Color Thief v2.0 - Customized for Kast needs only
240     * by Lokesh Dhakar - http://www.lokeshdhakar.com
241     *
242     * Thanks
243     * ------
244     * Nick Rabinowitz - For creating quantize.js.
245     * John Schulz - For clean up and optimization. @JFSIII
246     * Nathan Spady - For adding drag and drop support to the demo page.
247     *
248     * License
249     * -------
250     * Copyright 2011, 2015 Lokesh Dhakar
251     * Released under the MIT license
252     * https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
253     *
254     * Note: Lokesh's comments are removed here
255     */
256
257    var CanvasImage = function (image) {
258            this.canvas = document.createElement('canvas');
259            this.context = this.canvas.getContext('2d');
260
261            document.body.appendChild(this.canvas);
262
263            this.width = this.canvas.width = image.width;
264            this.height = this.canvas.height = image.height;
265
266            this.context.drawImage(image, 0, 0, this.width, this.height);
267        },
268        ColorThief = function () {};
269
270    CanvasImage.prototype = {
271        clear: function () {
272            this.context.clearRect(0, 0, this.width, this.height);
273        },
274        update: function (imageData) {
275            this.context.putImageData(imageData, 0, 0);
276        },
277        getPixelCount: function () {
278            return this.width * this.height;
279        },
280        getImageData: function () {
281            return this.context.getImageData(0, 0, this.width, this.height);
282        },
283        removeCanvas: function () {
284            this.canvas.parentNode.removeChild(this.canvas);
285        }
286    }
287    ColorThief.prototype.getPalette = function (sourceImage, colorCount) {
288
289        // Create custom CanvasImage object
290        var image = new CanvasImage(sourceImage);
291        var imageData = image.getImageData();
292        var pixels = imageData.data;
293        var pixelCount = image.getPixelCount();
294
295        // Store the RGB values in an array format suitable for quantize function
296        var pixelArray = [];
297        for (var i = 0, offset, r, g, b, a; i < pixelCount; i = i + 10) { // ** 10 is quality
298            offset = i * 4;
299            r = pixels[offset + 0];
300            g = pixels[offset + 1];
301            b = pixels[offset + 2];
302            a = pixels[offset + 3];
303            // If pixel is mostly opaque and not white
304            if (a >= 125) {
305                if (!(r > 250 && g > 250 && b > 250)) {
306                    pixelArray.push([r, g, b]);
307                }
308            }
309        }
310
311        // Send array to quantize function which clusters values
312        // using median cut algorithm
313        var cmap = MMCQ.quantize(pixelArray, colorCount);
314        var palette = cmap ? cmap.palette() : null;
315
316        // Clean up
317        image.removeCanvas();
318
319        return palette;
320    };
321
322    if (!pv) {
323        var pv = {
324            map: function (array, f) {
325                var o = {};
326                return f ? array.map(function (d, i) {
327                    o.index = i;
328                    return f.call(o, d);
329                }) : array.slice();
330            },
331            naturalOrder: function (a, b) {
332                return (a < b) ? -1 : ((a > b) ? 1 : 0);
333            },
334            sum: function (array, f) {
335                var o = {};
336                return array.reduce(f ? function (p, d, i) {
337                    o.index = i;
338                    return p + f.call(o, d);
339                } : function (p, d) {
340                    return p + d;
341                }, 0);
342            },
343            max: function (array, f) {
344                return Math.max.apply(null, f ? pv.map(array, f) : array);
345            }
346        };
347    }
348    var MMCQ = (function () {
349        // private constants
350        var sigbits = 5,
351            rshift = 8 - sigbits,
352            maxIterations = 1000,
353            fractByPopulations = 0.75;
354
355        // get reduced-space color index for a pixel
356        function getColorIndex(r, g, b) {
357            return (r << (2 * sigbits)) + (g << sigbits) + b;
358        }
359
360        // Simple priority queue
361        function PQueue(comparator) {
362            var contents = [],
363                sorted = false;
364
365            function sort() {
366                contents.sort(comparator);
367                sorted = true;
368            }
369
370            return {
371                push: function (o) {
372                    contents.push(o);
373                    sorted = false;
374                },
375                peek: function (index) {
376                    if (!sorted) sort();
377                    if (index === undefined) index = contents.length - 1;
378                    return contents[index];
379                },
380                pop: function () {
381                    if (!sorted) sort();
382                    return contents.pop();
383                },
384                size: function () {
385                    return contents.length;
386                },
387                map: function (f) {
388                    return contents.map(f);
389                },
390                debug: function () {
391                    if (!sorted) sort();
392                    return contents;
393                }
394            };
395        }
396
397        // 3d color space box
398        function VBox(r1, r2, g1, g2, b1, b2, histo) {
399            var vbox = this;
400            vbox.r1 = r1;
401            vbox.r2 = r2;
402            vbox.g1 = g1;
403            vbox.g2 = g2;
404            vbox.b1 = b1;
405            vbox.b2 = b2;
406            vbox.histo = histo;
407        }
408        VBox.prototype = {
409            volume: function (force) {
410                var vbox = this;
411                if (!vbox._volume || force) {
412                    vbox._volume = ((vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1));
413                }
414                return vbox._volume;
415            },
416            count: function (force) {
417                var vbox = this,
418                    histo = vbox.histo;
419                if (!vbox._count_set || force) {
420                    var npix = 0,
421                        i, j, k;
422                    for (i = vbox.r1; i <= vbox.r2; i++) {
423                        for (j = vbox.g1; j <= vbox.g2; j++) {
424                            for (k = vbox.b1; k <= vbox.b2; k++) {
425                                index = getColorIndex(i, j, k);
426                                npix += (histo[index] || 0);
427                            }
428                        }
429                    }
430                    vbox._count = npix;
431                    vbox._count_set = true;
432                }
433                return vbox._count;
434            },
435            copy: function () {
436                var vbox = this;
437                return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo);
438            },
439            avg: function (force) {
440                var vbox = this,
441                    histo = vbox.histo;
442                if (!vbox._avg || force) {
443                    var ntot = 0,
444                        mult = 1 << (8 - sigbits),
445                        rsum = 0,
446                        gsum = 0,
447                        bsum = 0,
448                        hval,
449                        i, j, k, histoindex;
450                    for (i = vbox.r1; i <= vbox.r2; i++) {
451                        for (j = vbox.g1; j <= vbox.g2; j++) {
452                            for (k = vbox.b1; k <= vbox.b2; k++) {
453                                histoindex = getColorIndex(i, j, k);
454                                hval = histo[histoindex] || 0;
455                                ntot += hval;
456                                rsum += (hval * (i + 0.5) * mult);
457                                gsum += (hval * (j + 0.5) * mult);
458                                bsum += (hval * (k + 0.5) * mult);
459                            }
460                        }
461                    }
462                    if (ntot) {
463                        vbox._avg = [~~(rsum / ntot), ~~(gsum / ntot), ~~(bsum / ntot)];
464                    } else {
465                        //                    console.log('empty box');
466                        vbox._avg = [
467                        ~~(mult * (vbox.r1 + vbox.r2 + 1) / 2),
468                        ~~(mult * (vbox.g1 + vbox.g2 + 1) / 2),
469                        ~~(mult * (vbox.b1 + vbox.b2 + 1) / 2)
470                    ];
471                    }
472                }
473                return vbox._avg;
474            },
475            contains: function (pixel) {
476                var vbox = this,
477                    rval = pixel[0] >> rshift;
478                gval = pixel[1] >> rshift;
479                bval = pixel[2] >> rshift;
480                return (rval >= vbox.r1 && rval <= vbox.r2 &&
481                    gval >= vbox.g1 && gval <= vbox.g2 &&
482                    bval >= vbox.b1 && bval <= vbox.b2);
483            }
484        };
485
486        // Color map
487        function CMap() {
488            this.vboxes = new PQueue(function (a, b) {
489                return pv.naturalOrder(
490                    a.vbox.count() * a.vbox.volume(),
491                    b.vbox.count() * b.vbox.volume()
492                );
493            });
494        }
495        CMap.prototype = {
496            push: function (vbox) {
497                this.vboxes.push({
498                    vbox: vbox,
499                    color: vbox.avg()
500                });
501            },
502            palette: function () {
503                return this.vboxes.map(function (vb) {
504                    return vb.color;
505                });
506            },
507            size: function () {
508                return this.vboxes.size();
509            },
510            map: function (color) {
511                var vboxes = this.vboxes;
512                for (var i = 0; i < vboxes.size(); i++) {
513                    if (vboxes.peek(i).vbox.contains(color)) {
514                        return vboxes.peek(i).color;
515                    }
516                }
517                return this.nearest(color);
518            },
519            nearest: function (color) {
520                var vboxes = this.vboxes,
521                    d1, d2, pColor;
522                for (var i = 0; i < vboxes.size(); i++) {
523                    d2 = Math.sqrt(
524                        Math.pow(color[0] - vboxes.peek(i).color[0], 2) +
525                        Math.pow(color[1] - vboxes.peek(i).color[1], 2) +
526                        Math.pow(color[2] - vboxes.peek(i).color[2], 2)
527                    );
528                    if (d2 < d1 || d1 === undefined) {
529                        d1 = d2;
530                        pColor = vboxes.peek(i).color;
531                    }
532                }
533                return pColor;
534            },
535            forcebw: function () {
536                // XXX: won't  work yet
537                var vboxes = this.vboxes;
538                vboxes.sort(function (a, b) {
539                    return pv.naturalOrder(pv.sum(a.color), pv.sum(b.color));
540                });
541
542                // force darkest color to black if everything < 5
543                var lowest = vboxes[0].color;
544                if (lowest[0] < 5 && lowest[1] < 5 && lowest[2] < 5)
545                    vboxes[0].color = [0, 0, 0];
546
547                // force lightest color to white if everything > 251
548                var idx = vboxes.length - 1,
549                    highest = vboxes[idx].color;
550                if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251)
551                    vboxes[idx].color = [255, 255, 255];
552            }
553        };
554
555        // histo (1-d array, giving the number of pixels in
556        // each quantized region of color space), or null on error
557        function getHisto(pixels) {
558            var histosize = 1 << (3 * sigbits),
559                histo = new Array(histosize),
560                index, rval, gval, bval;
561            pixels.forEach(function (pixel) {
562                rval = pixel[0] >> rshift;
563                gval = pixel[1] >> rshift;
564                bval = pixel[2] >> rshift;
565                index = getColorIndex(rval, gval, bval);
566                histo[index] = (histo[index] || 0) + 1;
567            });
568            return histo;
569        }
570
571        function vboxFromPixels(pixels, histo) {
572            var rmin = 1000000,
573                rmax = 0,
574                gmin = 1000000,
575                gmax = 0,
576                bmin = 1000000,
577                bmax = 0,
578                rval, gval, bval;
579            // find min/max
580            pixels.forEach(function (pixel) {
581                rval = pixel[0] >> rshift;
582                gval = pixel[1] >> rshift;
583                bval = pixel[2] >> rshift;
584                if (rval < rmin) rmin = rval;
585                else if (rval > rmax) rmax = rval;
586                if (gval < gmin) gmin = gval;
587                else if (gval > gmax) gmax = gval;
588                if (bval < bmin) bmin = bval;
589                else if (bval > bmax) bmax = bval;
590            });
591            return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
592        }
593
594        function medianCutApply(histo, vbox) {
595            if (!vbox.count()) return;
596
597            var rw = vbox.r2 - vbox.r1 + 1,
598                gw = vbox.g2 - vbox.g1 + 1,
599                bw = vbox.b2 - vbox.b1 + 1,
600                maxw = pv.max([rw, gw, bw]);
601            // only one pixel, no split
602            if (vbox.count() == 1) {
603                return [vbox.copy()];
604            }
605            /* Find the partial sum arrays along the selected axis. */
606            var total = 0,
607                partialsum = [],
608                lookaheadsum = [],
609                i, j, k, sum, index;
610            if (maxw == rw) {
611                for (i = vbox.r1; i <= vbox.r2; i++) {
612                    sum = 0;
613                    for (j = vbox.g1; j <= vbox.g2; j++) {
614                        for (k = vbox.b1; k <= vbox.b2; k++) {
615                            index = getColorIndex(i, j, k);
616                            sum += (histo[index] || 0);
617                        }
618                    }
619                    total += sum;
620                    partialsum[i] = total;
621                }
622            } else if (maxw == gw) {
623                for (i = vbox.g1; i <= vbox.g2; i++) {
624                    sum = 0;
625                    for (j = vbox.r1; j <= vbox.r2; j++) {
626                        for (k = vbox.b1; k <= vbox.b2; k++) {
627                            index = getColorIndex(j, i, k);
628                            sum += (histo[index] || 0);
629                        }
630                    }
631                    total += sum;
632                    partialsum[i] = total;
633                }
634            } else { /* maxw == bw */
635                for (i = vbox.b1; i <= vbox.b2; i++) {
636                    sum = 0;
637                    for (j = vbox.r1; j <= vbox.r2; j++) {
638                        for (k = vbox.g1; k <= vbox.g2; k++) {
639                            index = getColorIndex(j, k, i);
640                            sum += (histo[index] || 0);
641                        }
642                    }
643                    total += sum;
644                    partialsum[i] = total;
645                }
646            }
647            partialsum.forEach(function (d, i) {
648                lookaheadsum[i] = total - d;
649            });
650
651            function doCut(color) {
652                var dim1 = color + '1',
653                    dim2 = color + '2',
654                    left, right, vbox1, vbox2, d2, count2 = 0;
655                for (i = vbox[dim1]; i <= vbox[dim2]; i++) {
656                    if (partialsum[i] > total / 2) {
657                        vbox1 = vbox.copy();
658                        vbox2 = vbox.copy();
659                        left = i - vbox[dim1];
660                        right = vbox[dim2] - i;
661                        if (left <= right)
662                            d2 = Math.min(vbox[dim2] - 1, ~~(i + right / 2));
663                        else d2 = Math.max(vbox[dim1], ~~(i - 1 - left / 2));
664                        // avoid 0-count boxes
665                        while (!partialsum[d2]) d2++;
666                        count2 = lookaheadsum[d2];
667                        while (!count2 && partialsum[d2 - 1]) count2 = lookaheadsum[--d2];
668                        // set dimensions
669                        vbox1[dim2] = d2;
670                        vbox2[dim1] = vbox1[dim2] + 1;
671                        //                    console.log('vbox counts:', vbox.c
671ount(), vbox1.count(), vbox2.count());
672                        return [vbox1, vbox2];
673                    }
674                }
675
676            }
677            // determine the cut planes
678            return maxw == rw ? doCut('r') :
679                maxw == gw ? doCut('g') :
680                doCut('b');
681        }
682
683        function quantize(pixels, maxcolors) {
684            // short-circuit
685            if (!pixels.length || maxcolors < 2 || maxcolors > 256) {
686                //            console.log('wrong number of maxcolors');
687                return false;
688            }
689
690            // XXX: check color content and convert to grayscale if insufficient
691
692            var histo = getHisto(pixels),
693                histosize = 1 << (3 * sigbits);
694
695            // check that we aren't below maxcolors already
696            var nColors = 0;
697            histo.forEach(function () {
698                nColors++;
699            });
700            if (nColors <= maxcolors) {
701                // XXX: generate the new colors from the histo and return
702            }
703
704            // get the beginning vbox from the colors
705            var vbox = vboxFromPixels(pixels, histo),
706                pq = new PQueue(function (a, b) {
707                    return pv.naturalOrder(a.count(), b.count());
708                });
709            pq.push(vbox);
710
711            // inner function to do the iteration
712            function iter(lh, target) {
713                var ncolors = 1,
714                    niters = 0,
715                    vbox;
716                while (niters < maxIterations) {
717                    vbox = lh.pop();
718                    if (!vbox.count()) { /* just put it back */
719                        lh.push(vbox);
720                        niters++;
721                        continue;
722                    }
723                    // do the cut
724                    var vboxes = medianCutApply(histo, vbox),
725                        vbox1 = vboxes[0],
726                        vbox2 = vboxes[1];
727
728                    if (!vbox1) {
729                        //                    console.log("vbox1 not defined; shouldn't happen!");
730                        return;
731                    }
732                    lh.push(vbox1);
733                    if (vbox2) { /* vbox2 can be null */
734                        lh.push(vbox2);
735                        ncolors++;
736                    }
737                    if (ncolors >= target) return;
738                    if (niters++ > maxIterations) {
739                        //                    console.log("infinite loop; perhaps too few pixels!");
740                        return;
741                    }
742                }
743            }
744
745            // first set of colors, sorted by population
746            iter(pq, fractByPopulations * maxcolors);
747
748            // Re-sort by the product of pixel occupancy times the size in color space.
749            var pq2 = new PQueue(function (a, b) {
750                return pv.naturalOrder(a.count() * a.volume(), b.count() * b.volume());
751            });
752            while (pq.size()) {
753                pq2.push(pq.pop());
754            }
755
756            // next set - generate the median cuts using the (npix * vol) sorting.
757            iter(pq2, maxcolors - pq2.size());
758
759            // calculate the actual colors
760            var cmap = new CMap();
761            while (pq2.size()) {
762                cmap.push(pq2.pop());
763            }
764
765            return cmap;
766        }
767
768        return {
769            quantize: quantize
770        };
771    })();
772
773    /*
774     * getColorContrast - Customized for Material Design and RGB usage only
775     *     Return suggested contrast color (dark or light) for the color (rgba/rgb) given.
776     *     Takes advantage of YIQ: https://en.wikipedia.org/wiki/YIQ
777     *         dark = background is light, use dark colors for text, images, etc..
778     *         light = background is dark, use light colors for text, images, etc..
779     *     Inspired by: http://24ways.org/2010/calculating-color-contrast/
780     *
781     * @param color string A valid rgb value
782     * @return      string dark|light
783     * @copyright (c) 2015 Julian Richen
784     * @license Licensed under MIT License https://raw.githubusercontent.com/julianrichen/javascript-dynamic-contrast/master/LICENSE
785     *
786     */
787
788    var getColorContrast = function (color) {
789        if (!color) {
790            return null;
791        }
792        var rgbExp = /^rgba?[\s+]?\(\s*(([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]))\s*,\s*([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\s*,\s*([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\s*,?(?:\s*([\d.]+))?\s*\)?\s*/im,
793            rgb = color.match(rgbExp),
794            r,
795            g,
796            b,
797            yiq;
798        if (rgb) {
799            r = parseInt(rgb[1], 10);
800            g = parseInt(rgb[2], 10);
801            b = parseInt(rgb[3], 10);
802        } else {
803            return null;
804        }
805        yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
806
807        // 128~150 and not 128, based on Material Design colors
808        // Unfortunately there's no specific/right number
809        return (yiq >= 146) ? 'dark' : 'light';
810
811    }
812
813    // -- -- Initialize color thief once
814    var colorThiefNew = new ColorThief();
815
816
817
818    /*
819     * Helpers
820     */
821
822    // -- -- Process Server Info
823    // ** ** Will be called in init() and autoUpdate()
824    var processServerInfo = function (ot, sI, data, KA) {
825        var sIL = sI.length,
826            sIFrag = document.createDocumentFragment(),
827            serverInfo;
828        for (var i = 0; i < sIL; i++) {
829            if (ot.direct) {
830                serverInfo = data[sI[i]];
831            } else if (!data.body) {
832                serverInfo = data.getElementsByTagName(sI[i].toUpperCase())[0].textContent;
833            } else {
834                var serverInfoV1 = data.body.textContent.split(','); // Data here are not XML
835
836                if (sI[i] === 'currentlisteners') {
837                    serverInfo = serverInfoV1[0]
838                } else if (sI[i] === 'streamstatus') {
839                    serverInfo = serverInfoV1[1]
840                } else if (sI[i] === 'peaklisteners') {
841                    serverInfo = serverInfoV1[2]
842                } else if (sI[i] === 'maxlisteners') {
843                    serverInfo = serverInfoV1[3]
844                } else if (sI[i] === 'uniquelisteners') {
845                    serverInfo = serverInfoV1[4]
846                } else if (sI[i] === 'bitrate') {
847                    serverInfo = serverInfoV1[5]
848                } else if (sI[i] === 'songtitle') {
849                    serverInfo = serverInfoV1[6]
850                }
851            }
852
853            if (i === sIL - 1) {
854                sIFrag.appendChild(document.createTextNode(serverInfo));
855            } else {
856                sIFrag.appendChild(document.createTextNode(serverInfo + ' - '));
857            }
858        }
859
860        KA.appendChild(sIFrag); // Now append to DOM at last, like a BOSS
861    }
862
863
864    // -- Custom smart Cross-browser ajax request designed for SHOUTcast servers
865    // -- options @object: url, timeout, arraybuffer, load, error
866    var XHR = function (op) {
867        var req;
868        req = (window.XMLHttpRequest) ? new XMLHttpRequest() : new XDomainRequest();
869
870        try { // ** for IE (Access is denied) *bug in proxies
871            req.open('GET', op.url, true);
872
873            if (op.arraybuffer) {
874                if (typeof req.responseType === 'string') { // XHR2 check first
875                    req.responseType = 'arraybuffer';
876                } else {
877                    if (op.error) {
878                        op.error()
879                    }
880                    return // kill request
881                }
882
883            }
884
885            req.timeout = op.timeout || 3000;
886        } catch (e) {
887            if (op.error) {
888                op.error()
889            }
890            return // kill request
891        }
892
893        req.onload = function () {
894
895            if (req.readyState === 4) {
896
897                if (req.status === 200) { // For safety! According to Mozilla docs
898                    var data = (op.arraybuffer) ? req.response : req.responseText;
899                    op.load(data, req)
900                } else {
901                    if (op.error) {
902                        op.error()
903                    }
904                }
905
906            }
907
908        }
909
910        if (op.error) {
911            req.onerror = op.error
912        }
913
914
915        req.ontimeout = req.onerror;
916
917        setTimeout(function () { // ** for IE9 (Multi requests)
918            req.send();
919        }, 0);
920
921        // Global it, so I could abort any active requests when I destroy Kast
922        window.kastXHR = req
923    }
924
925    // -- Remove brackets + Remove invaild characters
926    var chaRe = function (strie) {
927        return strie.replace(/(\[.*?\]|\(.*?\))/g, '').replace(/[|&^'`;:$%@"#<>+,]/g, '')
928    };
929
930    // -- Create HTML Document
931    function createHTMLDocument(str) {
932        var doc = document.implementation.createHTMLDocument('html');
933        doc.documentElement.innerHTML = str;
934        return doc
935    }
936
937
938    /*
939     * Feature Detection (ONCE)
940     */
941
942    // -- -- Document clasList support (Extremely fast and easy Add/Remove/Toggle classes)
943    var classL = document.documentElement.classList;
944    
945    
946    var _popup;
947    var _document;
948
949
950
951    // Plugin prototypes
952    // ======================================================== //
953    Plugin.prototype = {
954        init: function () {
955            // -- load options (theme, colors, etc...)
956            // -- Place player elements here
957            // -- Call prototypes
958
959            // -- that
960            var that = this,
961                ot = that.opt;
962
963            // -- Cache container
964            if (ot.popup) {
965                _popup = window.open('','kast','width='+ot.popupWidth+',height='+ot.popupHeight+',toolbar=0,menubar=0,location=0,directories=0,status=0,scrollbars=0,resizable=1,left=0,top=0');
966                
967                // Redefine and write document.
968                document = _popup.document; 
969                document.write('<html><head><title>Kast</title><link href="'+ot.popupCSSLink+'" rel="stylesheet" type="text/css"></head><body></body></html>');
970                
971                _popup.onbeforeunload = function(){
972                    $.kast('destroy');
973                }
974                
975                ot.container = 'body';
976            }
977            
978            var docContainer = document.querySelector(ot.container);
979
980            // -- Some classes
981            var clrs = ot.colors,
982                primaryColor = (typeof clrs === 'object') ? 'kast-primary-' + clrs.primary.replace(' ', '') : '', // Primary color
983                accentColor = (typeof clrs === 'object') ? 'kast-accent-' + clrs.accent.replace(' ', '') : '', // Accent color
984                dynamicColors = (typeof clrs === 'string') ? 'kast-colors-' + clrs : '', // Dynamic colors
985                ultra = (that.mobile && that.mobileUltra) ? 'kast-ultra' : '', // Mobile ultra mode
986                notFullBar = (!ot.statusBar || !ot.muteUnmute || !ot.minimizeMaximize) ? 'kast-not-full-statusbar' : '', // So i could make text longer
987                played = (ot.played) ? ' kast-played ' : ' kast-current ', // Played or current
988                container = (ot.container !== 'body') ? 'kast-custom-container' : '', // Custom container (Not sticky if not body)
989                thin = (docContainer.offsetWidth < 380) ? 'kast-thin-container' : '', // Thin container just to fix scrollbar in CSS
990                dynamicTheme = (ot.theme === 'dynamic') ? 'kast-light' : '', // light theme if dynamic
991                statusBarHtml = '', // Status bar HTML
992                playedHtml = '';
993
994            // -- Status bar HTML
995            if (ot.statusBar) {
996                var muteUnmuteHtml = (ot.muteUnmute) ? '<i id="kast-volume" class="kast-mdi kast-mdi-volume-high"></i>' : '',
997                    minMaxHtml = (ot.minimizeMaximize) ? '<i id="kast-minmax" class="kast-mdi kast-mdi-chevron-down"></i>' : '';
998
999                statusBarHtml = '<div id="kast-bar"><p class="kast-offline-status">' + ot.language.offlineText + '</p><p id="kast-server"></p>' + muteUnmuteHtml + minMaxHtml + '</div>';
1000            }
1001
1002            // -- Played/Bottom HTML
1003            if (ot.played) {
1004                playedHtml = '<div id="kast-bottom" class="kast-clearfix"><p>' + ot.language.playedText + '</p><ul id="kast-playedlist" class="kast-clearfix"></ul></div>';
1005            }
1006
1007            var kastPlayClass = ot.continuous ? 'kast-paused kast-continuous ' : 'kast-stopped ';
1008
1009            // -- Kast HTML  
1010            var html = '';
1011            html += '<div id="kast" class="kast-recss kast-' + ot.theme + ' kast-' + ot.ui + ' ' + primaryColor + ' ' + accentColor + ' ' + dynamicColors + ' kast-' + ot.startTemplate + ' kast-' + ot.position + ' ' + ultra + ' ' + notFullBar + played + ' ' + container + ' ' + dynamicTheme + ' ' + thin + '">'
1012            html += '<div id="kast-wrapper">';
1013            html += statusBarHtml; // isn't better than appending it later ?
1014            html += '<div id="kast-top" class="kast-default-art">';
1015            html += '<div class="kast-album-wrapper"><i class="kast-mdi kast-mdi-album"></i></div>';
1016            html += '<div id="kast-top-content" class="kast-top-content">';
1017            html += '<div id="kast-nowplaying"><p id="kast-np-title"></p><p id="kast-np-artist"></p></div>';
1018            html += '<div id="kast-linear"></div>';
1019            html += '</div>';
1020            html += '<div id="kast-play" class="' + kastPlayClass + '">';
1021            html += '<i class="kast-mdi kast-mdi-play"></i>';
1022            html += '<i class="kast-mdi kast-mdi-pause"></i>';
1023            html += '<i class="kast-mdi kast-mdi-stop"></i>';
1024            html += '</div></div>' + playedHtml + '</div></div>';
1025
1026
1027            docContainer.insertAdjacentHTML("beforeend", html); // DOM access: Insert last
1028            ot.onReady(); // Event
1029
1030
1031            // -- Status bar, CurrentTrack, CurrentArtist
1032            if (ot.statusBar || ot.currentTrack || ot.currentArtist) {
1033
1034                // -- -- Stats (CurrentTrack, CurrentArtist)
1035                this.stats(function (data) {
1036
1037                    // -- Set Server Info once
1038                    var sI = ot.serverInfo;
1039
1040                    if (sI && ot.statusBar) {
1041                        var kastServer = document.getElementById('kast-server');
1042                        if (typeof sI === 'string') { // Custom Server info
1043                            kastServer.textContent = sI;
1044                        } else {
1045                            processServerInfo(ot, sI, data, kastServer)
1046                        }
1047                    }
1048
1049                    // -- Passing data
1050                    // ** that.stats(data) --> processStats(data) #Without_repulling
1051                    that.stats(data);
1052
1053                });
1054
1055            }
1056
1057            // -- Played
1058            if (ot.played) {
1059
1060                // -- Set played tracks/artists
1061                if (Array.isArray && Array.isArray(ot.artwork)) {
1062                    that.played(false, true)
1063                } else {
1064                    that.played()
1065                }
1066
1067            }
1068
1069            // -- Auto Update
1070            var aU = ot.autoUpdate;
1071            if (aU === 'all') {
1072                that.autoUpdate('all');
1073            } else if (aU) {
1074                that.autoUpdate(true);
1075            }
1076
1077
1078
1079
1080            // -- Binding (addEventListener isn't necessary, beside onclick is extremely fast)           
1081            document.getElementById('kast-play').onclick = function () {
1082                if (this.className.indexOf('playing') === -1) {
1083                    that.play(!ot.continuous) // ot.continuous for reload
1084                } else {
1085                    ot.continuous ? that.pause() : that.stop()
1086                }
1087            };
1088
1089            if (ot.statusBar) {
1090
1091                if (ot.muteUnmute) {
1092                    document.getElementById('kast-volume').onclick = function () {
1093                        if (this.className.indexOf('high') !== -1) {
1094                            that.mute()
1095                        } else {
1096                            that.unmute()
1097                        }
1098                    };
1099                }
1100
1101                if (ot.minimizeMaximize) {
1102                    document.getElementById('kast-minmax').onclick = function () {
1103                        if (document.getElementById('kast').className.indexOf('minimized') !== -1) {
1104                            that.maximize()
1105                        } else {
1106                            that.minimize()
1107                        }
1108                    };
1109                }
1110
1111            }
1112
1113
1114            // -- Finally
1115            ot.autoPlay && that.play();
1116            ot.startMuted && that.mute();
1117
1118
1119
1120            return this;
1121        },
1122        play: function (reload) {
1123            // -- Play the radio
1124            // -- addClass/removeClass
1125			
1126            var k = document.getElementById('kast'),
1127                kP = document.getElementById('kast-play');
1128            if (classL) {
1129                var kPC = kP.classList;
1130                kPC.remove('kast-paused')
1131                kPC.remove('kast-stopped')
1132                kPC.add('kast-playing')
1133            } else {
1134                $(kP).removeClass('kast-paused kast-stopped').addClass('kast-playing')
1135            }
1136
1137            var wasOffline = (this.opt.offlineCheck && k.getAttribute('data-offline') === 'false');
1138
1139            // ** Safe Play() for dumb browsers: I force reload the audio, if was Offline, and back online again
1140            // ** + Continuous
1141            if (reload || wasOffline) {
1142                this.audio.load();
1143                wasOffline && k.removeAttribute('data-offline') // here we remove it not set it (important)
1144            }
1145			
1146            this.audio.play();
1147            this.opt.onPlay(this.audio) // Event
1148			
1149			// DURATION 
1150			// BNJ AddOn
1151			// Play Live at the end
1152			if(this.opt.isMedia == true) {
1153				
1154				document.getElementsByTagName('body')[0].appendChild(this.audio);
1155				
1156				var AudioPlayer = document.getElementById('AudioPlayer');
1157				
1158				//var Duration = AudioPlayer.duration;
1159				AudioPlayer.addEventListener('loadedmetadata', function() {
1160					var Duration = AudioPlayer.duration;
1161					$('#pg-duration').text(getDurationHours(Duration));
1162				});
1163				
1164				AudioPlayer.addEventListener('timeupdate', function(){
1165					
1166					var CurrentTime = Math.round(AudioPlayer.currentTime);
1167					var PercentTime = CurrentTime*100/AudioPlayer.duration;
1168					var PercentTime = PercentTime + '%';
1169					
1170					$('.pg-current').css('width', PercentTime);
1171					
1172				});
1173				
1174				$('#pg-progress-bar').click(function(e){
1175					
1176					var $this = $(this);
1177					
1178					var posX = $(this).offset().left, 
1179						posY = $(this).offset().top;
1180					var posX = e.pageX - posX;
1181					var wBar = $this.width();
1182					
1183					var PercentPosition = posX*100/wBar;
1184					
1185					// Calculate Percent of duration
1186					var newTime = PercentPosition * AudioPlayer.duration / 100;
1187					AudioPlayer.currentTime = newTime;
1188					
1189						
1190				});
1191				
1192				// Replay Live
1193				AudioPlayer.addEventListener('ended', function(){
1194					$('.pg-current').css('width', '0px');
1195					$('.pg-current').remove();
1196					$('#PlayGrrif').click();
1197				});
1198				
1199			}
1200
1201            return this;
1202        },
1203        pause: function () {
1204            // -- pause the radio
1205            // -- addClass/removeClass
1206
1207            var kP = document.getElementById('kast-play');
1208            if (classL) {
1209                var kPC = kP.classList;
1210                kPC.remove('kast-playing')
1211                kPC.remove('kast-stopped')
1212                kPC.add('kast-paused')
1213                kPC.add('kast-continuous')
1214            } else {
1215                $(kP).removeClass('kast-playing kast-stopped').addClass('kast-paused kast-continuous')
1216            }
1217
1218            this.audio.pause();
1219
1220            this.opt.onPause(this.audio) // Event
1221			
1222			$('#pg-progress-bar').off('click');
1223			
1224            return this;
1225        },
1226        stop: function () {
1227            // -- pause the radio
1228            // -- addClass/removeClass
1229
1230            var kP = document.getElementById('kast-play');
1231            if (classL) {
1232                var kPC = kP.classList;
1233                kPC.remove('kast-playing')
1234                kPC.remove('kast-paused') // For caution when user uses API
1235                kPC.remove('kast-continuous') // For caution when user uses API
1236                kPC.add('kast-stopped')
1237            } else {
1238                $(kP).removeClass('kast-playing kast-paused kast-continuous').addClass('kast-stopped')
1239            }
1240
1241            // Stop is same as pause, play() takes care of the rest
1242            this.audio.pause();
1243
1244            this.opt.onStop(this.audio) // Event
1245
1246            return this;
1247
1248        },
1249        mute: function () {
1250            // -- mute audio
1251            // -- className
1252
1253            document.getElementById('kast-volume').className = 'kast-mdi kast-mdi-volume-off';
1254
1255            this.audio.muted = true;
1256
1257            this.opt.onMute(this.audio) // Event
1258
1259            return this
1260        },
1261        unmute: function () {
1262            // -- unmute audio
1263            // -- className
1264
1265            document.getElementById('kast-volume').className = 'kast-mdi kast-mdi-volume-high';
1266
1267            this.audio.muted = false;
1268
1269            this.opt.onUnmute(this.audio) // Event
1270
1271            return this
1272        },
1273        minimize: function () {
1274            // -- minimize the radio
1275            // -- addClass/removeClass
1276
1277            var k = document.getElementById('kast');
1278            if (classL) {
1279                var kC = k.classList;
1280                kC.remove('kast-maximized')
1281                kC.add('kast-minimized')
1282            } else {
1283                $(k).removeClass('kast-maximized').addClass('kast-minimized');
1284            }
1285
1286            this.opt.onMinimize() // Event
1287
1288            return this;
1289        },
1290        maximize: function () {
1291            // -- maximize the radio
1292            // -- addClass/removeClass
1293
1294            var k = document.getElementById('kast');
1295            if (classL) {
1296                var kC = k.classList;
1297                kC.remove('kast-minimized')
1298                kC.add('kast-maximized')
1299            } else {
1300                $(k).removeClass('kast-minimized').addClass('kast-maximized');
1301            }
1302
1303            this.opt.onMaximize() // Event
1304
1305            return this;
1306        },
1307        stats: function (callback) {
1308            // -- Pull current info
1309            // -- return it. callback contains data
1310            // -- data:
1311            // -- {averagetime, backupstatus, bitrate, content,
1312            // -- currentlisteners, maxlisteners, peaklisteners,
1313            // -- servergenre[ -1-5], servertitle, serverurl,
1314            // -- songtitle, streamhits, streamlisted,
1315            // -- streamlistederror, streampath, streamstatus,
1316            // -- streamuptime, uniquelisteners, version}
1317
1318            var that = this,
1319                ot = that.opt;
1320
1321
1322            // -- Cached selectors
1323            var kast = document.getElementById('kast'),
1324                kastTitle = document.getElementById('kast-np-title'),
1325                kastArtist = document.getElementById('kast-np-artist'),
1326                kastTop = document.getElementById('kast-top'),
1327                kastBar = document.getElementById('kast-bar'),
1328                kastTopC = document.getElementById('kast-top-content'),
1329                kastPlay = document.getElementById('kast-play');
1330
1331            var processStats = function (data) {
1332                // -- Mutual data for current track/artist and current artist artwork
1333                var songTitle, currentTrack, currentArtist, _titleSplit;
1334
1335                var unTrack = ot.language.unknownTrackText,
1336                    unArtist = ot.language.unknownArtistText;
1337
1338
1339                try {
1340                    songTitle = (ot.direct ? data.songtitle : (data.body) ? data.body.textContent.split(',')[6] : data.getElementsByTagName('SONGTITLE')[0].textContent) || unArtist + ' - ' + unTrack; // V2 || V1
1341                } catch (e) { // aka 'error'
1342                    songTitle = unArtist + ' - ' + unTrack;
1343                }
1344
1345                // -- Remove CDATA if found
1346                var songTitleHasCDATA = songTitle.indexOf('//<![CDATA');
1347                if (songTitleHasCDATA > -1) {
1348                    songTitle = songTitle.slice(0, songTitleHasCDATA - 1);
1349                }
1350
1351
1352                // -- Current track/artist
1353                // ** when custom track/artist is set, everything is still normall, i just set them
1354                
1355                _titleSplit = songTitle.split(' - ');
1356                
1357                if (typeof ot.currentTrack === 'string') {
1358                    currentTrack = ot.currentTrack;
1359                } else {
1360                    currentTrack = _titleSplit[1] || songTitle || unTrack;
1361                }
1362
1363                if (typeof ot.currentArtist === 'string') {
1364                    currentArtist = ot.currentArtist;
1365                } else {
1366                    currentArtist = _titleSplit[1] ? _titleSplit[0] : unArtist;
1367                }
1368
1369                if (ot.skipInfo) {
1370                    var i = 0;
1371                    var len = ot.skipInfo.length;
1372
1373                    for (; i < len; i++) {
1374
1375                        if (ot.skipInfo[i].track && ot.skipInfo[i].artist) {
1376
1377                            if (ot.skipInfo[i].track === currentTrack && ot.skipInfo[i].artist === currentArtist) return;
1378
1379                        } else if ((ot.skipInfo[i].track && ot.skipInfo[i].track === currentTrack) || (ot.skipInfo[i].artist && ot.skipInfo[i].artist === currentArtist)) {
1380                            return;
1381                        }
1382
1383                    }
1384                }
1385
1386                if (ot.currentTrack) {
1387                    kastTitle.textContent = currentTrack;
1388                }
1389                if (ot.currentArtist) {
1390                    kastArtist.textContent = currentArtist;
1391                }
1392
1393
1394                // -- Pull Current artist artwork
1395                if (ot.artwork) {
1396                    that.artwork(currentArtist, currentTrack, function (imgAr) {
1397
1398                        if (!imgAr) {
1399                            // -- -- Remove default art
1400                            // -- -- Reset backgrounds/colors
1401                            // -- -- Reset theme
1402                            // ** ** kast addClass/removeClass
1403                            // ** ** kastTop className
1404                            // ** ** kastTopC className
1405                            // ** ** kastPlay addClass/removeClass
1406                            // ** ** kastBar className
1407
1408                            if (classL) {
1409
1410                                var kC = kast.classList,
1411                                    kPC = kastPlay.classList;
1412                                kPC.remove('kast-accent-light')
1413                                kPC.remove('kast-accent-dark')
1414
1415                                if (ot.theme === 'dark') {
1416                                    kC.remove('kast-light')
1417                                    kC.add('kast-dark')
1418                                } else {
1419                                    kC.remove('kast-dark')
1420                                    kC.add('kast-light')
1421                                }
1422                            } else {
1423                                $(kastPlay).removeClass('kast-accent-light kast-accent-dark');
1424
1425                                if (ot.theme === 'dark') {
1426                                    $(kast).removeClass('kast-light').addClass('kast-dark');
1427                                } else {
1428                                    $(kast).removeClass('kast-dark').addClass('kast-light');
1429                                }
1430                            }
1431                            kastTop.className = 'kast-default-art';
1432                            if (ot.statusBar) {
1433                                kastBar.removeAttribute('class') // removeAttr works here
1434                            }
1435                            kastPlay.removeAttribute('style')
1436                            kastTop.removeAttribute('style');
1437                            kastTopC.removeAttribute('style');
1438                            kastTopC.className = 'kast-top-content';
1439
1440
1441
1442                        } else {
1443
1444                            var imgURL = imgAr[0],
1445                                imgLite = imgAr[1] || imgURL; // the small one to work with Color Thief (Faster and lighter on the CPU/GPU)
1446
1447                            kastTop.style.backgroundImage = 'url(' + imgURL + ')';
1448                            kastTop.removeAttribute('class')
1449
1450
1451                            ot.onCurrentArtwork(imgURL) // Event
1452
1453                            // -- Dynamic colors & Dynamic theme
1454                            // -- both requires artwork obviously
1455
1456                            // ** XHR: is already smart and checks for XHR2 support
1457                            // ** so we just need to check Blob constractor support
1458                            if ((ot.theme === 'dynamic' || ot.colors === 'dynamic') && window.Blob) {
1459
1460                                // -- Golden method for getting colors from XHR images
1461                                // ** other methods: http://jsfiddle.net/manarkamel/bmvqavjf/                            
1462                                XHR({
1463                                    url: imgLite,
1464                                    arraybuffer: true,
1465                                    load: function (data, req) {
1466                                        window.URL = window.URL || window.webkitURL; // support for Safari/Old Chrome
1467                                        var img = document.createElement('img'),
1468                                            contentType = req.getResponseHeader('content-type'),
1469                                            uData = new Uint8Array(data),
1470                                            blob = new Blob([uData], {
1471                                                type: contentType
1472                                            });
1473                                        img.src = window.URL.createObjectURL(blob);
1474                                        img.onload = function () {
1475                                            var color = colorThiefNew.getPalette(img, 5),
1476                                                primaryBG = 'rgb(' + color[0] + ')',
1477                                                accentBG = 'rgb(' + color[1] + ')',
1478                                                primaryContrast = getColorContrast(primaryBG) || 'light',
1479                                                primaryCC = 'kast-primary-' + primaryContrast,
1480                                                accentContrast = getColorContrast(accentBG) || 'dark',
1481                                                accentCC = 'kast-accent-' + accentContrast;
1482
1483
1484                                            // -- Dynamic colors
1485                                            if (ot.colors === 'dynamic') {
1486
1487                                                var kastPlayClass = ot.continuous ? 'kast-paused ' : 'kast-stopped ';
1488                                                var kPC = kastPlay.className;
1489                                                if (kPC.indexOf('playing') !== -1) {
1490                                                    kastPlay.className = 'kast-playing ' + accentCC
1491                                                } else {
1492                                                    kastPlay.className = kastPlayClass + accentCC
1493                                                }
1494
1495                                                if (ot.continuous) {
1496                                                    kastPlay.className += ' kast-continuous ';
1497                                                }
1498
1499                                                kastTopC.className = 'kast-top-content ' + primaryCC;
1500                                                if (ot.statusBar) {
1501                                                    kastBar.className = primaryCC;
1502                                                }
1503                                                kastPlay.style.backgroundColor = accentBG;
1504
1505                                                if (ot.ui === 'colored') {
1506                                                    kastTopC.style.backgroundColor = primaryBG;
1507                                                }
1508
1509                                                ot.onDynamicColors(primaryBG, accentBG) // Event
1510                                                ot.onDynamicColorsContrast(primaryContrast, accentContrast) // Event
1511                                            }
1512
1513
1514                                            // -- Dynamic theme
1515                                            if (ot.theme === 'dynamic') {
1516                                                if (primaryContrast === 'light') {
1517                                                    if (classL) {
1518                                                        var kC = kast.classList;
1519                                                        kC.remove('kast-dark')
1520                                                        kC.add('kast-light')
1521                                                    } else {
1522                                                        $(kast).removeClass('kast-dark').addClass('kast-light')
1523                                                    }
1524
1525                                                    ot.onDynamicTheme('light') // Event
1526                                                } else {
1527                                                    if (classL) {
1528                                                        var kC = kast.classList;
1529                                                        kC.remove('kast-light')
1530                                                        kC.add('kast-dark')
1531                                                    } else {
1532                                                        $(kast).removeClass('kast-light').addClass('kast-dark')
1533                                                    }
1534
1535                                                    ot.onDynamicTheme('dark') // Event
1536                                                }
1537                                            }
1538
1539                                            // -- objectURL is no longer needed
1540                                            // ** now we should release it to free cache/memory :)
1541
1542                                            // -- So... FINISH HIM! (him = objectURL)
1543                                            window.URL.revokeObjectURL(img.src)
1544                                            img = null
1545                                        }
1546                                    }
1547                                })
1548
1549
1550                            }
1551
1552                        }
1553
1554                    })
1555                }
1556
1557                ot.onCurrentInfo(currentTrack, currentArtist) // Event 
1558            };
1559
1560            if (typeof callback === 'object') {
1561                processStats(callback) // because it's not a callback, it's DATA
1562            } else {
1563                var ajaxStats = function (hostCORSproxy, showError) {
1564                    var url = hostCORSproxy + ot.statsPath + '?sid=' + ot.sid,
1565                        urlParm = (ot.version === 1) ? '#' : '?_='; // V1 servers are dumb
1566                    if (ot.version === 1) {
1567                        url = hostCORSproxy + '7.html';
1568                    }
1569                    XHR({
1570                        url: url + urlParm + Math.random(),
1571                        timeout: 8000,
1572                        load: function (data) {
1573
1574                            var dataDOM = data.indexOf('<?xml') > -1 ? new DOMParser().parseFromString(data, 'text/xml') : createHTMLDocument(data);
1575
1576                            if (typeof callback === 'function') {
1577                                callback(dataDOM);
1578                            } else {
1579                                processStats(dataDOM)
1580                            }
1581                        },
1582                        error: function () {
1583                            if (showError) {
1584                                var isOffline = kast.hasAttribute('data-offline'),
1585                                    kastNT = document.getElementById('kast-np-title'); // artist could work too
1586
1587                                // ** ** OfflineCheck requires autoUpdate
1588                                if (ot.offlineCheck && ot.autoUpdate && !isOffline) {
1589                                    if (classL) {
1590                                        kast.classList.add('kast-offline');
1591                                    } else {
1592                                        $(kast).addClass('kast-offline');
1593                                    }
1594
1595
1596                                    kast.setAttribute('data-offline', 'true')
1597                                    ot.onOffline();
1598                                }
1599
1600                                // ** ** If empty add unknown
1601                                if (!kastNT.hasChildNodes()) {
1602                                    processStats('error')
1603                                }
1604
1605                            } else {
1606                                // -- -- fallback to another proxy and show error this time
1607                                ajaxStats(that.hostCORS[ot.betaProxies ? 3 : 1], true)
1608                            }
1609                        }
1610                    })
1611
1612                }
1613                var directStats = function () {
1614
1615                    $.ajax({
1616                        url: that.host + ot.statsPath + '?sid=' + ot.sid + '&json=1&_=' + Math.random(),
1617                        dataType: 'jsonp',
1618                        timeout: 8000,
1619                        success: function (data) {
1620
1621                            if (typeof callback === 'function') {
1622                                callback(data);
1623                            } else {
1624                                processStats(data)
1625                            }
1626
1627                        },
1628                        error: function () {
1629
1630                            var isOffline = kast.hasAttribute('data-offline'),
1631                                kastNT = document.getElementById('kast-np-title'); // artist could work too
1632
1633                            // ** ** OfflineCheck requires autoUpdate
1634                            if (ot.offlineCheck && ot.autoUpdate && !isOffline) {
1635                                if (classL) {
1636                                    kast.classList.add('kast-offline');
1637                                } else {
1638                                    $(kast).addClass('kast-offline');
1639                                }
1640
1641
1642                                kast.setAttribute('data-offline', 'true')
1643                                ot.onOffline();
1644                            }
1645
1646                            // ** ** If empty add unknown
1647                            if (!kastNT.hasChildNodes()) {
1648                                processStats('error')
1649                            }
1650
1651                        }
1652                    })
1653
1654                }
1655
1656                if (ot.direct) {
1657                   // directStats()
1658                } else {
1659                   // ajaxStats(that.hostCORS[ot.betaProxies ? 2 : 0], false)
1660                }
1661
1662
1663            }
1664
1665
1666            return this
1667        },
1668        played: function (callback, woArtwork) {
1669            // -- Pull played info
1670            // -- return it. callback contains data
1671
1672            var that = this,
1673                ot = that.opt;
1674
1675            // Cached selectors
1676            var kastPL = document.getElementById('kast-playedlist');
1677
1678            var processPlayed = function (data) {
1679
1680                    // -- Empty
1681                    while (kastPL.firstChild) {
1682                        kastPL.removeChild(kastPL.firstChild);
1683                    }
1684
1685                    var song, songL, _tables,
1686                        songsFrag = document.createDocumentFragment(),
1687                        unTrack = ot.language.unknownTrackText,
1688                        unArtist = ot.language.unknownArtistText;
1689
1690                    // Note: SHOUTcast V2.5x has 2 tables only,
1691                    // others have 3 tables.
1692                    // Played list is usually the last table, but not always.
1693                    // So to get the played list table, we must loop and search for 'played @' text for that.
1694                    try {
1695                        if (ot.direct) {
1696                            song = data;
1697                        } else if (data.body) {
1698                            _tables = data.getElementsByTagName('table');
1699                            
1700                            var i = 0;
1701                            var len = _tables.length;
1702                            
1703                            // find table index that has songs
1704                            for (;i < len; i++) if (_tables[i].textContent.toLowerCase().indexOf('played @') > -1) break;
1705                            
1706                            song = _tables[i].getElementsByTagName('tr');
1707                        } else {
1708                            song = data.getElementsByTagName('SONG');
1709                        }
1710                        songL = song.length;
1711                    } catch (e) {
1712                        songL = 3;
1713                    }
1714
1715                    var processPL = function (j, song, songL, songsFrag) {
1716
1717                        var playedTrack, playedArtist, title, _titleSplit;
1718
1719                        try {
1720                            title = (ot.direct ? song[j].title : (data.body) ? song[j].getElementsByTagName('td')[1].textContent : song[j].getElementsByTagName('TITLE')[0].textContent) || unArtist + ' - ' + unTrack;
1721                        } catch (e) {
1722                            title = unArtist + ' - ' + unTrack;
1723                        }
1724
1725                        _titleSplit = title.split(' - ');
1726
1727                        if (ot.playedTracks) {
1728                            playedTrack = _titleSplit[1] || title || unTrack;
1729                        }
1730                        if (ot.playedArtists) {
1731                            playedArtist = _titleSplit[1] ? _titleSplit[0] : unArtist;
1732                        }
1733
1734                        if (ot.skipInfo) {
1735                            var i = 0;
1736                            var len = ot.skipInfo.length;
1737
1738                            for (; i < len; i++) {
1739
1740                                if (ot.skipInfo[i].track && ot.skipInfo[i].artist) {
1741
1742                                    if (ot.skipInfo[i].track === playedTrack && ot.skipInfo[i].artist === playedArtist) return;
1743
1744                                } else if ((ot.skipInfo[i].track && ot.skipInfo[i].track === playedTrack) || (ot.skipInfo[i].artist && ot.skipInfo[i].artist === playedArtist)) {
1745                                    return;
1746                                }
1747
1748                            }
1749                        }
1750
1751                        // -- Track Element
1752                        var li = document.createElement('li'),
1753                            divA = document.createElement('div'),
1754                            i = document.createElement('i'),
1755                            divI = document.createElement('div'),
1756                            pTitle = document.createElement('p'),
1757                            pArtist = document.createElement('p');
1758                        divA.className = 'kast-p-art';
1759                        i.className = 'kast-mdi kast-mdi-album';
1760                        divI.className = 'kast-p-info';
1761                        pTitle.className = 'kast-p-title';
1762                        pTitle.textContent = playedTrack;
1763                        pArtist.className = 'kast-p-artist';
1764                        pArtist.textContent = playedArtist;
1765
1766                        li.appendChild(divA);
1767                        li.appendChild(divI);
1768                        divA.appendChild(i);
1769                        divI.appendChild(pTitle);
1770                        divI.appendChild(pArtist);
1771
1772
1773                        // -- Played artworks
1774                        if (ot.artwork && !woArtwork) {
1775
1776                            that.artwork(playedArtist, playedTrack, function (imgAr) {
1777
1778                                if (imgAr) {
1779                                    divA.style.backgroundImage = 'url(' + (imgAr[1] || imgAr[0]) + ')'; // 0 here for defaultArtwork fix
1780
1781                                    ot.onPlayedArtworks(imgAr[1] || imgAr[0]) // Event
1782
1783                                } else {
1784
1785                                    divA.className = 'kast-p-art kast-p-default-art';
1786                                }
1787
1788                            })
1789                        } else {
1790                            divA.className = 'kast-p-art kast-p-default-art';
1791                        }
1792
1793                        ot.onPlayedInfo(playedTrack, playedArtist) // Event
1794
1795                        songsFrag.appendChild(li)
1796
1797                    }
1798                    var jV = (!data.body) ? 1 : 2; // // i = 1; Not first child. P.S. V1 must skip the first two childs
1799                    for (var j = jV; j < songL; j++) {
1800                        processPL(j, song, songL, songsFrag)
1801                    }
1802
1803                    kastPL.appendChild(songsFrag); // Append at last, like a boss
1804                },
1805                ajaxPlayed = function (hostCORSproxy, showError) {
1806                    var url = hostCORSproxy + ot.playedPath + '?sid=' + ot.sid + '&type=xml',
1807                        urlParm = (ot.version === 1) ? '#' : '&_='; // V1 servers are dumb
1808                    if (ot.version === 1) {
1809                        url = hostCORSproxy + ot.playedPath + '.html';
1810                    }
1811
1812
1813                    XHR({
1814                        url: url + urlParm + Math.random(),
1815                        timeout: 8000,
1816                        load: function (data) {
1817                            // ** Bug in crossorigin.me, data sometimes is string 'Error: Error: Parse Error' (500 Error)
1818                            // ** reported https://github.com/technoboy10/crossorigin.me/issues/28
1819
1820                            // ** becuz text/html has lower browser support
1821
1822
1823                            var dataDOM = (data.indexOf('<?xml') > -1) ? new DOMParser().parseFromString(data, 'text/xml') : createHTMLDocument(data);
1824
1825                            if (callback) {
1826                                callback(dataDOM)
1827                            } else {
1828                                processPlayed(dataDOM)
1829                            }
1830                        },
1831                        error: function (data) {
1832                            if (showError) {
1833                                if (!kastPL.hasChildNodes()) {
1834                                    processPlayed('error')
1835                                }
1836                            } else {
1837                                // -- -- fallback to another proxy and show error this time
1838                                ajaxPlayed(that.hostCORS[ot.betaProxies ? 3 : 1], true)
1839                            }
1840                        }
1841                    })
1842
1843
1844                },
1845                directPlayed = function () {
1846
1847                    $.ajax({
1848                        url: that.host + ot.playedPath + '?sid=' + ot.sid + '&type=json',
1849                        timeout: 8000,
1850                        dataType: 'jsonp',
1851                        success: function (data) {
1852                            if (callback) {
1853                                callback(data)
1854                            } else {
1855                                processPlayed(data)
1856                            }
1857                        },
1858                        error: function () {
1859                            if (!kastPL.hasChildNodes()) {
1860                                processPlayed('error')
1861                            }
1862                        }
1863                    })
1864
1865                }
1866
1867            if (ot.direct) {
1868                directPlayed()
1869            } else {
1870                ajaxPlayed(that.hostCORS[ot.betaProxies ? 2 : 0], false)
1871            }
1872
1873            return this
1874        },
1875        autoUpdate: function (uKey) {
1876            var that = this,
1877                ot = that.opt;
1878
1879            // -- Cached selectors
1880            var kast = document.getElementById('kast'),
1881                kastServer = document.getElementById('kast-server'),
1882                kastPlay = document.getElementById('kast-play'),
1883                kastC;
1884
1885            if (classL) {
1886                kastC = kast.classList;
1887            }
1888
1889            // -- Turn off
1890            if (!uKey) {
1891                clearInterval(kast.getAttribute('data-interval'))
1892                kast.removeAttribute('data-interval')
1893                return
1894            }
1895
1896            // -- Checker fn to check for songtitle change
1897            var checker = function () {
1898                    that.stats(function (data) {
1899
1900                        var isPlaying = (kastPlay.className.indexOf('playing') !== -1);
1901
1902                        // -- Offline check
1903                        if (ot.offlineCheck) {
1904
1905                            var SS = ot.direct ? data.streamstatus : (data.body) ? data.body.textContent.split(',')[1] : data.getElementsByTagName('STREAMSTATUS')[0].textContent;
1906
1907                            var isOffline = kast.hasAttribute('data-offline');
1908
1909                            if (!data || SS != 1) { // Yes! String not integer
1910
1911                                if (!isOffline) {
1912                                    if (classL) {
1913                                        kastC.add('kast-offline') // I already have classList cached
1914                                    } else {
1915                                        $(kast).addClass('kast-offline')
1916                                    }
1917
1918                                    kast.setAttribute('data-offline', 'true');
1919                                    ot.onOffline();
1920                                }
1921
1922                            } else {
1923
1924                                if (isOffline) {
1925                                    if (classL) {
1926                                        kastC.remove('kast-offline')
1927                                    } else {
1928                                        $(kast).removeClass('kast-offline')
1929                                    }
1930
1931                                    //isPlaying && that.play(true); // we need true
1932
1933                                    kast.setAttribute('data-offline', 'false');
1934                                    ot.onOnline()
1935                                }
1936                            }
1937
1938                        }
1939
1940
1941                        // ** When offline->online back again, we call play() again
1942                        // ** and there's bug in some SHOUTcast servers (AAC+) or maybe in Chrome,
1943                        // ** sometimes the audio goes off for no reason,
1944                        // ** so in order to play, we need to pause->play to play audio,
1945                        // ** that's no longer necessary 
1946                        isPlaying && that.play();
1947
1948                        // -- update server info 
1949                        if (uKey === 'all') {
1950                            var sI = ot.serverInfo,
1951                                sB = ot.statusBar;
1952                            if (typeof sI !== 'string' && sB) { // I don't update custom server info
1953                                kastServer.textContent = '';
1954                                processServerInfo(ot, sI, data, kastServer)
1955                            }
1956                        }
1957
1958                        // ** If both are string, and played is false
1959                        // ** then there's absolutely no need to check for songtitle and continue
1960                        var customTA = (typeof ot.currentTrack === 'string' && typeof ot.currentArtist === 'string');
1961                        if (customTA && !ot.played) {
1962                            ot.onUpdate(data) // Event
1963                            ot.onUpdateAll(data) // Event
1964                            return
1965                        }
1966
1967                        var streamSTitle = ot.direct ? data.songtitle : (data.body) ? data.body.textContent.split(',')[6] : data.getElementsByTagName('SONGTITLE')[0].textContent, // V2 || V1
1968                            currentSTitle = kast.getAttribute('data-current');
1969
1970                        // ** on start, data is undefined, so let's define it
1971                        if (!currentSTitle) {
1972                            return kast.setAttribute('data-current', streamSTitle);
1973                        }
1974
1975                        if (currentSTitle !== streamSTitle) {
1976                            // ** These functions will only be called on SHOUTcast songtitle change, not every 5 seconds,
1977                            // ** So if there's some heavy DOM manipulation here, no problem at all
1978
1979                            if (!customTA) { // I don't update custom T/A *if both are string
1980                                that.stats(data);
1981                            }
1982
1983                            if (ot.played) {
1984                                var isArtArr = (Array.isArray && Array.isArray(ot.artwork));
1985                                if (isArtArr) {
1986                                    that.played(false, true)
1987                                } else {
1988                                    that.played()
1989                                }
1990                            }
1991
1992
1993                            kast.setAttribute('data-current', streamSTitle);
1994
1995
1996                            ot.onUpdate(data) // Event
1997                        }
1998
1999                        ot.onUpdateAll(data) // Event
2000                    })
2001                },
2002                setTimer = function (fn, time) {
2003                    if (!kast.hasAttribute('data-interval')) {
2004                        var setInterID = setInterval(fn, time);
2005                        kast.setAttribute('data-interval', setInterID)
2006                    }
2007                },
2008                timeMS = 8000;
2009
2010            if (that.mobile) {
2011                timeMS = (that.mobileMedium) ? 16000 : 12000;
2012            }
2013
2014            setTimer(checker, ot.customCheckerInterval || timeMS)
2015
2016            return this
2017        },
2018        artwork: function (artist, track, callback) {
2019            // -- Pull album artwork from spotify API
2020            // -- or artist artwork as fallback
2021            // -- or Default art as fallback
2022            // -- return it
2023
2024            var that = this,
2025                ot = this.opt;
2026
2027            var defaultArtwork = (Array.isArray && Array.isArray(ot.defaultArtwork)) ? ot.defaultArtwork : null;
2028
2029            if (Array.isArray && Array.isArray(ot.artwork)) {
2030                return callback(ot.artwork)
2031            }
2032
2033            if (artist === ot.language.unknownArtistText) { // If artist is unknown
2034                return callback(defaultArtwork)
2035            }
2036
2037            var artistC = chaRe(artist),
2038                trackC = chaRe(track),
2039                processArtworkX = function (data) { // @retuns images array if found or null
2040                    var imgAr = null;
2041                    if (data.tracks && data.tracks.items[0]) {
2042                        var imagesArrayT = data.tracks.items[0].album.images || '',
2043                            imgLarge,
2044                            imgSmall = imagesArrayT[2].url || '';
2045
2046                        if (that.mobileMedium) { // ** Mobile first :)
2047                            imgLarge = imagesArrayT[1].url || ''; // ** Medium quailty
2048                        } else {
2049                            imgLarge = imagesArrayT[0].url || '';
2050                        }
2051
2052                        if (imgLarge && imgSmall) {
2053                            imgAr = [imgLarge, imgSmall];
2054                        }
2055                    } else if (data.artists && data.artists.items[0] && data.artists.items[0].images[0]) {
2056                        var imagesArrayA = data.artists.items[0].images || '',
2057                            imgLarge = null,
2058                            imgSmall = null;
2059
2060                        if (data.artists.items[0].images.length === 4) { // ** Not normal artist images                       
2061                            if (that.mobileMedium) { // ** Mobile first :)
2062                                imgLarge = imagesArrayA[2].url || ''; // ** Medium quailty
2063                            } else {
2064                                imgLarge = imagesArrayA[1].url || '';
2065                            }
2066
2067                            imgSmall = imagesArrayA[3].url || '';
2068
2069                        } else {
2070                            if (that.mobileMedium) { // ** Mobile first :)
2071                                imgLarge = imagesArrayA[1].url || ''; // ** Medium quailty
2072                            } else {
2073                                imgLarge = imagesArrayA[0].url || '';
2074                            }
2075
2076                            imgSmall = imagesArrayA[2].url || '';
2077                        }
2078                        if (imgLarge && imgSmall) {
2079                            imgAr = [imgLarge, imgSmall];
2080                        }
2081                    }
2082
2083                    return imgAr
2084                },
2085                ajaxArtwork = function (artistN, trackN, noLuck) {
2086                    var spotifyTrackAPI = 'https://api.spotify.com/v1/search?q=artist:' + artistN + '%20track:' + trackN + '&limit=1&type=track',
2087                        spotifyArtistAPI = 'https://api.spotify.com/v1/search?q=artist:' + artistN + '&limit=1&type=artist',
2088                        spotifyAPI = noLuck ? spotifyArtistAPI : spotifyTrackAPI;
2089
2090                    // Smart pulling
2091                    XHR({
2092                        url: spotifyAPI,
2093                        load: function (data) {
2094                            var dataJSON = JSON.parse(data);
2095                            var imgAr = processArtworkX(dataJSON); // @returns artwork array or null
2096                            if (imgAr) {
2097                                callback(imgAr)
2098                            } else {
2099
2100                                if (noLuck) {
2101                                    callback(defaultArtwork)
2102                                } else {
2103                                    ajaxArtwork(artistN, trackN, true) // ** this time get artist art and if !imgAr return null
2104                                }
2105
2106                            }
2107                        },
2108                        error: function () {
2109                            callback(defaultArtwork)
2110                        }
2111                    })
2112                },
2113                iW = ot.irrelevantWords;
2114
2115            if (iW) {
2116                var iWL = iW.length;
2117                for (var i = 0; i < iWL; i++) {
2118                    artistC = artistC.replace(iW[i], '')
2119                    trackC = trackC.replace(iW[i], '')
2120                }
2121
2122                ajaxArtwork(artistC, trackC) // smart artwork pulling
2123
2124            } else {
2125                ajaxArtwork(artistC, trackC) // smart artwork pulling
2126            }
2127
2128
2129
2130            return this
2131        },
2132        destroy: function () {
2133
2134            // -- Destroy the plugin     
2135            window.kastXHR && window.kastXHR.abort(); // Terminate any active XHR
2136
2137            this.autoUpdate(false); // Clear AutoUpdate data
2138            this.audio.pause(); // Pause audio
2139
2140            $.data(window, 'plugin_kast', null); // Clear Kast data
2141            var k = document.getElementById('kast');
2142            k && k.parentNode.removeChild(k) // Remove Kast element
2143            
2144            _popup && _popup.close();
2145            
2146			// remove BNJ AddOn
2147			$('#pg-progress-bar').unbind('click');
2148			$('#pg-current').width(0);
2149			$('#pg-current').remove();
2150			//$('#pg-current').css('width', '0px');
2151									
2152            // redefine document, if touched when using popup option
2153            document = window.document;
2154
2155            return this
2156        }
2157    }
2158
2159    // Plugin wrapper
2160    // ======================================================== //
2161    $.kast = function (options, keys) {
2162
2163        if (!$.data(window, 'plugin_kast')) {
2164
2165            if (options === 'destroy') {
2166                return
2167            }
2168
2169            $.data(window, 'plugin_kast', new Plugin(this, options));
2170        } else if (typeof Plugin.prototype[options] === 'function') {
2171            $.data(window, 'plugin_kast')[options](keys);
2172        }
2173
2174    }
2175
2176
2177})(jQuery, window, document);

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.