You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1044 lines
43 KiB
1044 lines
43 KiB
<!DOCTYPE html>
|
|
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
|
|
<html><head>
|
|
<%@ include file="head.jsp" %>
|
|
<%@ include file="jquery.jsp" %>
|
|
<script type="text/javascript" src="<c:url value='/script/utils.js'/>"></script>
|
|
<script type="text/javascript" src="<c:url value='/dwr/interface/nowPlayingService.js'/>"></script>
|
|
<script type="text/javascript" src="<c:url value='/dwr/interface/playQueueService.js'/>"></script>
|
|
<script type="text/javascript" src="<c:url value='/dwr/interface/playlistService.js'/>"></script>
|
|
<script type="text/javascript" src="<c:url value='/dwr/engine.js'/>"></script>
|
|
<script type="text/javascript" src="<c:url value='/dwr/util.js'/>"></script>
|
|
<script type="text/javascript" src="<c:url value='/script/mediaelement/mediaelement-and-player.min.js'/>"></script>
|
|
<script type="text/javascript" src="<c:url value='/script/playQueueCast.js'/>"></script>
|
|
<style type="text/css">
|
|
.ui-slider .ui-slider-handle {
|
|
width: 11px;
|
|
height: 11px;
|
|
cursor: pointer;
|
|
}
|
|
.ui-slider a {
|
|
outline:none;
|
|
}
|
|
.ui-slider {
|
|
cursor: pointer;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body class="bgcolor2 playlistframe" onload="init()">
|
|
|
|
<span id="dummy-animation-target" style="max-width: ${model.autoHide ? 50 : 150}px; display: none"></span>
|
|
|
|
<script type="text/javascript" language="javascript">
|
|
|
|
// These variables store the media player state, received from DWR in the
|
|
// playQueueCallback function below.
|
|
|
|
// List of songs (of type PlayQueueInfo.Entry)
|
|
var songs = null;
|
|
|
|
// Stream URL of the media being played
|
|
var currentStreamUrl = null;
|
|
|
|
// Is autorepeat enabled?
|
|
var repeatEnabled = false;
|
|
|
|
// Is the "shuffle radio" playing? (More > Shuffle Radio)
|
|
var shuffleRadioEnabled = false;
|
|
|
|
// Is the "internet radio" playing?
|
|
var internetRadioEnabled = false;
|
|
|
|
// Is the play queue visible? (Initially hidden if set to "auto-hide" in the settings)
|
|
var isVisible = ${model.autoHide ? 'false' : 'true'};
|
|
|
|
// Initialize the Cast player (ChromeCast support)
|
|
var CastPlayer = new CastPlayer();
|
|
|
|
function init() {
|
|
<c:if test="${model.autoHide}">initAutoHide();</c:if>
|
|
|
|
dwr.engine.setErrorHandler(null);
|
|
startTimer();
|
|
|
|
$("#dialog-select-playlist").dialog({resizable: true, height: 220, autoOpen: false,
|
|
buttons: {
|
|
"<fmt:message key="common.cancel"/>": function() {
|
|
$(this).dialog("close");
|
|
}
|
|
}});
|
|
|
|
<c:if test="${model.player.web}">createMediaElementPlayer();</c:if>
|
|
|
|
$("#playlistBody").sortable({
|
|
stop: function(event, ui) {
|
|
var indexes = [];
|
|
$("#playlistBody").children().each(function() {
|
|
var id = $(this).attr("id").replace("pattern", "");
|
|
if (id.length > 0) {
|
|
indexes.push(parseInt(id) - 1);
|
|
}
|
|
});
|
|
onRearrange(indexes);
|
|
},
|
|
cursor: "move",
|
|
axis: "y",
|
|
containment: "parent",
|
|
helper: function(e, tr) {
|
|
var originals = tr.children();
|
|
var trclone = tr.clone();
|
|
trclone.children().each(function(index) {
|
|
// Set cloned cell sizes to match the original sizes
|
|
$(this).width(originals.eq(index).width());
|
|
$(this).css("maxWidth", originals.eq(index).width());
|
|
$(this).css("border-top", "1px solid black");
|
|
$(this).css("border-bottom", "1px solid black");
|
|
});
|
|
return trclone;
|
|
}
|
|
});
|
|
|
|
/** Toggle between <a> and <span> in order to disable play queue action buttons */
|
|
$.fn.toggleLink = function(newState) {
|
|
$(this).each(function(ix, elt) {
|
|
|
|
var node, currentState;
|
|
if (elt.tagName.toLowerCase() === "a") currentState = true;
|
|
else if (elt.tagName.toLowerCase() === "span") currentState = false;
|
|
else return true;
|
|
if (typeof newState === 'undefined') newState = !currentState;
|
|
if (newState === currentState) return true;
|
|
|
|
if (newState) node = document.createElement("a");
|
|
else node = document.createElement("span");
|
|
|
|
node.innerHTML = elt.innerHTML;
|
|
if (elt.hasAttribute("id")) node.setAttribute("id", elt.getAttribute("id"));
|
|
if (elt.hasAttribute("style")) node.setAttribute("style", elt.getAttribute("style"));
|
|
if (elt.hasAttribute("class")) node.setAttribute("class", elt.getAttribute("class"));
|
|
|
|
if (newState) {
|
|
if (elt.hasAttribute("data-href")) node.setAttribute("href", elt.getAttribute("data-href"));
|
|
node.classList.remove("disabled");
|
|
node.removeAttribute("aria-disabled");
|
|
} else {
|
|
if (elt.hasAttribute("href")) node.setAttribute("data-href", elt.getAttribute("href"));
|
|
node.classList.add("disabled");
|
|
node.setAttribute("aria-disabled", "true");
|
|
}
|
|
|
|
elt.parentNode.replaceChild(node, elt);
|
|
return true;
|
|
});
|
|
};
|
|
|
|
getPlayQueue();
|
|
}
|
|
|
|
function onHidePlayQueue() {
|
|
setFrameHeight(50);
|
|
isVisible = false;
|
|
}
|
|
|
|
function onShowPlayQueue() {
|
|
var height = $("body").height() + 25;
|
|
height = Math.min(height, window.top.innerHeight * 0.8);
|
|
setFrameHeight(height);
|
|
isVisible = true;
|
|
}
|
|
|
|
function onTogglePlayQueue() {
|
|
if (isVisible) onHidePlayQueue();
|
|
else onShowPlayQueue();
|
|
}
|
|
|
|
function initAutoHide() {
|
|
$(window).mouseleave(function (event) {
|
|
if (event.clientY < 30) onHidePlayQueue();
|
|
});
|
|
|
|
$(window).mouseenter(function () {
|
|
onShowPlayQueue();
|
|
});
|
|
}
|
|
|
|
function setFrameHeight(height) {
|
|
<%-- Disable animation in Chrome. It stopped working in Chrome 44. --%>
|
|
var duration = navigator.userAgent.indexOf("Chrome") != -1 ? 0 : 400;
|
|
|
|
$("#dummy-animation-target").stop();
|
|
$("#dummy-animation-target").animate({"max-width": height}, {
|
|
step: function (now, fx) {
|
|
top.document.getElementById("playQueueFrameset").rows = "*," + now;
|
|
},
|
|
duration: duration
|
|
});
|
|
}
|
|
|
|
function startTimer() {
|
|
<!-- Periodically check if the current song has changed. -->
|
|
nowPlayingService.getNowPlayingForCurrentPlayer(nowPlayingCallback);
|
|
setTimeout("startTimer()", 10000);
|
|
}
|
|
|
|
function nowPlayingCallback(nowPlayingInfo) {
|
|
if (nowPlayingInfo != null && nowPlayingInfo.streamUrl != currentStreamUrl) {
|
|
getPlayQueue();
|
|
<c:if test="${not model.player.web}">
|
|
currentStreamUrl = nowPlayingInfo.streamUrl;
|
|
updateCurrentImage();
|
|
</c:if>
|
|
}
|
|
}
|
|
|
|
function onEnded() {
|
|
onNext(repeatEnabled);
|
|
}
|
|
|
|
function createMediaElementPlayer() {
|
|
// Manually run MediaElement.js initialization.
|
|
//
|
|
// Warning: Bugs will happen if MediaElement.js is not initialized when
|
|
// we modify the media elements (e.g. adding event handlers). Running
|
|
// MediaElement.js's automatic initialization does not guarantee that
|
|
// (it depends on when we call createMediaElementPlayer at load time).
|
|
$('#audioPlayer').mediaelementplayer();
|
|
|
|
// Once playback reaches the end, go to the next song, if any.
|
|
$('#audioPlayer').on("ended", onEnded);
|
|
}
|
|
|
|
function getPlayQueue() {
|
|
playQueueService.getPlayQueue(playQueueCallback);
|
|
}
|
|
|
|
function onClear() {
|
|
var ok = true;
|
|
<c:if test="${model.partyMode}">
|
|
ok = confirm("<fmt:message key="playlist.confirmclear"/>");
|
|
</c:if>
|
|
if (ok) {
|
|
playQueueService.clear(playQueueCallback);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start/resume playing from the current playlist
|
|
*/
|
|
function onStart() {
|
|
if (CastPlayer.castSession) {
|
|
CastPlayer.playCast();
|
|
} else if ($('#audioPlayer').get(0)) {
|
|
if ($('#audioPlayer').get(0).src) {
|
|
$('#audioPlayer').get(0).play(); // Resume playing if the player was paused
|
|
}
|
|
else {
|
|
skip(0); // Start the first track if the player was not yet loaded
|
|
}
|
|
} else {
|
|
playQueueService.start(playQueueCallback);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pause playing
|
|
*/
|
|
function onStop() {
|
|
if (CastPlayer.castSession) {
|
|
CastPlayer.pauseCast();
|
|
} else if ($('#audioPlayer').get(0)) {
|
|
$('#audioPlayer').get(0).pause();
|
|
} else {
|
|
playQueueService.stop(playQueueCallback);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggle play/pause
|
|
*
|
|
* FIXME: Only works for the Web player for now
|
|
*/
|
|
function onToggleStartStop() {
|
|
if (CastPlayer.castSession) {
|
|
var playing = CastPlayer.mediaSession && CastPlayer.mediaSession.playerState == chrome.cast.media.PlayerState.PLAYING;
|
|
if (playing) onStop();
|
|
else onStart();
|
|
} else if ($('#audioPlayer').get(0)) {
|
|
var playing = $("#audioPlayer").get(0).paused != null && !$("#audioPlayer").get(0).paused;
|
|
if (playing) onStop();
|
|
else onStart();
|
|
} else {
|
|
playQueueService.toggleStartStop(playQueueCallback);
|
|
}
|
|
}
|
|
|
|
function onGain(gain) {
|
|
playQueueService.setGain(gain);
|
|
}
|
|
function onJukeboxVolumeChanged() {
|
|
var value = parseInt($("#jukeboxVolume").slider("option", "value"));
|
|
onGain(value / 100);
|
|
}
|
|
function onCastVolumeChanged() {
|
|
var value = parseInt($("#castVolume").slider("option", "value"));
|
|
CastPlayer.setCastVolume(value / 100, false);
|
|
}
|
|
|
|
/**
|
|
* Increase or decrease volume by a certain amount
|
|
*
|
|
* @param gain amount to add or remove from the current volume
|
|
*/
|
|
function onGainAdd(gain) {
|
|
if (CastPlayer.castSession) {
|
|
var volume = parseInt($("#castVolume").slider("option", "value")) + gain;
|
|
if (volume > 100) volume = 100;
|
|
if (volume < 0) volume = 0;
|
|
CastPlayer.setCastVolume(volume / 100, false);
|
|
$("#castVolume").slider("option", "value", volume); // Need to update UI
|
|
} else if ($('#audioPlayer').get(0)) {
|
|
var volume = parseFloat($('#audioPlayer').get(0).volume)*100 + gain;
|
|
if (volume > 100) volume = 100;
|
|
if (volume < 0) volume = 0;
|
|
$('#audioPlayer').get(0).volume = volume / 100;
|
|
} else {
|
|
var volume = parseInt($("#jukeboxVolume").slider("option", "value")) + gain;
|
|
if (volume > 100) volume = 100;
|
|
if (volume < 0) volume = 0;
|
|
onGain(volume / 100);
|
|
$("#jukeboxVolume").slider("option", "value", volume); // Need to update UI
|
|
}
|
|
}
|
|
|
|
function onSkip(index) {
|
|
<c:choose>
|
|
<c:when test="${model.player.web}">
|
|
skip(index);
|
|
</c:when>
|
|
<c:otherwise>
|
|
currentStreamUrl = songs[index].streamUrl;
|
|
if (isJavaJukeboxPresent()) {
|
|
updateJavaJukeboxPlayerControlBar(songs[index]);
|
|
}
|
|
playQueueService.skip(index, playQueueCallback);
|
|
</c:otherwise>
|
|
</c:choose>
|
|
}
|
|
function onNext(wrap) {
|
|
var index = parseInt(getCurrentSongIndex()) + 1;
|
|
if (shuffleRadioEnabled && index >= songs.length) {
|
|
playQueueService.reloadSearchCriteria(function(playQueue) {
|
|
playQueueCallback(playQueue);
|
|
onSkip(index);
|
|
});
|
|
return;
|
|
} else if (wrap) {
|
|
index = index % songs.length;
|
|
}
|
|
onSkip(index);
|
|
}
|
|
function onPrevious() {
|
|
onSkip(parseInt(getCurrentSongIndex()) - 1);
|
|
}
|
|
function onPlay(id) {
|
|
playQueueService.play(id, playQueueCallback);
|
|
}
|
|
function onPlayShuffle(albumListType, offset, size, genre, decade) {
|
|
playQueueService.playShuffle(albumListType, offset, size, genre, decade, playQueueCallback);
|
|
}
|
|
function onPlayPlaylist(id, index) {
|
|
playQueueService.playPlaylist(id, index, playQueueCallback);
|
|
}
|
|
function onPlayInternetRadio(id, index) {
|
|
playQueueService.playInternetRadio(id, index, playQueueCallback);
|
|
}
|
|
function onPlayTopSong(id, index) {
|
|
playQueueService.playTopSong(id, index, playQueueCallback);
|
|
}
|
|
function onPlayPodcastChannel(id) {
|
|
playQueueService.playPodcastChannel(id, playQueueCallback);
|
|
}
|
|
function onPlayPodcastEpisode(id) {
|
|
playQueueService.playPodcastEpisode(id, playQueueCallback);
|
|
}
|
|
function onPlayNewestPodcastEpisode(index) {
|
|
playQueueService.playNewestPodcastEpisode(index, playQueueCallback);
|
|
}
|
|
function onPlayStarred() {
|
|
playQueueService.playStarred(playQueueCallback);
|
|
}
|
|
function onPlayRandom(id, count) {
|
|
playQueueService.playRandom(id, count, playQueueCallback);
|
|
}
|
|
function onPlaySimilar(id, count) {
|
|
playQueueService.playSimilar(id, count, playQueueCallback);
|
|
}
|
|
function onAdd(id) {
|
|
playQueueService.add(id, playQueueCallback);
|
|
}
|
|
function onAddNext(id) {
|
|
playQueueService.addAt(id, getCurrentSongIndex() + 1, playQueueCallback);
|
|
}
|
|
function onAddPlaylist(id, index) {
|
|
playQueueService.addPlaylist(id, index, playQueueCallback);
|
|
}
|
|
function onShuffle() {
|
|
playQueueService.shuffle(playQueueCallback);
|
|
}
|
|
function onStar(index) {
|
|
playQueueService.toggleStar(index, playQueueCallback);
|
|
}
|
|
function onStarCurrent() {
|
|
onStar(getCurrentSongIndex());
|
|
}
|
|
function onRemove(index) {
|
|
playQueueService.remove(index, playQueueCallback);
|
|
}
|
|
function onRemoveSelected() {
|
|
var indexes = new Array();
|
|
var counter = 0;
|
|
for (var i = 0; i < songs.length; i++) {
|
|
var index = i + 1;
|
|
if ($("#songIndex" + index).is(":checked")) {
|
|
indexes[counter++] = i;
|
|
}
|
|
}
|
|
playQueueService.removeMany(indexes, playQueueCallback);
|
|
}
|
|
|
|
function onRearrange(indexes) {
|
|
playQueueService.rearrange(indexes, playQueueCallback);
|
|
}
|
|
function onToggleRepeat() {
|
|
playQueueService.toggleRepeat(playQueueCallback);
|
|
}
|
|
function onUndo() {
|
|
playQueueService.undo(playQueueCallback);
|
|
}
|
|
function onSortByTrack() {
|
|
playQueueService.sortByTrack(playQueueCallback);
|
|
}
|
|
function onSortByArtist() {
|
|
playQueueService.sortByArtist(playQueueCallback);
|
|
}
|
|
function onSortByAlbum() {
|
|
playQueueService.sortByAlbum(playQueueCallback);
|
|
}
|
|
function onSavePlayQueue() {
|
|
var positionMillis = $('#audioPlayer').get(0) ? Math.round(1000.0 * $('#audioPlayer').get(0).currentTime) : 0;
|
|
playQueueService.savePlayQueue(getCurrentSongIndex(), positionMillis);
|
|
$().toastmessage("showSuccessToast", "<fmt:message key="playlist.toast.saveplayqueue"/>");
|
|
}
|
|
function onLoadPlayQueue() {
|
|
playQueueService.loadPlayQueue(playQueueCallback);
|
|
}
|
|
function onSavePlaylist() {
|
|
playlistService.createPlaylistForPlayQueue(function (playlistId) {
|
|
top.left.updatePlaylists();
|
|
top.left.showAllPlaylists();
|
|
top.main.location.href = "playlist.view?id=" + playlistId;
|
|
$().toastmessage("showSuccessToast", "<fmt:message key="playlist.toast.saveasplaylist"/>");
|
|
});
|
|
}
|
|
function onAppendPlaylist() {
|
|
playlistService.getWritablePlaylists(playlistCallback);
|
|
}
|
|
function playlistCallback(playlists) {
|
|
$("#dialog-select-playlist-list").empty();
|
|
for (var i = 0; i < playlists.length; i++) {
|
|
var playlist = playlists[i];
|
|
$("<p class='dense'><b><a href='#' onclick='appendPlaylist(" + playlist.id + ")'>" + escapeHtml(playlist.name)
|
|
+ "</a></b></p>").appendTo("#dialog-select-playlist-list");
|
|
}
|
|
$("#dialog-select-playlist").dialog("open");
|
|
}
|
|
function appendPlaylist(playlistId) {
|
|
$("#dialog-select-playlist").dialog("close");
|
|
|
|
var mediaFileIds = new Array();
|
|
for (var i = 0; i < songs.length; i++) {
|
|
if ($("#songIndex" + (i + 1)).is(":checked")) {
|
|
mediaFileIds.push(songs[i].id);
|
|
}
|
|
}
|
|
playlistService.appendToPlaylist(playlistId, mediaFileIds, function (){
|
|
top.left.updatePlaylists();
|
|
top.main.location.href = "playlist.view?id=" + playlistId;
|
|
$().toastmessage("showSuccessToast", "<fmt:message key="playlist.toast.appendtoplaylist"/>");
|
|
});
|
|
}
|
|
|
|
function isJavaJukeboxPresent() {
|
|
return $("#javaJukeboxPlayerControlBarContainer").length==1;
|
|
}
|
|
|
|
function playQueueCallback(playQueue) {
|
|
songs = playQueue.entries;
|
|
repeatEnabled = playQueue.repeatEnabled;
|
|
shuffleRadioEnabled = playQueue.shuffleRadioEnabled;
|
|
internetRadioEnabled = playQueue.internetRadioEnabled;
|
|
|
|
// If an internet radio has no sources, display a message to the user.
|
|
if (internetRadioEnabled && songs.length == 0) {
|
|
top.main.$().toastmessage("showErrorToast", "<fmt:message key="playlist.toast.radioerror"/>");
|
|
onStop();
|
|
}
|
|
|
|
if ($("#start")) {
|
|
$("#start").toggle(!playQueue.stopEnabled);
|
|
$("#stop").toggle(playQueue.stopEnabled);
|
|
}
|
|
|
|
if ($("#toggleRepeat")) {
|
|
if (shuffleRadioEnabled) {
|
|
$("#toggleRepeat").html("<fmt:message key="playlist.repeat_radio"/>");
|
|
} else if (repeatEnabled) {
|
|
$("#toggleRepeat").attr('src', '<spring:theme code="repeatOn"/>');
|
|
$("#toggleRepeat").attr('alt', 'Repeat On');
|
|
} else {
|
|
$("#toggleRepeat").attr('src', '<spring:theme code="repeatOff"/>');
|
|
$("#toggleRepeat").attr('alt', 'Repeat Off');
|
|
}
|
|
}
|
|
|
|
// Disable some UI items if internet radio is playing
|
|
$("select#moreActions #loadPlayQueue").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #savePlayQueue").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #savePlaylist").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #downloadPlaylist").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #sharePlaylist").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #sortByTrack").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #sortByAlbum").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #sortByArtist").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #selectAll").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #selectNone").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #removeSelected").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #download").prop("disabled", internetRadioEnabled);
|
|
$("select#moreActions #appendPlaylist").prop("disabled", internetRadioEnabled);
|
|
$("#shuffleQueue").toggleLink(!internetRadioEnabled);
|
|
$("#repeatQueue").toggleLink(!internetRadioEnabled);
|
|
$("#undoQueue").toggleLink(!internetRadioEnabled);
|
|
|
|
if (songs.length == 0) {
|
|
$("#songCountAndDuration").text("");
|
|
$("#empty").show();
|
|
} else {
|
|
$("#songCountAndDuration").html(songs.length + " <fmt:message key="playlist2.songs"/> – " + playQueue.durationAsString);
|
|
$("#empty").hide();
|
|
}
|
|
|
|
// Delete all the rows except for the "pattern" row
|
|
dwr.util.removeAllRows("playlistBody", { filter:function(tr) {
|
|
return (tr.id != "pattern");
|
|
}});
|
|
|
|
// Create a new set cloned from the pattern row
|
|
for (var i = 0; i < songs.length; i++) {
|
|
var song = songs[i];
|
|
var id = i + 1;
|
|
dwr.util.cloneNode("pattern", { idSuffix:id });
|
|
if ($("#trackNumber" + id)) {
|
|
$("#trackNumber" + id).text(song.trackNumber);
|
|
}
|
|
|
|
if (!internetRadioEnabled) {
|
|
// Show star/remove buttons in all cases...
|
|
$("#starSong" + id).show();
|
|
$("#removeSong" + id).show();
|
|
$("#songIndex" + id).show();
|
|
|
|
// Show star rating
|
|
if (song.starred) {
|
|
$("#starSong" + id).attr("src", "<spring:theme code='ratingOnImage'/>");
|
|
} else {
|
|
$("#starSong" + id).attr("src", "<spring:theme code='ratingOffImage'/>");
|
|
}
|
|
} else {
|
|
// ...except from when internet radio is playing.
|
|
$("#starSong" + id).hide();
|
|
$("#removeSong" + id).hide();
|
|
$("#songIndex" + id).hide();
|
|
}
|
|
|
|
if ($("#currentImage" + id) && song.streamUrl == currentStreamUrl) {
|
|
$("#currentImage" + id).show();
|
|
if (isJavaJukeboxPresent()) {
|
|
updateJavaJukeboxPlayerControlBar(song);
|
|
}
|
|
}
|
|
if ($("#title" + id)) {
|
|
$("#title" + id).text(song.title);
|
|
$("#title" + id).attr("title", song.title);
|
|
}
|
|
if ($("#titleUrl" + id)) {
|
|
$("#titleUrl" + id).text(song.title);
|
|
$("#titleUrl" + id).attr("title", song.title);
|
|
$("#titleUrl" + id).click(function () {onSkip(this.id.substring(8) - 1)});
|
|
}
|
|
if ($("#album" + id)) {
|
|
$("#album" + id).text(song.album);
|
|
$("#album" + id).attr("title", song.album);
|
|
$("#albumUrl" + id).attr("href", song.albumUrl);
|
|
// Open external internet radio links in new windows
|
|
if (internetRadioEnabled) {
|
|
$("#albumUrl" + id).attr({
|
|
target: "_blank",
|
|
rel: "noopener noreferrer",
|
|
});
|
|
}
|
|
}
|
|
if ($("#artist" + id)) {
|
|
$("#artist" + id).text(song.artist);
|
|
$("#artist" + id).attr("title", song.artist);
|
|
}
|
|
if ($("#genre" + id)) {
|
|
$("#genre" + id).text(song.genre);
|
|
}
|
|
if ($("#year" + id)) {
|
|
// If song.year is not an int, this will return NaN, which
|
|
// conveniently returns false in all boolean operations.
|
|
if (parseInt(song.year) > 0) {
|
|
$("#year" + id).text(song.year);
|
|
} else {
|
|
$("#year" + id).text("");
|
|
}
|
|
}
|
|
if ($("#bitRate" + id)) {
|
|
$("#bitRate" + id).text(song.bitRate);
|
|
}
|
|
if ($("#duration" + id)) {
|
|
$("#duration" + id).text(song.durationAsString);
|
|
}
|
|
if ($("#format" + id)) {
|
|
$("#format" + id).text(song.format);
|
|
}
|
|
if ($("#fileSize" + id)) {
|
|
$("#fileSize" + id).text(song.fileSize);
|
|
}
|
|
|
|
$("#pattern" + id).addClass((i % 2 == 0) ? "bgcolor1" : "bgcolor2");
|
|
|
|
// Note: show() method causes page to scroll to top.
|
|
$("#pattern" + id).css("display", "table-row");
|
|
}
|
|
|
|
if (playQueue.sendM3U) {
|
|
parent.frames.main.location.href="play.m3u?";
|
|
}
|
|
|
|
var jukeboxVolume = $("#jukeboxVolume");
|
|
if (jukeboxVolume) {
|
|
jukeboxVolume.slider("option", "value", Math.floor(playQueue.gain * 100));
|
|
}
|
|
|
|
<c:if test="${model.player.web}">
|
|
triggerPlayer(playQueue.startPlayerAt, playQueue.startPlayerAtPosition);
|
|
</c:if>
|
|
}
|
|
|
|
function triggerPlayer(index, positionMillis) {
|
|
if (index != -1) {
|
|
if (songs.length > index) {
|
|
skip(index);
|
|
if (positionMillis != 0) {
|
|
$('#audioPlayer').get(0).currentTime = positionMillis / 1000;
|
|
}
|
|
}
|
|
}
|
|
updateCurrentImage();
|
|
if (songs.length == 0) {
|
|
$('#audioPlayer').get(0).stop();
|
|
}
|
|
}
|
|
|
|
function loadMediaElementPlayer(song, position) {
|
|
var player = $('#audioPlayer').get(0);
|
|
|
|
// Is this a new song?
|
|
if (player.src != song.streamUrl) {
|
|
// Stop the current playing song and change the media source.
|
|
player.src = song.streamUrl;
|
|
// Inform MEJS that we need to load a new media source. The
|
|
// 'canplay' event will be fired once playback is possible.
|
|
player.load();
|
|
// The 'skip' function takes a 'position' argument. We don't
|
|
// usually send it, and in this case it's better to do nothing.
|
|
// Otherwise, the 'canplay' event will also be fired after
|
|
// setting 'currentTime'.
|
|
if (position && position > 0) {
|
|
player.currentTime = position;
|
|
}
|
|
|
|
// Are we seeking on an already-playing song?
|
|
} else {
|
|
// Seeking also starts playing. The 'canplay' event will be
|
|
// fired after setting 'currentTime'.
|
|
player.currentTime = position || 0;
|
|
}
|
|
|
|
// Start playback immediately.
|
|
player.play();
|
|
}
|
|
|
|
function skip(index, position) {
|
|
if (index < 0 || index >= songs.length) {
|
|
return;
|
|
}
|
|
|
|
var song = songs[index];
|
|
currentStreamUrl = song.streamUrl;
|
|
updateCurrentImage();
|
|
|
|
// Handle ChromeCast player.
|
|
if (CastPlayer.castSession) {
|
|
CastPlayer.loadCastMedia(song, position);
|
|
// Handle MediaElement (HTML5) player.
|
|
} else {
|
|
loadMediaElementPlayer(song, position);
|
|
}
|
|
|
|
updateWindowTitle(song);
|
|
|
|
<c:if test="${model.notify}">
|
|
showNotification(song);
|
|
</c:if>
|
|
}
|
|
|
|
function updateWindowTitle(song) {
|
|
top.document.title = song.title + " - " + song.artist + " - Airsonic";
|
|
}
|
|
|
|
function showNotification(song) {
|
|
if (!("Notification" in window)) {
|
|
return;
|
|
}
|
|
if (Notification.permission === "granted") {
|
|
createNotification(song);
|
|
}
|
|
else if (Notification.permission !== 'denied') {
|
|
Notification.requestPermission(function (permission) {
|
|
Notification.permission = permission;
|
|
if (permission === "granted") {
|
|
createNotification(song);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function createNotification(song) {
|
|
var n = new Notification(song.title, {
|
|
tag: "airsonic",
|
|
body: song.artist + " - " + song.album,
|
|
icon: "coverArt.view?id=" + song.id + "&size=110"
|
|
});
|
|
n.onshow = function() {
|
|
setTimeout(function() {n.close()}, 5000);
|
|
}
|
|
}
|
|
|
|
function updateCurrentImage() {
|
|
for (var i = 0; i < songs.length; i++) {
|
|
var song = songs[i];
|
|
var id = i + 1;
|
|
var image = $("#currentImage" + id);
|
|
|
|
if (image) {
|
|
if (song.streamUrl == currentStreamUrl) {
|
|
image.show();
|
|
} else {
|
|
image.hide();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getCurrentSongIndex() {
|
|
for (var i = 0; i < songs.length; i++) {
|
|
if (songs[i].streamUrl == currentStreamUrl) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
<!-- actionSelected() is invoked when the users selects from the "More actions..." combo box. -->
|
|
function actionSelected(id) {
|
|
var selectedIndexes = getSelectedIndexes();
|
|
if (id == "top") {
|
|
return;
|
|
} else if (id == "savePlayQueue") {
|
|
onSavePlayQueue();
|
|
} else if (id == "loadPlayQueue") {
|
|
onLoadPlayQueue();
|
|
} else if (id == "savePlaylist") {
|
|
onSavePlaylist();
|
|
} else if (id == "downloadPlaylist") {
|
|
location.href = "download.view?player=${model.player.id}";
|
|
} else if (id == "sharePlaylist") {
|
|
parent.frames.main.location.href = "createShare.view?player=${model.player.id}&" + getSelectedIndexes();
|
|
} else if (id == "sortByTrack") {
|
|
onSortByTrack();
|
|
} else if (id == "sortByArtist") {
|
|
onSortByArtist();
|
|
} else if (id == "sortByAlbum") {
|
|
onSortByAlbum();
|
|
} else if (id == "selectAll") {
|
|
selectAll(true);
|
|
} else if (id == "selectNone") {
|
|
selectAll(false);
|
|
} else if (id == "removeSelected") {
|
|
onRemoveSelected();
|
|
} else if (id == "download" && selectedIndexes != "") {
|
|
location.href = "download.view?player=${model.player.id}&" + selectedIndexes;
|
|
} else if (id == "appendPlaylist" && selectedIndexes != "") {
|
|
onAppendPlaylist();
|
|
}
|
|
$("#moreActions").prop("selectedIndex", 0);
|
|
}
|
|
|
|
function getSelectedIndexes() {
|
|
var result = "";
|
|
for (var i = 0; i < songs.length; i++) {
|
|
if ($("#songIndex" + (i + 1)).is(":checked")) {
|
|
result += "i=" + i + "&";
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function selectAll(b) {
|
|
for (var i = 0; i < songs.length; i++) {
|
|
if (b) {
|
|
$("#songIndex" + (i + 1)).attr("checked", "checked");
|
|
} else {
|
|
$("#songIndex" + (i + 1)).removeAttr("checked");
|
|
}
|
|
}
|
|
}
|
|
|
|
</script>
|
|
|
|
<c:choose>
|
|
<c:when test="${model.player.javaJukebox}">
|
|
<div id="javaJukeboxPlayerControlBarContainer">
|
|
<%@ include file="javaJukeboxPlayerControlBar.jspf" %>
|
|
</div>
|
|
</c:when>
|
|
<c:otherwise>
|
|
<div class="bgcolor2" style="position:fixed; bottom:0; width:100%;padding-top:10px;">
|
|
<table style="white-space:nowrap; margin-bottom:0;">
|
|
<tr style="white-space:nowrap;">
|
|
<c:if test="${model.user.settingsRole and fn:length(model.players) gt 1}">
|
|
<td style="padding-right: 5px"><select name="player" onchange="location='playQueue.view?player=' + options[selectedIndex].value;">
|
|
<c:forEach items="${model.players}" var="player">
|
|
<option ${player.id eq model.player.id ? "selected" : ""} value="${player.id}">${player.shortDescription}</option>
|
|
</c:forEach>
|
|
</select></td>
|
|
</c:if>
|
|
<c:if test="${model.player.web}">
|
|
<td>
|
|
<div id="player" style="width:340px; height:40px">
|
|
<audio id="audioPlayer" data-mejsoptions='{"alwaysShowControls": true, "enableKeyboard": false}' width="340px" height="40px" tabindex="-1" />
|
|
</div>
|
|
<div id="castPlayer" style="display: none">
|
|
<div style="float:left">
|
|
<img alt="Play" id="castPlay" src="<spring:theme code='castPlayImage'/>" onclick="CastPlayer.playCast()" style="cursor:pointer">
|
|
<img alt="Pause" id="castPause" src="<spring:theme code='castPauseImage'/>" onclick="CastPlayer.pauseCast()" style="cursor:pointer; display:none">
|
|
<img alt="Mute on" id="castMuteOn" src="<spring:theme code='volumeImage'/>" onclick="CastPlayer.castMuteOn()" style="cursor:pointer">
|
|
<img alt="Mute off" id="castMuteOff" src="<spring:theme code='muteImage'/>" onclick="CastPlayer.castMuteOff()" style="cursor:pointer; display:none">
|
|
</div>
|
|
<div style="float:left">
|
|
<div id="castVolume" style="width:80px;height:4px;margin-left:10px;margin-right:10px;margin-top:8px"></div>
|
|
<script type="text/javascript">
|
|
$("#castVolume").slider({max: 100, value: 50, animate: "fast", range: "min"});
|
|
$("#castVolume").on("slidestop", onCastVolumeChanged);
|
|
</script>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<img alt="Cast on" id="castOn" src="<spring:theme code='castIdleImage'/>" onclick="CastPlayer.launchCastApp()" style="cursor:pointer; display:none">
|
|
<img alt="Cast off" id="castOff" src="<spring:theme code='castActiveImage'/>" onclick="CastPlayer.stopCastApp()" style="cursor:pointer; display:none">
|
|
</td>
|
|
</c:if>
|
|
|
|
<c:if test="${model.user.streamRole and not model.player.web}">
|
|
<td>
|
|
<img alt="Start" id="start" src="<spring:theme code='castPlayImage'/>" onclick="onStart()" style="cursor:pointer">
|
|
<img alt="Stop" id="stop" src="<spring:theme code='castPauseImage'/>" onclick="onStop()" style="cursor:pointer; display:none">
|
|
</td>
|
|
</c:if>
|
|
|
|
<c:if test="${model.player.jukebox}">
|
|
<td style="white-space:nowrap;">
|
|
<img src="<spring:theme code='volumeImage'/>" alt="">
|
|
</td>
|
|
<td style="white-space:nowrap;">
|
|
<div id="jukeboxVolume" style="width:80px;height:4px"></div>
|
|
<script type="text/javascript">
|
|
$("#jukeboxVolume").slider({max: 100, value: 50, animate: "fast", range: "min"});
|
|
$("#jukeboxVolume").on("slidestop", onJukeboxVolumeChanged);
|
|
</script>
|
|
</td>
|
|
</c:if>
|
|
|
|
<c:if test="${model.player.web}">
|
|
<td><span class="header">
|
|
<img src="<spring:theme code='backImage'/>" alt="Play next" title="Play next" onclick="onPrevious()" style="cursor:pointer"></span>
|
|
</td>
|
|
<td><span class="header">
|
|
<img src="<spring:theme code='forwardImage'/>" alt="Play next" title="Play next" onclick="onNext(false)" style="cursor:pointer"></span> |
|
|
</td>
|
|
</c:if>
|
|
|
|
<td style="white-space:nowrap;">
|
|
<span class="header">
|
|
<a href="javascript:onClear()" class="player-control">
|
|
<img src="<spring:theme code='clearImage'/>" alt="Clear playlist" title="Clear playlist" style="cursor:pointer; height:18px">
|
|
</a>
|
|
</span> |</td>
|
|
|
|
<td style="white-space:nowrap;">
|
|
<span class="header">
|
|
<a href="javascript:onShuffle()" id="shuffleQueue" class="player-control">
|
|
<img src="<spring:theme code='shuffleImage'/>" alt="Shuffle" title="Shuffle" style="cursor:pointer; height:18px">
|
|
</a>
|
|
</span> |</td>
|
|
|
|
<c:if test="${model.player.web or model.player.jukebox or model.player.external}">
|
|
<td style="white-space:nowrap;">
|
|
<span class="header">
|
|
<a href="javascript:onToggleRepeat()" id="repeatQueue" class="player-control">
|
|
<img id="toggleRepeat" src="<spring:theme code='repeatOn'/>" alt="Toggle repeat" title="Toggle repeat" style="cursor:pointer; height:18px">
|
|
</a>
|
|
</span> |</td>
|
|
</c:if>
|
|
|
|
<td style="white-space:nowrap;">
|
|
<span class="header">
|
|
<a href="javascript:onUndo()" id="undoQueue" class="player-control">
|
|
<img src="<spring:theme code='undoImage'/>" alt="Undo" title="Undo" style="cursor:pointer; height:18px">
|
|
</a>
|
|
</span> |</td>
|
|
|
|
<c:if test="${model.user.settingsRole}">
|
|
<td style="white-space:nowrap;">
|
|
<span class="header">
|
|
<a href="playerSettings.view?id=${model.player.id}" target="main" class="player-control">
|
|
<img src="<spring:theme code='settingsImage'/>" alt="Settings" title="Settings" style="cursor:pointer; height:18px">
|
|
</a>
|
|
</span> |</td>
|
|
</c:if>
|
|
|
|
<td style="white-space:nowrap;"><select id="moreActions" onchange="actionSelected(this.options[selectedIndex].id)">
|
|
<option id="top" selected="selected"><fmt:message key="playlist.more"/></option>
|
|
<optgroup label="<fmt:message key='playlist.more.playlist'/>">
|
|
<option id="savePlayQueue"><fmt:message key="playlist.saveplayqueue"/></option>
|
|
<option id="loadPlayQueue"><fmt:message key="playlist.loadplayqueue"/></option>
|
|
<option id="savePlaylist"><fmt:message key="playlist.save"/></option>
|
|
<c:if test="${model.user.downloadRole}">
|
|
<option id="downloadPlaylist"><fmt:message key="common.download"/></option>
|
|
</c:if>
|
|
<c:if test="${model.user.shareRole}">
|
|
<option id="sharePlaylist"><fmt:message key="main.more.share"/></option>
|
|
</c:if>
|
|
<option id="sortByTrack"><fmt:message key="playlist.more.sortbytrack"/></option>
|
|
<option id="sortByAlbum"><fmt:message key="playlist.more.sortbyalbum"/></option>
|
|
<option id="sortByArtist"><fmt:message key="playlist.more.sortbyartist"/></option>
|
|
</optgroup>
|
|
<optgroup label="<fmt:message key='playlist.more.selection'/>">
|
|
<option id="selectAll"><fmt:message key="playlist.more.selectall"/></option>
|
|
<option id="selectNone"><fmt:message key="playlist.more.selectnone"/></option>
|
|
<option id="removeSelected"><fmt:message key="playlist.remove"/></option>
|
|
<c:if test="${model.user.downloadRole}">
|
|
<option id="download"><fmt:message key="common.download"/></option>
|
|
</c:if>
|
|
<option id="appendPlaylist"><fmt:message key="playlist.append"/></option>
|
|
</optgroup>
|
|
</select>
|
|
</td>
|
|
|
|
</tr></table>
|
|
</div>
|
|
</c:otherwise>
|
|
</c:choose>
|
|
|
|
|
|
<h2 style="float:left"><fmt:message key="playlist.more.playlist"/></h2>
|
|
<h2 id="songCountAndDuration" style="float:right;padding-right:1em"></h2>
|
|
<div style="clear:both"></div>
|
|
<p id="empty"><em><fmt:message key="playlist.empty"/></em></p>
|
|
|
|
<table class="music indent" style="cursor:pointer">
|
|
<tbody id="playlistBody">
|
|
<tr id="pattern" style="display:none;margin:0;padding:0;border:0">
|
|
<td class="fit">
|
|
<img id="starSong" onclick="onStar(this.id.substring(8) - 1)" src="<spring:theme code='ratingOffImage'/>"
|
|
style="cursor:pointer;height:18px;" alt="" title=""></td>
|
|
<td class="fit">
|
|
<img id="removeSong" onclick="onRemove(this.id.substring(10) - 1)" src="<spring:theme code='removeImage'/>"
|
|
style="cursor:pointer; height:18px;" alt="<fmt:message key='playlist.remove'/>" title="<fmt:message key='playlist.remove'/>"></td>
|
|
<td class="fit"><input type="checkbox" class="checkbox" id="songIndex"></td>
|
|
|
|
<c:if test="${model.visibility.trackNumberVisible}">
|
|
<td class="fit rightalign"><span class="detail" id="trackNumber">1</span></td>
|
|
</c:if>
|
|
|
|
<td class="truncate">
|
|
<img id="currentImage" src="<spring:theme code='currentImage'/>" alt="" style="display:none;padding-right: 0.5em">
|
|
<c:choose>
|
|
<c:when test="${model.player.externalWithPlaylist}">
|
|
<span id="title" class="songTitle">Title</span>
|
|
</c:when>
|
|
<c:otherwise>
|
|
<span class="songTitle"><a id="titleUrl" href="javascript:void(0)">Title</a></span>
|
|
</c:otherwise>
|
|
</c:choose>
|
|
</td>
|
|
|
|
<c:if test="${model.visibility.albumVisible}">
|
|
<td class="truncate"><a id="albumUrl" target="main"><span id="album" class="detail">Album</span></a></td>
|
|
</c:if>
|
|
<c:if test="${model.visibility.artistVisible}">
|
|
<td class="truncate"><span id="artist" class="detail">Artist</span></td>
|
|
</c:if>
|
|
<c:if test="${model.visibility.genreVisible}">
|
|
<td class="truncate"><span id="genre" class="detail">Genre</span></td>
|
|
</c:if>
|
|
<c:if test="${model.visibility.yearVisible}">
|
|
<td class="fit rightalign"><span id="year" class="detail">Year</span></td>
|
|
</c:if>
|
|
<c:if test="${model.visibility.formatVisible}">
|
|
<td class="fit rightalign"><span id="format" class="detail">Format</span></td>
|
|
</c:if>
|
|
<c:if test="${model.visibility.fileSizeVisible}">
|
|
<td class="fit rightalign"><span id="fileSize" class="detail">Format</span></td>
|
|
</c:if>
|
|
<c:if test="${model.visibility.durationVisible}">
|
|
<td class="fit rightalign"><span id="duration" class="detail">Duration</span></td>
|
|
</c:if>
|
|
<c:if test="${model.visibility.bitRateVisible}">
|
|
<td class="fit rightalign"><span id="bitRate" class="detail">Bit Rate</span></td>
|
|
</c:if>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div style="height:3.2em"></div>
|
|
|
|
<div id="dialog-select-playlist" title="<fmt:message key='main.addtoplaylist.title'/>" style="display: none;">
|
|
<p><fmt:message key="main.addtoplaylist.text"/></p>
|
|
<div id="dialog-select-playlist-list"></div>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
window['__onGCastApiAvailable'] = function(isAvailable) {
|
|
if (isAvailable) {
|
|
CastPlayer.initializeCastPlayer();
|
|
}
|
|
};
|
|
</script>
|
|
<script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
|
|
|
|
</body></html>
|
|
|