1<?php
2
3use Ampache\Config\AmpConfig;
4use Ampache\Module\Broadcast\Broadcast_Server;
5use Ampache\Module\Playback\Stream;
6use Ampache\Module\Util\AjaxUriRetrieverInterface;
7use Ampache\Module\Util\Ui;
8
9global $dic;
10$ajaxUriRetriever = $dic->get(AjaxUriRetrieverInterface::class);
11$web_path         = AmpConfig::get('web_path');
12$cookie_string    = (make_bool(AmpConfig::get('cookie_secure')))
13    ? "path: '/', secure: true, samesite: 'Strict'"
14    : "path: '/', samesite: 'Strict'";
15
16if ($iframed || $is_share) { ?>
17<link rel="stylesheet" href="<?php echo $web_path . Ui::find_template('jplayer.midnight.black-iframed.css', true) ?>" type="text/css" />
18<?php
19} else { ?>
20<link rel="stylesheet" href="<?php echo $web_path . Ui::find_template('jplayer.midnight.black.css', true) ?>" type="text/css" />
21<?php
22    }
23
24if (!$iframed) {
25    require_once Ui::find_template('stylesheets.inc.php'); ?>
26<link rel="stylesheet" href="<?php echo $web_path . Ui::find_template('jquery-editdialog.css', true); ?>" type="text/css" media="screen" />
27<link rel="stylesheet" href="<?php echo $web_path; ?>/lib/modules/jquery-ui-ampache/jquery-ui.min.css" type="text/css" media="screen" />
28<script src="<?php echo $web_path; ?>/lib/components/jquery/jquery.min.js"></script>
29<script src="<?php echo $web_path; ?>/lib/components/jquery-ui/jquery-ui.min.js"></script>
30<script src="<?php echo $web_path; ?>/lib/components/js-cookie/js-cookie-built.js"></script>
31<script src="<?php echo $web_path; ?>/lib/javascript/base.js"></script>
32<script src="<?php echo $web_path; ?>/lib/javascript/ajax.js"></script>
33<script src="<?php echo $web_path; ?>/lib/javascript/tools.js"></script>
34<script>
35var jsAjaxServer = "<?php echo $ajaxUriRetriever->getAjaxServerUri(); ?>";
36var jsAjaxUrl = "<?php echo $ajaxUriRetriever->getAjaxUri(); ?>";
37
38function update_action()
39{
40    // Stub
41}
42</script>
43<?php
44} ?>
45<link href="<?php echo $web_path; ?>/lib/modules/UberViz/style.css" rel="stylesheet" type="text/css">
46<?php if (AmpConfig::get('webplayer_aurora')) { ?>
47    <script src="<?php echo $web_path; ?>/lib/modules/aurora.js/aurora.js"></script>
48<?php
49} ?>
50<script src="<?php echo $web_path; ?>/lib/components/happyworm-jplayer/dist/jplayer/jquery.jplayer.min.js"></script>
51<script src="<?php echo $web_path; ?>/lib/components/happyworm-jplayer/dist/add-on/jplayer.playlist.min.js"></script>
52<script src="<?php echo $web_path; ?>/lib/javascript/jplayer.ext.js"></script>
53<script src="<?php echo $web_path; ?>/lib/javascript/jplayer.playlist.ext.js"></script>
54
55<script>
56var jplaylist = new Array();
57var jtypes = new Array();
58
59function convertMediaToJPMedia(media)
60{
61    var jpmedia = {};
62    jpmedia['title'] = media['title'];
63    jpmedia['artist'] = media['artist'];
64    jpmedia[media['filetype']] = media['url'];
65    jpmedia['poster'] = media['poster'];
66    jpmedia['artist_id'] = media['artist_id'];
67    jpmedia['album_id'] = media['album_id'];
68    jpmedia['media_id'] = media['media_id'];
69    jpmedia['media_type'] = media['media_type'];
70    jpmedia['replaygain_track_gain'] = media['replaygain_track_gain'];
71    jpmedia['replaygain_track_peak'] = media['replaygain_track_peak'];
72    jpmedia['replaygain_album_gain'] = media['replaygain_album_gain'];
73    jpmedia['replaygain_album_peak'] = media['replaygain_album_peak'];
74    jpmedia['r128_track_gain'] = media['r128_track_gain'];
75    jpmedia['r128_album_gain'] = media['r128_album_gain'];
76
77    return jpmedia;
78}
79
80function addMedia(media)
81{
82    var jpmedia = convertMediaToJPMedia(media);
83    jplaylist.add(jpmedia);
84}
85
86function playNext(media)
87{
88    var jpmedia = convertMediaToJPMedia(media);
89    jplaylist.addAfter(jpmedia, jplaylist.current);
90}
91</script>
92<script>
93function ExitPlayer()
94{
95    $("#webplayer").text('');
96    $("#webplayer").hide();
97
98<?php
99if (AmpConfig::get('song_page_title')) {
100        echo "window.parent.document.title = '" . addslashes(AmpConfig::get('site_title')) . "';";
101    } ?>
102    document.onbeforeunload = null;
103}
104
105function TogglePlayerVisibility()
106{
107    if ($("#webplayer").is(":visible")) {
108        $("#webplayer").slideUp();
109    } else {
110        $("#webplayer").slideDown();
111    }
112}
113
114function TogglePlaylistExpand()
115{
116    if ($(".jp-playlist").css("opacity") !== '1') {
117        $(".jp-playlist").css('top', '-255%');
118        $(".jp-playlist").css('opacity', '1');
119        $(".jp-playlist").css('height', '350%');
120    } else {
121        $(".jp-playlist").css('top', '0px');
122        $(".jp-playlist").css('opacity', '0.9');
123        $(".jp-playlist").css('height', '95%');
124    }
125}
126</script>
127<?php
128if ($iframed) { ?>
129<script>
130function NotifyOfNewSong(title, artist, icon)
131{
132    if (artist === null) {
133        artist = '';
134    }
135    if (!("Notification" in window)) {
136        console.error("This browser does not support desktop notification");
137    } else {
138        if (Notification.permission !== 'denied') {
139            if (Notification.permission === 'granted') {
140                NotifyBrowser(title, artist, icon);
141            } else {
142                Notification.requestPermission(function (permission) {
143                    if (!('permission' in Notification)) {
144                        Notification.permission = permission;
145                    }
146                    NotifyBrowser(title, artist, icon);
147                });
148            }
149        } else {
150            console.error("Desktop notification denied.");
151        }
152    }
153}
154
155function NotifyBrowser(title, artist, icon)
156{
157    var notyTimeout = <?php echo AmpConfig::get('browser_notify_timeout'); ?>;
158    var notification = new Notification(title, {
159        body: artist,
160        icon: icon,
161        silent: true
162    });
163
164    if (notyTimeout > 0) {
165        setTimeout(function(){
166            notification.close()
167        }, notyTimeout * 1000);
168    }
169}
170
171function NotifyOfNewArtist()
172{
173    refresh_slideshow();
174}
175
176function SwapSlideshow()
177{
178    swap_slideshow();
179}
180
181function initAudioContext()
182{
183    if (typeof AudioContext !== 'undefined') {
184        audioContext = new AudioContext();
185    } else if (typeof webkitAudioContext !== 'undefined') {
186        audioContext = new webkitAudioContext();
187    } else {
188        audioContext = null;
189    }
190}
191
192function isVisualizerEnabled()
193{
194    return ($('#uberviz').css('visibility') == 'visible');
195}
196
197var vizInitialized = false;
198var vizPrevHeaderColor = "#000";
199var vizPrevPlayerColor = "#000";
200function ShowVisualizer()
201{
202    if (isVisualizerEnabled()) {
203        $('#uberviz').css('visibility', 'hidden');
204        $('#equalizerbtn').css('visibility', 'hidden');
205        $('#equalizer').css('visibility', 'hidden');
206        $('#header').css('background-color', vizPrevHeaderColor);
207        $('#webplayer').css('background-color', vizPrevPlayerColor);
208        $('.jp-interface').css('background-color', 'rgb(25, 25, 25)');
209        $('.jp-playlist').css('background-color', 'rgb(20, 20, 20)');
210    } else {
211        // Resource not yet initialized? Do it.
212        if (!vizInitialized) {
213            if ((typeof AudioContext !== 'undefined') || (typeof webkitAudioContext !== 'undefined')) {
214                UberVizMain.init();
215                vizInitialized = true;
216                AudioHandler.loadMediaSource($('.jp-jplayer').find('audio').get(0));
217            }
218        }
219
220        if (vizInitialized) {
221            $('#uberviz').css('visibility', 'visible');
222            $('#equalizerbtn').css('visibility', 'visible');
223            vizPrevHeaderColor = $('#header').css('background-color');
224            $('#header').css('background-color', 'transparent');
225            vizPrevPlayerColor = $('#webplayer').css('background-color');
226            $('#webplayer').css('cssText', 'background-color: #000 !important;');
227            $('#webplayer').show();
228            $("#webplayer-minimize").show();
229            $('.jp-interface').css('background-color', '#000');
230            $('.jp-playlist').css('background-color', '#000');
231        } else {
232            alert("<?php echo T_("Your browser doesn't support this feature."); ?>");
233        }
234    }
235}
236
237function ShowVisualizerFullScreen()
238{
239    if (!isVisualizerEnabled()) {
240        ShowVisualizer();
241    }
242
243    var element = document.getElementById("viz");
244    if (element.requestFullScreen) {
245        element.requestFullScreen();
246    } else if (element.webkitRequestFullScreen) {
247        element.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
248    } else if (element.mozRequestFullScreen) {
249        element.mozRequestFullScreen();
250    } else {
251        alert('Full-Screen not supported by your browser');
252    }
253}
254
255function ShowEqualizer()
256{
257    if (isVisualizerEnabled()) {
258        if ($('#equalizer').css('visibility') == 'visible') {
259            $('#equalizer').css('visibility', 'hidden');
260        } else {
261            $('#equalizer').css('visibility', 'visible');
262        }
263    }
264}
265
266function SavePlaylist()
267{
268    if (jplaylist['playlist'].length > 0) {
269        var url = "<?php echo $ajaxUriRetriever->getAjaxUri(); ?>?page=playlist&action=append_item&item_type=" + jplaylist['playlist'][0]["media_type"] + "&item_id=";
270        for (var i = 0; i < jplaylist['playlist'].length; i++) {
271            url += "," + jplaylist['playlist'][i]["media_id"];
272        }
273        handlePlaylistAction(url, 'rb_append_dplaylist_new');
274    }
275}
276
277function SaveToExistingPlaylist(event)
278{
279    if (jplaylist['playlist'].length > 0) {
280        var item_ids = "";
281        for (var i = 0; i < jplaylist['playlist'].length; i++) {
282            item_ids += "," + jplaylist['playlist'][i]["media_id"];
283        }
284        showPlaylistDialog(event, jplaylist['playlist'][0]["media_type"], item_ids);
285    }
286}
287
288var audioContext = null;
289var mediaSource = null;
290var replaygainEnabled = false;
291var replaygainNode = null;
292initAudioContext();
293
294function ToggleReplayGain()
295{
296    if (replaygainNode === null) {
297        var mediaElement = $('.jp-jplayer').find('audio').get(0);
298        if (mediaElement) {
299            if (audioContext !== null) {
300                mediaSource = audioContext.createMediaElementSource(mediaElement);
301                replaygainNode = audioContext.createGain();
302                replaygainNode.gain.value = 1;
303                mediaSource.connect(replaygainNode);
304                replaygainNode.connect(audioContext.destination);
305            }
306        }
307    }
308
309    if (replaygainNode != null) {
310        replaygainEnabled = !replaygainEnabled;
311        Cookies.set('replaygain', replaygainEnabled, {<?php echo $cookie_string ?>});
312        ApplyReplayGain();
313
314        if (replaygainEnabled) {
315            $('#replaygainbtn').css('box-shadow', '0px 1px 0px 0px orange');
316        } else {
317            $('#replaygainbtn').css('box-shadow', '');
318        }
319    }
320}
321
322function ApplyReplayGain()
323{
324    if (replaygainNode != null) {
325        var gainlevel = 1;
326        var replaygain = 0;
327        var peakamplitude = 1;
328        if (replaygainEnabled && currentjpitem != null) {
329            var replaygain_track_gain   = currentjpitem.attr("data-replaygain_track_gain");
330            var r128_track_gain = currentjpitem.attr("data-r128_track_gain");
331
332            if (r128_track_gain !== 'null') {
333                // R128 PREFERRED
334                replaygain = parseInt(r128_track_gain / 256); // LU/dB away from baseline of -23 LUFS/dB, stored as Q7.8 (2 ^ 8) https://tools.ietf.org/html/rfc7845.html#page-25
335                var referenceLevel = parseInt(-23); // LUFS https://en.wikipedia.org/wiki/EBU_R_128#Specification
336                var targetLevel = parseInt(-18); // LUFS/dB;
337                var masteredVolume = referenceLevel - replaygain;
338                var difference = targetLevel - masteredVolume;
339
340                gainlevel = (Math.pow(10, ((difference /* + Gpre-amp */) / 20)));
341            } else if (replaygain_track_gain !== 'null') {
342                // REPLAYGAIN FALLBACK
343                replaygain = parseFloat(replaygain_track_gain);
344
345                if (replaygain != null) {
346                    var track_peak = currentjpitem.attr("data-replaygain_track_peak");
347                    if (track_peak !== 'null') {
348                        peakamplitude = parseFloat(track_peak);
349                    }
350                    gainlevel = Math.min(Math.pow(10, ((replaygain /* + Gpre-amp */) / 20)), (1 / peakamplitude));
351                }
352            }
353        }
354
355        replaygainNode.gain.value = gainlevel;
356    }
357}
358</script>
359<?php
360} ?>
361<script>
362<?php if (AmpConfig::get('waveform') && !$is_share) { ?>
363var wavclicktimer = null;
364var shouts = {};
365function WaveformClick(songid, time)
366{
367    // Double click
368    if (wavclicktimer != null) {
369        clearTimeout(wavclicktimer);
370        wavclicktimer = null;
371        NavigateTo('<?php echo $web_path ?>/shout.php?action=show_add_shout&type=song&id=' + songid + '&offset=' + time);
372    } else {
373        // Single click
374        if (brconn === null) {
375            wavclicktimer = setTimeout(function() {
376                wavclicktimer = null;
377                $("#jquery_jplayer_1").data("jPlayer").play(time);
378            }, 250);
379        }
380    }
381}
382
383function ClickTimeOffset(e)
384{
385    var parrentOffset = $(".waveform").offset().left;
386    var offset = e.pageX - parrentOffset;
387    var duration = $("#jquery_jplayer_1").data("jPlayer").status.duration;
388    var time = duration * (offset / 400);
389
390    return time;
391}
392
393function ShowWaveform()
394{
395    $('.waveform').css('visibility', 'visible');
396}
397
398function HideWaveform()
399{
400    $('.waveform').css('visibility', 'hidden');
401}
402<?php
403    } ?>
404
405var brkey = '';
406var brconn = null;
407
408function startBroadcast(key)
409{
410    brkey = key;
411
412    listenBroadcast();
413    brconn.onopen = function(e) {
414        sendBroadcastMessage('AUTH_SID', '<?php echo session_id(); ?>');
415        sendBroadcastMessage('REGISTER_BROADCAST', brkey);
416        sendBroadcastMessage('SONG', currentjpitem.attr("data-media_id"));
417    };
418}
419
420function startBroadcastListening(broadcast_id)
421{
422    listenBroadcast();
423
424    // Hide few UI information on listening mode
425    $('.jp-previous').css('visibility', 'hidden');
426    $('.jp-play').css('visibility', 'hidden');
427    $('.jp-pause').css('visibility', 'hidden');
428    $('.jp-next').css('visibility', 'hidden');
429    $('.jp-stop').css('visibility', 'hidden');
430    $('.jp-toggles').css('visibility', 'hidden');
431    $('.jp-playlist').css('visibility', 'hidden');
432    $('#broadcast').css('visibility', 'hidden');
433
434    $('.jp-seek-bar').css('pointer-events', 'none');
435
436    brconn.onopen = function(e) {
437        sendBroadcastMessage('AUTH_SID', '<?php echo Stream::get_session(); ?>');
438        sendBroadcastMessage('REGISTER_LISTENER', broadcast_id);
439    };
440}
441
442function listenBroadcast()
443{
444    if (brconn != null) {
445        stopBroadcast();
446    }
447
448    brconn = new WebSocket('<?php echo Broadcast_Server::get_address(); ?>');
449    brconn.onmessage = receiveBroadcastMessage;
450}
451
452var brLoadingSong = false;
453var brBufferingSongPos = -1;
454
455function receiveBroadcastMessage(e)
456{
457    var jp = $("#jquery_jplayer_1").data("jPlayer");
458    var msgs = e.data.split(';');
459
460    for (var i = 0; i < msgs.length; ++i) {
461        var msg = msgs[i].split(':');
462        if (msg.length == 2) {
463            switch (msg[0]) {
464                case 'PLAYER_PLAY':
465                    if (msg[1] == '1') {
466                        if (jp.status.paused) {
467                            jp.play();
468                        }
469                    } else {
470                        if (!jp.status.paused) {
471                            jp.pause();
472                        }
473                    }
474                    break;
475                case 'SONG':
476                    addMedia($.parseJSON(atob(msg[1])));
477                    brLoadingSong = true;
478                    // Buffering song position in case it is asked in the next sec.
479                    // Otherwise we will move forward on the previous song instead of the new currently loading one
480                    setTimeout(function() {
481                        if (brBufferingSongPos > -1) {
482                            jp.play(brBufferingSongPos);
483                            brBufferingSongPos = -1;
484                        }
485                        brLoadingSong = false;
486                    }, 1000);
487                    jplaylist.next();
488                    break;
489                case 'SONG_POSITION':
490                    if (brLoadingSong) {
491                        brBufferingSongPos = parseFloat(msg[1]);
492                    } else {
493                        jp.play(parseFloat(msg[1]));
494                    }
495                    break;
496                case 'NB_LISTENERS':
497                    $('#broadcast_listeners').html(msg[1]);
498                    break;
499                case 'INFO':
500                    // Display information notification to user here
501                    break;
502                case 'ENDED':
503                    jp.stop();
504                    break;
505                default:
506                    alert('Unknown message code');
507                    break;
508            }
509        }
510    }
511}
512
513function sendBroadcastMessage(cmd, value)
514{
515    if (brconn != null && brconn.readyState == 1) {
516        var msg = cmd + ':' + value + ';';
517        brconn.send(msg);
518    }
519}
520
521function stopBroadcast()
522{
523    brkey = '';
524    if (brconn != null && brconn.readyState == 1) {
525        brconn.close();
526    }
527    brconn = null;
528}
529
530<?php if ($iframed && AmpConfig::get('webplayer_confirmclose') && !$is_share) { ?>
531window.parent.onbeforeunload = function (evt) {
532    if ($("#jquery_jplayer_1") !== undefined && $("#jquery_jplayer_1").data("jPlayer") !== undefined && !$("#jquery_jplayer_1").data("jPlayer").status.paused &&
533            (document.activeElement === undefined || (document.activeElement.href.indexOf('/batch.php') < 0 && document.activeElement.href.indexOf('/stream.php') < 0))) {
534        var message = '<?php echo T_('Media is currently playing, are you sure you want to close?') . ' ' . AmpConfig::get('site_title') . '?'; ?>';
535        if (typeof evt == "undefined") {
536            evt = window.event;
537        }
538        if (evt) {
539            evt.returnValue = message;
540        }
541        return message;
542    }
543
544    return null;
545}
546<?php
547    } ?>
548<?php if ($iframed && AmpConfig::get('webplayer_confirmclose') && !$is_share) { ?>
549window.addEventListener('storage', function (event) {
550  if (event.key == 'ampache-current-webplayer') {
551    // The latest used webplayer is not this player, pause song if playing
552    if (event.newValue != jpuqid) {
553        if (!$("#jquery_jplayer_1").data("jPlayer").status.paused) {
554            $("#jquery_jplayer_1").data("jPlayer").pause();
555        }
556    }
557  }
558});
559<?php
560    } ?>
561</script>
562